Skip to content

Configuration keys

What this module reads, and what it does not

This module reads no configuration and no environment of its own. It exports the key names and the merge rule; loading YAML, flags or environment into those structs is the caller's job. Nothing in otelcore, tracing, metrics or logs calls os.Getenv — the only environment reading happens inside the OpenTelemetry SDK, and that is covered in Environment variables.

That means a wrong key name is your loader's error, not this module's. The names below exist so every service in the toolkit spells them the same way.

The telemetry.* key tree

The prefix and the three signal names are exported constants, so a loader can build the keys without string literals:

otelcore.Root          // "telemetry"
otelcore.SignalTracing // "tracing"
otelcore.SignalMetrics // "metrics"
otelcore.SignalLogs    // "logs"

Shared keys sit directly under telemetry; each signal takes the same keys again under telemetry.<signal>, plus enabled:

telemetry:
  endpoint: https://collector.internal:4318
  headers:
    authorization: Bearer <token>
  insecure: false

  tracing:
    enabled: true
    endpoint: https://tail-sampler.internal:4318
  metrics:
    enabled: true
  logs:
    enabled: false

Config, SignalConfig and Settings carry mapstructure, yaml and json tags on every field, so the same struct decodes from any of the three.

Shared keys: telemetry.*

These populate otelcore.Config.

Key Type Go field Default If it is wrong
telemetry.endpoint string Config.Endpoint "" An empty value is valid and means "fall back to OTEL_EXPORTER_OTLP_*". A non-empty value that is not an http/https URL with a host fails at NewProvider, not at export — see Errors.
telemetry.headers map[string]string Config.Headers nil Sent verbatim as exporter HTTP headers. An empty map is indistinguishable from unset after resolution (see Empty maps). A non-empty map replaces OTEL_EXPORTER_OTLP_HEADERS entirely rather than merging with it.
telemetry.insecure bool Config.Insecure false true forces plaintext OTLP even against an https:// endpoint. false does not force TLS — OTEL_EXPORTER_OTLP_INSECURE=true can still downgrade the connection.

There is no shared telemetry.enabled. Enabling is per-signal only, deliberately: see ResolveSettings never inherits Enabled.

Per-signal keys: telemetry.<signal>.*

These populate otelcore.SignalConfig, where <signal> is tracing, metrics or logs.

Key Type Go field Default If it is wrong
telemetry.<signal>.enabled bool SignalConfig.Enabled false Nothing in this module reads it. NewProvider builds a provider whether or not Enabled is true; honouring it is the caller's job. See Enabled is yours to honour.
telemetry.<signal>.endpoint string SignalConfig.Endpoint inherits telemetry.endpoint Only overrides the shared value when the matching SignalOverrides.Endpoint bit is set. Same validation as the shared key.
telemetry.<signal>.headers map[string]string SignalConfig.Headers inherits telemetry.headers Only overrides when SignalOverrides.Headers is set, and then it replaces the shared map rather than merging into it.
telemetry.<signal>.insecure bool SignalConfig.Insecure inherits telemetry.insecure Only overrides when SignalOverrides.Insecure is set.

Which fields inherit, and which do not

ResolveSettings(shared, signal, overrides) produces the Settings a provider factory consumes:

Settings field Comes from
Enabled signal.Enabledalways, never inherited from shared config
Endpoint signal.Endpoint when overrides.Endpoint is true, otherwise shared.Endpoint
Headers signal.Headers when overrides.Headers is true, otherwise shared.Headers (copied, not aliased)
Insecure signal.Insecure when overrides.Insecure is true, otherwise shared.Insecure

SignalOverrides has no Enabled bit, because Enabled has nothing to inherit from.

What SignalOverrides is for

A zero SignalConfig.Endpoint is ambiguous: it could mean the operator set it to empty or the operator did not set it at all. Go's zero values cannot tell those apart, so ResolveSettings takes an explicit mask:

overrides := otelcore.SignalOverrides{
    Endpoint: cfg.IsSet("telemetry.tracing.endpoint"),
    Headers:  cfg.IsSet("telemetry.tracing.headers"),
    Insecure: cfg.IsSet("telemetry.tracing.insecure"),
}

You define what "set" means — a key present in the file, a changed flag, a non-empty environment variable. A field with its bit false can never clobber the shared value with a zero.

Getting the mask wrong is silent, not loud. Leaving Endpoint: false while supplying a per-signal endpoint means the signal quietly exports to the shared collector.

Why an empty headers map behaves as unset

ResolveSettings copies header maps through cloneHeaders, which returns nil for a map of length zero. After resolution, headers: {} and an absent headers key are the same value. This matters in one place: it means you cannot use an empty per-signal map to clear inherited shared headers. Setting SignalOverrides.Headers: true with an empty SignalConfig.Headers gives the signal nil headers, which is the clearing behaviour you want — but only because the override bit, not the map, does the work.

The copy is also why mutating the map you passed in afterwards does not change a resolved Settings.

Enabled is yours to honour

Settings.Enabled is resolved and carried, and then no code in this module looks at it. tracing.NewProvider, metrics.NewProvider and logs.NewProvider each build an exporter and a provider regardless of its value.

Skip the call yourself:

if settings.Enabled {
    tp, err := tracing.NewProvider(ctx, res, settings)
    // ...
}

Building a provider for a disabled signal is not harmless. The metric provider starts a background export goroutine on a 60-second ticker, and the trace and log providers start batch processors, all of which will try to reach a collector.

Resource attributes are not configuration keys

otelcore.Resource(name, version) takes its two values as arguments, not from the telemetry.* tree. It sets exactly service.name and service.version, against OpenTelemetry semantic-convention schema https://opentelemetry.io/schemas/1.26.0.

Anything else you want on the resource — deployment.environment, service.instance.id, host or process attributes — has to arrive through OTEL_RESOURCE_ATTRIBUTES, which the SDK merges underneath these two. See Environment variables.