2023-06-08 22:46:07 +02:00
|
|
|
/*
|
|
|
|
Copyright 2020 Docker Compose CLI authors
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package tracing
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2023-06-15 18:05:57 +02:00
|
|
|
"github.com/docker/compose/v2/internal"
|
|
|
|
"go.opentelemetry.io/otel/attribute"
|
|
|
|
|
2023-06-08 22:46:07 +02:00
|
|
|
"github.com/docker/cli/cli/command"
|
|
|
|
"github.com/moby/buildkit/util/tracing/detect"
|
|
|
|
_ "github.com/moby/buildkit/util/tracing/detect/delegated" //nolint:blank-imports
|
|
|
|
_ "github.com/moby/buildkit/util/tracing/env" //nolint:blank-imports
|
|
|
|
"go.opentelemetry.io/otel"
|
|
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
|
|
|
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
|
|
|
"go.opentelemetry.io/otel/propagation"
|
|
|
|
"go.opentelemetry.io/otel/sdk/resource"
|
|
|
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
2023-12-18 18:14:08 +01:00
|
|
|
semconv "go.opentelemetry.io/otel/semconv/v1.19.0"
|
2023-06-08 22:46:07 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
detect.ServiceName = "compose"
|
|
|
|
// do not log tracing errors to stdio
|
|
|
|
otel.SetErrorHandler(skipErrors{})
|
|
|
|
}
|
|
|
|
|
|
|
|
var Tracer = otel.Tracer("compose")
|
|
|
|
|
|
|
|
// OTLPConfig contains the necessary values to initialize an OTLP client
|
|
|
|
// manually.
|
|
|
|
//
|
|
|
|
// This supports a minimal set of options based on what is necessary for
|
|
|
|
// automatic OTEL configuration from Docker context metadata.
|
|
|
|
type OTLPConfig struct {
|
|
|
|
Endpoint string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShutdownFunc flushes and stops an OTEL exporter.
|
|
|
|
type ShutdownFunc func(ctx context.Context) error
|
|
|
|
|
|
|
|
// envMap is a convenience type for OS environment variables.
|
|
|
|
type envMap map[string]string
|
|
|
|
|
|
|
|
func InitTracing(dockerCli command.Cli) (ShutdownFunc, error) {
|
|
|
|
// set global propagator to tracecontext (the default is no-op).
|
|
|
|
otel.SetTextMapPropagator(propagation.TraceContext{})
|
|
|
|
|
|
|
|
if v, _ := strconv.ParseBool(os.Getenv("COMPOSE_EXPERIMENTAL_OTEL")); !v {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return InitProvider(dockerCli)
|
|
|
|
}
|
|
|
|
|
|
|
|
func InitProvider(dockerCli command.Cli) (ShutdownFunc, error) {
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
var errs []error
|
|
|
|
var exporters []sdktrace.SpanExporter
|
|
|
|
|
|
|
|
envClient, otelEnv := traceClientFromEnv()
|
|
|
|
if envClient != nil {
|
|
|
|
if envExporter, err := otlptrace.New(ctx, envClient); err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
} else if envExporter != nil {
|
|
|
|
exporters = append(exporters, envExporter)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if dcClient, err := traceClientFromDockerContext(dockerCli, otelEnv); err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
} else if dcClient != nil {
|
|
|
|
if dcExporter, err := otlptrace.New(ctx, dcClient); err != nil {
|
|
|
|
errs = append(errs, err)
|
|
|
|
} else if dcExporter != nil {
|
|
|
|
exporters = append(exporters, dcExporter)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(errs) != 0 {
|
|
|
|
return nil, errors.Join(errs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
res, err := resource.New(
|
|
|
|
ctx,
|
|
|
|
resource.WithAttributes(
|
|
|
|
semconv.ServiceName("compose"),
|
2023-06-15 18:05:57 +02:00
|
|
|
semconv.ServiceVersion(internal.Version),
|
|
|
|
attribute.String("docker.context", dockerCli.CurrentContext()),
|
2023-06-08 22:46:07 +02:00
|
|
|
),
|
|
|
|
)
|
|
|
|
if err != nil {
|
2023-09-26 00:57:12 +02:00
|
|
|
return nil, fmt.Errorf("failed to create resource: %w", err)
|
2023-06-08 22:46:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
muxExporter := MuxExporter{exporters: exporters}
|
|
|
|
sp := sdktrace.NewSimpleSpanProcessor(muxExporter)
|
|
|
|
tracerProvider := sdktrace.NewTracerProvider(
|
|
|
|
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
|
|
|
sdktrace.WithResource(res),
|
|
|
|
sdktrace.WithSpanProcessor(sp),
|
|
|
|
)
|
|
|
|
otel.SetTracerProvider(tracerProvider)
|
|
|
|
|
|
|
|
// Shutdown will flush any remaining spans and shut down the exporter.
|
|
|
|
return tracerProvider.Shutdown, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// traceClientFromEnv creates a GRPC OTLP client based on OS environment
|
|
|
|
// variables.
|
|
|
|
//
|
|
|
|
// https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/
|
|
|
|
func traceClientFromEnv() (otlptrace.Client, envMap) {
|
|
|
|
hasOtelEndpointInEnv := false
|
|
|
|
otelEnv := make(map[string]string)
|
|
|
|
for _, kv := range os.Environ() {
|
|
|
|
k, v, ok := strings.Cut(kv, "=")
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.HasPrefix(k, "OTEL_") {
|
|
|
|
otelEnv[k] = v
|
|
|
|
if strings.HasSuffix(k, "ENDPOINT") {
|
|
|
|
hasOtelEndpointInEnv = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !hasOtelEndpointInEnv {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
client := otlptracegrpc.NewClient()
|
|
|
|
return client, otelEnv
|
|
|
|
}
|