How-to: Logging and Tracing Messages with Middleware
Problem
You want consistent logging and/or distributed tracing for Postboy operations without modifying every handler or subscriber. You need visibility into:
what messages are being processed,
which stage they run in (Publish / Callback / Execute),
whether they succeed or get cancelled,
and (optionally) how long they take.
Prerequisites
Middleware is attached to the Postboy bus.
You understand:
stages (Publish / Callback / Execute)
canHandlefor scopingbefore/afterfor start/end hooksinterrupt/cancel semantics (Interrupt happens in
before)
Optional prerequisites (for tracing):
You have a tracing system concept (trace id / span id / correlation id).
You have a place to store per-operation correlation state (e.g., a request-local context mechanism in your app).
Steps
1) Decide your observability goal
Pick one of these goals (do not mix them blindly):
Audit logging
Log “attempted” and “completed” operations.
Useful for security and compliance.
Debug logging
Log only selected message types during investigation.
Must be easy to turn off and scope narrowly.
Tracing
Create a span per operation and link it to an existing trace context.
Useful for end-to-end latency and cross-service correlation.
Metrics-like logs
Log duration and outcomes in a structured format that can be aggregated.
2) Choose what to log (minimal and safe)
Log only fields that are:
stable (good for searching),
cheap to compute,
safe (no secrets / no PII unless you have a clear policy).
Recommended baseline fields:
stage
message type/category (or a stable name)
message id
middleware name (if relevant)
outcome: attempted / succeeded / cancelled
duration (if you measure it)
Avoid logging:
full message payloads by default
credentials, tokens, personal data
large objects (performance + noise)
3) Decide whether you want to log cancelled attempts
Because interrupt/cancel happens in before:
If you only log in
afteryou will not see cancelled operations.If you want to log attempted operations (including cancelled), log in
before.
A common pattern:
before: log “attempt” (or start span)after: log “success” (or close span)
If you also want to log cancellations, ensure your guard middleware emits a clear cancellation reason, and your logging middleware logs attempts before any guard can interrupt (ordering matters).
4) Implement stage-first scoping in canHandle
To reduce noise and cost:
decide which stages you want to observe
and which message subset you care about
Examples:
only Execute operations for “business actions”
only Callback operations for “requests”
all Publish operations but only for selected event types
5) Add timing (optional) using before/after state
If you measure durations:
capture start time in
beforecompute duration in
after
Important constraints:
aftermay not run for cancellations.Use bounded state (do not leak memory if
afteris skipped).
6) Attach the middleware early enough (and in the right order)
Ordering guidance:
If you want to log attempts + cancellations, put logging/tracing middleware before guard middleware.
If you only want to log successful completions, put it after guard middleware.
Verification (expected outcome)
Basic logging check
Trigger one operation per stage:
publish a message (Publish)
fire a callback message (Callback)
execute an executor (Execute)
Expected:
logs include correct stage and message identity
“attempt” appears in
before“success” appears in
afterfor successful operations
Cancellation visibility check (if you have guards)
Trigger a blocked operation.
Expected:
you see an “attempt” log entry
you do NOT see a “success” entry (because
afterdoes not run on cancel)you can correlate the cancellation via message id and middleware name/reason
Troubleshooting
“I only see successful logs, but not cancellations”
Cause:
you log only in
after. Fix:log attempts in
before.ensure ordering places logging middleware before the middleware that interrupts.
“Logs are too noisy”
Likely causes:
canHandleis too broad (applies to all stages and all message types). Fix:filter by stage first.
filter by message category/type.
add a feature flag or sampling policy for debug logs.
“Logging is slow”
Likely causes:
heavy stringification of messages
synchronous I/O logging on hot paths Fix:
log structured, minimal fields
avoid serializing payloads
use an async logger backend if available
measure overhead with Execute-stage timings
“Trace spans don’t match the user request”
Cause:
you are not propagating correlation context across async boundaries (especially for Callback). Fix:
store trace context in a request-local mechanism in your app runtime.
for Callback, decide whether you trace “request accepted” or “request completed” and instrument accordingly.
Diagram: logging/tracing around a Postboy operation
@startuml title Logging/tracing middleware around an operation (attempt + success)
actor Caller participant "Postboy" as Bus participant "Logging/Tracing MW" as Obs participant "Guard MW (optional)" as Guard participant "Handlers/Subscribers" as H
Caller -> Bus: start operation Bus -> Obs: before(context) note over Obs: log attempt / start span
Bus -> Guard: before(context) alt Guard interrupts note over Guard: policy violation Bus --> Caller: cancelled else Guard continues Bus -> H: run operation H --> Bus: success (result?) Bus -> Obs: after(context, result?) note over Obs: log success / end span Bus --> Caller: completed end
@enduml
Best practices
Always scope aggressively: stage-first, then message subset.
Prefer structured logs (fields) over long strings.
Decide explicitly:
log attempts vs log only successes
include cancellations or not
Avoid payload logging by default; provide an opt-in “verbose mode” if needed.
Use message id as the primary correlation key in logs.
Keep tracing logic inside middleware thin; delegate to your tracing library adapter.
Next steps
[How-to: enable middleware only for a subset of messages][How-to: block execution with a guard middleware (Interrupt)][Middleware lifecycle and contract (before/after/canHandle/dispose)][Middleware stages: Publish / Callback / Execute][How-to: measure duration and collect metrics]