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 }