// setupTelemetry builds one tracer provider and one meter provider, each with
// two exporters, so both backends see identical data from the same sdk state.
func setupTelemetry(ctx context.Context, c config) (func(context.Context) error, error) {
res, err := resource.Merge(resource.Default(), resource.NewWithAttributes(semconv.SchemaURL,
semconv.ServiceName(c.serviceName)))
if err != nil {
return nil, err
}
auth := map[string]string{"Authorization": "Bearer " + c.platformToken}
tenantSpans, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(c.tenantEndpoint+"/v1/traces"))
if err != nil {
return nil, err
}
platformSpans, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(c.platformEndpoint+"/v1/traces"),
otlptracehttp.WithHeaders(auth))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(sdktrace.WithResource(res),
sdktrace.WithBatcher(tenantSpans), sdktrace.WithBatcher(platformSpans))
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
shutdowns := []func(context.Context) error{tp.Shutdown}
if c.metricsEnabled {
temporality := sdkmetric.DefaultTemporalitySelector
if c.delta {
temporality = func(sdkmetric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality }
}
aggregation := sdkmetric.DefaultAggregationSelector
if c.exponential {
// max size and scale are the sdk defaults for base2 exponential histograms
aggregation = func(k sdkmetric.InstrumentKind) sdkmetric.Aggregation {
if k == sdkmetric.InstrumentKindHistogram {
return sdkmetric.AggregationBase2ExponentialHistogram{MaxSize: 160, MaxScale: 20}
}
return sdkmetric.DefaultAggregationSelector(k)
}
}
tenantMetrics, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(c.tenantEndpoint+"/v1/metrics"),
otlpmetrichttp.WithTemporalitySelector(temporality), otlpmetrichttp.WithAggregationSelector(aggregation))
if err != nil {
return nil, err
}
platformMetrics, err := otlpmetrichttp.New(ctx, otlpmetrichttp.WithEndpointURL(c.platformEndpoint+"/v1/metrics"),
otlpmetrichttp.WithHeaders(auth),
otlpmetrichttp.WithTemporalitySelector(temporality), otlpmetrichttp.WithAggregationSelector(aggregation))
if err != nil {
return nil, err
}
mp := sdkmetric.NewMeterProvider(sdkmetric.WithResource(res),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(tenantMetrics, sdkmetric.WithInterval(c.interval))),
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(platformMetrics, sdkmetric.WithInterval(c.interval))))
otel.SetMeterProvider(mp)
// the meter provider shuts down first so its final collection still has a live tracer
shutdowns = append([]func(context.Context) error{mp.Shutdown}, shutdowns...)
}
return func(ctx context.Context) error {
var errs []error
for _, s := range shutdowns {
errs = append(errs, s(ctx))
}
return errors.Join(errs...)
}, nil
}