Files
go-cart-actor/pkg/telemetry/logs.go
T
mats e6f69f500a
Build and Publish / BuildAndDeployArm64 (push) Failing after 7s
Build and Publish / BuildAndDeployAmd64 (push) Failing after 7s
uniform
2026-07-20 00:13:24 +02:00

85 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package telemetry holds shared OpenTelemetry setup helpers used by the
// service main packages (cart, checkout, profile, …).
package telemetry
import (
"context"
"math/rand/v2"
"os"
"strconv"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
"go.opentelemetry.io/otel/sdk/log"
)
// LoggerConfig controls the optional OTLP log pipeline.
// The zero value disables logging (Enabled = false, SampleRatio = 0).
type LoggerConfig struct {
Enabled bool
SampleRatio float64 // 0.01.0, clamped by NewLoggerProvider
}
// LoggerConfigFromEnv reads OTEL_LOGS_ENABLED and OTEL_LOGS_SAMPLE_RATIO from
// the environment. Call this from main(); library consumers that want explicit
// control should construct LoggerConfig directly.
func LoggerConfigFromEnv() LoggerConfig {
var enabled bool
switch os.Getenv("OTEL_LOGS_ENABLED") {
case "1", "true", "TRUE", "True", "yes", "on":
enabled = true
}
ratio := 1.0
if v := os.Getenv("OTEL_LOGS_SAMPLE_RATIO"); v != "" {
if r, err := strconv.ParseFloat(v, 64); err == nil && r >= 0 && r <= 1 {
ratio = r
}
}
return LoggerConfig{Enabled: enabled, SampleRatio: ratio}
}
// NewLoggerProvider builds the OTLP log provider from cfg, or returns
// (nil, nil) when cfg.Enabled is false — in which case the caller MUST skip
// global.SetLoggerProvider and the shutdown registration.
//
// Use LoggerConfigFromEnv() to read the conventional OTEL_LOGS_ENABLED /
// OTEL_LOGS_SAMPLE_RATIO env vars, or construct LoggerConfig directly for
// explicit control (e.g. tests). Default OFF: the batch processor clones its
// record ring on every export, which profiling showed to be the dominant heap
// allocator in these services. Traces and metrics are always on regardless.
func NewLoggerProvider(ctx context.Context, cfg LoggerConfig) (*log.LoggerProvider, error) {
if !cfg.Enabled {
return nil, nil
}
exporter, err := otlploggrpc.New(ctx)
if err != nil {
return nil, err
}
var proc log.Processor = log.NewBatchProcessor(exporter)
if cfg.SampleRatio < 1.0 {
proc = &samplingProcessor{next: proc, ratio: cfg.SampleRatio}
}
return log.NewLoggerProvider(log.WithProcessor(proc)), nil
}
// samplingProcessor forwards a random `ratio` fraction of records to the wrapped
// processor and drops the rest before they reach the (allocation-heavy) batch
// ring. This is head sampling: cheap, stateless, and per-record independent.
type samplingProcessor struct {
next log.Processor
ratio float64
}
func (s *samplingProcessor) Enabled(ctx context.Context, param log.EnabledParameters) bool {
return s.next.Enabled(ctx, param)
}
func (s *samplingProcessor) OnEmit(ctx context.Context, record *log.Record) error {
if rand.Float64() >= s.ratio {
return nil // dropped by sampling
}
return s.next.OnEmit(ctx, record)
}
func (s *samplingProcessor) Shutdown(ctx context.Context) error { return s.next.Shutdown(ctx) }
func (s *samplingProcessor) ForceFlush(ctx context.Context) error { return s.next.ForceFlush(ctx) }