Observability
The span and events the queue worker emits per job — what you see at debug, info, and warn levels, and the boot lines that precede them.
Every queue event the framework emits targets nest_rs::queue. One
target, every worker-side log filterable by RUST_LOG=nest_rs::queue=debug,
one span wrapping each job so its events share the same fields. The
service-level logs your #[process] method emits keep their own target
(e.g. features::audio) — same convention as the rest of the framework.
Targets and levels
Section titled “Targets and levels”| Target | What | Level |
|---|---|---|
nest_rs::queue | Boot registration, per-job span, skipped/unversioned warnings, and the reason a job failed. | debug on job start, warn on a retryable failure, error on a dead-letter. |
nest_rs::operation | One queue.job line per attempt — identity, outcome, duration_ms. Shared with every other edge. | info. |
nest_rs::app | Boot: attached module-contributed transport transport="RedisWorker". Emitted by the composition root, which attaches every transport — so the target is the app’s, not the queue’s. | info. |
features::<feature> | Your service-level logs inside the #[process] method. | Your call. |
oxana::* | The job runtime’s own lines: Job started and Job finished per job on oxana::executor, which restate queue.job; a job requeued from a dead replica on oxana::storage_internal, which nothing else reports. Below info, oxana logs whole job records, payload included — on the producer’s side at trace as well as the worker’s. | info, warn on a transient Redis error. |
Hot paths respect RUST_LOG=info: job started sits at debug, so a
worker at info logs one queue.job line per attempt and, on failure, one
event saying why — no per-poll noise.
The per-job span
Section titled “The per-job span”The worker opens one span per job attempt, target nest_rs::queue,
name process job. The handler runs inside it, so any tracing event
your service emits — and the framework’s own start/end events — are
tagged with the span’s fields:
| Field | Notes |
|---|---|
queue | The Redis queue name. |
processor | The processor host name, e.g. AudioProcessor::transcode. |
job_id | The job’s id in Redis — a UUID, the same across its retries. |
attempt | Counted by the worker: 1 on the first try, higher on retries. |
trace_id / span_id | This job’s W3C trace context — always present. |
parent_span_id | The span that enqueued the job: the HTTP request, the message, the tick. |
continued_trace | true when the envelope carried a traceparent, false when this job started a trace of its own. |
actor_id | Who the enqueue was served for. Absent when nobody was authenticated — a job pushed by a scheduled tick has none. |
otel.kind | consumer — a job is work delivered to this process. |
attempt distinguishes retries of the same job_id. The OTLP appender
from nest-rs-opentelemetry exports these as span attributes.
The trace fields cross a process boundary: the producer seals them into the job envelope and the consumer continues from them — see Correlation.
What a job’s life looks like
Section titled “What a job’s life looks like”job started at debug, then whatever the handler logs, then one
queue.job line on nest_rs::operation — the same line every edge files for
a unit of work, carrying the job’s identity, its outcome and its
duration_ms:
DEBUG features::audio: transcoded file="01a0978b-938c-718c-bd77-ac49831a8510-e2e-multipart-87068-1789248902027799293.mp3" derived_key="transcoded/01a0978b-938c-718c-bd77-ac49831a8510-e2e-multipart-87068-1789248902027799293.mp3" byte_size=41 trace_id=01a0978b93aa746c8754d7c900ef3597 span_id=8a42eb4688f9bb3d INFO nest_rs::operation: queue.job queue="audio" processor="AudioProcessor::transcode" job_id="ccb2f3ed-924c-49f5-9370-10970d083b29" attempt=1 outcome="ok" duration_ms=29.803 trace_id=01a0978b93aa746c8754d7c900ef3597 span_id=8a42eb4688f9bb3dA failure adds one event on nest_rs::queue saying why — at warn
when the retry budget will re-run it, at error when the job
dead-letters — and the queue.job line reports outcome="error" (or
outcome="panic") beside it. The two never restate each other: the
detail carries the cause, the line carries the timing and the identity.
attempt is how you correlate a retry: the worker re-runs the same job_id
with attempt=2, opening a fresh process job span and filing a fresh
line — same trace_id, new span_id. See
Retries and failure for the policy behind
attempt.
What boot looks like
Section titled “What boot looks like”Each active processor logs one attached line (from the transport) and
one registered queue processor line carrying its retries budget. A #[process] method that compiled in but whose
provider isn’t reachable from this app’s module tree is skipped with a
warn:
INFO nest_rs::app: attached module-contributed transport transport="RedisWorker" INFO nest_rs::queue: registered queue processor processor="AudioProcessor::transcode" queue="audio" retries=3 WARN nest_rs::queue: skipped #[process] method: no instance of the provider in this app's container processor=ReportJobs::weekly queue=reports origin="features::reports::queue::processor" hint="nothing is registered under that exact type. Common causes: its module is not imported by this app (import it, or delete the methods); it is bound only as `dyn Trait`; it is imported through a `for_root`; it is registered by hand under a key or a trait"The skip line is warn — a linked-but-unimported handler is usually a
wiring mistake worth surfacing, and it confirms a shared features
crate isn’t accidentally activating handlers this binary doesn’t want.
See Wiring the worker for the module-gating rule.
Filtering recipes
Section titled “Filtering recipes”RUST_LOG=info,oxana::executor=warn,nest_rs::queue=debug nestrs run dev workerinfo everywhere, debug on the queue path: adds the job started
line per poll on top of the queue.job line and any failure detail, while
the runtime’s own per-job lines stay quiet. Silence oxana::executor rather
than oxana: the latter also hides the line saying a dead replica’s job was
put back on its queue, and never lower oxana below info in production,
where it logs payloads.
RUST_LOG=nest_rs::queue=warn nestrs run dev workerStrictly failures, skips, and unversioned warnings — useful for tailing a healthy worker where the only thing you care about is “did anything go wrong.”
Going further
Section titled “Going further”- OpenTelemetry — installing the OTLP appender so these spans and structured logs reach a backend.
- OpenTelemetry / traces — how
nest_rs::queuespans appear in a trace UI, attribute conventions. - OpenTelemetry / logs — structured-log export, severity mapping.
- Retries and failure — the policy
behind the
attemptfield and thefailedoutcome.