cmdx 0.5.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.DS_Store +0 -0
- data/.cursor/rules/cursor-instructions.mdc +6 -0
- data/.rubocop.yml +19 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +95 -28
- data/README.md +73 -25
- data/docs/ai_prompts.md +319 -0
- data/docs/basics/call.md +234 -14
- data/docs/basics/chain.md +280 -0
- data/docs/basics/context.md +241 -33
- data/docs/basics/setup.md +85 -12
- data/docs/callbacks.md +283 -0
- data/docs/configuration.md +155 -30
- data/docs/getting_started.md +145 -22
- data/docs/internationalization.md +148 -0
- data/docs/interruptions/exceptions.md +198 -11
- data/docs/interruptions/faults.md +196 -44
- data/docs/interruptions/halt.md +188 -35
- data/docs/logging.md +204 -53
- data/docs/middlewares.md +745 -0
- data/docs/outcomes/result.md +305 -10
- data/docs/outcomes/states.md +212 -31
- data/docs/outcomes/statuses.md +284 -30
- data/docs/parameters/coercions.md +411 -29
- data/docs/parameters/defaults.md +258 -25
- data/docs/parameters/definitions.md +247 -72
- data/docs/parameters/namespacing.md +259 -27
- data/docs/parameters/validations.md +173 -168
- data/docs/testing.md +560 -0
- data/docs/tips_and_tricks.md +103 -42
- data/docs/workflows.md +329 -0
- data/lib/cmdx/.DS_Store +0 -0
- data/lib/cmdx/callback.rb +69 -0
- data/lib/cmdx/callback_registry.rb +106 -0
- data/lib/cmdx/chain.rb +190 -0
- data/lib/cmdx/chain_inspector.rb +149 -0
- data/lib/cmdx/chain_serializer.rb +175 -0
- data/lib/cmdx/coercions/array.rb +37 -0
- data/lib/cmdx/coercions/big_decimal.rb +33 -0
- data/lib/cmdx/coercions/boolean.rb +41 -1
- data/lib/cmdx/coercions/complex.rb +31 -0
- data/lib/cmdx/coercions/date.rb +39 -0
- data/lib/cmdx/coercions/date_time.rb +39 -0
- data/lib/cmdx/coercions/float.rb +31 -0
- data/lib/cmdx/coercions/hash.rb +42 -0
- data/lib/cmdx/coercions/integer.rb +32 -0
- data/lib/cmdx/coercions/rational.rb +31 -0
- data/lib/cmdx/coercions/string.rb +31 -0
- data/lib/cmdx/coercions/time.rb +39 -0
- data/lib/cmdx/coercions/virtual.rb +31 -0
- data/lib/cmdx/configuration.rb +217 -9
- data/lib/cmdx/context.rb +173 -2
- data/lib/cmdx/core_ext/hash.rb +72 -0
- data/lib/cmdx/core_ext/module.rb +94 -0
- data/lib/cmdx/core_ext/object.rb +105 -0
- data/lib/cmdx/correlator.rb +217 -0
- data/lib/cmdx/error.rb +210 -8
- data/lib/cmdx/errors.rb +256 -1
- data/lib/cmdx/fault.rb +177 -2
- data/lib/cmdx/faults.rb +158 -2
- data/lib/cmdx/immutator.rb +121 -2
- data/lib/cmdx/lazy_struct.rb +261 -18
- data/lib/cmdx/log_formatters/json.rb +46 -0
- data/lib/cmdx/log_formatters/key_value.rb +46 -0
- data/lib/cmdx/log_formatters/line.rb +54 -0
- data/lib/cmdx/log_formatters/logstash.rb +64 -0
- data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
- data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
- data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
- data/lib/cmdx/log_formatters/raw.rb +54 -0
- data/lib/cmdx/logger.rb +85 -0
- data/lib/cmdx/logger_ansi.rb +93 -7
- data/lib/cmdx/logger_serializer.rb +116 -0
- data/lib/cmdx/middleware.rb +74 -0
- data/lib/cmdx/middleware_registry.rb +106 -0
- data/lib/cmdx/middlewares/correlate.rb +266 -0
- data/lib/cmdx/middlewares/timeout.rb +232 -0
- data/lib/cmdx/parameter.rb +228 -1
- data/lib/cmdx/parameter_inspector.rb +61 -0
- data/lib/cmdx/parameter_registry.rb +125 -0
- data/lib/cmdx/parameter_serializer.rb +83 -0
- data/lib/cmdx/parameter_validator.rb +62 -0
- data/lib/cmdx/parameter_value.rb +109 -1
- data/lib/cmdx/parameters_inspector.rb +59 -0
- data/lib/cmdx/parameters_serializer.rb +102 -0
- data/lib/cmdx/railtie.rb +123 -3
- data/lib/cmdx/result.rb +367 -25
- data/lib/cmdx/result_ansi.rb +105 -9
- data/lib/cmdx/result_inspector.rb +76 -0
- data/lib/cmdx/result_logger.rb +90 -3
- data/lib/cmdx/result_serializer.rb +137 -0
- data/lib/cmdx/rspec/result_matchers.rb +917 -0
- data/lib/cmdx/rspec/task_matchers.rb +570 -0
- data/lib/cmdx/task.rb +405 -37
- data/lib/cmdx/task_serializer.rb +74 -2
- data/lib/cmdx/utils/ansi_color.rb +95 -0
- data/lib/cmdx/utils/log_timestamp.rb +48 -0
- data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
- data/lib/cmdx/utils/name_affix.rb +78 -0
- data/lib/cmdx/validators/custom.rb +82 -0
- data/lib/cmdx/validators/exclusion.rb +94 -0
- data/lib/cmdx/validators/format.rb +102 -8
- data/lib/cmdx/validators/inclusion.rb +104 -0
- data/lib/cmdx/validators/length.rb +128 -0
- data/lib/cmdx/validators/numeric.rb +128 -0
- data/lib/cmdx/validators/presence.rb +93 -7
- data/lib/cmdx/version.rb +7 -1
- data/lib/cmdx/workflow.rb +394 -0
- data/lib/cmdx.rb +25 -64
- data/lib/generators/cmdx/install_generator.rb +37 -1
- data/lib/generators/cmdx/task_generator.rb +69 -1
- data/lib/generators/cmdx/templates/install.rb +43 -15
- data/lib/generators/cmdx/workflow_generator.rb +109 -0
- data/lib/locales/ar.yml +36 -0
- data/lib/locales/cs.yml +36 -0
- data/lib/locales/da.yml +36 -0
- data/lib/locales/de.yml +36 -0
- data/lib/locales/el.yml +36 -0
- data/lib/locales/en.yml +20 -20
- data/lib/locales/es.yml +20 -20
- data/lib/locales/fi.yml +36 -0
- data/lib/locales/fr.yml +36 -0
- data/lib/locales/he.yml +36 -0
- data/lib/locales/hi.yml +36 -0
- data/lib/locales/it.yml +36 -0
- data/lib/locales/ja.yml +36 -0
- data/lib/locales/ko.yml +36 -0
- data/lib/locales/nl.yml +36 -0
- data/lib/locales/no.yml +36 -0
- data/lib/locales/pl.yml +36 -0
- data/lib/locales/pt.yml +36 -0
- data/lib/locales/ru.yml +36 -0
- data/lib/locales/sv.yml +36 -0
- data/lib/locales/th.yml +36 -0
- data/lib/locales/tr.yml +36 -0
- data/lib/locales/vi.yml +36 -0
- data/lib/locales/zh.yml +36 -0
- metadata +77 -15
- data/docs/basics/run.md +0 -34
- data/docs/batch.md +0 -53
- data/docs/example.md +0 -82
- data/docs/hooks.md +0 -62
- data/lib/cmdx/batch.rb +0 -43
- data/lib/cmdx/parameters.rb +0 -35
- data/lib/cmdx/run.rb +0 -39
- data/lib/cmdx/run_inspector.rb +0 -26
- data/lib/cmdx/run_serializer.rb +0 -20
- data/lib/cmdx/task_hook.rb +0 -18
- data/lib/generators/cmdx/batch_generator.rb +0 -30
- /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
data/docs/interruptions/halt.md
CHANGED
@@ -1,80 +1,233 @@
|
|
1
1
|
# Interruptions - Halt
|
2
2
|
|
3
|
-
Halting stops execution of a task
|
4
|
-
|
3
|
+
Halting stops execution of a task with explicit intent signaling. Tasks provide
|
4
|
+
two primary halt methods that control execution flow and result in different
|
5
|
+
outcomes, each serving specific use cases in business logic.
|
5
6
|
|
6
|
-
##
|
7
|
+
## Table of Contents
|
7
8
|
|
8
|
-
|
9
|
+
- [TLDR](#tldr)
|
10
|
+
- [Skip (`skip!`)](#skip-skip)
|
11
|
+
- [Fail (`fail!`)](#fail-fail)
|
12
|
+
- [Metadata Enrichment](#metadata-enrichment)
|
13
|
+
- [State Transitions](#state-transitions)
|
14
|
+
- [Exception Behavior](#exception-behavior)
|
15
|
+
- [The Reason Key](#the-reason-key)
|
16
|
+
|
17
|
+
## TLDR
|
18
|
+
|
19
|
+
- **`skip!`** - Controlled interruption when task shouldn't execute (not an error)
|
20
|
+
- **`fail!`** - Controlled interruption when task encounters an error condition
|
21
|
+
- **Metadata** - Both methods accept metadata hash: `skip!(reason: "...", error_code: "...")`
|
22
|
+
- **State changes** - Both transition to `interrupted` state, `skipped` or `failed` status
|
23
|
+
- **Exception behavior** - `call` returns results, `call!` raises `CMDx::Skipped/Failed` based on task_halt config
|
24
|
+
|
25
|
+
## Skip (`skip!`)
|
26
|
+
|
27
|
+
The `skip!` method indicates that a task did not meet the criteria to continue
|
28
|
+
execution. This represents a controlled, intentional interruption where the
|
29
|
+
task determines that execution is not necessary or appropriate under current
|
30
|
+
conditions.
|
31
|
+
|
32
|
+
### Basic Usage
|
9
33
|
|
10
34
|
```ruby
|
11
|
-
class
|
35
|
+
class ProcessUserOrderTask < CMDx::Task
|
12
36
|
|
13
37
|
def call
|
14
|
-
|
38
|
+
context.order = Order.find(context.order_id)
|
39
|
+
|
40
|
+
# Skip if order is already processed
|
41
|
+
skip!(reason: "Order already processed") if context.order.processed?
|
15
42
|
|
16
|
-
#
|
43
|
+
# Skip if prerequisites aren't met
|
44
|
+
skip!(reason: "Payment method not configured") unless context.order.payment_method
|
45
|
+
|
46
|
+
# Continue with business logic
|
47
|
+
context.order.process!
|
17
48
|
end
|
18
49
|
|
19
50
|
end
|
20
51
|
```
|
21
52
|
|
22
|
-
|
53
|
+
> [!NOTE]
|
54
|
+
> Use `skip!` when a task cannot or should not execute under current conditions, but this is not an error. Skipped tasks are considered successful outcomes.
|
55
|
+
|
56
|
+
## Fail (`fail!`)
|
57
|
+
|
58
|
+
The `fail!` method indicates that a task encountered an error condition that
|
59
|
+
prevents successful completion. This represents controlled failure where the
|
60
|
+
task explicitly determines that execution cannot continue successfully.
|
23
61
|
|
24
|
-
|
62
|
+
### Basic Usage
|
25
63
|
|
26
64
|
```ruby
|
27
|
-
class
|
65
|
+
class ProcessOrderPaymentTask < CMDx::Task
|
28
66
|
|
29
67
|
def call
|
30
|
-
|
68
|
+
context.payment = Payment.find(context.payment_id)
|
31
69
|
|
32
|
-
#
|
70
|
+
# Fail on validation errors
|
71
|
+
fail!(reason: "Payment amount must be positive") unless context.payment.amount > 0
|
72
|
+
|
73
|
+
# Fail on business rule violations
|
74
|
+
fail!(reason: "Insufficient funds") unless sufficient_funds?
|
75
|
+
|
76
|
+
# Continue with processing
|
77
|
+
process_payment
|
33
78
|
end
|
34
79
|
|
35
80
|
end
|
36
81
|
```
|
37
82
|
|
38
|
-
|
83
|
+
> [!IMPORTANT]
|
84
|
+
> Use `fail!` when a task encounters an error that prevents successful completion. Failed tasks represent error conditions that need to be handled or corrected.
|
85
|
+
|
86
|
+
## Metadata Enrichment
|
39
87
|
|
40
|
-
|
41
|
-
|
42
|
-
|
88
|
+
Both halt methods accept metadata to provide context about the interruption.
|
89
|
+
Metadata is stored as a hash and becomes available through the result object.
|
90
|
+
|
91
|
+
### Structured Metadata
|
43
92
|
|
44
93
|
```ruby
|
45
|
-
class
|
94
|
+
class ProcessUserOrderTask < CMDx::Task
|
46
95
|
|
47
96
|
def call
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
97
|
+
context.order = Order.find(context.order_id)
|
98
|
+
|
99
|
+
if context.order.status == "cancelled"
|
100
|
+
skip!(
|
101
|
+
reason: "Order was cancelled",
|
102
|
+
order_id: context.order.id,
|
103
|
+
cancelled_at: context.order.cancelled_at,
|
104
|
+
reason_code: context.order.cancellation_reason
|
105
|
+
)
|
106
|
+
end
|
107
|
+
|
108
|
+
unless inventory_available?
|
109
|
+
fail!(
|
110
|
+
reason: "Insufficient inventory",
|
111
|
+
required_quantity: context.order.quantity,
|
112
|
+
available_quantity: current_inventory,
|
113
|
+
restock_date: estimated_restock_date,
|
114
|
+
error_code: "INVENTORY_DEPLETED"
|
115
|
+
)
|
54
116
|
end
|
117
|
+
|
118
|
+
process_order
|
55
119
|
end
|
56
120
|
|
57
121
|
end
|
122
|
+
```
|
123
|
+
|
124
|
+
### Accessing Metadata
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
result = ProcessUserOrderTask.call(order_id: 123)
|
128
|
+
|
129
|
+
# Check result status
|
130
|
+
result.skipped? #=> true
|
131
|
+
result.failed? #=> false
|
132
|
+
|
133
|
+
# Access metadata
|
134
|
+
result.metadata[:reason] #=> "Order was cancelled"
|
135
|
+
result.metadata[:order_id] #=> 123
|
136
|
+
result.metadata[:cancelled_at] #=> 2023-01-01 10:00:00 UTC
|
137
|
+
result.metadata[:reason_code] #=> "customer_request"
|
138
|
+
```
|
139
|
+
|
140
|
+
## State Transitions
|
141
|
+
|
142
|
+
Halt methods trigger specific state and status transitions:
|
143
|
+
|
144
|
+
### Skip Transitions
|
145
|
+
- **State**: `initialized` → `executing` → `interrupted`
|
146
|
+
- **Status**: `success` → `skipped`
|
147
|
+
- **Result**: `good? = true`, `bad? = true`
|
148
|
+
|
149
|
+
### Fail Transitions
|
150
|
+
- **State**: `initialized` → `executing` → `interrupted`
|
151
|
+
- **Status**: `success` → `failed`
|
152
|
+
- **Result**: `good? = false`, `bad? = true`
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
result = ProcessUserOrderTask.call(order_id: 123)
|
156
|
+
|
157
|
+
# State information
|
158
|
+
result.state #=> "interrupted"
|
159
|
+
result.status #=> "skipped" or "failed"
|
160
|
+
result.interrupted? #=> true
|
161
|
+
result.complete? #=> false
|
162
|
+
|
163
|
+
# Outcome categorization
|
164
|
+
result.good? #=> true for skipped, false for failed
|
165
|
+
result.bad? #=> true for both skipped and failed
|
166
|
+
```
|
167
|
+
|
168
|
+
## Exception Behavior
|
169
|
+
|
170
|
+
Halt methods behave differently depending on the call method used:
|
171
|
+
|
172
|
+
### With `call` (Non-bang)
|
173
|
+
Returns a result object without raising exceptions:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
result = ProcessUserOrderTask.call(order_id: 123)
|
177
|
+
|
178
|
+
case result.status
|
179
|
+
when "success"
|
180
|
+
puts "Order processed successfully"
|
181
|
+
when "skipped"
|
182
|
+
puts "Order skipped: #{result.metadata[:reason]}"
|
183
|
+
when "failed"
|
184
|
+
puts "Order failed: #{result.metadata[:reason]}"
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
### With `call!` (Bang)
|
189
|
+
Raises fault exceptions based on `task_halt` configuration:
|
58
190
|
|
59
|
-
|
60
|
-
|
191
|
+
```ruby
|
192
|
+
begin
|
193
|
+
result = ProcessUserOrderTask.call!(order_id: 123)
|
194
|
+
puts "Success: #{result.context.order.id}"
|
195
|
+
rescue CMDx::Skipped => e
|
196
|
+
puts "Skipped: #{e.message}"
|
197
|
+
puts "Order ID: #{e.context.order_id}"
|
198
|
+
rescue CMDx::Failed => e
|
199
|
+
puts "Failed: #{e.message}"
|
200
|
+
puts "Error code: #{e.result.metadata[:error_code]}"
|
201
|
+
end
|
61
202
|
```
|
62
203
|
|
63
|
-
> [!
|
64
|
-
> The
|
65
|
-
|
204
|
+
> [!WARNING]
|
205
|
+
> The `call!` method raises exceptions for halt conditions based on the `task_halt` configuration. The `call` method always returns result objects without raising exceptions.
|
206
|
+
|
207
|
+
## The Reason Key
|
66
208
|
|
67
|
-
|
209
|
+
The `:reason` key in metadata has special significance:
|
68
210
|
|
69
|
-
|
211
|
+
- Used as the exception message when faults are raised
|
212
|
+
- Provides human-readable explanation of the halt
|
213
|
+
- Strongly recommended for all halt calls
|
70
214
|
|
71
215
|
```ruby
|
72
|
-
|
73
|
-
|
74
|
-
|
216
|
+
# Good: Provides clear reason
|
217
|
+
skip!(reason: "User already has an active session")
|
218
|
+
fail!(reason: "Credit card expired", code: "EXPIRED_CARD")
|
219
|
+
|
220
|
+
# Acceptable: Other metadata without reason
|
221
|
+
skip!(status: "redundant", timestamp: Time.now)
|
222
|
+
|
223
|
+
# Fallback: Default message if no reason provided
|
224
|
+
skip! # Exception message: "no reason given"
|
75
225
|
```
|
76
226
|
|
227
|
+
> [!TIP]
|
228
|
+
> Always try to include a `:reason` key in metadata when using halt methods. This provides clear context for debugging and creates meaningful exception messages when using `call!`.
|
229
|
+
|
77
230
|
---
|
78
231
|
|
79
|
-
- **Prev:** [Basics -
|
80
|
-
- **Next:** [Interruptions - Faults](
|
232
|
+
- **Prev:** [Basics - Chain](../basics/chain.md)
|
233
|
+
- **Next:** [Interruptions - Faults](faults.md)
|
data/docs/logging.md
CHANGED
@@ -1,104 +1,255 @@
|
|
1
1
|
# Logging
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
-
|
10
|
-
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
3
|
+
CMDx provides comprehensive automatic logging for task execution with structured data, customizable formatters, and intelligent severity mapping. All task results are logged after completion with rich metadata for debugging and monitoring.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
- [TLDR](#tldr)
|
7
|
+
- [Log Formatters](#log-formatters)
|
8
|
+
- [Standard Formatters](#standard-formatters)
|
9
|
+
- [Stylized Formatters (ANSI Colors)](#stylized-formatters-ansi-colors)
|
10
|
+
- [Sample Output](#sample-output)
|
11
|
+
- [Success Result](#success-result)
|
12
|
+
- [Skipped Result](#skipped-result)
|
13
|
+
- [Failed Result](#failed-result)
|
14
|
+
- [Failure Chain (Workflow Workflows)](#failure-chain-workflow-workflows)
|
15
|
+
- [Configuration](#configuration)
|
16
|
+
- [Global Configuration](#global-configuration)
|
17
|
+
- [Task-Specific Configuration](#task-specific-configuration)
|
18
|
+
- [Environment-Specific Configuration](#environment-specific-configuration)
|
19
|
+
- [Severity Mapping](#severity-mapping)
|
20
|
+
- [Manual Logging](#manual-logging)
|
21
|
+
- [Advanced Formatter Usage](#advanced-formatter-usage)
|
22
|
+
- [Custom Formatter](#custom-formatter)
|
23
|
+
- [Multi-Destination Logging](#multi-destination-logging)
|
24
|
+
- [Log Data Structure](#log-data-structure)
|
25
|
+
|
26
|
+
## TLDR
|
27
|
+
|
28
|
+
- **Automatic logging** - All task results logged after completion with structured data
|
29
|
+
- **8 formatters** - Standard (Line, Json, KeyValue, Logstash, Raw) and Stylized (Pretty variants)
|
30
|
+
- **Configuration** - Global via `CMDx.configure` or task-specific via `task_settings!`
|
31
|
+
- **Severity mapping** - Success=INFO, Skipped=WARN, Failed=ERROR
|
32
|
+
- **Rich metadata** - Includes runtime, chain_id, status, context, and failure chains
|
33
|
+
- **Manual logging** - Access `logger` within tasks for custom messages
|
34
|
+
|
35
|
+
## Log Formatters
|
36
|
+
|
37
|
+
CMDx provides 8 built-in log formatters organized into standard and stylized categories:
|
38
|
+
|
39
|
+
### Standard Formatters
|
40
|
+
- **`Line`** - Traditional single-line format similar to Ruby's Logger
|
41
|
+
- **`Json`** - Compact single-line JSON for structured logging systems
|
42
|
+
- **`KeyValue`** - Space-separated key=value pairs for easy parsing
|
43
|
+
- **`Logstash`** - ELK stack compatible JSON with @version and @timestamp fields
|
44
|
+
- **`Raw`** - Minimal output containing only the message content
|
45
|
+
|
46
|
+
### Stylized Formatters (ANSI Colors)
|
47
|
+
|
48
|
+
> [!NOTE]
|
49
|
+
> Stylized formatters include ANSI color codes for terminal readability and are best suited for development environments.
|
50
|
+
|
51
|
+
- **`PrettyLine`** - Colorized line format (default)
|
52
|
+
- **`PrettyJson`** - Human-readable multi-line JSON with syntax highlighting
|
53
|
+
- **`PrettyKeyValue`** - Colorized key=value pairs for terminal readability
|
54
|
+
|
55
|
+
## Sample Output
|
56
|
+
|
57
|
+
### Success Result
|
18
58
|
```txt
|
19
|
-
|
59
|
+
I, [2022-07-17T18:43:15.000000 #3784] INFO -- CreateOrderTask: index=0 chain_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=CreateOrderTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=complete status=success outcome=success metadata={order_id: 123, confirmation: "ABC123"} runtime=0.45 origin=CMDx
|
20
60
|
```
|
21
61
|
|
22
|
-
|
62
|
+
### Skipped Result
|
23
63
|
```txt
|
24
|
-
|
64
|
+
W, [2022-07-17T18:43:15.000000 #3784] WARN -- ValidatePaymentTask: index=0 chain_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=ValidatePaymentTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=skipped outcome=skipped metadata={reason: "Order already processed"} runtime=0.02 origin=CMDx
|
25
65
|
```
|
26
66
|
|
27
|
-
|
67
|
+
### Failed Result
|
28
68
|
```txt
|
29
|
-
E, [2022-07-17T18:43:15.000000 #3784] ERROR --
|
69
|
+
E, [2022-07-17T18:43:15.000000 #3784] ERROR -- ProcessPaymentTask: index=0 chain_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=ProcessPaymentTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=failed metadata={reason: "Payment declined", error_code: "INSUFFICIENT_FUNDS"} runtime=0.15 origin=CMDx
|
30
70
|
```
|
31
71
|
|
32
|
-
|
72
|
+
### Failure Chain (Workflow Workflows)
|
33
73
|
```txt
|
34
|
-
E, [2022-07-17T18:43:15.000000 #3784] ERROR --
|
74
|
+
E, [2022-07-17T18:43:15.000000 #3784] ERROR -- OrderCreationWorkflow: index=0 chain_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Workflow class=OrderCreationWorkflow id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=interrupted metadata={} runtime=0.75 caused_failure={index: 2, class: "ValidatePaymentTask", status: "failed"} threw_failure={index: 1, class: "ProcessPaymentTask", status: "failed"} origin=CMDx
|
35
75
|
```
|
36
76
|
|
37
|
-
##
|
77
|
+
## Configuration
|
38
78
|
|
39
|
-
|
40
|
-
[stdlib documentation](http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html).
|
79
|
+
### Global Configuration
|
41
80
|
|
42
|
-
|
81
|
+
Configure logging globally in your CMDx initializer:
|
43
82
|
|
44
83
|
```ruby
|
45
84
|
CMDx.configure do |config|
|
46
|
-
|
47
|
-
config.logger = Logger
|
48
|
-
|
49
|
-
# Multiline declarations:
|
50
|
-
config.logger = Rails.logger
|
51
|
-
config.logger.formatter = CMDx::LogFormatters::Line.new
|
52
|
-
config.logger.level = Logger::WARN
|
85
|
+
config.logger = Logger.new("log/cmdx.log", formatter: CMDx::LogFormatters::Json.new)
|
86
|
+
config.logger.level = Logger::INFO
|
53
87
|
end
|
54
88
|
```
|
55
89
|
|
56
|
-
|
90
|
+
### Task-Specific Configuration
|
57
91
|
|
58
|
-
|
59
|
-
class ProcessOrderTask < CMDx::Task
|
92
|
+
Override logging settings for individual tasks:
|
60
93
|
|
61
|
-
|
94
|
+
```ruby
|
95
|
+
class SendEmailTask < CMDx::Task
|
96
|
+
task_settings!(
|
97
|
+
logger: Rails.logger,
|
98
|
+
log_formatter: CMDx::LogFormatters::Json.new,
|
99
|
+
log_level: Logger::WARN
|
100
|
+
)
|
62
101
|
|
63
102
|
def call
|
64
|
-
#
|
103
|
+
# Task implementation
|
65
104
|
end
|
105
|
+
end
|
66
106
|
|
107
|
+
# Base class with shared logging configuration
|
108
|
+
class ApplicationTask < CMDx::Task
|
109
|
+
task_settings!(
|
110
|
+
logger: Logger.new("log/tasks.log"),
|
111
|
+
log_formatter: CMDx::LogFormatters::Logstash.new,
|
112
|
+
log_level: Logger::INFO
|
113
|
+
)
|
67
114
|
end
|
68
115
|
```
|
69
116
|
|
70
|
-
|
71
|
-
|
72
|
-
>
|
117
|
+
## Severity Mapping
|
118
|
+
|
119
|
+
> [!IMPORTANT]
|
120
|
+
> CMDx automatically maps result statuses to appropriate log severity levels. Manual log level overrides are not recommended.
|
121
|
+
|
122
|
+
| Result Status | Log Level | Use Case |
|
123
|
+
| ------------- | --------- | -------- |
|
124
|
+
| `success` | `INFO` | Normal successful completion |
|
125
|
+
| `skipped` | `WARN` | Intentional skip (business logic) |
|
126
|
+
| `failed` | `ERROR` | Task failure or exception |
|
73
127
|
|
74
|
-
##
|
128
|
+
## Manual Logging
|
75
129
|
|
76
|
-
|
130
|
+
Access the configured logger within tasks for custom log messages:
|
77
131
|
|
78
132
|
```ruby
|
79
133
|
class ProcessOrderTask < CMDx::Task
|
80
|
-
|
81
134
|
def call
|
82
|
-
logger.info "
|
83
|
-
|
135
|
+
logger.info "Starting order processing", order_id: context.order_id
|
136
|
+
|
137
|
+
# Performance-optimized debug logging
|
138
|
+
logger.debug { "Order details: #{context.order.inspect}" }
|
139
|
+
|
140
|
+
# Structured logging
|
141
|
+
logger.info "Payment processed", {
|
142
|
+
order_id: context.order_id,
|
143
|
+
amount: context.order.total,
|
144
|
+
payment_method: context.payment_method
|
145
|
+
}
|
146
|
+
|
147
|
+
# Exception handling with logging
|
148
|
+
begin
|
149
|
+
validate_inventory
|
150
|
+
rescue StandardError => e
|
151
|
+
logger.error "Inventory validation failed: #{e.message}", {
|
152
|
+
exception: e.class.name,
|
153
|
+
order_id: context.order_id
|
154
|
+
}
|
155
|
+
raise
|
156
|
+
end
|
84
157
|
end
|
85
|
-
|
86
158
|
end
|
87
159
|
```
|
88
160
|
|
89
|
-
##
|
161
|
+
## Advanced Formatter Usage
|
90
162
|
|
91
|
-
|
163
|
+
### Custom Formatter
|
164
|
+
|
165
|
+
Create custom formatters for specific output requirements:
|
92
166
|
|
93
167
|
```ruby
|
94
|
-
class
|
168
|
+
class SlackLogFormatter
|
95
169
|
def call(severity, time, task, message)
|
96
|
-
|
170
|
+
emoji = case severity
|
171
|
+
when 'INFO' then '✅'
|
172
|
+
when 'WARN' then '⚠️'
|
173
|
+
when 'ERROR' then '❌'
|
174
|
+
else '📝'
|
175
|
+
end
|
176
|
+
|
177
|
+
"#{emoji} #{task.class.name}: #{message}\n"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class SendNotificationTask < CMDx::Task
|
182
|
+
task_settings!(
|
183
|
+
logger: Logger.new("log/notifications.log", formatter: SlackLogFormatter.new)
|
184
|
+
)
|
185
|
+
end
|
186
|
+
```
|
187
|
+
|
188
|
+
### Multi-Destination Logging
|
189
|
+
|
190
|
+
> [!TIP]
|
191
|
+
> Use multi-destination logging to send output to both console and files simultaneously during development.
|
192
|
+
|
193
|
+
```ruby
|
194
|
+
class MultiLogger
|
195
|
+
def initialize(*loggers)
|
196
|
+
@loggers = loggers
|
197
|
+
end
|
198
|
+
|
199
|
+
%w[debug info warn error fatal].each do |level|
|
200
|
+
define_method(level) do |message = nil, &block|
|
201
|
+
@loggers.each { |logger| logger.send(level, message, &block) }
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def formatter=(formatter)
|
206
|
+
@loggers.each { |logger| logger.formatter = formatter }
|
207
|
+
end
|
208
|
+
|
209
|
+
def level=(level)
|
210
|
+
@loggers.each { |logger| logger.level = level }
|
97
211
|
end
|
98
212
|
end
|
213
|
+
|
214
|
+
# Usage
|
215
|
+
CMDx.configure do |config|
|
216
|
+
config.logger = MultiLogger.new(
|
217
|
+
Logger.new(STDOUT, formatter: CMDx::LogFormatters::PrettyLine.new),
|
218
|
+
Logger.new("log/cmdx.log", formatter: CMDx::LogFormatters::Json.new)
|
219
|
+
)
|
220
|
+
end
|
99
221
|
```
|
100
222
|
|
223
|
+
## Log Data Structure
|
224
|
+
|
225
|
+
CMDx logs contain comprehensive execution metadata:
|
226
|
+
|
227
|
+
#### Standard Fields
|
228
|
+
- `severity` - Log level (INFO, WARN, ERROR)
|
229
|
+
- `pid` - Process ID for multi-process debugging
|
230
|
+
- `timestamp` - ISO 8601 formatted execution time
|
231
|
+
- `origin` - Always "CMDx" for filtering
|
232
|
+
|
233
|
+
#### Task Identification
|
234
|
+
- `index` - Position in execution sequence
|
235
|
+
- `chain_id` - Unique identifier for execution chain
|
236
|
+
- `type` - Task or Workflow
|
237
|
+
- `class` - Task class name
|
238
|
+
- `id` - Unique task instance identifier
|
239
|
+
- `tags` - Custom tags for categorization
|
240
|
+
|
241
|
+
#### Execution Information
|
242
|
+
- `state` - Execution lifecycle state (initialized, executing, complete, interrupted)
|
243
|
+
- `status` - Business logic outcome (success, skipped, failed)
|
244
|
+
- `outcome` - Final result classification
|
245
|
+
- `metadata` - Custom data from skip!/fail! calls
|
246
|
+
- `runtime` - Execution time in seconds
|
247
|
+
|
248
|
+
#### Failure Chain (Complex Workflows)
|
249
|
+
- `caused_failure` - Original failing task information
|
250
|
+
- `threw_failure` - Task that propagated the failure
|
251
|
+
|
101
252
|
---
|
102
253
|
|
103
|
-
- **Prev:** [
|
104
|
-
- **Next:** [
|
254
|
+
- **Prev:** [Workflows](workflows.md)
|
255
|
+
- **Next:** [Internationalization (i18n)](internationalization.md)
|