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  }