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.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/.DS_Store +0 -0
  3. data/.cursor/rules/cursor-instructions.mdc +6 -0
  4. data/.rubocop.yml +19 -1
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +95 -28
  7. data/README.md +73 -25
  8. data/docs/ai_prompts.md +319 -0
  9. data/docs/basics/call.md +234 -14
  10. data/docs/basics/chain.md +280 -0
  11. data/docs/basics/context.md +241 -33
  12. data/docs/basics/setup.md +85 -12
  13. data/docs/callbacks.md +283 -0
  14. data/docs/configuration.md +155 -30
  15. data/docs/getting_started.md +145 -22
  16. data/docs/internationalization.md +148 -0
  17. data/docs/interruptions/exceptions.md +198 -11
  18. data/docs/interruptions/faults.md +196 -44
  19. data/docs/interruptions/halt.md +188 -35
  20. data/docs/logging.md +204 -53
  21. data/docs/middlewares.md +745 -0
  22. data/docs/outcomes/result.md +305 -10
  23. data/docs/outcomes/states.md +212 -31
  24. data/docs/outcomes/statuses.md +284 -30
  25. data/docs/parameters/coercions.md +411 -29
  26. data/docs/parameters/defaults.md +258 -25
  27. data/docs/parameters/definitions.md +247 -72
  28. data/docs/parameters/namespacing.md +259 -27
  29. data/docs/parameters/validations.md +173 -168
  30. data/docs/testing.md +560 -0
  31. data/docs/tips_and_tricks.md +103 -42
  32. data/docs/workflows.md +329 -0
  33. data/lib/cmdx/.DS_Store +0 -0
  34. data/lib/cmdx/callback.rb +69 -0
  35. data/lib/cmdx/callback_registry.rb +106 -0
  36. data/lib/cmdx/chain.rb +190 -0
  37. data/lib/cmdx/chain_inspector.rb +149 -0
  38. data/lib/cmdx/chain_serializer.rb +175 -0
  39. data/lib/cmdx/coercions/array.rb +37 -0
  40. data/lib/cmdx/coercions/big_decimal.rb +33 -0
  41. data/lib/cmdx/coercions/boolean.rb +41 -1
  42. data/lib/cmdx/coercions/complex.rb +31 -0
  43. data/lib/cmdx/coercions/date.rb +39 -0
  44. data/lib/cmdx/coercions/date_time.rb +39 -0
  45. data/lib/cmdx/coercions/float.rb +31 -0
  46. data/lib/cmdx/coercions/hash.rb +42 -0
  47. data/lib/cmdx/coercions/integer.rb +32 -0
  48. data/lib/cmdx/coercions/rational.rb +31 -0
  49. data/lib/cmdx/coercions/string.rb +31 -0
  50. data/lib/cmdx/coercions/time.rb +39 -0
  51. data/lib/cmdx/coercions/virtual.rb +31 -0
  52. data/lib/cmdx/configuration.rb +217 -9
  53. data/lib/cmdx/context.rb +173 -2
  54. data/lib/cmdx/core_ext/hash.rb +72 -0
  55. data/lib/cmdx/core_ext/module.rb +94 -0
  56. data/lib/cmdx/core_ext/object.rb +105 -0
  57. data/lib/cmdx/correlator.rb +217 -0
  58. data/lib/cmdx/error.rb +210 -8
  59. data/lib/cmdx/errors.rb +256 -1
  60. data/lib/cmdx/fault.rb +177 -2
  61. data/lib/cmdx/faults.rb +158 -2
  62. data/lib/cmdx/immutator.rb +121 -2
  63. data/lib/cmdx/lazy_struct.rb +261 -18
  64. data/lib/cmdx/log_formatters/json.rb +46 -0
  65. data/lib/cmdx/log_formatters/key_value.rb +46 -0
  66. data/lib/cmdx/log_formatters/line.rb +54 -0
  67. data/lib/cmdx/log_formatters/logstash.rb +64 -0
  68. data/lib/cmdx/log_formatters/pretty_json.rb +57 -0
  69. data/lib/cmdx/log_formatters/pretty_key_value.rb +51 -0
  70. data/lib/cmdx/log_formatters/pretty_line.rb +60 -0
  71. data/lib/cmdx/log_formatters/raw.rb +54 -0
  72. data/lib/cmdx/logger.rb +85 -0
  73. data/lib/cmdx/logger_ansi.rb +93 -7
  74. data/lib/cmdx/logger_serializer.rb +116 -0
  75. data/lib/cmdx/middleware.rb +74 -0
  76. data/lib/cmdx/middleware_registry.rb +106 -0
  77. data/lib/cmdx/middlewares/correlate.rb +266 -0
  78. data/lib/cmdx/middlewares/timeout.rb +232 -0
  79. data/lib/cmdx/parameter.rb +228 -1
  80. data/lib/cmdx/parameter_inspector.rb +61 -0
  81. data/lib/cmdx/parameter_registry.rb +125 -0
  82. data/lib/cmdx/parameter_serializer.rb +83 -0
  83. data/lib/cmdx/parameter_validator.rb +62 -0
  84. data/lib/cmdx/parameter_value.rb +109 -1
  85. data/lib/cmdx/parameters_inspector.rb +59 -0
  86. data/lib/cmdx/parameters_serializer.rb +102 -0
  87. data/lib/cmdx/railtie.rb +123 -3
  88. data/lib/cmdx/result.rb +367 -25
  89. data/lib/cmdx/result_ansi.rb +105 -9
  90. data/lib/cmdx/result_inspector.rb +76 -0
  91. data/lib/cmdx/result_logger.rb +90 -3
  92. data/lib/cmdx/result_serializer.rb +137 -0
  93. data/lib/cmdx/rspec/result_matchers.rb +917 -0
  94. data/lib/cmdx/rspec/task_matchers.rb +570 -0
  95. data/lib/cmdx/task.rb +405 -37
  96. data/lib/cmdx/task_serializer.rb +74 -2
  97. data/lib/cmdx/utils/ansi_color.rb +95 -0
  98. data/lib/cmdx/utils/log_timestamp.rb +48 -0
  99. data/lib/cmdx/utils/monotonic_runtime.rb +71 -4
  100. data/lib/cmdx/utils/name_affix.rb +78 -0
  101. data/lib/cmdx/validators/custom.rb +82 -0
  102. data/lib/cmdx/validators/exclusion.rb +94 -0
  103. data/lib/cmdx/validators/format.rb +102 -8
  104. data/lib/cmdx/validators/inclusion.rb +104 -0
  105. data/lib/cmdx/validators/length.rb +128 -0
  106. data/lib/cmdx/validators/numeric.rb +128 -0
  107. data/lib/cmdx/validators/presence.rb +93 -7
  108. data/lib/cmdx/version.rb +7 -1
  109. data/lib/cmdx/workflow.rb +394 -0
  110. data/lib/cmdx.rb +25 -64
  111. data/lib/generators/cmdx/install_generator.rb +37 -1
  112. data/lib/generators/cmdx/task_generator.rb +69 -1
  113. data/lib/generators/cmdx/templates/install.rb +43 -15
  114. data/lib/generators/cmdx/workflow_generator.rb +109 -0
  115. data/lib/locales/ar.yml +36 -0
  116. data/lib/locales/cs.yml +36 -0
  117. data/lib/locales/da.yml +36 -0
  118. data/lib/locales/de.yml +36 -0
  119. data/lib/locales/el.yml +36 -0
  120. data/lib/locales/en.yml +20 -20
  121. data/lib/locales/es.yml +20 -20
  122. data/lib/locales/fi.yml +36 -0
  123. data/lib/locales/fr.yml +36 -0
  124. data/lib/locales/he.yml +36 -0
  125. data/lib/locales/hi.yml +36 -0
  126. data/lib/locales/it.yml +36 -0
  127. data/lib/locales/ja.yml +36 -0
  128. data/lib/locales/ko.yml +36 -0
  129. data/lib/locales/nl.yml +36 -0
  130. data/lib/locales/no.yml +36 -0
  131. data/lib/locales/pl.yml +36 -0
  132. data/lib/locales/pt.yml +36 -0
  133. data/lib/locales/ru.yml +36 -0
  134. data/lib/locales/sv.yml +36 -0
  135. data/lib/locales/th.yml +36 -0
  136. data/lib/locales/tr.yml +36 -0
  137. data/lib/locales/vi.yml +36 -0
  138. data/lib/locales/zh.yml +36 -0
  139. metadata +77 -15
  140. data/docs/basics/run.md +0 -34
  141. data/docs/batch.md +0 -53
  142. data/docs/example.md +0 -82
  143. data/docs/hooks.md +0 -62
  144. data/lib/cmdx/batch.rb +0 -43
  145. data/lib/cmdx/parameters.rb +0 -35
  146. data/lib/cmdx/run.rb +0 -39
  147. data/lib/cmdx/run_inspector.rb +0 -26
  148. data/lib/cmdx/run_serializer.rb +0 -20
  149. data/lib/cmdx/task_hook.rb +0 -18
  150. data/lib/generators/cmdx/batch_generator.rb +0 -30
  151. /data/lib/generators/cmdx/templates/{batch.rb.tt → workflow.rb.tt} +0 -0
@@ -1,80 +1,233 @@
1
1
  # Interruptions - Halt
2
2
 
3
- Halting stops execution of a task. Halt methods signal the intent as to why a task
4
- is stopped executing.
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
- ## Skip
7
+ ## Table of Contents
7
8
 
8
- The `skip!` method indicates that a task did not meet the criteria to continue execution.
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 ProcessOrderTask < CMDx::Task
35
+ class ProcessUserOrderTask < CMDx::Task
12
36
 
13
37
  def call
14
- skip! if cart_abandoned?
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
- # Do work...
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
- ## Fail
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
- The `fail!` method indicates that a task met with incomplete, broken, or failed logic.
62
+ ### Basic Usage
25
63
 
26
64
  ```ruby
27
- class ProcessOrderTask < CMDx::Task
65
+ class ProcessOrderPaymentTask < CMDx::Task
28
66
 
29
67
  def call
30
- fail! if cart_items_out_of_stock?
68
+ context.payment = Payment.find(context.payment_id)
31
69
 
32
- # Do work...
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
- ## Metadata
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
- Pass metadata to enrich faults with additional contextual information. Metadata requires
41
- that it be passed as a hash object. Internal failures will hydrate metadata into its result,
42
- eg: failed validations and unrescued exceptions.
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 ProcessOrderTask < CMDx::Task
94
+ class ProcessUserOrderTask < CMDx::Task
46
95
 
47
96
  def call
48
- if cart_abandoned?
49
- skip!(reason: "Cart was abandoned due to 30 days of inactivity")
50
- elsif cart_items_out_of_stock?
51
- fail!(reason: "Items in the cart are out of stock", item: [123, 987])
52
- else
53
- # Do work...
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
- result = ProcessOrderTask.call
60
- result.metadata #=> { reason: "Items in the cart are out of stock", item: [123, 987] }
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
- > [!Important]
64
- > The `:reason` key is used to define the fault exception message. While not
65
- > required, it is strongly recommended that it is used on every halt method.
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
- ## Results
209
+ The `:reason` key in metadata has special significance:
68
210
 
69
- The following represents a result output example of a halted task.
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
- result = ProcessOrderTask.call
73
- result.status #=> "failed"
74
- result.metadata #=> { reason: "Cart was abandoned due to 30 days of inactivity" }
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 - Run](https://github.com/drexed/cmdx/blob/main/docs/basics/run.md)
80
- - **Next:** [Interruptions - Faults](https://github.com/drexed/cmdx/blob/main/docs/interruptions/faults.md)
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
- Tasks log the result object after execution. Multi-threaded systems will have many
4
- tasks executing concurrently so `CMDx` uses a custom logger to make debugging easier.
5
-
6
- ## Output
7
-
8
- Built-in log formatters are:
9
- - Standard: `Line`, `Json`, `KeyValue`, `Logstash`, `Raw`
10
- - Stylized: `PrettyLine` (default), `PrettyJson`, `PrettyKeyValue`
11
-
12
- #### Success:
13
- ```txt
14
- I, [2022-07-17T18:43:15.000000 #3784] INFO -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=complete status=success outcome=success metadata={} runtime=0 origin=CMDx
15
- ```
16
-
17
- #### Skipped:
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
- W, [2022-07-17T18:43:15.000000 #3784] WARN -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=skipped outcome=skipped metadata={} runtime=0 origin=CMDx
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
- #### Failed:
62
+ ### Skipped Result
23
63
  ```txt
24
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=failed metadata={} runtime=0 origin=CMDx
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
- #### Level 1 subtask failure:
67
+ ### Failed Result
28
68
  ```txt
29
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=interrupted metadata={} runtime=0 caused_failure={index: 1, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", class: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "failed", metadata: {}, runtime: 0} threw_failure={index: 1, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", class: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "failed", metadata: {}, runtime: 0} origin=CMDx
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
- #### Level 2+ subtask failure:
72
+ ### Failure Chain (Workflow Workflows)
33
73
  ```txt
34
- E, [2022-07-17T18:43:15.000000 #3784] ERROR -- SimulationTask: index=0 run_id=018c2b95-b764-7615-a924-cc5b910ed1e5 type=Task class=SimulationTask id=018c2b95-b764-7615-a924-cc5b910ed1e5 tags=[] state=interrupted status=failed outcome=interrupted metadata={} runtime=0 caused_failure={index: 2, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", class: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "failed", metadata: {}, runtime: 0} threw_failure={index: 1, run_id: "018c2b95-b764-7615-a924-cc5b910ed1e5", type: "Task", class: "SimulationTask", id: "018c2b95-b764-7615-a924-cc5b910ed1e5", tags: [], state: "interrupted", status: "failed", outcome: "interrupted", metadata: {}, runtime: 0} origin=CMDx
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
- ## Logger
77
+ ## Configuration
38
78
 
39
- CMDx defaults to using Ruby's standard library Logger. Log levels thus follow the
40
- [stdlib documentation](http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger.html).
79
+ ### Global Configuration
41
80
 
42
- #### Global settings:
81
+ Configure logging globally in your CMDx initializer:
43
82
 
44
83
  ```ruby
45
84
  CMDx.configure do |config|
46
- # Single declaration:
47
- config.logger = Logger.new($stdout, formatter: CMDx::LogFormatters::Line.new, level: Logger::DEBUG)
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
- #### Task settings:
90
+ ### Task-Specific Configuration
57
91
 
58
- ```ruby
59
- class ProcessOrderTask < CMDx::Task
92
+ Override logging settings for individual tasks:
60
93
 
61
- task_settings!(logger: Rails.logger, log_format: CMDx::LogFormatters::Logstash.new, log_level: Logger::WARN)
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
- # Do work...
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
- > [!TIP]
71
- > In production environments, a log level of DEBUG may be too verbose for your needs.
72
- > For quieter logs that use less disk space, you can change the log level to only show INFO and higher.
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
- ## Write to log
128
+ ## Manual Logging
75
129
 
76
- Write to log via the `logger` method.
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 "Processing order"
83
- logger.debug { context.to_h }
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
- ## Output format
161
+ ## Advanced Formatter Usage
90
162
 
91
- Define a custom log formatter to match your expected output, for example one that changes the JSON keys:
163
+ ### Custom Formatter
164
+
165
+ Create custom formatters for specific output requirements:
92
166
 
93
167
  ```ruby
94
- class CustomCmdxLogFormat
168
+ class SlackLogFormatter
95
169
  def call(severity, time, task, message)
96
- # Return string, hash, array, etc to output...
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:** [Batch](https://github.com/drexed/cmdx/blob/main/docs/batch.md)
104
- - **Next:** [Tips & Tricks](https://github.com/drexed/cmdx/blob/main/docs/tips_and_tricks.md)
254
+ - **Prev:** [Workflows](workflows.md)
255
+ - **Next:** [Internationalization (i18n)](internationalization.md)