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
- Variables
- func EncodeLogsRequest(res *resource.Resource, recs []LogRecord) ([]byte, error)
- func EncodeMetricsRequest(rm *metricdata.ResourceMetrics) ([]byte, error)
- func EncodeTraceRequest(spans []sdktrace.ReadOnlySpan) ([]byte, error)
- func Fanout(handlers ...slog.Handler) slog.Handler
- func NewMetricExporter(cfg Config) (sdkmetric.Exporter, error)
- func NewSpanExporter(cfg Config) (sdktrace.SpanExporter, error)
- func ParseHeaderList(raw string) map[string]string
- func Setup(ctx context.Context, cfgs ...Config) (*obsx.TelemetryProvider, func(context.Context) error, error)
- type Config
- func Defaults() Config
- func EnvOverrides() Config
- func EnvOverridesFunc(getenv func(string) string) Config
- func FromEnv() Config
- func FromEnvFunc(getenv func(string) string) Config
- func Resolve(configBase Config, code *Config) Config
- func ResolveFunc(getenv func(string) string, configBase Config, code *Config) Config
- type LogHandler
- func (h *LogHandler) Dropped() int64
- func (h *LogHandler) Enabled(context.Context, slog.Level) bool
- func (h *LogHandler) ExportErrors() int64
- func (h *LogHandler) Handle(ctx context.Context, r slog.Record) error
- func (h *LogHandler) Shutdown(ctx context.Context) error
- func (h *LogHandler) WithAttrs(attrs []slog.Attr) slog.Handler
- func (h *LogHandler) WithGroup(name string) slog.Handler
- type LogRecord
- type Uploader
Constants ¶
const ( SignalTraces = "traces" SignalMetrics = "metrics" SignalLogs = "logs" )
Signal names accepted by SignalEndpoint / SignalHeaders / SignalTimeout.
Variables ¶
var ErrExportFailed = errors.New("otelx: export failed")
ErrExportFailed wraps permanent (non-retryable) export failures.
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 ¶
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 NewMetricExporter ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
ResolveFunc is Resolve with an injectable getenv (testability).
func (Config) Active ¶
Active reports whether the bootstrap should run: not disabled, and either forced on (Enabled) or given at least one OTLP endpoint.
func (Config) LogsActive ¶
LogsActive reports whether OTLP log delivery should run.
func (Config) Merge ¶
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 ¶
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 ¶
SignalHeaders merges the base headers with per-signal headers (per-signal wins on key collision).
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 ¶
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 ¶
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).
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 ¶
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.