@sfdxy/mule-lint 1.21.0 → 1.22.0

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.
@@ -0,0 +1,424 @@
1
+ # Event-Driven Architecture Patterns
2
+
3
+ > **Applies to:** Event-Driven (Platform Events, Anypoint MQ, Kafka)
4
+ > **Related Rules:** `SF-001` · `SF-002` · `MULE-003` · `MULE-007`
5
+ > **Last Updated:** April 2026
6
+
7
+ ## When to Read This
8
+
9
+ Read this when building Mule applications triggered by Salesforce Platform Events, Anypoint MQ messages, Kafka topics, or any asynchronous event source — as opposed to HTTP request/response APIs.
10
+
11
+ ---
12
+
13
+ ## Key Differences from HTTP APIs
14
+
15
+ | Aspect | HTTP API | Event-Driven |
16
+ | ------------------ | -------------------------------------- | -------------------------------------------------------------------- |
17
+ | **Trigger** | `http:listener` + `apikit:router` | `salesforce:replay-channel-listener`, `anypoint-mq:subscriber`, etc. |
18
+ | **Error response** | JSON → HTTP response with `httpStatus` | Writeback to source system + notification dispatch |
19
+ | **Correlation ID** | From `x-correlation-id` header | From event metadata (e.g., `EventUuid`) |
20
+ | **RAML/AsyncAPI** | RAML 1.0 or OAS 3.0 | AsyncAPI 2.6 (recommended for 2026+) |
21
+ | **Retry** | Client retries (HTTP 5xx) | Platform replay / redelivery policies |
22
+ | **Rate limiting** | API Manager policies | Consumer `maxConcurrency` + back-pressure |
23
+
24
+ ---
25
+
26
+ ## Patterns
27
+
28
+ ### Pattern 1: Salesforce Platform Event Listener
29
+
30
+ **Use when:** integrating with Salesforce real-time events (Change Data Capture, Custom Platform Events).
31
+
32
+ ```xml
33
+ <!-- listeners/sf-account-event-listener.xml -->
34
+ <flow name="sf-account-event-listener">
35
+ <salesforce:replay-channel-listener
36
+ config-ref="Salesforce_Config"
37
+ channel="/event/Account_Event__e"
38
+ replayOption="LATEST"
39
+ doc:name="Account Event Listener">
40
+ <reconnection>
41
+ <reconnect-forever frequency="5000"/>
42
+ </reconnection>
43
+ </salesforce:replay-channel-listener>
44
+
45
+ <!-- Set standard variables from event metadata -->
46
+ <ee:transform doc:name="Set Variables">
47
+ <ee:variables>
48
+ <ee:set-variable variableName="correlationId"><![CDATA[
49
+ payload.EventUuid default correlationId
50
+ ]]></ee:set-variable>
51
+ <ee:set-variable variableName="logCategory"><![CDATA[
52
+ "com.myorg.papi"
53
+ ]]></ee:set-variable>
54
+ <ee:set-variable variableName="salesforceId"><![CDATA[
55
+ payload.Record_Id__c default ""
56
+ ]]></ee:set-variable>
57
+ </ee:variables>
58
+ </ee:transform>
59
+
60
+ <logger level="INFO" category="#[vars.logCategory]"
61
+ message='#["[" ++ vars.correlationId ++ "] Account event received"]'/>
62
+
63
+ <flow-ref name="account-process-subflow"/>
64
+
65
+ <error-handler ref="global-error-handler"/>
66
+ </flow>
67
+ ```
68
+
69
+ **Key rules:**
70
+
71
+ - Always set `reconnect-forever` on event listeners — they must auto-recover from disconnects
72
+ - Use `replayOption="LATEST"` for new events; `EARLIEST` replays all cached events (24h window)
73
+ - Null-guard event payload fields — platform events can contain `null` values for optional fields
74
+
75
+ ### Pattern 2: Entity Process Sub-flow
76
+
77
+ **Use when:** processing events by entity type (Account, Contact, Opportunity, etc.).
78
+
79
+ Each entity gets its own process sub-flow that:
80
+
81
+ 1. Reads entity configuration from `entity-config/{entity}.yaml`
82
+ 2. Transforms SF event payload to target system format (DWL)
83
+ 3. Calls the downstream SAPI
84
+ 4. Calls writeback on success
85
+
86
+ ```xml
87
+ <!-- entities/account-process.xml -->
88
+ <sub-flow name="account-process-subflow">
89
+ <ee:transform doc:name="SF Account → NS Customer">
90
+ <ee:message>
91
+ <ee:set-payload resource="dwl/transforms/sf-account-to-ns-customer.dwl"/>
92
+ </ee:message>
93
+ </ee:transform>
94
+ <flow-ref name="sapi-request-subflow"/>
95
+ <flow-ref name="sf-writeback-subflow"/>
96
+ </sub-flow>
97
+ ```
98
+
99
+ ### Pattern 3: `initialState` Configuration
100
+
101
+ **Use when:** an entity integration is not yet ready for production but you want the flow scaffolded.
102
+
103
+ ```xml
104
+ <!-- Set initialState="stopped" to disable at startup -->
105
+ <flow name="sf-order-event-listener" initialState="stopped">
106
+ <salesforce:replay-channel-listener channel="/event/Order_Event__e" .../>
107
+ <!-- ... -->
108
+ </flow>
109
+ ```
110
+
111
+ Change to `initialState="started"` (or remove the attribute) when the entity is ready.
112
+
113
+ ### Pattern 4: Event Deduplication
114
+
115
+ **Use when:** platform events may be replayed and you need exactly-once processing.
116
+
117
+ ```xml
118
+ <!-- Use ObjectStore to track processed events by correlationId -->
119
+ <os:contains key="#[vars.correlationId]" objectStore="Event_Store"
120
+ target="alreadyProcessed"/>
121
+ <choice>
122
+ <when expression="#[vars.alreadyProcessed]">
123
+ <logger category="com.myorg" message="Duplicate event, skipping"/>
124
+ </when>
125
+ <otherwise>
126
+ <!-- Process the event -->
127
+ <flow-ref name="account-process-subflow"/>
128
+ <!-- Mark as processed -->
129
+ <os:store key="#[vars.correlationId]" objectStore="Event_Store">
130
+ <os:value>#[now()]</os:value>
131
+ </os:store>
132
+ </otherwise>
133
+ </choice>
134
+ ```
135
+
136
+ ### Pattern 5: Anypoint MQ Consumer
137
+
138
+ **Use when:** using Anypoint MQ for decoupled, asynchronous message processing.
139
+
140
+ ```xml
141
+ <anypoint-mq:subscriber config-ref="MQ_Config"
142
+ destination="orders-queue"
143
+ acknowledgementMode="MANUAL">
144
+ <reconnection>
145
+ <reconnect-forever frequency="5000"/>
146
+ </reconnection>
147
+ </anypoint-mq:subscriber>
148
+
149
+ <!-- Process message -->
150
+ <try>
151
+ <flow-ref name="process-order-subflow"/>
152
+ <anypoint-mq:ack config-ref="MQ_Config"/>
153
+ <error-handler>
154
+ <on-error-continue type="ANY">
155
+ <logger level="ERROR" message="#[error.description]"/>
156
+ <!-- Message returns to queue for redelivery -->
157
+ </on-error-continue>
158
+ </error-handler>
159
+ </try>
160
+ ```
161
+
162
+ **Key rules:**
163
+
164
+ - Configure Dead Letter Queues (DLQ) for undeliverable messages
165
+ - Use `MANUAL` acknowledgment for business-critical flows
166
+ - Set redelivery policies to avoid infinite retry loops
167
+
168
+ ### Pattern 6: VM Queue Dispatcher (Multi-Entity Orchestration)
169
+
170
+ **Use when:** a Process API handles 5+ entity types from mixed trigger sources (SF push topics, platform events, schedulers, ERP polling) and you need centralized routing with back-pressure control.
171
+
172
+ **Do NOT use when:** you have a simple 2–3 entity integration (direct listener → process is simpler), or you already use Anypoint MQ / Kafka externally (don't add a second internal queue).
173
+
174
+ **Decision: Direct vs. Queue-Mediated**
175
+
176
+ | Scenario | Direct? | Queue? | Why |
177
+ | --------------------------- | ------- | ------ | ------------------------------------- |
178
+ | 2–3 entity SAPI | ✅ | ❌ | Unnecessary indirection |
179
+ | 5+ entities, mixed triggers | ❌ | ✅ | Centralized routing, back-pressure |
180
+ | Strict ordering required | ❌ | ✅ | `maxConcurrency="1"` ensures ordering |
181
+ | Already using Anypoint MQ | ✅ | ❌ | Use external queue instead |
182
+
183
+ **Architecture:**
184
+
185
+ ```
186
+ Listeners (sf-listeners.xml, schedulers.xml)
187
+ ↓ Transform → { message: payload, recordType: "Check__c" }
188
+ ↓ vm:publish → "jobQueue" (PERSISTENT)
189
+
190
+ VM Queue Listener (vm-queue-listeners.xml, maxConcurrency="1")
191
+ ↓ vm:listener → extract recordType + payload
192
+ ↓ choice → route by recordType to implementation flow
193
+ ```
194
+
195
+ **Message contract — every queue message uses this shape:**
196
+
197
+ ```xml
198
+ <ee:transform doc:name="Wrap Queue Message">
199
+ <ee:set-payload><![CDATA[%dw 2.0
200
+ output application/java
201
+ ---
202
+ {
203
+ message: payload,
204
+ "recordType": "Check__c"
205
+ }]]></ee:set-payload>
206
+ </ee:transform>
207
+ <vm:publish queueName="jobQueue" config-ref="VM_Config" sendCorrelationId="AUTO"/>
208
+ ```
209
+
210
+ **Queue consumer with choice router:**
211
+
212
+ ```xml
213
+ <flow name="job-queue-listener" maxConcurrency="1">
214
+ <vm:listener queueName="jobQueue" config-ref="VM_Config" numberOfConsumers="2"/>
215
+ <set-variable variableName="recordType" value="#[payload.recordType]"/>
216
+ <set-payload value="#[payload.message]"/>
217
+ <choice>
218
+ <when expression='#[vars.recordType == "Check__c"]'>
219
+ <flow-ref name="processCheckFlow"/>
220
+ </when>
221
+ <when expression='#[vars.recordType == "Invoice__c"]'>
222
+ <flow-ref name="processInvoiceFlow"/>
223
+ </when>
224
+ <!-- Additional record types -->
225
+ </choice>
226
+ </flow>
227
+ ```
228
+
229
+ **Key rules:**
230
+
231
+ - Use `queueType="PERSISTENT"` — messages survive app restarts on CloudHub 2.0
232
+ - Set `maxConcurrency` on the consumer flow to control throughput
233
+ - Variable setup (correlationId, logCategory) moves to the **consumer**, not the listener
234
+ - For high-volume entities, consider a separate queue (e.g., `scheduledJobQueue`) to avoid head-of-line blocking
235
+
236
+ ### Pattern 7: Scheduler Watermarking with ObjectStore
237
+
238
+ **Use when:** a scheduler polls an external system for records modified since the last run. Fundamentally different from Pattern 4 (deduplication): deduplication tracks "have I seen this ID?", watermarking tracks "give me everything changed since timestamp X".
239
+
240
+ **Do NOT use when:** the trigger is push-based (platform events, MQ) — those systems handle replay/redelivery natively.
241
+
242
+ | Scenario | Watermark? | Why |
243
+ | ---------------------------------------- | ---------- | ------------------------------------- |
244
+ | Scheduler polls ERP for modified records | ✅ | Only fetch incremental changes |
245
+ | Platform Event listener | ❌ | Built-in replay (24h window) |
246
+ | Anypoint MQ consumer | ❌ | Redelivery handled by MQ |
247
+ | HTTP-triggered on-demand sync | ❌ | Request-response, no persistent state |
248
+
249
+ ```xml
250
+ <flow name="invoiceScheduler">
251
+ <scheduler>
252
+ <scheduling-strategy>
253
+ <fixed-frequency frequency="${scheduler.frequency}" timeUnit="MINUTES"/>
254
+ </scheduling-strategy>
255
+ </scheduler>
256
+
257
+ <!-- 1. Retrieve watermark (seeds with now() on first run) -->
258
+ <os:retrieve key="invoiceTimestamp" objectStore="timestamp-store"
259
+ target="invoiceTimestamp">
260
+ <os:default-value>#[now()]</os:default-value>
261
+ </os:retrieve>
262
+
263
+ <!-- 2. Store NEW timestamp BEFORE query (gap-free) -->
264
+ <os:store key="invoiceTimestamp" objectStore="timestamp-store">
265
+ <os:value>#[now() default vars.invoiceTimestamp]</os:value>
266
+ </os:store>
267
+
268
+ <!-- 3. Query for records modified after watermark -->
269
+ <http:request method="GET" path="/api/invoices">
270
+ <http:query-params>#[{ "modifiedAfter": vars.invoiceTimestamp }]</http:query-params>
271
+ </http:request>
272
+
273
+ <!-- 4. Process records... -->
274
+
275
+ <error-handler>
276
+ <on-error-propagate>
277
+ <!-- ROLL BACK timestamp on failure — records will be re-fetched -->
278
+ <os:store key="invoiceTimestamp" objectStore="timestamp-store">
279
+ <os:value>#[vars.invoiceTimestamp]</os:value>
280
+ </os:store>
281
+ <flow-ref name="errorLogFlow"/>
282
+ </on-error-propagate>
283
+ </error-handler>
284
+ </flow>
285
+ ```
286
+
287
+ **Key design decisions:**
288
+
289
+ 1. **Store timestamp BEFORE query, not after** — prevents gap where records modified between query and store are missed
290
+ 2. **Roll back on error** — `on-error-propagate` restores the previous timestamp so next run re-fetches
291
+ 3. **TTL on ObjectStore** — set `entryTtl` and `expirationInterval` to prevent unbounded growth
292
+ 4. **Round-robin for multiple schedulers** — if you have check, invoice, and credit memo schedulers, wrap them in `<round-robin>` under a single `<scheduler>` to prevent overlapping runs
293
+
294
+ ### Pattern 8: Deferred Task Polling (Async API Integration)
295
+
296
+ **Use when:** calling APIs that return a deferred/async task ID instead of an immediate result (common with financial, bulk, and provisioning APIs). You must poll a status endpoint until the task completes.
297
+
298
+ **Do NOT use when:** the API returns results synchronously, or when a webhook/callback is available (register the callback instead).
299
+
300
+ | Scenario | Use Polling? | Why |
301
+ | -------------------------------- | ----------------------------------- | ------------------------------ |
302
+ | API returns deferred task ID | ✅ | Must poll for completion |
303
+ | API returns result synchronously | ❌ | No polling needed |
304
+ | Webhook/callback available | ❌ | Event-driven is more efficient |
305
+ | Task runs > 10 minutes | ⚠️ Consider scheduler-based polling | May exceed Mule flow timeout |
306
+
307
+ **The pattern uses `raise-error` to drive `until-successful` as a polling loop:**
308
+
309
+ ```xml
310
+ <flow name="pollTaskStatusSubFlow">
311
+ <set-variable value="#[0]" variableName="retryCount"/>
312
+ <set-variable value='#[p("polling.max_retries") default "10"]'
313
+ variableName="maxRetries"/>
314
+
315
+ <until-successful maxRetries="#[vars.maxRetries]"
316
+ millisBetweenRetries="#[vars.retryInterval]">
317
+
318
+ <http:request method="GET" path="#[vars.pollPath]"
319
+ target="statusResponse" sendBodyMode="NEVER"/>
320
+
321
+ <set-variable value="#[vars.retryCount + 1]" variableName="retryCount"/>
322
+
323
+ <choice>
324
+ <when expression="#[vars.statusResponse.status == 'SUCCESS'
325
+ or vars.statusResponse.status == 'FAILED']">
326
+ <!-- Terminal state — exit loop -->
327
+ </when>
328
+ <otherwise>
329
+ <!-- Still pending — throw to trigger until-successful retry -->
330
+ <raise-error type="APP:TASK_PENDING"
331
+ description="Task still pending"/>
332
+ </otherwise>
333
+ </choice>
334
+ </until-successful>
335
+
336
+ <!-- Build structured result -->
337
+ <ee:transform>
338
+ <ee:set-payload><![CDATA[%dw 2.0
339
+ output application/json
340
+ ---
341
+ {
342
+ status: vars.statusResponse.status,
343
+ taskId: vars.taskId,
344
+ attempts: vars.retryCount,
345
+ data: vars.statusResponse.data default {},
346
+ message: if (vars.statusResponse.status == "SUCCESS") "Task completed"
347
+ else "Task failed: " ++ (vars.statusResponse.data.error default "")
348
+ }]]></ee:set-payload>
349
+ </ee:transform>
350
+
351
+ <error-handler>
352
+ <on-error-continue type="MULE:RETRY_EXHAUSTED">
353
+ <!-- Timeout — max retries exceeded -->
354
+ <ee:transform>
355
+ <ee:set-payload><![CDATA[%dw 2.0
356
+ output application/json
357
+ ---
358
+ {
359
+ status: "TIMEOUT",
360
+ taskId: vars.taskId,
361
+ attempts: vars.retryCount,
362
+ data: {},
363
+ message: "Polling timed out after " ++ vars.retryCount ++ " attempts"
364
+ }]]></ee:set-payload>
365
+ </ee:transform>
366
+ </on-error-continue>
367
+ </error-handler>
368
+ </flow>
369
+ ```
370
+
371
+ **Key design decisions:**
372
+
373
+ 1. **Custom error type** — `raise-error type="APP:TASK_PENDING"` drives the retry loop semantically (not a real error)
374
+ 2. **Parameterized sub-flow** — `vars.taskId` + `vars.taskType` make it reusable across entities (user creation, card termination, etc.)
375
+ 3. **Fixed interval, not exponential backoff** — task status endpoints are idempotent; fixed interval is fine
376
+ 4. **Orphan cleanup scheduler** — for tasks missed due to app restart, add a separate scheduler that queries for records missing the completion flag and re-polls
377
+
378
+ ---
379
+
380
+ ## AsyncAPI 2.6 (2026 Standard)
381
+
382
+ For event-driven APIs, design specifications using AsyncAPI 2.6 in Anypoint Code Builder:
383
+
384
+ ```yaml
385
+ # asyncapi.yaml
386
+ asyncapi: '2.6.0'
387
+ info:
388
+ title: Account Sync Events
389
+ version: '1.0.0'
390
+ channels:
391
+ account/created:
392
+ subscribe:
393
+ message:
394
+ payload:
395
+ type: object
396
+ properties:
397
+ recordId: { type: string }
398
+ operation: { type: string, enum: [CREATE, UPDATE] }
399
+ ```
400
+
401
+ Validate your AsyncAPI spec against organizational rulesets in API Governance before publishing to Exchange.
402
+
403
+ ---
404
+
405
+ ## Checklist
406
+
407
+ - [ ] Event listeners have `reconnect-forever` for auto-recovery
408
+ - [ ] Correlation ID sourced from event metadata (not HTTP headers)
409
+ - [ ] No `httpStatus` in error handlers (event-driven flows have no HTTP response)
410
+ - [ ] Error handling triggers writeback + notification (not HTTP error response)
411
+ - [ ] Entity-specific process sub-flows are separated into individual XML files
412
+ - [ ] `initialState="stopped"` used for entities not yet active
413
+ - [ ] Dead Letter Queue configured for Anypoint MQ consumers
414
+ - [ ] Null-guards on all event payload field accesses
415
+ - [ ] VM queues use `queueType="PERSISTENT"` for durability (Pattern 6)
416
+ - [ ] VM queue consumer flow has explicit `maxConcurrency` set (Pattern 6)
417
+ - [ ] Scheduler watermark stored BEFORE query, rolled back on error (Pattern 7)
418
+ - [ ] ObjectStore has `entryTtl` and `expirationInterval` configured (Pattern 7)
419
+ - [ ] Deferred task polling uses structured result `{ status, taskId, attempts, data }` (Pattern 8)
420
+ - [ ] Orphan cleanup scheduler exists for deferred tasks (Pattern 8)
421
+
422
+ ---
423
+
424
+ **See also:** [Error Handling](error-handling.md) · [Connector Patterns](connector-patterns.md) · [Variable Contracts](variable-contracts.md)
@@ -0,0 +1,163 @@
1
+ # Logging Best Practices
2
+
3
+ > **Applies to:** All
4
+ > **Related Rules:** `MULE-006` · `MULE-301` · `MULE-303` · `LOG-001` · `LOG-004` · `HYG-001` · `SEC-006`
5
+ > **Last Updated:** April 2026
6
+
7
+ ## When to Read This
8
+
9
+ Read this when adding loggers, configuring log4j2, or reviewing logging practices for production readiness.
10
+
11
+ ---
12
+
13
+ ## Key Principles
14
+
15
+ 1. **Always use categories** — enable log filtering in production
16
+ 2. **Never log full payloads** — may contain PII and cause performance issues
17
+ 3. **Include correlation IDs** — enable request tracing across distributed systems
18
+ 4. **Use structured logging** — JSON format for log aggregation tools
19
+ 5. **Keep logger count manageable** — max 5 loggers per flow
20
+
21
+ ---
22
+
23
+ ## Patterns
24
+
25
+ ### Pattern 1: Logger Category Convention
26
+
27
+ Use hierarchical category names following reverse-domain convention:
28
+
29
+ ```xml
30
+ <!-- ✅ Good — hierarchical categories -->
31
+ <logger category="com.myorg.sf.sapi" level="INFO"
32
+ message='#["[" ++ vars.correlationId ++ "] Processing order"]'/>
33
+
34
+ <!-- ❌ Bad — no category (defaults to root logger) -->
35
+ <logger message="#['Processing order']"/>
36
+ ```
37
+
38
+ **Category naming convention:**
39
+
40
+ | Pattern | Example | Use For |
41
+ | -------------------------------------- | ------------------------------ | --------------------- |
42
+ | `com.{org}.{system}.{layer}` | `com.rsm.sf.sapi` | Main flow logs |
43
+ | `com.{org}.{system}.{layer}.{feature}` | `com.rsm.sf.papi.notification` | Feature-specific logs |
44
+
45
+ ### Pattern 2: Structured Logging (JSON)
46
+
47
+ For production applications, configure JSON Logger Module for structured log output:
48
+
49
+ ```xml
50
+ <logger category="com.myorg.audit" level="INFO">
51
+ <message><![CDATA[#[%dw 2.0
52
+ output application/json
53
+ ---
54
+ {
55
+ correlationId: vars.correlationId,
56
+ event: "ORDER_CREATED",
57
+ orderId: payload.orderId,
58
+ timestamp: now()
59
+ }]]]></message>
60
+ </logger>
61
+ ```
62
+
63
+ This enables better log parsing with tools like Splunk, ELK, and Anypoint Monitoring.
64
+
65
+ ### Pattern 3: Log Level Usage
66
+
67
+ | Level | Use For | Production? |
68
+ | ------- | ----------------------------------------------- | ----------------- |
69
+ | `ERROR` | Critical failures requiring immediate attention | ✅ Always on |
70
+ | `WARN` | Unexpected events that don't stop processing | ✅ Always on |
71
+ | `INFO` | Essential milestones, operation status | ✅ Always on |
72
+ | `DEBUG` | Verbose diagnostics for troubleshooting | ❌ Off by default |
73
+
74
+ ### Pattern 4: Avoid Payload Logging
75
+
76
+ ```xml
77
+ <!-- ❌ Bad — logs entire payload (PII risk + performance) -->
78
+ <logger message="#[payload]"/>
79
+ <logger message="#[write(payload, 'application/json')]"/>
80
+
81
+ <!-- ✅ Good — logs specific, non-sensitive fields -->
82
+ <logger category="com.myorg" level="INFO"
83
+ message='#["Order " ++ payload.orderId ++ " for customer " ++ payload.customerId]'/>
84
+ ```
85
+
86
+ ### Pattern 5: Avoid Loggers in Retry Loops
87
+
88
+ ```xml
89
+ <!-- ❌ Bad — logger inside until-successful (floods logs on retries) -->
90
+ <until-successful maxRetries="5">
91
+ <logger message="Attempting..."/>
92
+ <http:request config-ref="HTTP_Config" path="/api"/>
93
+ </until-successful>
94
+
95
+ <!-- ✅ Good — log before and after -->
96
+ <logger category="com.myorg" message="Starting retry operation"/>
97
+ <until-successful maxRetries="5">
98
+ <http:request config-ref="HTTP_Config" path="/api"/>
99
+ </until-successful>
100
+ <logger category="com.myorg" message="Operation completed"/>
101
+ ```
102
+
103
+ ---
104
+
105
+ ## MDC / Tracing Module (2026+)
106
+
107
+ For distributed tracing, use the Mule Tracing module or Mapped Diagnostic Context (MDC):
108
+
109
+ ```xml
110
+ <!-- Inject correlation ID into MDC for log4j2 automatic inclusion -->
111
+ <tracing:set-logging-variable variableName="correlationId"
112
+ value="#[vars.correlationId]"/>
113
+ ```
114
+
115
+ With MDC configured, log4j2 can automatically include the correlation ID in every log line without explicit `#[vars.correlationId]` in each logger message.
116
+
117
+ ---
118
+
119
+ ## log4j2.xml Configuration
120
+
121
+ ```xml
122
+ <!-- src/main/resources/log4j2.xml -->
123
+ <Configuration>
124
+ <Appenders>
125
+ <RollingFile name="file"
126
+ fileName="${sys:mule.home}/logs/app.log"
127
+ filePattern="${sys:mule.home}/logs/app-%d{yyyy-MM-dd}.log.gz">
128
+ <PatternLayout pattern="%d{ISO8601} %-5p [%t] %c - %m%n"/>
129
+ <Policies>
130
+ <TimeBasedTriggeringPolicy/>
131
+ <SizeBasedTriggeringPolicy size="10 MB"/>
132
+ </Policies>
133
+ </RollingFile>
134
+ </Appenders>
135
+ <Loggers>
136
+ <!-- Application loggers -->
137
+ <Logger name="com.myorg" level="INFO"/>
138
+ <!-- Suppress noisy connectors -->
139
+ <Logger name="com.mulesoft.extension.salesforce" level="WARN"/>
140
+ <Logger name="org.mule.extension.http" level="WARN"/>
141
+ <Root level="INFO">
142
+ <AppenderRef ref="file"/>
143
+ </Root>
144
+ </Loggers>
145
+ </Configuration>
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Checklist
151
+
152
+ - [ ] All loggers have a `category` attribute
153
+ - [ ] No `#[payload]` in logger messages
154
+ - [ ] Correlation ID included in all log messages
155
+ - [ ] No loggers inside `until-successful` or retry scopes
156
+ - [ ] Max 5 loggers per flow
157
+ - [ ] No sensitive data (passwords, tokens, PII) in log messages
158
+ - [ ] `log4j2.xml` configured with appropriate log levels for production
159
+ - [ ] Noisy connector loggers suppressed to WARN
160
+
161
+ ---
162
+
163
+ **See also:** [Variable Contracts](variable-contracts.md) · [Security](security.md) · [Error Handling](error-handling.md)