Logging¶
CMDx logs every task execution at INFO with the full Result#to_h payload — a structured event stream suitable for log aggregators. Pick a formatter that matches your logging infrastructure.
Formatters¶
| Formatter | Use Case | Output Style |
|---|---|---|
Line |
Traditional logging (default) | Single-line Logger::Formatter style |
JSON |
Structured systems | Compact JSON, one object per line |
KeyValue |
Log parsing | key=value.inspect pairs |
Logstash |
ELK stack | JSON with @version / @timestamp |
Raw |
Minimal output | Message body only (no severity/timestamp) |
Note
The class name is CMDx::LogFormatters::JSON (uppercase). The other formatter classes use CamelCase: Line, KeyValue, Logstash, Raw.
{"severity":"INFO","timestamp":"2026-04-19T10:30:45.123456Z","progname":"cmdx","pid":12345,"message":{"cid":"...","index":0,"root":true,"type":"Task","task":"MyTask","tid":"...","state":"complete","status":"success","reason":null,"metadata":{},"strict":false,"deprecated":false,"retried":false,"retries":0,"duration":12.34,"tags":[]}}
Sample Lifecycle¶
A representative line showing a failed leaf with propagation fields:
I, [2026-04-19T17:04:15.875306Z #20173] INFO -- cmdx: cid="019b4c2b-2a02-..." index=1 root=false type="Task" task=CalculateTax tid="019b4c2b-2a00-..." state="interrupted" status="failed" reason="tax service unavailable" metadata={error_code: "TAX_SERVICE_UNAVAILABLE"} duration=8.92 cause=nil origin=nil threw_failure=<CalculateTax ...> caused_failure=<CalculateTax ...> rolled_back=false
Tip
Pair cid with your APM's correlation field for distributed tracing. To carry an external request id end-to-end, configure a correlation_id resolver and filter logs on xid — see Configuration - Correlation ID. A rescued StandardError (not a fail! call) sets cause=#<TheError: …> and rewrites reason to "[TheError] message".
Structure¶
Every log entry is built from Result#to_h. Available fields:
Severity / Time (added by formatter)¶
| Field | Description | Example |
|---|---|---|
severity |
Logger level name | INFO, WARN, ERROR |
timestamp |
UTC ISO 8601 with microseconds | 2026-04-19T18:43:15.000000Z |
pid |
Process ID | 3784 |
progname |
Logger progname | cmdx (default) |
Raw is the exception — it emits only the message body.
Identity¶
| Field | Description | Example |
|---|---|---|
cid |
Chain UUID (uuid_v7) | "018c2b95-b764-7615-..." |
xid |
External correlation id (e.g. Rails request_id); nil unless config.correlation_id is set |
"req-abc-123" |
index |
Position in chain (root is 0) | 0, 1, 2 |
root |
true for the root task's result |
true, false |
type |
"Task" or "Workflow" |
"Task" |
task |
Task class | GenerateInvoice |
tid |
Task UUID (uuid_v7) | "018c2b95-..." |
context |
Frozen CMDx::Context (root teardown) |
#<CMDx::Context ...> |
tags |
Tags from settings(tags: [...]) |
["billing"] |
Outcome¶
| Field | Description | Example |
|---|---|---|
state |
Lifecycle state | "complete", "interrupted" |
status |
Business outcome | "success", "skipped", "failed" |
reason |
String passed to halt method | "payment declined" or nil |
metadata |
Hash passed to halt method | { code: "INSUFFICIENT_FUNDS" } |
Lifecycle¶
| Field | Description | Example |
|---|---|---|
strict |
true when produced via execute! |
false |
deprecated |
true when the task class is deprecated |
false |
retried |
true when at least one retry happened |
false |
retries |
Number of retry attempts performed | 0 |
duration |
Lifecycle duration in milliseconds | 12.34 |
Failure-only fields¶
These are present only when status == "failed":
| Field | Description |
|---|---|
cause |
Underlying exception (or nil for fail!) |
origin |
{ task:, tid: } of the upstream Result this failure was echoed from, or nil for a locally originated failure |
threw_failure |
{ task:, tid: } of the nearest upstream failed result, or this result |
caused_failure |
{ task:, tid: } of the originating failed result, or this result |
rolled_back |
true when the task's #rollback ran |
Configuration¶
Formatter, level, and logger are set globally on CMDx.configure or per-task via settings(...). See Configuration - Logging for the full option list.
Note
When a task overrides :log_level or :log_formatter, LoggerProxy dups the global logger so settings don't leak across sibling tasks.
Custom Logger¶
Swap the underlying Logger instance per task to route lifecycle entries to a different sink (separate file, syslog, structured-log gem, etc.):
class AuditTransfer < CMDx::Task
settings(
logger: Logger.new(Rails.root.join("log/audit.log"), progname: "audit"),
log_formatter: CMDx::LogFormatters::JSON.new
)
end
The given logger is used as-is — LoggerProxy only dups it when :log_level or :log_formatter differ from what the logger already has set.
Silencing a Task¶
Raise the per-task level above INFO to suppress the lifecycle log line:
Excluding Fields¶
Strip specific keys from the logged Result#to_h with log_exclusions. Useful for dropping bulky or sensitive fields (:context, :metadata) from the log stream while keeping them on the returned Result and telemetry payloads.
CMDx.configure do |config|
config.log_exclusions = [:context, :metadata]
end
class ImportPayroll < CMDx::Task
settings(log_exclusions: [:context])
end
Exclusions match top-level Result#to_h keys only (no deep paths). When empty (the default), the full result is logged.
Log Levels¶
CMDx logs each task result at INFO once the lifecycle completes. The framework itself emits no WARN or ERROR lines — use callbacks (on_failed, on_skipped) or telemetry subscribers (:task_retried, :task_executed) to log at higher severities.
class VerboseTask < CMDx::Task
settings(log_level: Logger::DEBUG)
def work
logger.debug { "feature flags: #{Features.active_flags.inspect}" }
# ...
end
end
Note
Strict-mode failures (execute!) still produce the lifecycle log line and the :task_executed telemetry event — Runtime finalizes the result before re-raising the Fault.
Usage Inside Work¶
Task#logger returns the per-task Logger (with the task's overrides applied):