github.1git.de/docker/cli@v26.1.3+incompatible/cli/command/telemetry_docker.go (about)

     1  // FIXME(jsternberg): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package command
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"net/url"
    10  	"os"
    11  	"path"
    12  
    13  	"github.com/pkg/errors"
    14  	"go.opentelemetry.io/otel"
    15  	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
    16  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    17  	sdkmetric "go.opentelemetry.io/otel/sdk/metric"
    18  	sdktrace "go.opentelemetry.io/otel/sdk/trace"
    19  )
    20  
    21  const (
    22  	otelContextFieldName     string = "otel"
    23  	otelExporterOTLPEndpoint string = "OTEL_EXPORTER_OTLP_ENDPOINT"
    24  	debugEnvVarPrefix        string = "DOCKER_CLI_"
    25  )
    26  
    27  // dockerExporterOTLPEndpoint retrieves the OTLP endpoint used for the docker reporter
    28  // from the current context.
    29  func dockerExporterOTLPEndpoint(cli Cli) (endpoint string, secure bool) {
    30  	meta, err := cli.ContextStore().GetMetadata(cli.CurrentContext())
    31  	if err != nil {
    32  		otel.Handle(err)
    33  		return "", false
    34  	}
    35  
    36  	var otelCfg any
    37  	switch m := meta.Metadata.(type) {
    38  	case DockerContext:
    39  		otelCfg = m.AdditionalFields[otelContextFieldName]
    40  	case map[string]any:
    41  		otelCfg = m[otelContextFieldName]
    42  	}
    43  
    44  	if otelCfg != nil {
    45  		otelMap, ok := otelCfg.(map[string]any)
    46  		if !ok {
    47  			otel.Handle(errors.Errorf(
    48  				"unexpected type for field %q: %T (expected: %T)",
    49  				otelContextFieldName,
    50  				otelCfg,
    51  				otelMap,
    52  			))
    53  		}
    54  		// keys from https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
    55  		endpoint, _ = otelMap[otelExporterOTLPEndpoint].(string)
    56  	}
    57  
    58  	// Override with env var value if it exists AND IS SET
    59  	// (ignore otel defaults for this override when the key exists but is empty)
    60  	if override := os.Getenv(debugEnvVarPrefix + otelExporterOTLPEndpoint); override != "" {
    61  		endpoint = override
    62  	}
    63  
    64  	if endpoint == "" {
    65  		return "", false
    66  	}
    67  
    68  	// Parse the endpoint. The docker config expects the endpoint to be
    69  	// in the form of a URL to match the environment variable, but this
    70  	// option doesn't correspond directly to WithEndpoint.
    71  	//
    72  	// We pretend we're the same as the environment reader.
    73  	u, err := url.Parse(endpoint)
    74  	if err != nil {
    75  		otel.Handle(errors.Errorf("docker otel endpoint is invalid: %s", err))
    76  		return "", false
    77  	}
    78  
    79  	switch u.Scheme {
    80  	case "unix":
    81  		// Unix sockets are a bit weird. OTEL seems to imply they
    82  		// can be used as an environment variable and are handled properly,
    83  		// but they don't seem to be as the behavior of the environment variable
    84  		// is to strip the scheme from the endpoint, but the underlying implementation
    85  		// needs the scheme to use the correct resolver.
    86  		//
    87  		// We'll just handle this in a special way and add the unix:// back to the endpoint.
    88  		endpoint = fmt.Sprintf("unix://%s", path.Join(u.Host, u.Path))
    89  	case "https":
    90  		secure = true
    91  		fallthrough
    92  	case "http":
    93  		endpoint = path.Join(u.Host, u.Path)
    94  	}
    95  	return endpoint, secure
    96  }
    97  
    98  func dockerSpanExporter(ctx context.Context, cli Cli) []sdktrace.TracerProviderOption {
    99  	endpoint, secure := dockerExporterOTLPEndpoint(cli)
   100  	if endpoint == "" {
   101  		return nil
   102  	}
   103  
   104  	opts := []otlptracegrpc.Option{
   105  		otlptracegrpc.WithEndpoint(endpoint),
   106  	}
   107  	if !secure {
   108  		opts = append(opts, otlptracegrpc.WithInsecure())
   109  	}
   110  
   111  	exp, err := otlptracegrpc.New(ctx, opts...)
   112  	if err != nil {
   113  		otel.Handle(err)
   114  		return nil
   115  	}
   116  	return []sdktrace.TracerProviderOption{sdktrace.WithBatcher(exp, sdktrace.WithExportTimeout(exportTimeout))}
   117  }
   118  
   119  func dockerMetricExporter(ctx context.Context, cli Cli) []sdkmetric.Option {
   120  	endpoint, secure := dockerExporterOTLPEndpoint(cli)
   121  	if endpoint == "" {
   122  		return nil
   123  	}
   124  
   125  	opts := []otlpmetricgrpc.Option{
   126  		otlpmetricgrpc.WithEndpoint(endpoint),
   127  	}
   128  	if !secure {
   129  		opts = append(opts, otlpmetricgrpc.WithInsecure())
   130  	}
   131  
   132  	exp, err := otlpmetricgrpc.New(ctx, opts...)
   133  	if err != nil {
   134  		otel.Handle(err)
   135  		return nil
   136  	}
   137  	return []sdkmetric.Option{sdkmetric.WithReader(newCLIReader(exp))}
   138  }