OPENTELEMETRY
Swamp uses OpenTelemetry (OTel) for distributed tracing and structured log export. Both signals are opt-in and controlled entirely through standard OTel environment variables. When no provider is registered, all tracer, span, and logger operations are no-ops with zero overhead.
Configuration
| Variable | Purpose | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
Collector URL (telemetry off when unset) | (unset = off) |
OTEL_TRACES_EXPORTER |
Trace exporter: otlp, console, or none |
otlp |
OTEL_LOGS_EXPORTER |
Log exporter: otlp, console (stderr), or none |
otlp |
OTEL_BLRP_USE |
1 to batch log exports (for long-running swamp serve) |
(per-record) |
OTEL_SERVICE_NAME |
Service name in traces and logs | swamp |
OTEL_RESOURCE_ATTRIBUTES |
Custom resource attributes (comma-separated key=val) |
(none) |
OTEL_EXPORTER_OTLP_HEADERS |
Auth headers (comma-separated key=val) |
(none) |
TRACEPARENT |
W3C traceparent for the run (flag takes precedence) |
(unset) |
TRACESTATE |
W3C tracestate for the run (flag takes precedence) |
(unset) |
Telemetry is enabled when OTEL_EXPORTER_OTLP_ENDPOINT is set or either
OTEL_TRACES_EXPORTER or OTEL_LOGS_EXPORTER is console. When neither
condition is met, no SDK packages are loaded and no async hooks are installed.
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 swamp workflow run my-workflowSetting OTEL_TRACES_EXPORTER=console prints spans to stderr without requiring
a collector endpoint. Setting OTEL_LOGS_EXPORTER=console prints log records to
stderr in the same way.
Logs
Structured log lines emitted during a run are exported as native OTLP log
records, sent to the collector's /v1/logs endpoint. Logs share the same
OTEL_EXPORTER_OTLP_ENDPOINT as traces — when the endpoint is set, both signals
are active by default.
Trace Correlation
Each log record carries the trace_id and span_id of the active span at the
time the log was emitted. This links log lines to the span that produced them,
so a collector or backend can display logs inline with their parent trace.
Redaction
Run secrets (vault values, environment variables marked sensitive) are redacted from log record bodies and attributes before export. Redaction happens in the export pipeline, so secrets never leave the process in OTLP payloads.
Coverage
Log export covers swamp serve and worker subprocesses. Worker subprocesses
receive trace context via the propagated TRACEPARENT environment variable, so
their log records are correlated with the parent trace.
Batching
By default, log records are exported individually (one OTLP request per record).
For long-running processes like swamp serve, set OTEL_BLRP_USE=1 to enable
the BatchLogRecordProcessor, which buffers records and flushes them
periodically — reducing request volume to the collector.
Span Naming Convention
All spans use the prefix swamp. followed by the domain and operation:
swamp.<domain>.<operation>Span Hierarchy
A workflow run produces a trace with the following structure:
swamp.cli
├─ swamp.lock.acquire
├─ swamp.datastore.sync
└─ swamp.workflow.run.command
└─ swamp.workflow.run
├─ swamp.workflow.evaluate
├─ swamp.workflow.job
│ ├─ swamp.workflow.step
│ │ └─ swamp.model.method
│ └─ swamp.workflow.step
│ └─ swamp.model.method
└─ swamp.workflow.job
└─ swamp.workflow.step
└─ swamp.model.methodA scheduled/cron run creates a separate root span:
swamp.scheduled.fire
└─ swamp.workflow.run
└─ ...CLI Root Span
Every CLI invocation creates a swamp.cli root span encompassing the entire
command lifecycle.
| Attribute | Type | Description |
|---|---|---|
swamp.command |
string |
Top-level command name |
swamp.subcommand |
string |
Subcommand name |
swamp.version |
string |
Swamp version |
swamp.args |
string |
Sanitized positional arguments |
swamp.option_keys |
string |
Command-specific option names |
swamp.global_options |
string |
Global flags (--json, --verbose, etc) |
Libswamp Generator Spans
Every libswamp generator is wrapped with withGeneratorSpan, producing a span
for the full duration of the operation. Generator spans automatically detect
kind: "error" events and mark the span status as ERROR.
| Domain | Span Names |
|---|---|
auth |
swamp.auth.login |
audit |
swamp.audit.timeline |
data |
swamp.data.get, .list, .search, .versions, .rename, .gc |
datastore |
swamp.datastore.setup, .status, .sync.command, .lock.status, .lock.release |
extension |
swamp.extension.pull, .push, .search, .update, .yank, .rm, .fmt, .list |
issue |
swamp.issue.create |
model |
swamp.model.create, .delete, .edit, .get, .search, .validate |
model |
swamp.model.method.run, .method.describe, .method.history.logs |
model |
swamp.model.output.search, .output.get, .output.logs, .output.data |
report |
swamp.report.describe, .get, .search |
repo |
swamp.repo.create, .upgrade |
source |
swamp.source.fetch, .clean |
telemetry |
swamp.telemetry.stats |
type |
swamp.type.describe, .search |
update |
swamp.update.check |
vault |
swamp.vault.create, .describe, .edit, .get, .list_keys, .put, .search, .type_search |
workflow |
swamp.workflow.create, .delete, .edit, .get, .search, .validate, .schema |
workflow |
swamp.workflow.run.command, .history.get, .history.logs, .history.search, .run_search |
Domain-Layer Spans
The workflow execution engine and method execution service create fine-grained spans for the execution hierarchy.
| Span Name | Attributes |
|---|---|
swamp.workflow.run |
workflow.name, workflow.id, workflow.run_id |
swamp.workflow.evaluate |
workflow.name, workflow.expressions_evaluated |
swamp.workflow.job |
job.name, job.status |
swamp.workflow.step |
step.name, job.name, step.task.type |
swamp.model.method |
model.name, model.type, method.name |
Infrastructure Spans
| Span Name | Attributes |
|---|---|
swamp.lock.acquire |
lock.key, lock.label |
swamp.datastore.sync |
sync.direction (pull/push), sync.file_count |
swamp.repo.init |
(none) |
Context Propagation
In-Process
The OTel SDK uses AsyncLocalStorageContextManager to propagate trace context
through async call chains. tracer.startActiveSpan() and tracer.startSpan()
establish parent-child relationships automatically within the active context.
yield*delegation in async generators preserves context across generator boundaries.
Cross-Process
For out-of-process execution, W3C Trace Context headers are propagated via environment variables.
| Step | Component | Action |
|---|---|---|
| 1 | Host process | injectTraceContext() extracts traceparent/tracestate |
| 2 | ExecutionRequest |
traceHeaders field carries the headers |
| 3 | Container | Extension reads env vars and connects to parent trace |
Per-Invocation
Callers can inject a W3C parent trace context into a single run so it appears as a child of an external trace.
CLI flags — workflow run and model method run accept --traceparent and
--tracestate:
swamp workflow run my-workflow --traceparent "00-<trace-id>-<span-id>-01"
swamp model method run my-model execute --traceparent "00-<trace-id>-<span-id>-01" --tracestate "vendor=value"The TRACEPARENT and TRACESTATE environment variables provide the same
context. When both the flag and the env var are set, the flag takes precedence.
An empty env var is treated as unset.
WebSocket protocol — the WorkflowRunPayload and ModelMethodRunPayload
messages accept traceparent and tracestate fields, propagating the caller's
trace context into the server-side execution.
Webhook header extraction — when a webhook request includes traceparent
and/or tracestate HTTP headers, swamp serve propagates them into the triggered
workflow execution automatically. No configuration is needed.
Scheduled/cron runs — scheduled runs mint a fresh swamp.scheduled.fire
root span as the top of the trace, since there is no external caller to inherit
context from.
SDK Lifecycle
initTracing()
├─ No endpoint set, exporter not console → return (no-op)
└─ Endpoint set or console exporter
├─ Create AsyncLocalStorageContextManager
├─ Create BasicTracerProvider with Resource
├─ Add BatchSpanProcessor with OTLP span exporter
├─ Register as global tracer provider
├─ Create LoggerProvider with Resource
├─ Add log record processor with OTLP log exporter
│ (BatchLogRecordProcessor when OTEL_BLRP_USE=1,
│ SimpleLogRecordProcessor otherwise)
└─ Register as global logger provider
runCli(args) ← spans and log records created during execution
shutdownTracing() ← flush pending spans and log records, disable providersAll OTel SDK packages (sdk-trace-base, sdk-logs, otlp-transformer,
context-async-hooks, resources, semantic-conventions) are loaded via
dynamic import() only when telemetry is enabled. @opentelemetry/api and
@opentelemetry/api-logs are statically imported — they return no-op
implementations when no provider is registered.
Both OTLP exporters (spans and logs) use Deno's native fetch API instead of
the Node.js http/https modules. Spans POST to /v1/traces and logs POST to
/v1/logs on the configured endpoint. @opentelemetry/otlp-transformer handles
JSON serialization for both signals.
Utilities
withSpan(name, attributes, fn)
Wraps an async function with a span. Creates the span, sets it as active, executes the function, records errors, and ends the span in all code paths.
await withSpan("swamp.workflow.run", { "workflow.name": name }, async () => {
// span is active for the duration
});withGeneratorSpan(name, attributes, generator)
Wraps an async generator with a span. The span starts when iteration begins and
ends when the generator completes, throws, or is abandoned. Events with
kind: "error" are detected and recorded as span errors.
yield * withGeneratorSpan("swamp.data.get", { "data.name": name }, getData());getTracer()
Returns the Swamp tracer from the global provider. Returns a no-op tracer when tracing is not initialized.
injectTraceContext()
Extracts W3C Trace Context headers (traceparent, tracestate) from the
current active context into a Record<string, string>. Returns an empty object
when tracing is disabled.
extractTraceContext(headers)
Creates a context from W3C Trace Context headers for connecting incoming spans to an existing trace.
withGeneratorTraceContext(traceparent, tracestate, generator)
Wraps an async generator so it runs within the trace context specified by the
given traceparent and tracestate values. Used internally to attach a
per-invocation parent context to workflow and model method executions.
Behavior When Disabled
When telemetry is disabled:
| Component | Behavior |
|---|---|
getTracer() |
Returns a no-op tracer |
tracer.startSpan() |
Returns a no-op span (all-zeros traceId) |
withSpan() |
Calls the wrapped function directly |
withGeneratorSpan() |
Iterates the wrapped generator directly |
injectTraceContext() |
Returns an empty object |
getLogger() |
Returns a no-op logger |
logger.emit() |
No-op |
| SDK packages | Not loaded |
| Async hooks | Not installed |