otlp

package
v1.14.1 Latest Latest
Warning

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

Go to latest
Published: Jun 17, 2026 License: Apache-2.0 Imports: 46 Imported by: 0

Documentation

Index

Constants

View Source
const DefaultScopeName = "dash0-cli"

DefaultScopeName is the default instrumentation scope name used by the CLI.

Variables

This section is empty.

Functions

func AnyValueToString added in v1.5.0

func AnyValueToString(v *dash0api.AnyValue) string

AnyValueToString converts an AnyValue to its string representation.

func DefaultScopeVersion

func DefaultScopeVersion() string

DefaultScopeVersion returns the default scope version (the CLI version).

func DerefHexBytes added in v1.5.0

func DerefHexBytes(b *[]byte) string

DerefHexBytes returns the hex encoding of a *[]byte, or "" if nil.

func DerefInt64 added in v1.5.0

func DerefInt64(i *int64) string

DerefInt64 returns the string representation of a *int64, or "" if nil.

func DerefString added in v1.5.0

func DerefString(s *string) string

DerefString returns the value of a *string, or "" if nil.

func FindAttribute added in v1.5.0

func FindAttribute(attrs []dash0api.KeyValue, key string) string

FindAttribute looks up key in attrs and returns its string representation. Returns "" if the key is not found or the value is empty.

func InstallWidthRefresh added in v1.14.0

func InstallWidthRefresh(ctx context.Context, ch chan<- struct{})

InstallWidthRefresh subscribes to SIGWINCH on Unix and writes to ch every time the terminal is resized. The writer can use this to redraw the stats line immediately on resize instead of waiting for the next 1- second stats tick.

Cleans up the signal handler when ctx is done. Non-blocking sends on ch: if the writer hasn't drained the previous resize event, the new one is dropped — the next tick's normal width refresh will pick up the latest size anyway.

func MergeAttributes added in v1.5.0

func MergeAttributes(attrSlices ...[]dash0api.KeyValue) []dash0api.KeyValue

MergeAttributes merges multiple attribute slices into a single slice. Later slices take precedence over earlier ones for duplicate keys.

func NewOtlpCmd added in v1.14.0

func NewOtlpCmd() *cobra.Command

NewOtlpCmd creates the otlp parent command. The proxy subcommand exposes a local OTLP forwarder that brokers traffic from local OpenTelemetry SDKs to the active Dash0 profile.

func ParseKeyValuePairs

func ParseKeyValuePairs(pairs []string) (map[string]string, error)

ParseKeyValuePairs parses a slice of "key=value" strings into a map.

func ParseSpanID

func ParseSpanID(s string) (pcommon.SpanID, error)

ParseSpanID parses a 16 hex character string into a pcommon.SpanID.

func ParseTraceID

func ParseTraceID(s string) (pcommon.TraceID, error)

ParseTraceID parses a 32 hex character string into a pcommon.TraceID.

func RenderLogs added in v1.14.0

func RenderLogs(ld plog.Logs) string

RenderLogs returns the multi-line debug-exporter-style rendering for a pdata Logs batch. An empty batch (zero ResourceLogs) returns the empty string so the caller can skip writing.

func RenderMetrics added in v1.14.0

func RenderMetrics(md pmetric.Metrics) string

RenderMetrics returns the multi-line debug-exporter-style rendering for a pdata Metrics batch.

func RenderTraces added in v1.14.0

func RenderTraces(td ptrace.Traces) string

RenderTraces returns the multi-line debug-exporter-style rendering for a pdata Traces batch.

func ResolveScopeDefaults

func ResolveScopeDefaults(cmd *cobra.Command, scopeName *string, scopeVersion *string)

ResolveScopeDefaults clears the default value for scope-name or scope-version when only the other flag is explicitly set. This avoids pairing a custom scope name with the dash0-cli version (or vice versa).

func SetTailColorEnabled added in v1.14.0

func SetTailColorEnabled(enabled bool)

SetTailColorEnabled is called by main() once `dashcolor.NoColor` has been resolved. Pass true to enable semantic coloring of `--tail` output. Safe to call before or after the proxy starts; the renderer reads the variable each time it formats a record.

func SeverityNumberToRange

func SeverityNumberToRange(n int32) string

SeverityNumberToRange maps an OTel severity number (0–24) to its severity range. Numbers outside the defined bands return a formatted string like "SEVERITY_25".

func Sparkline added in v1.14.0

func Sparkline(series []float64) string

Sparkline returns a string of len(series) characters from the sparklineGlyphs ramp, normalized against the maximum non-zero value in the series. Zero and negative values map to the lowest glyph; NaN and Inf are treated as zero so a single ill-formed sample doesn't blank the whole rendering.

Empty input returns the empty string so callers can suppress rendering without an additional length check.

Types

type Decorator added in v1.14.0

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

Decorator upserts user-provided resource attributes, scope attributes, scope name, and scope version onto every pdata batch flowing through the proxy before it is forwarded upstream. The mirror flags (`--resource-attribute`, `--scope-attribute`, `--scope-name`, `--scope-version`) on the proxy command match the same flags on `dash0 logs send` and `dash0 spans send` so the user experience is consistent across signal-authoring and signal-forwarding workflows.

All upserts are no-ops when the corresponding flag was not provided — the proxy does NOT default scope name/version to `dash0-cli` / CLI version the way the send commands do, because the inbound pdata already carries the SDK's own instrumentation-library identity and silently overwriting it would erase useful diagnostic context.

Decoration happens in the worker pool, after the consumer has returned 200 to the SDK. The pdata is therefore mutated only by the proxy's own goroutines; the receiver-side contract reported by ProxyConsumer.Capabilities is unaffected.

func NewDecorator added in v1.14.0

func NewDecorator(
	resourceAttrs, scopeAttrs map[string]string,
	scopeName, scopeVersion string,
	logAttrs, spanAttrs, metricAttrs map[string]string,
) *Decorator

NewDecorator returns a Decorator with the supplied user-controlled upserts. Any zero-valued field is a no-op when Decorate* is called.

func (*Decorator) DecorateLogs added in v1.14.0

func (d *Decorator) DecorateLogs(ld plog.Logs)

DecorateLogs upserts the configured fields onto each ResourceLogs, each ScopeLogs scope, and each LogRecord in ld.

func (*Decorator) DecorateMetrics added in v1.14.0

func (d *Decorator) DecorateMetrics(md pmetric.Metrics)

DecorateMetrics upserts the configured fields onto each ResourceMetrics, each ScopeMetrics scope, and each metric data point in md. Metric data points come in five flavors — Gauge, Sum, Histogram, ExponentialHistogram, Summary — and the decorator iterates the appropriate slice for each.

func (*Decorator) DecorateTraces added in v1.14.0

func (d *Decorator) DecorateTraces(td ptrace.Traces)

DecorateTraces upserts the configured fields onto each ResourceSpans, each ScopeSpans scope, and each Span in td.

func (*Decorator) IsEmpty added in v1.14.0

func (d *Decorator) IsEmpty() bool

IsEmpty reports whether the decorator carries any user-provided changes. Callers can skip the decoration call entirely when true, avoiding the iteration cost.

type Emitter added in v1.14.0

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

Emitter builds OTLP/JSON event records about the proxy's own lifecycle and sends them on a channel. The actual stdout write is performed by a separate writer goroutine (StdoutWriter) so the hot path (consumer + workers calling EmitForwarded / EmitError) never blocks on I/O.

When the channel is nil (the no-agent-mode case), every Emit method is a silent no-op — the caller constructs one Emitter shape regardless of mode and the events naturally drop when no one is listening.

func NewEmitter added in v1.14.0

func NewEmitter(instanceID string, ch chan<- plog.Logs) *Emitter

NewEmitter returns an Emitter that pushes events on ch. Pass a nil channel in non-agent mode to turn every Emit call into a no-op.

func (*Emitter) EmitError added in v1.14.0

func (e *Emitter) EmitError(kind ErrorKind, reason string, code int)

EmitError emits an error event with KTD14 classification. code is the HTTP/gRPC status code from upstream when applicable; pass 0 when not.

func (*Emitter) EmitForwarded added in v1.14.0

func (e *Emitter) EmitForwarded(sig Signal, count, bytes int64)

EmitForwarded emits one event per inbound batch the consumer enqueued for forwarding.

func (*Emitter) EmitShutdown added in v1.14.0

func (e *Emitter) EmitShutdown(reason string, finalTotals [signalCount]int64)

EmitShutdown emits the terminating event with final cumulative totals. Callers should invoke this synchronously before cancelling the writer's context so the event lands before the process exits.

func (*Emitter) EmitStarted added in v1.14.0

func (e *Emitter) EmitStarted(httpEndpoint, grpcEndpoint, dataset, profileName string)

EmitStarted emits the proxy-startup event once both listeners are bound and the receiver is ready to accept traffic.

func (*Emitter) EmitStats added in v1.14.0

func (e *Emitter) EmitStats(snap SnapshotWithRate)

EmitStats emits the per-interval rate + total snapshot for agents to consume. Carries the same data as the TTY stats line, in structured form.

type ErrorKind added in v1.14.0

type ErrorKind string

ErrorKind classifies a worker outbound failure (KTD14). Agents reading the event stream can use this to differentiate transient upstream errors (the SDK should keep retrying) from terminal ones (the proxy's own credentials are bad and retry won't help).

const (
	ErrorKindUpstreamUnreachable ErrorKind = "upstream_unreachable"
	ErrorKindUpstream5xx         ErrorKind = "upstream_5xx"
	ErrorKindUpstream4xxAuth     ErrorKind = "upstream_4xx_auth"
	ErrorKindUpstream4xxOther    ErrorKind = "upstream_4xx_other"
	ErrorKindInternalPanic       ErrorKind = "internal_panic"
)

type Forwarder added in v1.14.0

type Forwarder interface {
	SendLogs(ctx context.Context, ld plog.Logs, dataset *string) error
	SendTraces(ctx context.Context, td ptrace.Traces, dataset *string) error
	SendMetrics(ctx context.Context, md pmetric.Metrics, dataset *string) error
}

Forwarder abstracts the dash0api.Client OTLP send surface so the worker pool can be exercised with a fake in unit tests without standing up the full API client. dash0api.Client satisfies this implicitly.

type LifecycleEvent added in v1.14.0

type LifecycleEvent struct {
	Kind    LifecycleKind
	Message string
}

LifecycleEvent is the typed channel input for one-shot stderr messages that should print above the live stats block. The writer renders Message verbatim followed by a newline; Kind is reserved for future color or prefix customization and currently informational only.

type LifecycleKind added in v1.14.0

type LifecycleKind int

LifecycleKind classifies lifecycle messages. v1 doesn't differentiate visually; the kind is recorded so a future polish pass can color or prefix the rendering without changing the channel signature.

const (
	LifecycleInfo LifecycleKind = iota
	LifecycleWarning
	LifecycleError
	LifecycleBanner
)

type OtlpLogSeverityRange

type OtlpLogSeverityRange string

OtlpLogSeverityRange represents the OpenTelemetry log severity ranges. Each range covers a band of four severity numbers as defined by the OpenTelemetry specification.

const (
	Unknown OtlpLogSeverityRange = "UNKNOWN"
	Trace   OtlpLogSeverityRange = "TRACE"
	Debug   OtlpLogSeverityRange = "DEBUG"
	Info    OtlpLogSeverityRange = "INFO"
	Warn    OtlpLogSeverityRange = "WARN"
	Error   OtlpLogSeverityRange = "ERROR"
	Fatal   OtlpLogSeverityRange = "FATAL"
)

type Pipeline added in v1.14.0

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

Pipeline owns the otlpreceiver-based listener stack. Construction pre-binds ports (so the supervisor can decide collision behavior before the receiver internals fire), builds the receiver Config, and creates the three signal-receiver instances (logs/traces/metrics). Start transfers control to the receiver, which actually binds the HTTP and gRPC servers.

func BuildPipeline added in v1.14.0

func BuildPipeline(ctx context.Context, httpPort, grpcPort int, consumer *ProxyConsumer) (*Pipeline, error)

BuildPipeline pre-binds the requested ports (falling back per PortRequest semantics), assembles the receiver Config, and constructs the three signal receivers wired to the supplied consumer. The returned Pipeline has not started any servers yet; call Start to bind the listeners and accept traffic.

On failure mid-construction the function returns an error; no resources are leaked because pre-bound listeners are closed before construction returns.

func (*Pipeline) Endpoints added in v1.14.0

func (p *Pipeline) Endpoints() PipelineEndpoints

Endpoints returns the bound HTTP and gRPC host:port pairs. Stable after BuildPipeline returns successfully.

func (*Pipeline) Shutdown added in v1.14.0

func (p *Pipeline) Shutdown(ctx context.Context) error

Shutdown stops both listeners. Safe to call multiple times and without a prior Start (per the component.Component contract).

func (*Pipeline) Start added in v1.14.0

func (p *Pipeline) Start(ctx context.Context) error

Start brings up both HTTP and gRPC listeners via the receiver. Because the three signal-receivers returned by the otlpreceiver factory share a single underlying instance (sharedcomponent semantics), one Start call is sufficient — but we Start all three for symmetry and to surface any future divergence in the upstream library quickly.

On any partial-start failure, Start best-effort-Shutdowns the others and returns the first error. The caller's supervisor (U5) treats this as a fatal startup error per KTD5.

type PipelineEndpoints added in v1.14.0

type PipelineEndpoints struct {
	HTTPEndpoint string // e.g. "127.0.0.1:4318" or "127.0.0.1:53291"
	GRPCEndpoint string // e.g. "127.0.0.1:4317"
}

PipelineEndpoints reports the actual host:port each listener bound to, after pre-bind fallback to OS-assigned ports. The supervisor (U5) uses these to compose the banner and the agent-mode `started` event.

type ProxyConsumer added in v1.14.0

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

ProxyConsumer implements consumer.Logs / consumer.Traces / consumer.Metrics and bridges the otlpreceiver's pdata delivery into the worker pool's per-signal channels.

Per KTD3a (async-forward), the consumer returns nil (mapping to HTTP 200 / gRPC OK) after enqueueing — the actual upstream forward happens asynchronously on the worker goroutines. The OTLP spec scopes acceptance to a single hop, so this is spec-compliant.

Lifecycle side-effects on each Consume call:

  1. stats forwarded counter incremented (U6) — drives both the TTY sparkline (U7) and the agent-mode stats event (U8).
  2. agent-mode `dash0.cli.otlp_proxy.forwarded` event emitted (U8).
  3. --tail rendering pushed to TailCh when --tail is enabled (U7/U9).
  4. Non-blocking enqueue to the per-signal channel.
  5. Non-empty channel → return nil (200). Full channel → return retryable error (503 + SDK exponential backoff).

func NewProxyConsumer added in v1.14.0

func NewProxyConsumer(stats *Stats, emitter *Emitter, tailCh chan<- string) *ProxyConsumer

NewProxyConsumer constructs the consumer plus its three per-signal channels. tailCh may be nil; when non-nil the consumer pushes the debug-exporter-style rendering of each inbound batch to it.

func (*ProxyConsumer) Capabilities added in v1.14.0

func (c *ProxyConsumer) Capabilities() consumer.Capabilities

Capabilities reports MutatesData=false so the receiver knows it can hand us the pdata directly without making a defensive copy. The consumer never modifies inbound data — workers send it through dash0api.Client as-is.

func (*ProxyConsumer) ConsumeLogs added in v1.14.0

func (c *ProxyConsumer) ConsumeLogs(_ context.Context, ld plog.Logs) error

ConsumeLogs implements consumer.Logs.

func (*ProxyConsumer) ConsumeMetrics added in v1.14.0

func (c *ProxyConsumer) ConsumeMetrics(_ context.Context, md pmetric.Metrics) error

ConsumeMetrics implements consumer.Metrics.

func (*ProxyConsumer) ConsumeTraces added in v1.14.0

func (c *ProxyConsumer) ConsumeTraces(_ context.Context, td ptrace.Traces) error

ConsumeTraces implements consumer.Traces.

func (*ProxyConsumer) LogsChannel added in v1.14.0

func (c *ProxyConsumer) LogsChannel() <-chan plog.Logs

LogsChannel returns the channel the worker pool drains for log batches.

func (*ProxyConsumer) MetricsChannel added in v1.14.0

func (c *ProxyConsumer) MetricsChannel() <-chan pmetric.Metrics

MetricsChannel returns the channel the worker pool drains for metric batches.

func (*ProxyConsumer) TracesChannel added in v1.14.0

func (c *ProxyConsumer) TracesChannel() <-chan ptrace.Traces

TracesChannel returns the channel the worker pool drains for trace batches.

type RateSampler added in v1.14.0

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

RateSampler tracks a rolling window of snapshots so consumers can render a sparkline timeline of forwarded rate per signal. The sampler owns the per-interval ticking and fans snapshots out to subscribed sink channels.

func NewRateSampler added in v1.14.0

func NewRateSampler(stats *Stats, interval time.Duration, capacity int) *RateSampler

NewRateSampler constructs a sampler. capacity is the size of the rolling window the sparkline renderer (U7) reads from — at the proxy's hardcoded 1s tick interval, capacity=30 gives a 30-second history.

func (*RateSampler) History added in v1.14.0

func (rs *RateSampler) History() []SnapshotWithRate

History returns the current rolling window in chronological order (oldest first). The returned slice is a defensive copy so callers can iterate without holding the sampler's lock.

func (*RateSampler) Run added in v1.14.0

func (rs *RateSampler) Run(ctx context.Context, sinks ...chan<- SnapshotWithRate)

Run loops on a ticker at the sampler's interval, calls Sample on each tick, and fans the result out to every sink channel. Returns when ctx is done or when ctx is already done at entry. Non-blocking sends to sinks prevent a slow consumer from stalling the stats pipeline — if a sink's channel is full at tick time, that sink misses one sample rather than the whole pipeline backing up.

func (*RateSampler) Sample added in v1.14.0

func (rs *RateSampler) Sample() SnapshotWithRate

Sample takes a fresh snapshot, computes per-signal rate against the previous sample in the window, appends to the window with ring-buffer semantics (oldest drops at capacity), and returns the result.

type Signal added in v1.14.0

type Signal int

Signal enumerates the three OTLP signal types the proxy tracks. Profiles is intentionally absent — the OTel Profiles signal is not stable and adding it would lock the proxy to a moving target.

const (
	SignalLogs Signal = iota
	SignalSpans
	SignalMetrics
)

func (Signal) String added in v1.14.0

func (s Signal) String() string

String returns the lowercase signal name used in stats lines, NDJSON event attributes, and `--tail` output headers.

type Snapshot added in v1.14.0

type Snapshot struct {
	Forwarded [signalCount]int64
	Failed    [signalCount]int64
	Timestamp time.Time
}

Snapshot is a moment-in-time view of the counters. Captured atomically per signal but not transactionally across signals — a snapshot taken during a burst may see logs from after a span counter increment but spans from before. For visibility purposes this is fine.

type SnapshotWithRate added in v1.14.0

type SnapshotWithRate struct {
	Snapshot
	// Rate[i] is records-per-second for signal i, derived from the delta
	// against the previous snapshot. Rate is 0 on the first sample (no prior
	// reference) or when the time delta is zero.
	Rate [signalCount]float64
}

SnapshotWithRate annotates a Snapshot with per-signal forwarded rate (records per second) computed against the previous sample in the sampler's rolling window.

type Stats added in v1.14.0

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

Stats holds atomic counters per signal. Methods are concurrency-safe; the hot path (consumer.ConsumeX + worker outcome) calls them with no locking overhead beyond an atomic add.

func (*Stats) Failed added in v1.14.0

func (s *Stats) Failed(sig Signal) int64

Failed returns the current failed count for a signal (lock-free read).

func (*Stats) Forwarded added in v1.14.0

func (s *Stats) Forwarded(sig Signal) int64

Forwarded returns the current forwarded count for a signal (lock-free read).

func (*Stats) RecordFailed added in v1.14.0

func (s *Stats) RecordFailed(sig Signal, n int)

RecordFailed adds n to the per-signal failed counter. Workers (U4) call this after a terminal outbound failure (retries exhausted).

func (*Stats) RecordForwarded added in v1.14.0

func (s *Stats) RecordForwarded(sig Signal, n int)

RecordForwarded adds n to the per-signal forwarded counter. The consumer (U13) calls this at enqueue time — it counts "accepted by the proxy", not "delivered to Dash0". Upstream failures are tracked separately via RecordFailed.

type StderrWriter added in v1.14.0

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

StderrWriter owns os.Stderr in TTY mode and interleaves the live stats block with one-shot lifecycle messages.

The writer maintains its own rolling per-signal rate history independent of the RateSampler — the stats channel only carries the current snapshot, not the full history, so the writer accumulates samples as they arrive. This decouples the writer from the sampler beyond the channel.

func NewStderrWriter added in v1.14.0

func NewStderrWriter(out io.Writer, fd int) (*StderrWriter, chan SnapshotWithRate, chan LifecycleEvent)

NewStderrWriter returns a writer plus the two channels callers push to. Stats updates feed the live block; lifecycle events render above it.

func (*StderrWriter) Run added in v1.14.0

func (w *StderrWriter) Run(ctx context.Context, statsCh <-chan SnapshotWithRate, lifecycleCh <-chan LifecycleEvent)

Run drains the stats and lifecycle channels until ctx is done.

In TTY mode the writer renders a multi-line stats block — one line per signal — and redraws it in place on each tick using ANSI cursor controls. Lifecycle messages are inserted above the block by erasing the block first, printing the message, then redrawing.

In non-TTY mode the writer emits lifecycle events as plain lines but skips the live stats display — piping stderr to a file or another process means in-place redraw would litter the output with control sequences rather than produce a useful log.

type StdoutWriter added in v1.14.0

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

StdoutWriter drains an event channel and writes one OTLP/JSON line per event to its underlying writer. The single-goroutine ownership of os.Stdout guarantees one valid JSON record per line: concurrent emitters produce no interleaving because everyone funnels through one channel and one writer.

Each event triggers a marshal → write → newline → flush cycle, so a downstream consumer reading the stream line-by-line sees complete records without head-of-line buffering delays.

func NewStdoutWriter added in v1.14.0

func NewStdoutWriter(out io.Writer) (*StdoutWriter, chan plog.Logs)

NewStdoutWriter returns a writer that drains events to out. The caller also receives the event channel that emitters should push to.

func (*StdoutWriter) Run added in v1.14.0

func (w *StdoutWriter) Run(ctx context.Context, ch <-chan plog.Logs) error

Run drains ch into the writer's out until ctx is done or the channel is closed. Returns nil on graceful exit; returns an error only when writing to out fails irrecoverably (which should be rare on os.Stdout — typically only when the receiving pipe has closed).

type WorkerPool added in v1.14.0

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

WorkerPool drains the per-signal channels populated by ProxyConsumer (U13), calls the upstream Forwarder, classifies outcomes per KTD14, and surfaces failures via the Stats counters, the agent-mode event channel, and an at-most-once-per-throttle-window stderr line for credential issues.

Concurrency is one goroutine per signal — the transport's max-concurrent-requests semaphore (KTD3b) provides the actual outbound concurrency cap, and per-signal goroutines keep batch ordering intact for downstream sanity (e.g., a metrics burst arriving in order goes upstream in order, modulo SDK retries).

func NewWorkerPool added in v1.14.0

func NewWorkerPool(forwarder Forwarder, dataset *string, stats *Stats, emitter *Emitter, consumer *ProxyConsumer, lifecycleCh chan<- LifecycleEvent, decorator *Decorator) *WorkerPool

NewWorkerPool constructs a pool. No goroutines are started until Run is called. The decorator may be nil — workers treat that the same as an empty decorator and skip the per-batch upsert step.

func (*WorkerPool) Run added in v1.14.0

func (p *WorkerPool) Run(ctx context.Context)

Run launches one goroutine per signal and blocks until ctx is cancelled. When ctx fires, each worker stops accepting new work from its channel; any in-flight forward finishes (the transport has its own per-request deadline) and the worker returns. Tests that want immediate shutdown should cancel and not wait on queued batches.

Jump to

Keyboard shortcuts

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