otelx

package
v0.17.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: Apache-2.0 Imports: 30 Imported by: 0

Documentation

Overview

Package otelx bootstraps the OpenTelemetry SDK from the standard OTEL_* environment variables (https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/) and delivers traces, metrics, and logs over OTLP/HTTP using the JSON encoding — stdlib + the already-vendored OTEL SDK only, no exporter module dependencies. Generated apic servers call Setup() so telemetry works out of the box in cloud-native deployments; code-level overrides flow in through Config (WithOTEL) and win over env vars.

Index

Constants

View Source
const (
	SignalTraces  = "traces"
	SignalMetrics = "metrics"
	SignalLogs    = "logs"
)

Signal names accepted by SignalEndpoint / SignalHeaders / SignalTimeout.

Variables

View Source
var ErrExportFailed = errors.New("otelx: export failed")

ErrExportFailed wraps permanent (non-retryable) export failures.

View Source
var ErrUnsupportedProtocol = errors.New("otelx: unsupported OTLP protocol")

ErrUnsupportedProtocol is returned for OTEL_EXPORTER_OTLP_PROTOCOL values otelx cannot serve (grpc). http/protobuf downgrades to http/json with a warning — collectors accept both encodings on the same :4318 port.

Functions

func EncodeLogsRequest

func EncodeLogsRequest(res *resource.Resource, recs []LogRecord) ([]byte, error)

EncodeLogsRequest marshals records into an OTLP/JSON ExportLogsServiceRequest. EncodeLogsRequest errors on an empty batch (unlike EncodeMetricsRequest, see its doc comment).

func EncodeMetricsRequest

func EncodeMetricsRequest(rm *metricdata.ResourceMetrics) ([]byte, error)

EncodeMetricsRequest marshals collected metrics into an OTLP/JSON ExportMetricsServiceRequest. Unlike EncodeTraceRequest and EncodeLogsRequest, an empty batch (nil or zero ScopeMetrics) is not an error: it encodes to a valid request with an empty scopeMetrics array. This is the intended contract for periodic exporters, where "nothing to report this tick" is a routine no-op rather than a failure.

func EncodeTraceRequest

func EncodeTraceRequest(spans []sdktrace.ReadOnlySpan) ([]byte, error)

EncodeTraceRequest marshals spans into an OTLP/JSON ExportTraceServiceRequest. All spans come from one TracerProvider, so the first span's Resource is authoritative; spans are grouped by instrumentation scope (name, version), matching OTLP's ScopeSpans grouping key. EncodeTraceRequest errors on an empty batch (unlike EncodeMetricsRequest, see its doc comment).

func Fanout

func Fanout(handlers ...slog.Handler) slog.Handler

Fanout duplicates records to every handler (stdout + OTLP delivery).

func NewMetricExporter

func NewMetricExporter(cfg Config) (sdkmetric.Exporter, error)

NewMetricExporter builds the OTLP/HTTP JSON metric exporter for cfg.

func NewSpanExporter

func NewSpanExporter(cfg Config) (sdktrace.SpanExporter, error)

NewSpanExporter builds the OTLP/HTTP JSON span exporter for cfg.

func ParseHeaderList

func ParseHeaderList(raw string) map[string]string

ParseHeaderList parses a W3C-style "k=v,k2=v2" header list (values URL-decoded). Exposed for config-driven header env indirection.

func Setup

func Setup(ctx context.Context, cfgs ...Config) (*obsx.TelemetryProvider, func(context.Context) error, error)

Setup bootstraps the OTEL SDK: FromEnv() merged with each cfg override in order (code wins over env), then tracer/meter providers wired to the in-repo OTLP/HTTP JSON exporters. When the merged config is inactive it returns (nil, noop, nil) so callers can wire unconditionally. The returned shutdown func flushes both pipelines; call it on server exit.

Types

type Config

type Config struct {
	// Enabled forces the bootstrap on even without any OTLP endpoint env
	// var (the spec default endpoint http://localhost:4318 is then used).
	Enabled bool
	// Disabled mirrors OTEL_SDK_DISABLED and wins over Enabled/endpoints.
	Disabled bool

	ServiceName        string            // OTEL_SERVICE_NAME
	ResourceAttributes map[string]string // OTEL_RESOURCE_ATTRIBUTES

	Endpoint        string // OTEL_EXPORTER_OTLP_ENDPOINT (base URL; /v1/<signal> appended)
	TracesEndpoint  string // OTEL_EXPORTER_OTLP_TRACES_ENDPOINT (used verbatim)
	MetricsEndpoint string // OTEL_EXPORTER_OTLP_METRICS_ENDPOINT (verbatim)
	LogsEndpoint    string // OTEL_EXPORTER_OTLP_LOGS_ENDPOINT (verbatim)

	// Protocol is the OTLP transport encoding. Native support is
	// "http/json"; "http/protobuf" is accepted with a downgrade warning
	// (collectors accept both on :4318); "grpc" is rejected by Setup.
	Protocol string // OTEL_EXPORTER_OTLP_PROTOCOL

	Headers        map[string]string // OTEL_EXPORTER_OTLP_HEADERS (W3C k=v list, values URL-decoded)
	TracesHeaders  map[string]string // OTEL_EXPORTER_OTLP_TRACES_HEADERS
	MetricsHeaders map[string]string // OTEL_EXPORTER_OTLP_METRICS_HEADERS
	LogsHeaders    map[string]string // OTEL_EXPORTER_OTLP_LOGS_HEADERS

	Timeout        time.Duration // OTEL_EXPORTER_OTLP_TIMEOUT (default 10s)
	TracesTimeout  time.Duration // OTEL_EXPORTER_OTLP_TRACES_TIMEOUT
	MetricsTimeout time.Duration // OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
	LogsTimeout    time.Duration // OTEL_EXPORTER_OTLP_LOGS_TIMEOUT

	Compression string // OTEL_EXPORTER_OTLP_COMPRESSION: "gzip" or "none"

	CACertFile     string // OTEL_EXPORTER_OTLP_CERTIFICATE
	ClientCertFile string // OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE
	ClientKeyFile  string // OTEL_EXPORTER_OTLP_CLIENT_KEY

	TracesExporter  string // OTEL_TRACES_EXPORTER: "otlp" (default) | "none"
	MetricsExporter string // OTEL_METRICS_EXPORTER: "otlp" (default) | "none"
	LogsExporter    string // OTEL_LOGS_EXPORTER: "otlp" (default) | "none"

	TracesSampler    string // OTEL_TRACES_SAMPLER (default parentbased_always_on)
	TracesSamplerArg string // OTEL_TRACES_SAMPLER_ARG

	Propagators []string // OTEL_PROPAGATORS (default tracecontext,baggage)

	MetricExportInterval time.Duration // OTEL_METRIC_EXPORT_INTERVAL (default 60s)
	MetricExportTimeout  time.Duration // OTEL_METRIC_EXPORT_TIMEOUT (default 30s)

	BSPScheduleDelay      time.Duration // OTEL_BSP_SCHEDULE_DELAY (default 5s)
	BSPExportTimeout      time.Duration // OTEL_BSP_EXPORT_TIMEOUT (default 30s)
	BSPMaxQueueSize       int           // OTEL_BSP_MAX_QUEUE_SIZE (default 2048)
	BSPMaxExportBatchSize int           // OTEL_BSP_MAX_EXPORT_BATCH_SIZE (default 512)

	BLRPScheduleDelay      time.Duration // OTEL_BLRP_SCHEDULE_DELAY (default 1s)
	BLRPMaxQueueSize       int           // OTEL_BLRP_MAX_QUEUE_SIZE (default 2048)
	BLRPMaxExportBatchSize int           // OTEL_BLRP_MAX_EXPORT_BATCH_SIZE (default 512)
}

Config is the effective OTEL bootstrap configuration. The zero value is inactive; FromEnv() fills it from OTEL_* env vars; code overrides are applied with Merge (non-zero fields win). All duration env vars are integer milliseconds per the OTEL spec.

func Defaults

func Defaults() Config

Defaults returns ONLY the OTEL spec default configuration — no env or config-file overlay. It is the immutable base every Resolve starts from. The zero-valued fields (endpoints, headers, service name, sampler arg, per-signal timeouts) are intentionally absent: a default of "" or 0 means "not set", so overlaying a config-file base via Merge never clobbers a configured value with a default.

func EnvOverrides

func EnvOverrides() Config

EnvOverrides reads the OTEL_* environment variables into a SPARSE Config: only fields whose env var is actually set (non-empty, and for durations/ints parseable and > 0) are populated. No spec defaults are baked in, so the result can overlay a config-file base via Merge WITHOUT clobbering configured values with defaults — an unset env var leaves the base intact. Disabled is true only when OTEL_SDK_DISABLED parses to a true boolean.

func EnvOverridesFunc

func EnvOverridesFunc(getenv func(string) string) Config

EnvOverridesFunc is EnvOverrides with an injectable getenv (testability).

func FromEnv

func FromEnv() Config

FromEnv reads the OTEL_* environment variables into a Config, with spec defaults filled in for anything unset.

func FromEnvFunc

func FromEnvFunc(getenv func(string) string) Config

FromEnvFunc is FromEnv with an injectable getenv (testability). It is exactly Defaults() overlaid with the env vars that are set — the defaults fill the gaps, the env vars win where present.

func Resolve

func Resolve(configBase Config, code *Config) Config

Resolve computes the effective OTEL config for a generated server from the full precedence chain: spec defaults < config-file block < set env vars < code override. configBase is the observability.otel config block; code is the WithOTEL override (nil when unset).

The chain is:

a. start  := Defaults().Merge(configBase)   // config beats defaults
b. env    := EnvOverrides(); merged := start.Merge(env)  // set env vars beat config
c. code   overlays env+config, EXCEPT the OTEL_SDK_DISABLED kill-switch.

The one asymmetry is disable precedence: a config-level disable (observability.otel.enabled:false → configBase.Disabled) IS overridable in code (WithOTEL re-enables it), but the OTEL_SDK_DISABLED environment kill-switch is NOT — once an operator sets it, no code path re-enables telemetry. Cloud-native operators need a no-rebuild emergency stop that the application binary cannot override.

func ResolveFunc

func ResolveFunc(getenv func(string) string, configBase Config, code *Config) Config

ResolveFunc is Resolve with an injectable getenv (testability).

func (Config) Active

func (c Config) Active() bool

Active reports whether the bootstrap should run: not disabled, and either forced on (Enabled) or given at least one OTLP endpoint.

func (Config) LogsActive

func (c Config) LogsActive() bool

LogsActive reports whether OTLP log delivery should run.

func (Config) Merge

func (c Config) Merge(o Config) Config

Merge returns c overlaid with the non-zero fields of o. Booleans are OR-combined (an override can force Enabled/Disabled on but not off — force-off is expressed with Disabled=true, which always wins downstream).

func (Config) SignalEndpoint

func (c Config) SignalEndpoint(signal string) string

SignalEndpoint resolves the full OTLP/HTTP URL for a signal per the OTLP spec: a per-signal endpoint is used verbatim; otherwise the base endpoint (default http://localhost:4318) gets /v1/<signal> appended.

func (Config) SignalHeaders

func (c Config) SignalHeaders(signal string) map[string]string

SignalHeaders merges the base headers with per-signal headers (per-signal wins on key collision).

func (Config) SignalTimeout

func (c Config) SignalTimeout(signal string) time.Duration

SignalTimeout resolves the per-signal export timeout (falls back to the base Timeout, then 10s).

type LogHandler

type LogHandler struct {
	// contains filtered or unexported fields
}

LogHandler is a batching slog.Handler that delivers records to an OTLP logs endpoint (log aggregation delivery). It never blocks the logging caller: records queue up to BLRPMaxQueueSize and overflow is dropped and counted. Callers MUST wrap it (typically via Fanout with the stdout handler) inside obsx.NewRedactingHandler so shipped records are redacted.

func NewLogHandler

func NewLogHandler(cfg Config) (*LogHandler, error)

NewLogHandler builds the OTLP log delivery handler from an already-merged Config (env + config-file + code overrides applied by the caller).

func (*LogHandler) Dropped

func (h *LogHandler) Dropped() int64

Dropped reports how many records were discarded due to queue overflow.

func (*LogHandler) Enabled

func (h *LogHandler) Enabled(context.Context, slog.Level) bool

Enabled ships every level; level filtering belongs to the caller's logger configuration, not the delivery pipe.

func (*LogHandler) ExportErrors added in v0.17.0

func (h *LogHandler) ExportErrors() int64

ExportErrors reports how many background-loop flush attempts failed (N-05): an unreachable collector, a TLS handshake failure, or a terminal non-2xx from Uploader.Upload. Distinct from Dropped, which only counts records discarded on queue overflow — a nonzero ExportErrors count with a perpetually low Dropped count is the signature of total, sustained export failure rather than a transient burst the queue could not absorb.

func (*LogHandler) Handle

func (h *LogHandler) Handle(ctx context.Context, r slog.Record) error

Handle converts and enqueues one record. Never blocks; drops on overflow.

func (*LogHandler) Shutdown

func (h *LogHandler) Shutdown(ctx context.Context) error

Shutdown flushes queued records and stops the background loop.

func (*LogHandler) WithAttrs

func (h *LogHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a handler that includes attrs on every record. The bound attrs are rendered to their final OTLP key/value strings ONCE here — group prefix applied, slog.LogValuer resolved (recursively into groups), value stringified — so Handle need only copy them, never re-render, per record (perf#2). The pre-set attrs keep the group prefix in effect at this point in the WithGroup chain, matching how they were established. Resolving here also honors secret:true redaction on bound attrs, including a LogValuer nested inside another LogValuer's group (appsec F-1, defense-in-depth for direct handler use).

func (*LogHandler) WithGroup

func (h *LogHandler) WithGroup(name string) slog.Handler

WithGroup returns a handler that dot-prefixes subsequent attr keys. The group prefix is precomputed here so Handle never rebuilds it per record (perf#2).

type LogRecord

type LogRecord struct {
	TimeUnixNano   uint64
	SeverityNumber int
	SeverityText   string
	Body           string
	Attrs          map[string]string
	TraceID        string
	SpanID         string
}

LogRecord is the minimal OTLP log record otelx ships (built from slog records by LogHandler — see logs.go).

type Uploader

type Uploader struct {
	// contains filtered or unexported fields
}

Uploader POSTs OTLP/JSON payloads for one signal.

func NewUploader

func NewUploader(cfg Config, signal string) (*Uploader, error)

NewUploader builds the signal-specific OTLP/HTTP client from cfg, loading any TLS material referenced by the OTEL_EXPORTER_OTLP_CERTIFICATE / _CLIENT_CERTIFICATE / _CLIENT_KEY env vars. Fails closed: a configured but unreadable cert file is a boot error, not a silent plaintext fallback. Every path is routed through obsx.ResolveSensitivePath first (N-P3-3) — the single shared symlink/regular-file guard the rotating log sink and the generated server's TLS/secret loaders also use — so a pre-placed symlink cannot redirect the read to an unintended target.

func (*Uploader) Upload

func (u *Uploader) Upload(ctx context.Context, body []byte) error

Upload POSTs one OTLP/JSON payload, retrying bounded times on retryable statuses (honoring Retry-After, capped). Non-2xx terminal statuses wrap ErrExportFailed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL