No task-execution metrics for the Celery worker
Summary
Saleor instruments its HTTP surface well — request count and duration, GraphQL operation count, duration, cost, field usage and slow operations. It also instruments outbound webhooks and the circuit breaker. There is no equivalent for Celery task execution: no task counter, no task duration, no failure counter, no queue-depth or backlog signal.
The result is that a worker outage is invisible to metrics.
Reproduction
Saleor 3.23 via saleor-platform, with an OTLP metrics exporter configured (the stock
backend.env sets OTEL_TRACES_EXPORTER only, so no metrics are exported by default — that is a
separate saleor-platform matter, noted here only because it is a prerequisite for reproducing).
docker compose stop worker
# then place an order through the GraphQL APIObserved:
- Orders place successfully. An end-to-end API run covering catalog → cart → shipping → payment → order completes with the worker stopped.
- Every HTTP metric stays healthy — the API is genuinely unaffected.
- Zero confirmation emails are delivered (verified against the bundled mailpit).
- No metric anywhere reflects the outage.
The only observable evidence is an empty inbox.
The convention already exists
This is not a request to invent metrics. OpenTelemetry's messaging semantic conventions define
this surface for a consumer — messaging.process.duration,
messaging.client.operation.duration, and the messaging.* attribute set. A Celery worker is a
textbook messaging consumer.
Saleor implements none of it. Verified against a live 3.23 stack:
messaging_process_duration 0 series
messaging_process_messages 0 series
messaging_client_operation_duration 0 series
grep -rl "messaging" --include="*.py" /app/saleor -> (no matches outside tests)The practical consequence: tooling that derives SLOs from declared workload kinds will correctly select the messaging convention for a Celery worker, and every resulting indicator binds to nothing.
Why this matters
For a commerce platform the async path carries order confirmations, invoices, webhooks and fulfilment. A silent worker means customers place orders and hear nothing — the failure is customer-visible, but only after the fact, and only to the customer.
Questions an operator cannot answer from Saleor's current metrics:
- Are tasks being processed at all?
- Is there a backlog, and is it growing?
- Which task types are failing, and how often?
- How long is a task taking relative to normal?
Suggested direction
Saleor's telemetry layer already has what is needed — meter.create_metric with Scope.SERVICE,
the MeterProxy post-fork initialisation in celeryconf.py, and the unit/attribute conventions in
saleor/core/telemetry/. A small set of instruments in the same style would close the gap:
| Metric | Type | Attributes |
|---|---|---|
messaging.process.duration |
histogram | messaging.system, messaging.destination.name, error.type |
messaging.process.messages |
counter | messaging.system, messaging.destination.name |
saleor.task.queue_depth |
up-down counter | queue.name |
The first two are the OTel convention names, which would make Saleor legible to conventional
consumer tooling out of the box. A saleor.task.* naming would work too but loses that. Queue
depth has no settled convention, so a Saleor-namespaced name seems reasonable there.
Celery's task_prerun / task_postrun / task_failure signals are the natural hook, mirroring
how graphql/metrics.py wraps the request path.
Prior art
celery-exporter and similar sidecars cover some of this from outside, but they cannot see
Saleor's own task semantics and sit outside the OTel pipeline the project has otherwise adopted.
Source: saleor/saleor