github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/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 return "", false 46 } 47 48 otelMap, ok := otelCfg.(map[string]any) 49 if !ok { 50 otel.Handle(errors.Errorf( 51 "unexpected type for field %q: %T (expected: %T)", 52 otelContextFieldName, 53 otelCfg, 54 otelMap, 55 )) 56 return "", false 57 } 58 59 // keys from https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/ 60 endpoint, _ = otelMap[otelExporterOTLPEndpoint].(string) 61 62 // Override with env var value if it exists AND IS SET 63 // (ignore otel defaults for this override when the key exists but is empty) 64 if override := os.Getenv(debugEnvVarPrefix + otelExporterOTLPEndpoint); override != "" { 65 endpoint = override 66 } 67 68 if endpoint == "" { 69 return "", false 70 } 71 72 // Parse the endpoint. The docker config expects the endpoint to be 73 // in the form of a URL to match the environment variable, but this 74 // option doesn't correspond directly to WithEndpoint. 75 // 76 // We pretend we're the same as the environment reader. 77 u, err := url.Parse(endpoint) 78 if err != nil { 79 otel.Handle(errors.Errorf("docker otel endpoint is invalid: %s", err)) 80 return "", false 81 } 82 83 switch u.Scheme { 84 case "unix": 85 // Unix sockets are a bit weird. OTEL seems to imply they 86 // can be used as an environment variable and are handled properly, 87 // but they don't seem to be as the behavior of the environment variable 88 // is to strip the scheme from the endpoint, but the underlying implementation 89 // needs the scheme to use the correct resolver. 90 // 91 // We'll just handle this in a special way and add the unix:// back to the endpoint. 92 endpoint = fmt.Sprintf("unix://%s", path.Join(u.Host, u.Path)) 93 case "https": 94 secure = true 95 fallthrough 96 case "http": 97 endpoint = path.Join(u.Host, u.Path) 98 } 99 return endpoint, secure 100 } 101 102 func dockerSpanExporter(ctx context.Context, cli Cli) []sdktrace.TracerProviderOption { 103 endpoint, secure := dockerExporterOTLPEndpoint(cli) 104 if endpoint == "" { 105 return nil 106 } 107 108 opts := []otlptracegrpc.Option{ 109 otlptracegrpc.WithEndpoint(endpoint), 110 } 111 if !secure { 112 opts = append(opts, otlptracegrpc.WithInsecure()) 113 } 114 115 exp, err := otlptracegrpc.New(ctx, opts...) 116 if err != nil { 117 otel.Handle(err) 118 return nil 119 } 120 return []sdktrace.TracerProviderOption{sdktrace.WithBatcher(exp, sdktrace.WithExportTimeout(exportTimeout))} 121 } 122 123 func dockerMetricExporter(ctx context.Context, cli Cli) []sdkmetric.Option { 124 endpoint, secure := dockerExporterOTLPEndpoint(cli) 125 if endpoint == "" { 126 return nil 127 } 128 129 opts := []otlpmetricgrpc.Option{ 130 otlpmetricgrpc.WithEndpoint(endpoint), 131 } 132 if !secure { 133 opts = append(opts, otlpmetricgrpc.WithInsecure()) 134 } 135 136 exp, err := otlpmetricgrpc.New(ctx, opts...) 137 if err != nil { 138 otel.Handle(err) 139 return nil 140 } 141 return []sdkmetric.Option{sdkmetric.WithReader(newCLIReader(exp))} 142 }