Skip to content

Observability

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.

TargetWhatLevel
nest_rs::queueBoot registration, per-job span + events, skipped/unversioned warnings.debug on job start, info on success, warn on failure.
nest_rs::transportBoot: attached module-contributed transport transport=QueueWorker.info.
features::<feature>Your service-level logs inside the #[process] method.Your call.

Hot paths respect RUST_LOG=info: job started sits at debug, so a worker at info logs one job ok line per successful ack and a job failed line per failed attempt — nothing per-poll noise.

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:

FieldNotes
queueThe Redis queue name.
processorThe processor host name, e.g. AudioProcessor::transcode.
job_idThe apalis TaskId for this job.
attemptThe apalis attempt counter (1 on the first try, higher on retries).

attempt distinguishes retries of the same job_id. The OTLP appender from nest-rs-opentelemetry exports these as span attributes.

On success the worker emits job started at debug and job ok at info (carrying elapsed_ms, the wall time of the handler call):

Terminal window
DEBUG process job{queue=audio processor=AudioProcessor::transcode job_id=01HFEX… attempt=1}: nest_rs::queue: job started
INFO process job{queue=audio processor=AudioProcessor::transcode job_id=01HFEX… attempt=1}: features::audio: transcoded file=track-1717405126521.mp3
INFO process job{queue=audio processor=AudioProcessor::transcode job_id=01HFEX… attempt=1}: nest_rs::queue: job ok elapsed_ms=342

A failed attempt emits job failed at warn, carrying elapsed_ms and the error:

Terminal window
DEBUG process job{queue=audio processor=AudioProcessor::transcode job_id=01HFFA… attempt=1}: nest_rs::queue: job started
WARN process job{queue=audio processor=AudioProcessor::transcode job_id=01HFFA… attempt=1}: nest_rs::queue: job failed elapsed_ms=12 error=connection refused

The attempt field on the span is how you correlate a retry: apalis re-runs the same job_id with attempt=2, opening a fresh process job span. There is no separate terminal line — the retry budget lives in the apalis RetryLayer; see Retries and failure for the policy behind attempt.

Each active processor logs one attached line (from the transport) and one registered queue processor line carrying its concurrency and 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:

Terminal window
INFO nest_rs::transport: attached module-contributed transport transport=QueueWorker
INFO nest_rs::queue: registered queue processor processor=AudioProcessor::transcode queue=audio concurrency=5 retries=3
WARN nest_rs::queue: skipped #[process] method: provider unreachable from app's module tree processor=ReportJobs::weekly queue=reports

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.

Terminal window
RUST_LOG=info,nest_rs::queue=debug nestrs run dev worker

info everywhere, debug on the queue path: adds the job started line per poll on top of the job ok / job failed outcomes.

Terminal window
RUST_LOG=nest_rs::queue=warn nestrs run dev worker

Strictly failures, skips, and unversioned warnings — useful for tailing a healthy worker where the only thing you care about is “did anything go wrong.”

Built by YV17labs