From ca5539b514bee68b482a36353ddc54b0dc8c7a0d Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 31 Oct 2022 15:29:31 -0500 Subject: [PATCH 1/3] feat: add option for exporting traces to a provided Honeycomb team --- cli/deployment/config.go | 15 ++++++--- cli/deployment/config_test.go | 11 +++++++ cli/server.go | 4 +-- coderd/tracing/exporter.go | 28 ++++++++++++++++ codersdk/deploymentconfig.go | 62 +++++++++++++++++++---------------- go.mod | 2 +- 6 files changed, 87 insertions(+), 35 deletions(-) diff --git a/cli/deployment/config.go b/cli/deployment/config.go index 12f62ab7be08d..ece8501f64038 100644 --- a/cli/deployment/config.go +++ b/cli/deployment/config.go @@ -289,10 +289,17 @@ func newConfig() *codersdk.DeploymentConfig { Default: "tls12", }, }, - TraceEnable: &codersdk.DeploymentConfigField[bool]{ - Name: "Trace Enable", - Usage: "Whether application tracing data is collected.", - Flag: "trace", + Trace: &codersdk.TraceConfig{ + Enable: &codersdk.DeploymentConfigField[bool]{ + Name: "Trace Enable", + Usage: "Whether application tracing data is collected. It exports to a backend configured by environment variables. See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md", + Flag: "trace", + }, + HoneycombAPIKey: &codersdk.DeploymentConfigField[string]{ + Name: "Trace Honeycomb API Key", + Usage: "Enables trace exporting to Honeycomb.io using the provided API Key.", + Flag: "trace-honeycomb-api-key", + }, }, SecureAuthCookie: &codersdk.DeploymentConfigField[bool]{ Name: "Secure Auth Cookie", diff --git a/cli/deployment/config_test.go b/cli/deployment/config_test.go index 4fd2cc5978673..d86c8e06a29b6 100644 --- a/cli/deployment/config_test.go +++ b/cli/deployment/config_test.go @@ -114,6 +114,16 @@ func TestConfig(t *testing.T) { require.Equal(t, config.TLS.Enable.Value, true) require.Equal(t, config.TLS.MinVersion.Value, "tls10") }, + }, { + Name: "Trace", + Env: map[string]string{ + "CODER_TRACE_ENABLE": "true", + "CODER_TRACE_HONEYCOMB_API_KEY": "my-honeycomb-key", + }, + Valid: func(config *codersdk.DeploymentConfig) { + require.Equal(t, config.Trace.Enable.Value, true) + require.Equal(t, config.Trace.HoneycombAPIKey.Value, "my-honeycomb-key") + }, }, { Name: "OIDC", Env: map[string]string{ @@ -192,6 +202,7 @@ func TestConfig(t *testing.T) { }} { tc := tc t.Run(tc.Name, func(t *testing.T) { + t.Helper() for key, value := range tc.Env { t.Setenv(key, value) } diff --git a/cli/server.go b/cli/server.go index 93de4e5e34960..60455f158d9be 100644 --- a/cli/server.go +++ b/cli/server.go @@ -125,9 +125,9 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co shouldCoderTrace = cfg.Telemetry.Trace.Value } - if cfg.TraceEnable.Value || shouldCoderTrace { + if cfg.Trace.Enable.Value || shouldCoderTrace { sdkTracerProvider, closeTracing, err := tracing.TracerProvider(ctx, "coderd", tracing.TracerOpts{ - Default: cfg.TraceEnable.Value, + Default: cfg.Trace.Enable.Value, Coder: shouldCoderTrace, }) if err != nil { diff --git a/coderd/tracing/exporter.go b/coderd/tracing/exporter.go index ce232c39a38ae..de4579889f527 100644 --- a/coderd/tracing/exporter.go +++ b/coderd/tracing/exporter.go @@ -13,6 +13,7 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.11.0" "golang.org/x/xerrors" + "google.golang.org/grpc/credentials" ) // TracerOpts specifies which telemetry exporters should be configured. @@ -23,6 +24,8 @@ type TracerOpts struct { // Coder exports traces to Coder's public tracing ingest service and is used // to improve the product. It is disabled when opting out of telemetry. Coder bool + // Exports traces to Honeycomb.io with the provided API key. + Honeycomb string } // TracerProvider creates a grpc otlp exporter and configures a trace provider. @@ -57,6 +60,14 @@ func TracerProvider(ctx context.Context, service string, opts TracerOpts) (*sdkt closers = append(closers, exporter.Shutdown) tracerOpts = append(tracerOpts, sdktrace.WithBatcher(exporter)) } + if opts.Honeycomb != "" { + exporter, err := HoneycombExporter(ctx, opts.Honeycomb) + if err != nil { + return nil, nil, xerrors.Errorf("honeycomb exporter: %w", err) + } + closers = append(closers, exporter.Shutdown) + tracerOpts = append(tracerOpts, sdktrace.WithBatcher(exporter)) + } tracerProvider := sdktrace.NewTracerProvider(tracerOpts...) otel.SetTracerProvider(tracerProvider) @@ -101,3 +112,20 @@ func CoderExporter(ctx context.Context) (*otlptrace.Exporter, error) { return exporter, nil } + +func HoneycombExporter(ctx context.Context, apiKey string) (*otlptrace.Exporter, error) { + opts := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint("api.honeycomb.io:443"), + otlptracegrpc.WithHeaders(map[string]string{ + "x-honeycomb-team": apiKey, + }), + otlptracegrpc.WithTLSCredentials(credentials.NewClientTLSFromCert(nil, "")), + } + + exporter, err := otlptrace.New(ctx, otlptracegrpc.NewClient(opts...)) + if err != nil { + return nil, xerrors.Errorf("create otlp exporter: %w", err) + } + + return exporter, nil +} diff --git a/codersdk/deploymentconfig.go b/codersdk/deploymentconfig.go index 35df5733d62ec..4a2d7dc537ce3 100644 --- a/codersdk/deploymentconfig.go +++ b/codersdk/deploymentconfig.go @@ -11,34 +11,35 @@ import ( // DeploymentConfig is the central configuration for the coder server. type DeploymentConfig struct { - AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` - WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` - AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` - DERP *DERP `json:"derp" typescript:",notnull"` - GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` - Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` - Pprof *PprofConfig `json:"pprof" typescript:",notnull"` - ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` - ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` - CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` - InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` - ProvisionerDaemons *DeploymentConfigField[int] `json:"provisioner_daemons" typescript:",notnull"` - PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` - OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` - OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` - Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` - TLS *TLSConfig `json:"tls" typescript:",notnull"` - TraceEnable *DeploymentConfigField[bool] `json:"trace_enable" typescript:",notnull"` - SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` - SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` - AutoImportTemplates *DeploymentConfigField[[]string] `json:"auto_import_templates" typescript:",notnull"` - MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` - AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` - AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` - BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` - SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` - UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"` + AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` + WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` + Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` + AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` + DERP *DERP `json:"derp" typescript:",notnull"` + GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` + Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` + Pprof *PprofConfig `json:"pprof" typescript:",notnull"` + ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` + ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` + CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` + InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` + ProvisionerDaemons *DeploymentConfigField[int] `json:"provisioner_daemons" typescript:",notnull"` + PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` + OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` + OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` + Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` + TLS *TLSConfig `json:"tls" typescript:",notnull"` + Trace *TraceConfig `json:"trace" typescript:",notnull"` + // TraceEnable *DeploymentConfigField[bool] `json:"trace_enable" typescript:",notnull"` + SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` + SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` + AutoImportTemplates *DeploymentConfigField[[]string] `json:"auto_import_templates" typescript:",notnull"` + MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` + AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` + AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` + BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` + SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` + UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"` } type DERP struct { @@ -107,6 +108,11 @@ type TLSConfig struct { MinVersion *DeploymentConfigField[string] `json:"min_version" typescript:",notnull"` } +type TraceConfig struct { + Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` + HoneycombAPIKey *DeploymentConfigField[string] `json:"honeycomb_api_key" typescript:",notnull"` +} + type GitAuthConfig struct { ID string `json:"id"` Type string `json:"type"` diff --git a/go.mod b/go.mod index 0dee1d439f3b4..afa94bed5368b 100644 --- a/go.mod +++ b/go.mod @@ -303,7 +303,7 @@ require ( golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de // indirect - google.golang.org/grpc v1.50.1 // indirect + google.golang.org/grpc v1.50.1 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v1.0.0 // indirect From 8117a7f49c213f50639ee6d78fbe24f003d6e92a Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 31 Oct 2022 16:01:52 -0500 Subject: [PATCH 2/3] fixup! feat: add option for exporting traces to a provided Honeycomb team --- cli/deployment/config.go | 7 ++++--- site/src/api/typesGenerated.ts | 8 +++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cli/deployment/config.go b/cli/deployment/config.go index ece8501f64038..f3e432fb559d3 100644 --- a/cli/deployment/config.go +++ b/cli/deployment/config.go @@ -296,9 +296,10 @@ func newConfig() *codersdk.DeploymentConfig { Flag: "trace", }, HoneycombAPIKey: &codersdk.DeploymentConfigField[string]{ - Name: "Trace Honeycomb API Key", - Usage: "Enables trace exporting to Honeycomb.io using the provided API Key.", - Flag: "trace-honeycomb-api-key", + Name: "Trace Honeycomb API Key", + Usage: "Enables trace exporting to Honeycomb.io using the provided API Key.", + Flag: "trace-honeycomb-api-key", + Secret: true, }, }, SecureAuthCookie: &codersdk.DeploymentConfigField[bool]{ diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 3d1e0dd33badf..2c704b1988e85 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -292,7 +292,7 @@ export interface DeploymentConfig { readonly oidc: OIDCConfig readonly telemetry: TelemetryConfig readonly tls: TLSConfig - readonly trace_enable: DeploymentConfigField + readonly trace: TraceConfig readonly secure_auth_cookie: DeploymentConfigField readonly ssh_keygen_algorithm: DeploymentConfigField readonly auto_import_templates: DeploymentConfigField @@ -664,6 +664,12 @@ export interface TemplateVersionsByTemplateRequest extends Pagination { readonly template_id: string } +// From codersdk/deploymentconfig.go +export interface TraceConfig { + readonly enable: DeploymentConfigField + readonly honeycomb_api_key: DeploymentConfigField +} + // From codersdk/templates.go export interface UpdateActiveTemplateVersion { readonly id: string From 6c7a36886679471e78b14275dc5d9166958e7628 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Mon, 31 Oct 2022 22:31:36 -0500 Subject: [PATCH 3/3] fixup! feat: add option for exporting traces to a provided Honeycomb team --- codersdk/deploymentconfig.go | 57 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/codersdk/deploymentconfig.go b/codersdk/deploymentconfig.go index 4a2d7dc537ce3..a73385670575a 100644 --- a/codersdk/deploymentconfig.go +++ b/codersdk/deploymentconfig.go @@ -11,35 +11,34 @@ import ( // DeploymentConfig is the central configuration for the coder server. type DeploymentConfig struct { - AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` - WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` - AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` - DERP *DERP `json:"derp" typescript:",notnull"` - GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` - Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` - Pprof *PprofConfig `json:"pprof" typescript:",notnull"` - ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` - ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` - CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` - InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` - ProvisionerDaemons *DeploymentConfigField[int] `json:"provisioner_daemons" typescript:",notnull"` - PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` - OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` - OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` - Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` - TLS *TLSConfig `json:"tls" typescript:",notnull"` - Trace *TraceConfig `json:"trace" typescript:",notnull"` - // TraceEnable *DeploymentConfigField[bool] `json:"trace_enable" typescript:",notnull"` - SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` - SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` - AutoImportTemplates *DeploymentConfigField[[]string] `json:"auto_import_templates" typescript:",notnull"` - MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` - AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` - AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` - BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` - SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` - UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"` + AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` + WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` + Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` + AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` + DERP *DERP `json:"derp" typescript:",notnull"` + GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` + Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` + Pprof *PprofConfig `json:"pprof" typescript:",notnull"` + ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` + ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` + CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` + InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` + ProvisionerDaemons *DeploymentConfigField[int] `json:"provisioner_daemons" typescript:",notnull"` + PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` + OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` + OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` + Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` + TLS *TLSConfig `json:"tls" typescript:",notnull"` + Trace *TraceConfig `json:"trace" typescript:",notnull"` + SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` + SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` + AutoImportTemplates *DeploymentConfigField[[]string] `json:"auto_import_templates" typescript:",notnull"` + MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` + AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` + AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` + BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` + SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` + UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"` } type DERP struct {