package otel import ( "context" "crypto/tls" "os" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/propagation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.linka.cloud/grpc-toolkit/logger" ) type config struct { dsn []string // Common options resourceAttributes []attribute.KeyValue resourceDetectors []resource.Detector resource *resource.Resource tlsConf *tls.Config // Tracing options tracingEnabled bool textMapPropagator propagation.TextMapPropagator tracerProvider *sdktrace.TracerProvider traceSampler sdktrace.Sampler spanProcessors []sdktrace.SpanProcessor prettyPrint bool bspOptions []sdktrace.BatchSpanProcessorOption // Metrics options metricsEnabled bool metricOptions []metric.Option metricPrometheusBridge bool // Logging options loggingEnabled bool // loggerProvider *sdklog.LoggerProvider } func newConfig(opts []Option) *config { conf := &config{ tracingEnabled: true, metricsEnabled: true, loggingEnabled: true, } if dsn, ok := os.LookupEnv("OTEL_DSN"); ok { conf.dsn = []string{dsn} } for _, opt := range opts { opt.apply(conf) } return conf } func (conf *config) newResource(ctx context.Context) *resource.Resource { log := logger.C(ctx) if conf.resource != nil { if len(conf.resourceAttributes) > 0 { log.Warnf("WithResource overrides WithResourceAttributes (discarding %v)", conf.resourceAttributes) } if len(conf.resourceDetectors) > 0 { log.Warnf("WithResource overrides WithResourceDetectors (discarding %v)", conf.resourceDetectors) } return conf.resource } res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithTelemetrySDK(), resource.WithHost(), resource.WithDetectors(conf.resourceDetectors...), resource.WithAttributes(conf.resourceAttributes...)) if err != nil { otel.Handle(err) return resource.Environment() } return res } // ------------------------------------------------------------------------------ type Option interface { apply(conf *config) } type option func(conf *config) func (fn option) apply(conf *config) { fn(conf) } // WithDSN configures a data source name that is used to connect to an open-telemetry collector, for example, // `https://@otel-collector.example.org/`. // // The default is to use OTEL_DSN environment variable. func WithDSN(dsn ...string) Option { return option(func(conf *config) { conf.dsn = dsn }) } // WithServiceName configures `service.name` resource attribute. func WithServiceName(serviceName string) Option { return option(func(conf *config) { attr := semconv.ServiceNameKey.String(serviceName) conf.resourceAttributes = append(conf.resourceAttributes, attr) }) } // WithServiceVersion configures `service.version` resource attribute, for example, `1.0.0`. func WithServiceVersion(serviceVersion string) Option { return option(func(conf *config) { attr := semconv.ServiceVersionKey.String(serviceVersion) conf.resourceAttributes = append(conf.resourceAttributes, attr) }) } // WithDeploymentEnvironment configures `deployment.environment` resource attribute, // for example, `production`. func WithDeploymentEnvironment(env string) Option { return option(func(conf *config) { attr := semconv.DeploymentEnvironmentKey.String(env) conf.resourceAttributes = append(conf.resourceAttributes, attr) }) } // WithResourceAttributes configures resource attributes that describe an entity that produces // telemetry, for example, such attributes as host.name, service.name, etc. // // The default is to use `OTEL_RESOURCE_ATTRIBUTES` env var, for example, // `OTEL_RESOURCE_ATTRIBUTES=service.name=myservice,service.version=1.0.0`. func WithResourceAttributes(attrs ...attribute.KeyValue) Option { return option(func(conf *config) { conf.resourceAttributes = append(conf.resourceAttributes, attrs...) }) } // WithResourceDetectors adds detectors to be evaluated for the configured resource. func WithResourceDetectors(detectors ...resource.Detector) Option { return option(func(conf *config) { conf.resourceDetectors = append(conf.resourceDetectors, detectors...) }) } // WithResource configures a resource that describes an entity that produces telemetry, // for example, such attributes as host.name and service.name. All produced spans and metrics // will have these attributes. // // WithResource overrides and replaces any other resource attributes. func WithResource(resource *resource.Resource) Option { return option(func(conf *config) { conf.resource = resource }) } func WithTLSConfig(tlsConf *tls.Config) Option { return option(func(conf *config) { conf.tlsConf = tlsConf }) } // ------------------------------------------------------------------------------ type TracingOption interface { Option tracing() } type tracingOption func(conf *config) var _ TracingOption = (*tracingOption)(nil) func (fn tracingOption) apply(conf *config) { fn(conf) } func (fn tracingOption) tracing() {} // WithTracingEnabled can be used to enable/disable tracing. func WithTracingEnabled(on bool) TracingOption { return tracingOption(func(conf *config) { conf.tracingEnabled = on }) } // WithTracingDisabled disables tracing. func WithTracingDisabled() TracingOption { return WithTracingEnabled(false) } // WithTracerProvider overwrites the default otel tracer provider. // You can use it to configure otel to use OTLP exporter. // // When this option is used, you might need to call otel.SetTracerProvider // to register the provider as the global trace provider. func WithTracerProvider(provider *sdktrace.TracerProvider) TracingOption { return tracingOption(func(conf *config) { conf.tracerProvider = provider }) } // WithTraceSampler configures a span sampler. func WithTraceSampler(sampler sdktrace.Sampler) TracingOption { return tracingOption(func(conf *config) { conf.traceSampler = sampler }) } // WithSpanProcessor configures an additional span processor. func WithSpanProcessor(sp sdktrace.SpanProcessor) TracingOption { return tracingOption(func(conf *config) { conf.spanProcessors = append(conf.spanProcessors, sp) }) } // WithPropagator sets the global TextMapPropagator used by OpenTelemetry. // The default is propagation.TraceContext and propagation.Baggage. func WithPropagator(propagator propagation.TextMapPropagator) TracingOption { return tracingOption(func(conf *config) { conf.textMapPropagator = propagator }) } // WithTextMapPropagator is an alias for WithPropagator. func WithTextMapPropagator(propagator propagation.TextMapPropagator) TracingOption { return WithPropagator(propagator) } // WithPrettyPrintSpanExporter adds a span exproter that prints spans to stdout. // It is useful for debugging or demonstration purposes. func WithPrettyPrintSpanExporter() TracingOption { return tracingOption(func(conf *config) { conf.prettyPrint = true }) } // WithBatchSpanProcessorOption specifies options used to created BatchSpanProcessor. func WithBatchSpanProcessorOption(opts ...sdktrace.BatchSpanProcessorOption) TracingOption { return tracingOption(func(conf *config) { conf.bspOptions = append(conf.bspOptions, opts...) }) } // ------------------------------------------------------------------------------ type MetricsOption interface { Option metrics() } type metricsOption func(conf *config) var _ MetricsOption = (*metricsOption)(nil) func (fn metricsOption) apply(conf *config) { fn(conf) } func (fn metricsOption) metrics() {} // WithMetricsEnabled can be used to enable/disable metrics. func WithMetricsEnabled(on bool) MetricsOption { return metricsOption(func(conf *config) { conf.metricsEnabled = on }) } // WithMetricsDisabled disables metrics. func WithMetricsDisabled() MetricsOption { return WithMetricsEnabled(false) } func WithMetricOption(options ...metric.Option) MetricsOption { return metricsOption(func(conf *config) { conf.metricOptions = append(conf.metricOptions, options...) }) } func WithMetricPrometheusBridge() MetricsOption { return metricsOption(func(conf *config) { conf.metricPrometheusBridge = true }) } // ------------------------------------------------------------------------------ type LoggingOption interface { Option logging() } type loggingOption func(conf *config) var _ LoggingOption = (*loggingOption)(nil) func (fn loggingOption) apply(conf *config) { fn(conf) } func (fn loggingOption) logging() {} // WithLoggingDisabled disables logging. func WithLoggingDisabled() LoggingOption { return WithLoggingEnabled(false) } // WithLoggingEnabled can be used to enable/disable logging. func WithLoggingEnabled(on bool) LoggingOption { return loggingOption(func(conf *config) { conf.loggingEnabled = on }) } // WithLoggerProvider overwrites the default otel logger provider. // You can use it to configure otel to use OTLP exporter. // // When this option is used, you might need to call otel.SetLoggerProvider // to register the provider as the global trace provider. // func WithLoggerProvider(provider *sdklog.LoggerProvider) LoggingOption { // return loggingOption(func(conf *config) { // conf.loggerProvider = provider // }) // }