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 }