github.com/TBD54566975/ftl@v0.219.0/internal/observability/client.go (about)

     1  package observability
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"strings"
     8  
     9  	"go.opentelemetry.io/otel"
    10  	"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
    11  	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    12  	"go.opentelemetry.io/otel/sdk/metric"
    13  	"go.opentelemetry.io/otel/sdk/resource"
    14  	"go.opentelemetry.io/otel/sdk/trace"
    15  	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
    16  
    17  	"github.com/TBD54566975/ftl/internal/log"
    18  )
    19  
    20  const schemaURL = semconv.SchemaURL
    21  
    22  type ExportOTELFlag bool
    23  
    24  func (e *ExportOTELFlag) UnmarshalText(text []byte) error {
    25  	// Default behaviour of Kong is to use strconv.ParseBool, but we want to be less strict.
    26  	v := strings.ToLower(string(text))
    27  	*e = ExportOTELFlag(!(v == "false" || v == "0" || v == "no" || v == ""))
    28  	return nil
    29  }
    30  
    31  type Config struct {
    32  	LogLevel   log.Level      `default:"error" help:"OTEL log level." env:"FTL_O11Y_LOG_LEVEL"`
    33  	ExportOTEL ExportOTELFlag `help:"Export observability data to OTEL." env:"OTEL_EXPORTER_OTLP_ENDPOINT"`
    34  }
    35  
    36  func Init(ctx context.Context, serviceName, serviceVersion string, config Config) error {
    37  	logger := log.FromContext(ctx)
    38  	if !config.ExportOTEL {
    39  		logger.Tracef("OTEL export is disabled, set OTEL_EXPORTER_OTLP_ENDPOINT to enable")
    40  		return nil
    41  	}
    42  
    43  	logger.Debugf("OTEL is enabled, exporting to %s", os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT"))
    44  
    45  	otelLogger := NewOtelLogger(logger, config.LogLevel)
    46  	otel.SetLogger(otelLogger)
    47  	otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) { logger.Errorf(err, "OTEL") }))
    48  
    49  	res, err := resource.Merge(resource.Default(),
    50  		resource.NewWithAttributes(
    51  			schemaURL,
    52  			semconv.ServiceName(serviceName),
    53  			semconv.ServiceVersion(serviceVersion),
    54  		))
    55  	if err != nil {
    56  		return fmt.Errorf("failed to create OTEL resource: %w", err)
    57  	}
    58  
    59  	otelMetricExporter, err := otlpmetricgrpc.New(ctx)
    60  	if err != nil {
    61  		return fmt.Errorf("failed to create OTEL metric exporter: %w", err)
    62  	}
    63  
    64  	meterProvider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(otelMetricExporter)), metric.WithResource(res))
    65  	otel.SetMeterProvider(meterProvider)
    66  
    67  	otelTraceExporter, err := otlptracegrpc.New(ctx)
    68  	if err != nil {
    69  		return fmt.Errorf("failed to create OTEL trace exporter: %w", err)
    70  	}
    71  	traceProvider := trace.NewTracerProvider(trace.WithBatcher(otelTraceExporter), trace.WithResource(res))
    72  	otel.SetTracerProvider(traceProvider)
    73  
    74  	return nil
    75  }