Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Observability

The runtime exposes three kinds of signals: structured logs on stderr, Prometheus metrics at /v1/metrics, and OpenTelemetry traces exported over OTLP/HTTP. Each execution’s full history is also available on demand through the CLI and the API. This page describes each signal, every metric, the span structure and attributes of traces, and how to configure them.

Logs

The server logs to stderr. Choose the format with --log-format:

text (default):

2026-11-15T09:38:33.954506Z  INFO orbit_runtime::engine: recovered active executions from history executions=1 in_flight=0
2026-11-15T09:38:33.954823Z  INFO orbit: starting ORBIT runtime db=/var/lib/orbit/orbit.db backend="sqlite" http_adapter=false auth=false ui=true
2026-11-15T09:38:33.955798Z  INFO orbit_runtime::server: ORBIT runtime listening addr=127.0.0.1:7233 tls=false ui=true
2026-11-15T09:38:34.957450Z  INFO orbit_runtime::engine: worker registered worker_id=py-1 runtime=python tasks=1
2026-11-15T09:38:34.990494Z  INFO orbit_runtime::engine: execution started execution_id=trace-1 workflow=Plain version=1.0.0
2026-11-15T09:38:35.075550Z  INFO orbit_runtime::engine: execution completed execution_id=trace-1

json, one object per line, for log pipelines:

{"timestamp":"2026-11-15T18:33:17.685910Z","level":"INFO","fields":{"message":"starting ORBIT runtime","db":"orbit.db","backend":"sqlite","http_adapter":false,"auth":false,"ui":true},"target":"orbit"}
{"timestamp":"2026-11-15T18:33:17.687448Z","level":"INFO","fields":{"message":"ORBIT runtime listening","addr":"127.0.0.1:7233","tls":false,"ui":true},"target":"orbit_runtime::server"}
{"timestamp":"2026-11-15T09:38:19.759674Z","level":"INFO","fields":{"message":"shutting down"},"target":"orbit"}

Log levels and filtering

ORBIT_LOG sets the filter, in tracing-subscriber EnvFilter syntax. The default is info. Setting ORBIT_LOG replaces the default, so include a base level:

ORBIT_LOG=info,orbit_runtime=debug orbit server   # every committed event at debug level
ORBIT_LOG=warn orbit server                       # warnings and errors only

At debug, every committed event is logged:

DEBUG orbit_runtime::engine: event execution_id=dbg-1 seq=3 event="StepScheduled" step="work"

Log messages

LevelMessageFields
INFOstarting ORBIT runtimedb (password redacted), backend, http_adapter, auth, ui
INFOORBIT runtime listeningaddr, tls, mtls (with TLS), ui
INFOgRPC worker protocol listeningaddr, tls
INFOexporting OpenTelemetry tracesendpoint, service
INFOrecovered active executions from historyexecutions, in_flight
INFOregistered workflow definitionworkflow, version, hash
INFOexecution startedexecution_id, workflow, version
INFOexecution completed, execution cancelledexecution_id
WARNexecution failedexecution_id, code, error, compensation
WARNexecution timed outexecution_id
WARNstep failedexecution_id, step, code, error
INFOretry scheduledexecution_id, step, attempt, delay_ms
WARNattempt outcome uncertain; redeliveringexecution_id, step, attempt, reason
INFOcancelling child workflow because its parent no longer needs itexecution_id
INFOworker registeredworker_id, runtime, tasks
INFOworker unregisteredworker_id
WARNreconnected to PostgreSQL
ERRORPostgreSQL reconnect failederror
WARNOTLP collector rejected spansstatus
WARNcannot export spans to the OTLP collectorerror
WARNOTLP export queue is full; dropping spans
ERRORtick failed, recording built-in task result failederror
INFOshutting down
DEBUGeventexecution_id, seq, event, step

Logs never include workflow inputs, step inputs, outputs or error details beyond the error code and message.

Prometheus metrics

GET /v1/metrics returns Prometheus text format (content-type: text/plain; version=0.0.4). It requires the admin token when authentication is enabled.

# prometheus.yml
scrape_configs:
  - job_name: orbit
    metrics_path: /v1/metrics
    scheme: https
    authorization:
      type: Bearer
      credentials_file: /etc/prometheus/orbit-token
    tls_config:
      ca_file: /etc/prometheus/orbit-ca.pem
    static_configs:
      - targets: ["orbit.internal:7233"]

Metric reference

The metrics have no labels, except reason on orbit_schedules_skipped_total. Counters are derived from committed events and from schedule fires on this server, are kept in memory, and start from zero when the server starts; Prometheus handles the reset in rate() and increase().

MetricTypeIncremented byMeaning
orbit_executions_started_totalcounterWorkflowCreatedExecutions started, including child workflows. A new run after continue_as_new is not counted.
orbit_executions_completed_totalcounterWorkflowCompletedExecutions completed.
orbit_executions_failed_totalcounterWorkflowFailedExecutions failed.
orbit_executions_cancelled_totalcounterWorkflowCancelledExecutions cancelled.
orbit_executions_timed_out_totalcounterWorkflowTimedOutExecutions timed out.
orbit_tasks_dispatched_totalcounterStepDispatchedTask attempts dispatched, including redeliveries, child workflow starts and built-in tasks.
orbit_tasks_succeeded_totalcounterStepSucceededUnits that succeeded, including compensations and completed for steps.
orbit_task_attempts_failed_totalcounterStepAttemptFailedFailed attempts, whether or not they are retried.
orbit_task_attempts_uncertain_totalcounterStepAttemptUncertainAttempts with an unknown outcome, redelivered.
orbit_retries_scheduled_totalcounterRetryScheduledRetries scheduled.
orbit_steps_skipped_totalcounterStepSkippedSteps skipped by when conditions.
orbit_stale_results_totalcounterStaleResultIgnoredDuplicate or late results ignored.
orbit_compensations_started_totalcounterCompensationStartedExecutions that started compensating.
orbit_compensations_failed_totalcounterCompensationFailedCompensations that failed permanently (one per failed compensation).
orbit_events_committed_totalcounterevery eventHistory events committed.
orbit_executions_continued_as_new_totalcounterWorkflowContinuedAsNewRuns that ended with continue_as_new.
orbit_schedules_fired_totalcountera schedule startExecutions this server started for a schedule, including triggers. A start of an id that already existed is not counted. In shared mode, the sum over the servers.
orbit_schedules_skipped_total{reason="overlap"}countera schedule occurrence or triggerStarts skipped by the skip overlap policy.
orbit_schedules_skipped_total{reason="missed"}countera schedule occurrenceOccurrences that passed while no server could fire them and that the catch-up policy did not start.
orbit_active_executionsgaugeExecutions not yet finished.
orbit_worker_sessionsgaugeWorker sessions the runtime remembers. A silent session is forgotten after four session timeouts.

Example output:

# HELP orbit_executions_started_total Executions started
# TYPE orbit_executions_started_total counter
orbit_executions_started_total 8
# HELP orbit_executions_completed_total Executions completed
# TYPE orbit_executions_completed_total counter
orbit_executions_completed_total 2
# HELP orbit_executions_failed_total Executions failed
# TYPE orbit_executions_failed_total counter
orbit_executions_failed_total 4
...
# HELP orbit_active_executions Executions not yet finished
# TYPE orbit_active_executions gauge
orbit_active_executions 0
# HELP orbit_worker_sessions Registered worker sessions
# TYPE orbit_worker_sessions gauge
orbit_worker_sessions 2

Suggested alerts

ConditionExpression
Executions failingrate(orbit_executions_failed_total[5m]) > 0
Compensation failing (needs a person)increase(orbit_compensations_failed_total[15m]) > 0
Workers going silentrate(orbit_task_attempts_uncertain_total[5m]) > 0
No workers connectedorbit_worker_sessions == 0
Runtime downup{job="orbit"} == 0

For backlog per task (tasks scheduled with no worker to take them), use queue_timeout on steps and alert on failures with code QUEUE_TIMEOUT from your logs, since the metrics have no per-task labels.

OpenTelemetry tracing

The runtime can export one trace per execution over OTLP/HTTP with JSON encoding, which the OpenTelemetry Collector, Jaeger, Grafana Tempo, Honeycomb and other OTLP backends accept.

Configuration

SettingMeaning
--otlp-endpoint URL or OTEL_EXPORTER_OTLP_ENDPOINTBase URL of the collector, for example http://localhost:4318. Spans are posted to <URL>/v1/traces. Export is off when unset.
OTEL_EXPORTER_OTLP_HEADERSExtra request headers as key=value pairs separated by commas, for example an API key.
OTEL_SERVICE_NAMEservice.name resource attribute. Default orbit-runtime.
OTEL_SERVICE_NAME=orbit-prod \
OTEL_EXPORTER_OTLP_HEADERS="x-api-key=$TRACING_KEY" \
orbit server --token "$ORBIT_TOKEN" --otlp-endpoint http://otel-collector:4318

Every request carries the resource attributes service.name and service.version (the runtime version), and the instrumentation scope orbit-runtime.

Span structure

trace  (one per top-level execution)
└── workflow OrderProcessing                     root span, execution start to end
    ├── step validate_order                      one span per attempt
    ├── step analyze_risk        (error)         attempt 1 failed
    ├── step analyze_risk                        attempt 2
    ├── step reserve_inventory
    ├── step process_payment     (error)
    ├── step compensate:reserve_inventory        compensation
    └── step ship                                child workflow step
        └── workflow Shipping                    child execution, same trace
            ├── step quote
            └── step book
  • Root span workflow <Name>: from WorkflowCreated to the terminal event. Exported when the execution finishes.
  • Unit spans step <unit key>: one per attempt of a task, from dispatch to result; one per sleep timer, from start to fire; one per compensation attempt (step compensate:<unit>). Exported when the attempt ends: succeeded, failed or uncertain. Parent: the root span.
  • Child workflows join their parent’s trace. The child’s root span is a child of the parent step’s span.
  • Attempts still running when an execution ends are not exported, because they have no end time.

All spans have kind INTERNAL (1). A span’s status is OK (1) or ERROR (2) with a message:

SpanError whenStatus message
Unitthe attempt failed<code>: <message>, for example CARD_DECLINED: card was declined
Unitthe outcome is uncertainoutcome unknown: <reason>
Rootthe execution did not complete<code>: <message> of the execution error, or the status (CANCELLED, TIMED_OUT)

Attributes

AttributeTypeOnMeaning
orbit.execution_idstringallExecution id.
orbit.workflowstringallWorkflow name.
orbit.workflow_versionstringrootWorkflow version.
orbit.statusstringrootFinal status, for example COMPLETED.
orbit.compensationstringrootnone, completed or failed, when the execution was aborted.
orbit.unitstringunitUnit key, for example reserve[2] or compensate:reserve_inventory.
orbit.attemptintegerunitAttempt number.
orbit.outcomestringunitsucceeded, failed, uncertain or timer fired.
orbit.worker_idstringunitWorker that ran the attempt, when it was dispatched.
orbit.redeliverybooleanunitPresent and true when the attempt was a redelivery.

Identifiers

Trace and span ids are derived from execution and unit identifiers with SHA-256, so a runtime restart never produces duplicate or orphaned spans:

IdDerived from
Trace id (16 bytes)orbit-trace:<top-level execution id>
Root span id (8 bytes)orbit-span:<execution id>
Unit span id (8 bytes)orbit-span:<execution id>/<unit key>#<dispatch number>

Because the trace id is a function of the execution id, you can compute it to find an execution’s trace. In Python:

import hashlib
trace_id = hashlib.sha256(b"orbit-trace:order-1002").hexdigest()[:32]

Example span

A unit span as received by a collector:

{
  "traceId": "6d61feb6ff6a03af93dec3e7836b3492",
  "spanId": "5e8f395157c28e15",
  "parentSpanId": "4c82e46ef6bc6222",
  "name": "step work",
  "kind": 1,
  "startTimeUnixNano": "1794735515047000000",
  "endTimeUnixNano": "1794735515072000000",
  "attributes": [
    {"key": "orbit.execution_id", "value": {"stringValue": "trace-1"}},
    {"key": "orbit.workflow", "value": {"stringValue": "Plain"}},
    {"key": "orbit.unit", "value": {"stringValue": "work"}},
    {"key": "orbit.attempt", "value": {"intValue": "1"}},
    {"key": "orbit.outcome", "value": {"stringValue": "succeeded"}},
    {"key": "orbit.worker_id", "value": {"stringValue": "py-1"}}
  ],
  "status": {"code": 1}
}

Export behavior

  • Spans are exported only after the events they describe are committed.
  • Export runs on a background thread and never blocks the engine. Spans queue in memory (up to 1024 batches) and are sent in requests of up to about 512 spans, with a 10-second timeout.
  • If the collector is unreachable or rejects a request, the spans of that request are dropped and a warning is logged. If the queue is full, new spans are dropped. Tracing is best-effort; the history is the complete record.
  • Timestamps have millisecond precision.
  • Worker SDKs do not create spans of their own in this version. Correlate worker logs with traces through execution_id, step_id and attempt, which every task carries.

Execution history

For any single execution, the history is the most detailed record: every event, in order, with its inputs and outputs. Use orbit status, orbit logs, orbit inspect and orbit ps, or the HTTP API. See Executions and history.

See also