Files
mats 528c59bfd3
Build and Publish / BuildAndDeployAmd64 (push) Failing after 4s
Build and Publish / BuildAndDeployArm64 (push) Failing after 6s
cart and checkout
2026-06-27 19:49:00 +02:00

89 lines
2.7 KiB
Go
Raw Permalink 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"
)
// NewLoggerProvider builds the OTLP log provider from the environment, or returns
// (nil, nil) when logs are disabled — in which case the caller MUST skip
// global.SetLoggerProvider and the shutdown registration.
//
// Env:
//
// OTEL_LOGS_ENABLED "1"/"true"/"yes"/"on" to enable the OTLP log pipeline.
// 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 of this flag.
// OTEL_LOGS_SAMPLE_RATIO fraction of records to export, 0.01.0 (default 1.0).
// e.g. 0.1 keeps ~10% of logs. Dropped records never reach
// the batch ring, so allocation churn falls ~proportionally.
func NewLoggerProvider(ctx context.Context) (*log.LoggerProvider, error) {
if !logsEnabled() {
return nil, nil
}
exporter, err := otlploggrpc.New(ctx)
if err != nil {
return nil, err
}
var proc log.Processor = log.NewBatchProcessor(exporter)
if ratio := sampleRatio(); ratio < 1.0 {
proc = &samplingProcessor{next: proc, ratio: ratio}
}
return log.NewLoggerProvider(log.WithProcessor(proc)), nil
}
func logsEnabled() bool {
switch os.Getenv("OTEL_LOGS_ENABLED") {
case "1", "true", "TRUE", "True", "yes", "on":
return true
default:
return false
}
}
func sampleRatio() float64 {
v := os.Getenv("OTEL_LOGS_SAMPLE_RATIO")
if v == "" {
return 1.0
}
r, err := strconv.ParseFloat(v, 64)
if err != nil || r < 0 {
return 1.0
}
if r > 1 {
return 1.0
}
return r
}
// 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) }