Something went wrong. Try again.
An ATProto Powered Reputation Network
Something went wrong. Try again.
3.3 kB · 94 lines
Go
at master
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495package telemetry
import ( "context" "errors" "log/slog" "net/url" "os" "time"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/trace")
const ( endpointEnv = "PROVENANCE_TRACE_ENDPOINT" exportTimeout = 2 * time.Second flushTimeout = 3 * time.Second queueSize = 1024 exportBatchSize = 128 batchDelay = time.Second)
// Opt in explicitly. Export errors never contain collector credentials or response bodies.func Setup(ctx context.Context) (func(), error) { endpoint := os.Getenv(endpointEnv) if endpoint == "" { return func() {}, nil } u, err := url.Parse(endpoint) if err != nil || u.Host == "" || (u.Scheme != "http" && u.Scheme != "https") || u.User != nil || u.RawQuery != "" || u.Fragment != "" { return nil, errors.New("PROVENANCE_TRACE_ENDPOINT requires an HTTP URL without credentials, query, or fragment") } exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpointURL(endpoint), otlptracehttp.WithTimeout(exportTimeout), otlptracehttp.WithRetry(otlptracehttp.RetryConfig{Enabled: false})) if err != nil { return nil, errors.New("trace exporter setup failed") } otel.SetErrorHandler(otel.ErrorHandlerFunc(func(error) { slog.Debug("trace export failed") })) provider := newProvider(exporter) otel.SetTracerProvider(provider) return func() { ctx, cancel := context.WithTimeout(context.Background(), flushTimeout) defer cancel() if err := provider.Shutdown(ctx); err != nil { slog.Warn("trace shutdown incomplete") } }, nil}
func newProvider(exporter sdktrace.SpanExporter) *sdktrace.TracerProvider { return sdktrace.NewTracerProvider( sdktrace.WithResource(resource.NewSchemaless(attribute.String("service.name", "provenance-appview"))), sdktrace.WithBatcher(exporter, sdktrace.WithMaxQueueSize(queueSize), sdktrace.WithMaxExportBatchSize(exportBatchSize), sdktrace.WithBatchTimeout(batchDelay), sdktrace.WithExportTimeout(exportTimeout)), )}
// Log operation starts so that an unfinished span remains visible during an I/O stall.func Start(ctx context.Context, name string, level slog.Level, attrs ...attribute.KeyValue) (context.Context, func(error)) { options := []trace.SpanStartOption{trace.WithAttributes(attrs...)} if name == "replay.batch" { // Keep each batch independent. A full archive must not create one unbounded trace. options = append(options, trace.WithNewRoot()) } ctx, span := otel.Tracer("provenance").Start(ctx, name, options...) if span.IsRecording() { slog.Log(ctx, level, "trace operation started", "operation", name, "trace_id", span.SpanContext().TraceID().String(), "span_id", span.SpanContext().SpanID().String()) } return ctx, func(err error) { if err != nil { // Error messages can contain account identifiers or SQL parameters. span.SetStatus(codes.Error, "operation failed") } span.End() }}
func LogAttrs(ctx context.Context) []any { sc := trace.SpanContextFromContext(ctx) if !sc.IsValid() { return nil } return []any{"trace_id", sc.TraceID().String(), "span_id", sc.SpanID().String()}}