knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/observability/tracing/provider.go (about)

     1  /*
     2  Copyright 2025 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package tracing
    18  
    19  import (
    20  	"cmp"
    21  	"context"
    22  	"fmt"
    23  	"net/url"
    24  	"os"
    25  	"strconv"
    26  
    27  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    28  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    29  	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
    30  	"go.opentelemetry.io/otel/propagation"
    31  	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    32  	"go.opentelemetry.io/otel/trace"
    33  	"go.opentelemetry.io/otel/trace/noop"
    34  )
    35  
    36  func noopFunc(context.Context) error { return nil }
    37  
    38  type TracerProvider struct {
    39  	trace.TracerProvider
    40  	shutdown func(context.Context) error
    41  }
    42  
    43  func (m *TracerProvider) Shutdown(ctx context.Context) error {
    44  	return m.shutdown(ctx)
    45  }
    46  
    47  func DefaultTextMapPropagator() propagation.TextMapPropagator {
    48  	return propagation.NewCompositeTextMapPropagator(
    49  		propagation.TraceContext{},
    50  		propagation.Baggage{},
    51  	)
    52  }
    53  
    54  func NewTracerProvider(
    55  	ctx context.Context,
    56  	cfg Config,
    57  	opts ...sdktrace.TracerProviderOption,
    58  ) (*TracerProvider, error) {
    59  	if cfg.Protocol == ProtocolNone {
    60  		return &TracerProvider{
    61  			TracerProvider: noop.NewTracerProvider(),
    62  			shutdown:       noopFunc,
    63  		}, nil
    64  	}
    65  
    66  	exp, err := exporterFor(ctx, cfg)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("error creating tracer exporter: %w", err)
    69  	}
    70  
    71  	sampler, err := sampleFor(cfg)
    72  	if err != nil {
    73  		return nil, fmt.Errorf("error creating tracer sampler: %w", err)
    74  	}
    75  
    76  	opts = append(opts,
    77  		sdktrace.WithBatcher(exp),
    78  		sdktrace.WithSampler(sampler),
    79  	)
    80  	provider := sdktrace.NewTracerProvider(opts...)
    81  	return &TracerProvider{
    82  		TracerProvider: provider,
    83  		shutdown:       provider.Shutdown,
    84  	}, nil
    85  }
    86  
    87  func exporterFor(ctx context.Context, cfg Config) (sdktrace.SpanExporter, error) {
    88  	switch cfg.Protocol {
    89  	case ProtocolGRPC:
    90  		return buildGRPC(ctx, cfg)
    91  	case ProtocolHTTPProtobuf:
    92  		return buildHTTP(ctx, cfg)
    93  	case ProtocolStdout:
    94  		return buildStdout()
    95  	default:
    96  		return nil, fmt.Errorf("unsupported metric exporter: %q", cfg.Protocol)
    97  	}
    98  }
    99  
   100  func buildStdout() (sdktrace.SpanExporter, error) {
   101  	return stdouttrace.New()
   102  }
   103  
   104  func buildGRPC(ctx context.Context, cfg Config) (sdktrace.SpanExporter, error) {
   105  	var grpcOpts []otlptracegrpc.Option
   106  
   107  	opt, err := endpointFor(cfg, otlptracegrpc.WithEndpointURL)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("unable to process traces endpoint: %w", err)
   110  	} else if opt != nil {
   111  		grpcOpts = append(grpcOpts, opt)
   112  	}
   113  
   114  	exporter, err := otlptracegrpc.New(ctx, grpcOpts...)
   115  	if err != nil {
   116  		return nil, fmt.Errorf("failed to build exporter: %w", err)
   117  	}
   118  	return exporter, nil
   119  }
   120  
   121  func buildHTTP(ctx context.Context, cfg Config) (sdktrace.SpanExporter, error) {
   122  	var httpOpts []otlptracehttp.Option
   123  
   124  	opt, err := endpointFor(cfg, otlptracehttp.WithEndpointURL)
   125  	if err != nil {
   126  		return nil, fmt.Errorf("unable to process traces endpoint: %w", err)
   127  	} else if opt != nil {
   128  		httpOpts = append(httpOpts, opt)
   129  	}
   130  
   131  	exporter, err := otlptracehttp.New(ctx, httpOpts...)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("failed to build exporter: %w", err)
   134  	}
   135  
   136  	return exporter, nil
   137  }
   138  
   139  // If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_TRACES_ENDPOINT is
   140  // set then we will prefer that over what's in the Config
   141  func endpointFor[T any](cfg Config, opt func(string) T) (T, error) {
   142  	var epOption T
   143  
   144  	override := cmp.Or(
   145  		os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT"),
   146  		os.Getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"),
   147  	)
   148  
   149  	if override != "" {
   150  		return epOption, nil
   151  	}
   152  
   153  	ep := cfg.Endpoint
   154  
   155  	u, err := url.Parse(cfg.Endpoint)
   156  	if err != nil {
   157  		return epOption, err
   158  	}
   159  	if u.Opaque != "" {
   160  		ep = "https://" + ep
   161  	}
   162  
   163  	epOption = opt(ep)
   164  	return epOption, nil
   165  }
   166  
   167  func sampleFor(cfg Config) (sdktrace.Sampler, error) {
   168  	// Don't override env arg
   169  	if os.Getenv("OTEL_TRACES_SAMPLER") != "" {
   170  		return nil, nil
   171  	}
   172  
   173  	if cfg.Protocol == ProtocolStdout {
   174  		return sdktrace.AlwaysSample(), nil
   175  	}
   176  
   177  	rate := cfg.SamplingRate
   178  
   179  	if val := os.Getenv("OTEL_TRACES_SAMPLER_ARG"); val != "" {
   180  		override, err := strconv.ParseFloat(val, 64)
   181  		if err != nil {
   182  			return nil, fmt.Errorf("unable to parse sample rate override: %w", err)
   183  		}
   184  
   185  		rate = override
   186  	}
   187  
   188  	if rate >= 1.0 {
   189  		return sdktrace.AlwaysSample(), nil
   190  	}
   191  
   192  	if cfg.SamplingRate <= 0.0 {
   193  		return sdktrace.NeverSample(), nil
   194  	}
   195  
   196  	root := sdktrace.TraceIDRatioBased(cfg.SamplingRate)
   197  	return sdktrace.ParentBased(root), nil
   198  }