@infineit/winston-logger 1.0.31 → 1.0.33

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.
package/README.md CHANGED
@@ -6,10 +6,11 @@ Enterprise-level logging library for NestJS applications with support for multip
6
6
 
7
7
  - ✅ **Multi-Context Support**: Works in HTTP services, Kafka consumers, Bull/BullMQ workers, CRON jobs, and CLI tasks
8
8
  - ✅ **Transport Architecture**: Pluggable transport system with Winston as one transport among many
9
+ - ✅ **Optional Central Forwarding**: Forward logs to centralized logging service via HTTP or Kafka (optional, fire-and-forget)
9
10
  - ✅ **Log Normalization**: Automatic normalization and error serialization before transport
10
11
  - ✅ **CLS Integration**: Correlation ID support via AsyncLocalStorage (CLS)
11
12
  - ✅ **Fire-and-Forget**: Non-blocking, failure-safe logging that never breaks business logic
12
- - ✅ **Zero Dependencies**: No database, HTTP framework, or ORM dependencies
13
+ - ✅ **Zero Dependencies**: No database, HTTP framework, or ORM dependencies (HTTP forwarding uses built-in Node.js modules)
13
14
  - ✅ **Type-Safe**: Full TypeScript support with comprehensive type definitions
14
15
 
15
16
  ## Table of Contents
@@ -20,8 +21,12 @@ Enterprise-level logging library for NestJS applications with support for multip
20
21
  - [Configuration](#configuration)
21
22
  - [Usage in Different Contexts](#usage-in-different-contexts)
22
23
  - [Transport Architecture](#transport-architecture)
24
+ - [Central Log Forwarding](#central-log-forwarding)
23
25
  - [Log Normalization](#log-normalization)
24
26
  - [Correlation ID (CLS)](#correlation-id-cls)
27
+ - [CLS Context Boundaries and Edge Cases](#cls-context-boundaries-and-edge-cases)
28
+ - [Correlation ID Format Validation](#correlation-id-format-validation)
29
+ - [Complete Integration Examples](#complete-integration-examples)
25
30
  - [API Reference](#api-reference)
26
31
  - [Migration Guide (Legacy app.useLogger Users)](#migration-guide-legacy-appuselogger-users)
27
32
  - [Best Practices](#best-practices)
@@ -44,6 +49,27 @@ This package requires the following peer dependencies (usually already installed
44
49
  - `@nestjs/config` 4.2.0
45
50
  - `@nestjs/core` 11.1.9
46
51
  - `nestjs-cls` 5.0.1
52
+ - `uuid` (required for correlation ID generation in HTTP contexts)
53
+
54
+ ### ⚠️ Critical Setup Requirement
55
+
56
+ **IMPORTANT:** `ClsModule.forRoot()` **MUST** be imported in your `AppModule` **BEFORE** `ContextModule`. This is required to avoid dependency resolution errors with `HttpAdapterHost`.
57
+
58
+ **Correct import order:**
59
+ ```typescript
60
+ @Module({
61
+ imports: [
62
+ ConfigModule.forRoot({ isGlobal: true }),
63
+ ClsModule.forRoot({ ... }), // ✅ FIRST - Required
64
+ ContextModule, // ✅ THEN - Uses existing ClsModule
65
+ LoggerModule.forRoot(), // ✅ THEN - Configures logger
66
+ ],
67
+ })
68
+ ```
69
+
70
+ **❌ Incorrect:** Importing `ContextModule` before `ClsModule` will cause `HttpAdapterHost` dependency errors.
71
+
72
+ **Note:** `ContextModule` does NOT import `ClsModule` internally. Applications must configure `ClsModule.forRoot()` themselves.
47
73
 
48
74
  ---
49
75
 
@@ -54,12 +80,26 @@ This package requires the following peer dependencies (usually already installed
54
80
  ```typescript
55
81
  import { Module } from '@nestjs/common';
56
82
  import { ConfigModule } from '@nestjs/config';
83
+ import { ClsModule } from 'nestjs-cls'; // REQUIRED: Must import ClsModule first
84
+ import { v4 as uuidv4 } from 'uuid'; // REQUIRED: For correlation ID generation
57
85
  import { ContextModule, LoggerModule } from '@infineit/winston-logger';
58
86
 
59
87
  @Module({
60
88
  imports: [
61
89
  ConfigModule.forRoot({ isGlobal: true }),
62
- ContextModule, // Required for CLS (correlation ID support)
90
+
91
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule
92
+ ClsModule.forRoot({
93
+ global: true,
94
+ middleware: {
95
+ mount: true, // Enable middleware for HTTP requests
96
+ generateId: true, // Auto-generate correlation ID
97
+ idGenerator: (req: Request) =>
98
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
99
+ },
100
+ }),
101
+
102
+ ContextModule, // Required for CLS (correlation ID support) - uses existing ClsModule
63
103
  LoggerModule.forRoot(), // Configure logger
64
104
  ],
65
105
  })
@@ -169,10 +209,29 @@ const config: LoggerModuleConfig = {
169
209
  organization: 'my-org', // Organization name (optional)
170
210
  context: 'user-service', // Bounded context name (optional)
171
211
  app: 'api-gateway', // Application name (optional)
212
+ // Optional: Central log forwarding (disabled by default)
213
+ forwardToCentral: false, // Enable forwarding to centralized logging service
214
+ transportType: 'http', // 'http' or 'kafka' (required if forwardToCentral=true)
215
+ httpEndpoint: 'https://...', // HTTP endpoint (required if transportType='http')
216
+ // kafkaTopic: 'project-logs', // Kafka topic (required if transportType='kafka')
172
217
  };
173
218
 
219
+ import { ClsModule } from 'nestjs-cls';
220
+ import { v4 as uuidv4 } from 'uuid';
221
+
174
222
  @Module({
175
223
  imports: [
224
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule
225
+ ClsModule.forRoot({
226
+ global: true,
227
+ middleware: {
228
+ mount: true,
229
+ generateId: true,
230
+ idGenerator: (req: Request) =>
231
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
232
+ },
233
+ }),
234
+ ContextModule, // Uses existing ClsModule
176
235
  LoggerModule.forRoot(config),
177
236
  ],
178
237
  })
@@ -192,6 +251,14 @@ LOG_IN_FILE=true
192
251
  LOGGER_ORGANIZATION=my-org
193
252
  LOGGER_CONTEXT=user-service
194
253
  LOGGER_APP=api-gateway
254
+
255
+ # Optional: Central log forwarding (disabled by default)
256
+ LOGGER_FORWARD_TO_CENTRAL=false
257
+ LOGGER_TRANSPORT_TYPE=http
258
+ LOGGER_HTTP_ENDPOINT=https://logging-service.example.com/api/logs
259
+ # OR for Kafka:
260
+ # LOGGER_TRANSPORT_TYPE=kafka
261
+ # LOGGER_KAFKA_TOPIC=project-logs-sales
195
262
  ```
196
263
 
197
264
  ```typescript
@@ -206,16 +273,35 @@ export default registerAs('logger', () => ({
206
273
  organization: process.env.LOGGER_ORGANIZATION,
207
274
  context: process.env.LOGGER_CONTEXT,
208
275
  app: process.env.LOGGER_APP,
276
+ // Optional: Central log forwarding
277
+ forwardToCentral: process.env.LOGGER_FORWARD_TO_CENTRAL,
278
+ transportType: process.env.LOGGER_TRANSPORT_TYPE,
279
+ httpEndpoint: process.env.LOGGER_HTTP_ENDPOINT,
280
+ kafkaTopic: process.env.LOGGER_KAFKA_TOPIC,
209
281
  }));
210
282
  ```
211
283
 
212
284
  ```typescript
285
+ import { ClsModule } from 'nestjs-cls';
286
+ import { v4 as uuidv4 } from 'uuid';
287
+
213
288
  @Module({
214
289
  imports: [
215
290
  ConfigModule.forRoot({
216
291
  isGlobal: true,
217
292
  load: [loggerConfig],
218
293
  }),
294
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule
295
+ ClsModule.forRoot({
296
+ global: true,
297
+ middleware: {
298
+ mount: true,
299
+ generateId: true,
300
+ idGenerator: (req: Request) =>
301
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
302
+ },
303
+ }),
304
+ ContextModule, // Uses existing ClsModule configuration
219
305
  LoggerModule.forRoot(), // Will read from ConfigService
220
306
  ],
221
307
  })
@@ -233,12 +319,52 @@ export class AppModule {}
233
319
  | `organization` | `string` | No | `undefined` | Organization or project name |
234
320
  | `context` | `string` | No | `undefined` | Bounded context name |
235
321
  | `app` | `string` | No | `undefined` | Application or microservice name |
322
+ | `forwardToCentral` | `boolean \| string` | No | `false` | Enable forwarding to centralized logging service (`true`/`false` or `'true'`/`'false'`) |
323
+ | `transportType` | `'kafka' \| 'http'` | No | `undefined` | Transport type for central forwarding (required if `forwardToCentral=true`) |
324
+ | `httpEndpoint` | `string` | No | `undefined` | HTTP endpoint for central forwarding (required if `transportType='http'`) |
325
+ | `kafkaTopic` | `string` | No | `undefined` | Kafka topic for central forwarding (required if `transportType='kafka'`) |
236
326
 
237
327
  ### Default Behavior
238
328
 
239
329
  - **Development/Testing**: Console and file logging enabled by default
240
330
  - **Production**: Console and file logging disabled by default (unless explicitly enabled)
241
331
  - **Slack**: Only enabled in production/testing when `slack_webhook` is provided
332
+ - **Central Forwarding**: Disabled by default (set `forwardToCentral=true` to enable)
333
+
334
+ ### Environment-Specific Configuration Tips
335
+
336
+ **Development:**
337
+ ```bash
338
+ # Disable forwarding in development (optional)
339
+ LOGGER_FORWARD_TO_CENTRAL=false
340
+ # OR use local endpoint for testing
341
+ LOGGER_FORWARD_TO_CENTRAL=true
342
+ LOGGER_TRANSPORT_TYPE=http
343
+ LOGGER_HTTP_ENDPOINT=http://localhost:3000/api/logs
344
+ ```
345
+
346
+ **Production:**
347
+ ```bash
348
+ # Enable forwarding in production
349
+ LOGGER_FORWARD_TO_CENTRAL=true
350
+ LOGGER_TRANSPORT_TYPE=http
351
+ LOGGER_HTTP_ENDPOINT=https://central-logging.example.com/api/logs
352
+ # OR use Kafka for better performance
353
+ # LOGGER_TRANSPORT_TYPE=kafka
354
+ # LOGGER_KAFKA_TOPIC=project-logs-<project-name>
355
+ ```
356
+
357
+ **Project-Specific Topics (Kafka):**
358
+ ```bash
359
+ # Sales project
360
+ LOGGER_KAFKA_TOPIC=project-logs-sales
361
+
362
+ # Document project
363
+ LOGGER_KAFKA_TOPIC=project-logs-document
364
+
365
+ # Common service
366
+ LOGGER_KAFKA_TOPIC=project-logs-common
367
+ ```
242
368
 
243
369
  ---
244
370
 
@@ -271,21 +397,26 @@ export class UserController {
271
397
  }
272
398
  ```
273
399
 
274
- **Important**: For HTTP services, you must configure CLS middleware manually:
400
+ **Important**: For HTTP services, you must configure CLS middleware manually. **ClsModule MUST be imported BEFORE ContextModule:**
275
401
 
276
402
  ```typescript
277
403
  import { ClsModule } from 'nestjs-cls';
404
+ import { v4 as uuidv4 } from 'uuid';
405
+ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
278
406
 
279
407
  @Module({
280
408
  imports: [
409
+ // ⚠️ CRITICAL: ClsModule MUST be imported FIRST
281
410
  ClsModule.forRoot({
411
+ global: true,
282
412
  middleware: {
283
413
  mount: true, // Enable middleware
284
414
  generateId: true, // Auto-generate correlation ID
285
- idGenerator: (req: Request) => req.headers['x-correlation-id'] || uuidv4(),
415
+ idGenerator: (req: Request) =>
416
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
286
417
  },
287
418
  }),
288
- ContextModule, // Provides CLS service
419
+ ContextModule, // Provides CLS service - uses existing ClsModule
289
420
  LoggerModule.forRoot(),
290
421
  ],
291
422
  })
@@ -478,8 +609,10 @@ export class KafkaTransport implements LoggerTransport {
478
609
  ### Registering Custom Transports
479
610
 
480
611
  ```typescript
481
- import { Provider } from '@nestjs/common';
482
- import { LoggerModule } from '@infineit/winston-logger';
612
+ import { Provider, Module } from '@nestjs/common';
613
+ import { ClsModule } from 'nestjs-cls';
614
+ import { v4 as uuidv4 } from 'uuid';
615
+ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
483
616
  import { KafkaTransport } from './kafka-transport';
484
617
 
485
618
  const customTransports: Provider[] = [
@@ -491,6 +624,17 @@ const customTransports: Provider[] = [
491
624
 
492
625
  @Module({
493
626
  imports: [
627
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule
628
+ ClsModule.forRoot({
629
+ global: true,
630
+ middleware: {
631
+ mount: true,
632
+ generateId: true,
633
+ idGenerator: (req: Request) =>
634
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
635
+ },
636
+ }),
637
+ ContextModule, // Uses existing ClsModule
494
638
  LoggerModule.forRoot(config, customTransports),
495
639
  ],
496
640
  })
@@ -509,6 +653,210 @@ All transports must:
509
653
 
510
654
  ---
511
655
 
656
+ ## Central Log Forwarding
657
+
658
+ ### Overview
659
+
660
+ The logger can optionally forward all logs to a centralized logging microservice via HTTP or Kafka. This is an **optional feature** that runs alongside existing logging (console, file, Slack) without modifying or affecting it.
661
+
662
+ **Key characteristics:**
663
+ - ✅ **Optional**: Only enabled when `forwardToCentral=true` (disabled by default)
664
+ - ✅ **Fire-and-forget**: Non-blocking, never throws, failures are swallowed
665
+ - ✅ **Preserves correlationId**: Automatically includes correlationId from CLS
666
+ - ✅ **Non-invasive**: Original logging (console, file, Slack) continues normally and is unaffected
667
+
668
+ ### Configuration
669
+
670
+ **Via Environment Variables:**
671
+ ```bash
672
+ LOGGER_FORWARD_TO_CENTRAL=true
673
+ LOGGER_TRANSPORT_TYPE=http
674
+ LOGGER_HTTP_ENDPOINT=https://logging-service.example.com/api/logs
675
+ # OR for Kafka:
676
+ # LOGGER_TRANSPORT_TYPE=kafka
677
+ # LOGGER_KAFKA_TOPIC=project-logs-sales
678
+ ```
679
+
680
+ **Via Module Configuration:**
681
+ ```typescript
682
+ import { ClsModule } from 'nestjs-cls';
683
+ import { v4 as uuidv4 } from 'uuid';
684
+ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
685
+
686
+ @Module({
687
+ imports: [
688
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule
689
+ ClsModule.forRoot({
690
+ global: true,
691
+ middleware: {
692
+ mount: true,
693
+ generateId: true,
694
+ idGenerator: (req: Request) =>
695
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
696
+ },
697
+ }),
698
+ ContextModule, // Uses existing ClsModule
699
+ LoggerModule.forRoot({
700
+ // ... existing config
701
+ forwardToCentral: true,
702
+ transportType: 'http', // or 'kafka'
703
+ httpEndpoint: 'https://logging-service.example.com/api/logs',
704
+ // OR for Kafka:
705
+ // kafkaTopic: 'project-logs-sales',
706
+ }),
707
+ ],
708
+ })
709
+ export class AppModule {}
710
+ ```
711
+
712
+ ### HTTP Forwarding
713
+
714
+ Forwards logs via HTTP POST requests to the configured endpoint. Uses Node.js built-in `http`/`https` modules (no external dependencies).
715
+
716
+ **Configuration:**
717
+ ```bash
718
+ LOGGER_FORWARD_TO_CENTRAL=true
719
+ LOGGER_TRANSPORT_TYPE=http
720
+ LOGGER_HTTP_ENDPOINT=https://central-logging.example.com/api/logs
721
+ ```
722
+
723
+ **Usage:** No code changes required. All logs are automatically forwarded with correlationId preserved.
724
+
725
+ **Request Format:**
726
+ - **Method**: POST
727
+ - **Headers**: `Content-Type: application/json`, `x-correlation-id: <correlationId>` (if available)
728
+ - **Body**: `NormalizedLog` JSON object
729
+
730
+ ### Kafka Forwarding
731
+
732
+ Forwards logs to a configured Kafka topic. Requires a Kafka producer instance provided by your application.
733
+
734
+ **Prerequisites:** Provide Kafka producer via `KafkaProducerKey`:
735
+
736
+ ```typescript
737
+ import { Module, Provider } from '@nestjs/common';
738
+ import { Kafka } from 'kafkajs';
739
+ import { LoggerModule, KafkaProducerKey } from '@infineit/winston-logger';
740
+
741
+ @Module({
742
+ imports: [
743
+ LoggerModule.forRoot({
744
+ forwardToCentral: true,
745
+ transportType: 'kafka',
746
+ kafkaTopic: 'project-logs-sales',
747
+ }),
748
+ ],
749
+ providers: [
750
+ {
751
+ provide: KafkaProducerKey,
752
+ useFactory: async () => {
753
+ const kafka = new Kafka({ brokers: ['localhost:9092'] });
754
+ const producer = kafka.producer();
755
+ await producer.connect();
756
+ return producer;
757
+ },
758
+ },
759
+ ],
760
+ })
761
+ export class AppModule {}
762
+ ```
763
+
764
+ **Usage:** No code changes required. All logs are automatically forwarded with correlationId preserved.
765
+
766
+ **Message Format:**
767
+ - **Topic**: Configured `kafkaTopic`
768
+ - **Key**: `correlationId` or `'no-correlation-id'` if not available
769
+ - **Headers**: `x-correlation-id: <correlationId>` (if available)
770
+ - **Value**: `NormalizedLog` JSON string
771
+
772
+ ### Correlation ID Preservation
773
+
774
+ Correlation ID is automatically preserved and forwarded:
775
+
776
+ **HTTP Forwarding:**
777
+ 1. Correlation ID from CLS is included in log normalization
778
+ 2. Forwarder sends log with `correlationId` in HTTP header (`x-correlation-id`) and body
779
+
780
+ **Kafka Forwarding:**
781
+ 1. Correlation ID from CLS is included in log normalization
782
+ 2. Forwarder sends log with `correlationId` as message key, header, and in message value
783
+
784
+ **No changes required:** If CLS is configured, correlationId is automatically included in forwarded logs.
785
+
786
+ ### Fire-and-Forget Behavior
787
+
788
+ Central log forwarding is completely fire-and-forget:
789
+
790
+ - ✅ **Never blocks**: Forwarding happens asynchronously, never blocks business logic
791
+ - ✅ **Never throws**: All errors are swallowed, never propagate to application code
792
+ - ✅ **Never affects business logic**: Forwarding failures are logged to `console.error` only
793
+ - ✅ **Never breaks logging**: Original logging (console, file, Slack) continues normally and is unaffected
794
+
795
+ **Important:** Original logging behavior remains completely unchanged. Forwarding is an additional, optional layer that runs in parallel.
796
+
797
+ ### Environment-Specific Configuration
798
+
799
+ **Development:**
800
+ ```bash
801
+ # Option 1: Disable forwarding in development
802
+ LOGGER_FORWARD_TO_CENTRAL=false
803
+
804
+ # Option 2: Use local endpoint for testing
805
+ LOGGER_FORWARD_TO_CENTRAL=true
806
+ LOGGER_TRANSPORT_TYPE=http
807
+ LOGGER_HTTP_ENDPOINT=http://localhost:3000/api/logs
808
+ ```
809
+
810
+ **Production:**
811
+ ```bash
812
+ # HTTP forwarding (simpler setup)
813
+ LOGGER_FORWARD_TO_CENTRAL=true
814
+ LOGGER_TRANSPORT_TYPE=http
815
+ LOGGER_HTTP_ENDPOINT=https://central-logging.example.com/api/logs
816
+
817
+ # OR Kafka forwarding (better performance, requires Kafka producer)
818
+ LOGGER_FORWARD_TO_CENTRAL=true
819
+ LOGGER_TRANSPORT_TYPE=kafka
820
+ LOGGER_KAFKA_TOPIC=project-logs-<project-name>
821
+ ```
822
+
823
+ **Project-Specific Topics (Kafka):**
824
+ Use different Kafka topics per project for better log organization:
825
+ ```bash
826
+ # Sales project
827
+ LOGGER_KAFKA_TOPIC=project-logs-sales
828
+
829
+ # Document project
830
+ LOGGER_KAFKA_TOPIC=project-logs-document
831
+
832
+ # Common service
833
+ LOGGER_KAFKA_TOPIC=project-logs-common
834
+ ```
835
+
836
+ ### Troubleshooting
837
+
838
+ **Forwarding Not Working:**
839
+ 1. Check configuration: `LOGGER_FORWARD_TO_CENTRAL` must be `'true'` or `true`
840
+ 2. Verify transport type: Must be `'http'` or `'kafka'`
841
+ 3. Check endpoint/topic: Must be configured correctly
842
+ 4. For Kafka: Ensure `KafkaProducerKey` is provided in module providers
843
+ 5. Check console errors: Forwarding errors are logged to `console.error` with message `"CentralLogForwarder forward error (swallowed): <error>"`
844
+
845
+ **Correlation ID Missing:**
846
+ - Ensure CLS is configured: `ClsModule.forRoot()` with `generateId: true`
847
+ - Ensure `ContextModule` is imported
848
+ - Check that original logs have `correlationId` field (if not, forwarded logs won't either)
849
+
850
+ **HTTP Forwarding Timeout:**
851
+ - HTTP forwarding has a 5-second timeout
852
+ - Timeouts are swallowed (no errors thrown)
853
+ - Consider using Kafka instead if central service is slow
854
+ - Or improve central service performance
855
+
856
+ **Note:** Forwarding failures do not affect your application. Original logging continues normally, and business logic is never impacted.
857
+
858
+ ---
859
+
512
860
  ## Log Normalization
513
861
 
514
862
  ### Automatic Normalization
@@ -624,159 +972,913 @@ Correlation ID is **optional** - `undefined` is valid:
624
972
 
625
973
  ### HTTP Context Setup
626
974
 
627
- For HTTP services, configure CLS middleware:
975
+ For HTTP services, configure CLS middleware. **ClsModule MUST be imported BEFORE ContextModule:**
628
976
 
629
977
  ```typescript
630
978
  import { ClsModule } from 'nestjs-cls';
631
979
  import { v4 as uuidv4 } from 'uuid';
980
+ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
632
981
 
633
982
  @Module({
634
983
  imports: [
984
+ // ⚠️ CRITICAL: ClsModule MUST be imported FIRST
635
985
  ClsModule.forRoot({
986
+ global: true,
636
987
  middleware: {
637
988
  mount: true,
638
989
  generateId: true,
639
990
  idGenerator: (req: Request) => {
640
991
  // Use existing correlation ID from header, or generate new one
641
- return req.headers['x-correlation-id'] as string || uuidv4();
992
+ return (req.headers['x-correlation-id'] as string) || uuidv4();
642
993
  },
643
994
  },
644
995
  }),
645
- ContextModule, // Provides CLS service
996
+ ContextModule, // Uses existing ClsModule configuration
646
997
  LoggerModule.forRoot(),
647
998
  ],
648
999
  })
649
1000
  export class AppModule {}
650
1001
  ```
651
1002
 
652
- ---
653
-
654
- ## API Reference
1003
+ **Important:** The import order is critical. `ClsModule.forRoot()` must be imported before `ContextModule` to avoid `HttpAdapterHost` dependency resolution errors.
655
1004
 
656
- ### LoggerService
1005
+ ### CLS Context Boundaries and Edge Cases
657
1006
 
658
- Main logging service injected into your classes.
1007
+ #### Async Context Propagation
659
1008
 
660
- #### Methods
1009
+ CLS (AsyncLocalStorage) automatically propagates correlation ID within the same async context chain:
661
1010
 
662
1011
  ```typescript
663
- // Generic log method
664
- log(level: LogLevel, message: string | Error, data?: LogData, profile?: string): void
665
-
666
- // Convenience methods
667
- debug(message: string, data?: LogData, profile?: string): void
668
- info(message: string, data?: LogData, profile?: string): void
669
- warn(message: string | Error, data?: LogData, profile?: string): void
670
- error(message: string | Error, data?: LogData, profile?: string): void
671
- fatal(message: string | Error, data?: LogData, profile?: string): void
672
- emergency(message: string | Error, data?: LogData, profile?: string): void
673
-
674
- // Profile (currently not implemented in transport architecture)
675
- startProfile(id: string): void
1012
+ // Works: Same async context
1013
+ async handleRequest() {
1014
+ this.contextStorage.setContextId('abc-123');
1015
+ await this.serviceA.process(); // Can read correlationId
1016
+ await this.serviceB.process(); // Can read correlationId
1017
+ }
676
1018
  ```
677
1019
 
678
- #### LogLevel
1020
+ #### Context Boundaries - Where Correlation ID is Lost
1021
+
1022
+ Correlation ID is **lost** when crossing async boundaries:
679
1023
 
680
1024
  ```typescript
681
- enum LogLevel {
682
- Emergency = 'emergency', // One or more systems are unusable
683
- Fatal = 'fatal', // A person must take an action immediately
684
- Error = 'error', // Error events are likely to cause problems
685
- Warn = 'warn', // Warning events might cause problems
686
- Info = 'info', // Routine information
687
- Debug = 'debug', // Debug or trace information
688
- }
1025
+ // Lost: Event emitter
1026
+ this.contextStorage.setContextId('abc-123');
1027
+ eventEmitter.on('event', () => {
1028
+ this.logger.info('Log'); // correlationId: undefined
1029
+ });
1030
+
1031
+ // Lost: setTimeout/setInterval
1032
+ this.contextStorage.setContextId('abc-123');
1033
+ setTimeout(() => {
1034
+ this.logger.info('Log'); // correlationId: undefined
1035
+ }, 100);
1036
+
1037
+ // ❌ Lost: Manual promise without context preservation
1038
+ this.contextStorage.setContextId('abc-123');
1039
+ new Promise((resolve) => {
1040
+ setTimeout(resolve, 100);
1041
+ }).then(() => {
1042
+ this.logger.info('Log'); // correlationId: undefined
1043
+ });
689
1044
  ```
690
1045
 
691
- #### LogData
1046
+ #### Solutions for Context Boundaries
1047
+
1048
+ **1. Event Emitters - Re-set correlationId:**
692
1049
 
693
1050
  ```typescript
694
- interface LogData {
695
- organization?: string; // Override config organization
696
- context?: string; // Override config context
697
- app?: string; // Override config app
698
- sourceClass?: string; // Override auto-detected class name
699
- correlationId?: string; // Override CLS correlation ID
700
- error?: SerializedError; // Error (must be serialized)
701
- props?: Record<string, any>; // Custom properties
702
- durationMs?: number; // Duration in milliseconds
703
- }
1051
+ // Solution: Set correlationId in event handler
1052
+ eventEmitter.on('event', async (data) => {
1053
+ this.contextStorage.setContextId(data.correlationId);
1054
+ this.logger.info('Processing event'); // correlationId available
1055
+ });
704
1056
  ```
705
1057
 
706
- ### ContextStorageService
707
-
708
- Service for managing CLS context (correlation ID).
709
-
710
- #### Methods
1058
+ **2. Timers - Wrap in CLS.run():**
711
1059
 
712
1060
  ```typescript
713
- // Get correlation ID from CLS
714
- getContextId(): string | undefined
1061
+ import { ClsService } from 'nestjs-cls';
1062
+
1063
+ // ✅ Solution: Use CLS.run() for timers
1064
+ const correlationId = this.contextStorage.getContextId();
1065
+ setTimeout(() => {
1066
+ this.clsService.run(async () => {
1067
+ this.clsService.set('CLS_ID', correlationId);
1068
+ this.logger.info('Log'); // correlationId available
1069
+ });
1070
+ }, 100);
1071
+ ```
715
1072
 
716
- // Set correlation ID in CLS
717
- setContextId(id: string): void
1073
+ **3. Worker Threads - Manual Propagation:**
718
1074
 
719
- // Get any value from CLS
720
- get<T>(key: string): T | undefined
1075
+ ```typescript
1076
+ // ✅ Solution: Pass correlationId explicitly to workers
1077
+ const correlationId = this.contextStorage.getContextId();
1078
+ worker.postMessage({ correlationId, data: ... });
721
1079
 
722
- // Set any value in CLS
723
- set<T>(key: string, value: T): void
1080
+ worker.on('message', (msg) => {
1081
+ this.contextStorage.setContextId(msg.correlationId);
1082
+ this.logger.info('Processing'); // correlationId available
1083
+ });
724
1084
  ```
725
1085
 
726
- ---
1086
+ **4. Queues and Background Jobs - Always Re-set:**
727
1087
 
728
- ## Migration Guide (Legacy app.useLogger Users)
1088
+ ```typescript
1089
+ // ✅ Solution: Extract and set at job start
1090
+ @Process('job')
1091
+ async handleJob(job: Job) {
1092
+ // Always set correlationId at job start
1093
+ const correlationId = job.data.correlationId || job.id.toString();
1094
+ this.contextStorage.setContextId(correlationId);
1095
+
1096
+ this.logger.info('Job started'); // correlationId available
1097
+ }
1098
+ ```
729
1099
 
730
- ### ⚠️ Deprecated: app.useLogger() and app.flushLogs()
1100
+ #### CLS Failure Handling
731
1101
 
732
- If you're migrating from an older version of `@infineit/winston-logger` that used `app.useLogger()` and `app.flushLogs()`, this section is for you.
1102
+ The logger handles CLS failures gracefully:
733
1103
 
734
- **Status**: These methods have been **removed** and are **no longer available**.
1104
+ ```typescript
1105
+ // If getContextId() throws, logger continues with undefined
1106
+ try {
1107
+ const id = this.contextStorage.getContextId(); // May throw
1108
+ } catch (error) {
1109
+ // LoggerService.getLogData() catches this
1110
+ // Falls back to undefined correlationId
1111
+ }
735
1112
 
736
- ### Why Was It Removed?
1113
+ this.logger.info('Log'); // Never throws, correlationId: undefined if CLS fails
1114
+ ```
737
1115
 
738
- The `app.useLogger()` and `app.flushLogs()` methods were removed for the following architectural reasons:
1116
+ ### Correlation ID Format Validation
739
1117
 
740
- 1. **HTTP Assumption Violation**: These methods assumed HTTP runtime, making the logger unusable in Kafka consumers, Bull workers, CRON jobs, and CLI tasks.
1118
+ The library does **not** validate correlation ID format. Applications should validate format before setting.
741
1119
 
742
- 2. **Middleware Auto-Mounting**: The old pattern auto-mounted middleware, violating the library's core principle that applications must configure middleware themselves.
1120
+ #### Recommended Format
743
1121
 
744
- 3. **Transport Architecture**: The new architecture uses a pluggable `LoggerTransport[]` system where Winston is one transport among many, not the only transport.
1122
+ **Standard UUID v4** (recommended for most cases):
745
1123
 
746
- 4. **Fire-and-Forget Guarantee**: The new architecture ensures all logging is truly fire-and-forget without requiring explicit `flushLogs()` calls.
1124
+ ```typescript
1125
+ import { v4 as uuidv4, validate as uuidValidate } from 'uuid';
1126
+
1127
+ // ✅ Validate before setting
1128
+ const correlationId = req.headers['x-correlation-id'] || uuidv4();
1129
+ if (uuidValidate(correlationId)) {
1130
+ this.contextStorage.setContextId(correlationId);
1131
+ } else {
1132
+ // Generate new one or use fallback
1133
+ this.contextStorage.setContextId(uuidv4());
1134
+ }
1135
+ ```
747
1136
 
748
- ### Old Deprecated Usage ❌
1137
+ **Custom Format Validation:**
749
1138
 
750
1139
  ```typescript
751
- // DEPRECATED - This no longer works
752
- import { NestFactory } from '@nestjs/core';
753
- import { LoggerModule } from '@infineit/winston-logger';
1140
+ // Example: Custom format (prefix + timestamp + random)
1141
+ const CORRELATION_ID_PATTERN = /^req-[0-9]{13}-[a-z0-9]{8}$/;
754
1142
 
755
- async function bootstrap() {
756
- const app = await NestFactory.create(AppModule);
757
-
758
- // ❌ DEPRECATED: app.useLogger() is removed
759
- app.useLogger(app.get(LoggerModule));
760
-
761
- await app.listen(3000);
762
-
763
- // ❌ DEPRECATED: app.flushLogs() is removed
764
- app.flushLogs();
1143
+ function validateCorrelationId(id: string): boolean {
1144
+ return CORRELATION_ID_PATTERN.test(id);
765
1145
  }
766
1146
 
767
- bootstrap();
1147
+ // ✅ Validate before setting
1148
+ const correlationId = message.correlationId;
1149
+ if (correlationId && validateCorrelationId(correlationId)) {
1150
+ this.contextStorage.setContextId(correlationId);
1151
+ } else {
1152
+ // Generate new one with custom format
1153
+ const newId = `req-${Date.now()}-${Math.random().toString(36).substr(2, 8)}`;
1154
+ this.contextStorage.setContextId(newId);
1155
+ }
768
1156
  ```
769
1157
 
770
- ### New Correct Usage ✅
1158
+ #### Format Guidelines
771
1159
 
772
- ```typescript
773
- //CORRECT: Use LoggerService injection
1160
+ **Recommended:**
1161
+ -UUID v4: `550e8400-e29b-41d4-a716-446655440000`
1162
+ - ✅ Alphanumeric: `req-abc123def456`
1163
+ - ✅ With timestamp: `req-1640000000000-abc123`
1164
+
1165
+ **Avoid:**
1166
+ - ❌ Empty strings: `""`
1167
+ - ❌ Very long strings: > 255 characters
1168
+ - ❌ Special characters that break systems: `null`, `undefined` as string
1169
+ - ❌ Newlines or control characters
1170
+
1171
+ #### Validation in HTTP Middleware
1172
+
1173
+ ```typescript
1174
+ import { ClsModule } from 'nestjs-cls';
1175
+ import { v4 as uuidv4, validate as uuidValidate } from 'uuid';
1176
+
1177
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule in AppModule
1178
+ ClsModule.forRoot({
1179
+ global: true,
1180
+ middleware: {
1181
+ mount: true,
1182
+ generateId: true,
1183
+ idGenerator: (req: Request) => {
1184
+ const headerId = req.headers['x-correlation-id'] as string;
1185
+
1186
+ // Validate format
1187
+ if (headerId && uuidValidate(headerId)) {
1188
+ return headerId;
1189
+ }
1190
+
1191
+ // Generate new one if invalid
1192
+ return uuidv4();
1193
+ },
1194
+ },
1195
+ })
1196
+ ```
1197
+
1198
+ #### Validation in Kafka Consumers
1199
+
1200
+ ```typescript
1201
+ @KafkaListener('order.created')
1202
+ async handleOrderCreated(message: OrderCreatedEvent) {
1203
+ // Validate before setting
1204
+ let correlationId = message.correlationId;
1205
+
1206
+ if (!correlationId || !uuidValidate(correlationId)) {
1207
+ // Use message ID as fallback or generate new
1208
+ correlationId = message.id || uuidv4();
1209
+ }
1210
+
1211
+ this.contextStorage.setContextId(correlationId);
1212
+ this.logger.info('Processing order');
1213
+ }
1214
+ ```
1215
+
1216
+ ---
1217
+
1218
+ ## Complete Integration Examples
1219
+
1220
+ ### HTTP Entry Point - Controller with Middleware
1221
+
1222
+ **app.module.ts:**
1223
+
1224
+ ```typescript
1225
+ import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
1226
+ import { ConfigModule } from '@nestjs/config';
1227
+ import { ClsModule } from 'nestjs-cls';
1228
+ import { v4 as uuidv4, validate as uuidValidate } from 'uuid';
1229
+ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
1230
+
1231
+ @Module({
1232
+ imports: [
1233
+ ConfigModule.forRoot({ isGlobal: true }),
1234
+ // ⚠️ CRITICAL: ClsModule MUST be imported FIRST
1235
+ ClsModule.forRoot({
1236
+ global: true,
1237
+ middleware: {
1238
+ mount: true,
1239
+ generateId: true,
1240
+ idGenerator: (req: Request) => {
1241
+ const headerId = req.headers['x-correlation-id'] as string;
1242
+ return (headerId && uuidValidate(headerId)) ? headerId : uuidv4();
1243
+ },
1244
+ },
1245
+ }),
1246
+ ContextModule, // Uses existing ClsModule configuration
1247
+ LoggerModule.forRoot({
1248
+ nodeEnv: process.env.NODE_ENV,
1249
+ organization: 'sales',
1250
+ context: 'api',
1251
+ app: 'gateway',
1252
+ }),
1253
+ ],
1254
+ })
1255
+ export class AppModule implements NestModule {
1256
+ configure(consumer: MiddlewareConsumer) {
1257
+ // Additional middleware can be added here
1258
+ // ClsModule middleware is already mounted above
1259
+ }
1260
+ }
1261
+ ```
1262
+
1263
+ **sales.controller.ts:**
1264
+
1265
+ ```typescript
1266
+ import { Controller, Get, Post, Body, Param } from '@nestjs/common';
1267
+ import { LoggerService, ContextStorageService } from '@infineit/winston-logger';
1268
+ import { KafkaProducer } from './kafka-producer';
1269
+
1270
+ @Controller('sales')
1271
+ export class SalesController {
1272
+ constructor(
1273
+ private readonly logger: LoggerService,
1274
+ private readonly contextStorage: ContextStorageService,
1275
+ private readonly kafkaProducer: KafkaProducer,
1276
+ ) {}
1277
+
1278
+ @Post('orders')
1279
+ async createOrder(@Body() orderData: CreateOrderDto) {
1280
+ // Correlation ID automatically available from CLS (set by middleware)
1281
+ this.logger.info('Creating order', {
1282
+ props: { userId: orderData.userId, amount: orderData.amount },
1283
+ });
1284
+
1285
+ try {
1286
+ const order = await this.orderService.create(orderData);
1287
+
1288
+ // Extract correlationId from CLS before publishing to Kafka
1289
+ const correlationId = this.contextStorage.getContextId();
1290
+
1291
+ // Publish to Kafka with correlationId
1292
+ await this.kafkaProducer.send({
1293
+ topic: 'order.created',
1294
+ messages: [{
1295
+ key: order.id,
1296
+ value: JSON.stringify({
1297
+ ...order,
1298
+ correlationId, // Propagate correlationId
1299
+ }),
1300
+ }],
1301
+ });
1302
+
1303
+ this.logger.info('Order created and published', {
1304
+ props: { orderId: order.id },
1305
+ });
1306
+
1307
+ return order;
1308
+ } catch (error) {
1309
+ this.logger.error(error, {
1310
+ props: { userId: orderData.userId },
1311
+ });
1312
+ throw error;
1313
+ }
1314
+ }
1315
+ }
1316
+ ```
1317
+
1318
+ ### Kafka Producer - Propagating Correlation ID
1319
+
1320
+ **kafka-producer.service.ts:**
1321
+
1322
+ ```typescript
1323
+ import { Injectable } from '@nestjs/common';
1324
+ import { LoggerService, ContextStorageService } from '@infineit/winston-logger';
1325
+ import { Kafka } from 'kafkajs';
1326
+
1327
+ @Injectable()
1328
+ export class KafkaProducer {
1329
+ constructor(
1330
+ private readonly kafka: Kafka,
1331
+ private readonly logger: LoggerService,
1332
+ private readonly contextStorage: ContextStorageService,
1333
+ ) {}
1334
+
1335
+ async send(topic: string, messages: Array<{ key?: string; value: string }>) {
1336
+ // Get current correlationId from CLS
1337
+ const correlationId = this.contextStorage.getContextId();
1338
+
1339
+ // Add correlationId to message headers
1340
+ const enrichedMessages = messages.map(msg => ({
1341
+ ...msg,
1342
+ headers: {
1343
+ 'x-correlation-id': correlationId || 'no-correlation-id',
1344
+ },
1345
+ }));
1346
+
1347
+ try {
1348
+ await this.kafka.producer().send({
1349
+ topic,
1350
+ messages: enrichedMessages,
1351
+ });
1352
+
1353
+ this.logger.info('Message published to Kafka', {
1354
+ props: { topic, messageCount: messages.length, correlationId },
1355
+ });
1356
+ } catch (error) {
1357
+ this.logger.error(error, {
1358
+ props: { topic, correlationId },
1359
+ });
1360
+ throw error;
1361
+ }
1362
+ }
1363
+ }
1364
+ ```
1365
+
1366
+ ### Kafka Consumer - Extracting and Setting Correlation ID
1367
+
1368
+ **order.consumer.ts:**
1369
+
1370
+ ```typescript
1371
+ import { Injectable } from '@nestjs/common';
1372
+ import { KafkaListener } from '@nestjs/microservices';
1373
+ import { LoggerService, ContextStorageService } from '@infineit/winston-logger';
1374
+ import { validate as uuidValidate } from 'uuid';
1375
+ import { v4 as uuidv4 } from 'uuid';
1376
+
1377
+ @Injectable()
1378
+ export class OrderConsumer {
1379
+ constructor(
1380
+ private readonly logger: LoggerService,
1381
+ private readonly contextStorage: ContextStorageService,
1382
+ ) {}
1383
+
1384
+ @KafkaListener('order.created')
1385
+ async handleOrderCreated(message: OrderCreatedEvent) {
1386
+ // Extract correlationId from message or headers
1387
+ let correlationId = message.correlationId;
1388
+
1389
+ // Fallback to message ID or generate new
1390
+ if (!correlationId || !uuidValidate(correlationId)) {
1391
+ correlationId = message.id || uuidv4();
1392
+ }
1393
+
1394
+ // CRITICAL: Set correlationId at the start of handler
1395
+ this.contextStorage.setContextId(correlationId);
1396
+
1397
+ this.logger.info('Order received from Kafka', {
1398
+ props: { orderId: message.id, correlationId },
1399
+ });
1400
+
1401
+ try {
1402
+ await this.processOrder(message);
1403
+ this.logger.info('Order processed successfully', {
1404
+ props: { orderId: message.id },
1405
+ });
1406
+ } catch (error) {
1407
+ this.logger.error(error, {
1408
+ props: { orderId: message.id, correlationId },
1409
+ });
1410
+ throw error;
1411
+ }
1412
+ }
1413
+ }
1414
+ ```
1415
+
1416
+ ### Bull/BullMQ Worker - Complete Example
1417
+
1418
+ **email.processor.ts:**
1419
+
1420
+ ```typescript
1421
+ import { Injectable } from '@nestjs/common';
1422
+ import { Processor, Process } from '@nestjs/bull';
1423
+ import { Job } from 'bull';
1424
+ import { LoggerService, ContextStorageService } from '@infineit/winston-logger';
1425
+
1426
+ interface EmailJob {
1427
+ to: string;
1428
+ subject: string;
1429
+ body: string;
1430
+ correlationId?: string;
1431
+ userId: string;
1432
+ }
1433
+
1434
+ @Processor('email')
1435
+ export class EmailProcessor {
1436
+ constructor(
1437
+ private readonly logger: LoggerService,
1438
+ private readonly contextStorage: ContextStorageService,
1439
+ private readonly emailService: EmailService,
1440
+ ) {}
1441
+
1442
+ @Process('send')
1443
+ async handleSendEmail(job: Job<EmailJob>) {
1444
+ const start = Date.now();
1445
+
1446
+ // CRITICAL: Set correlationId from job data or use job ID
1447
+ const correlationId = job.data.correlationId || job.id.toString();
1448
+ this.contextStorage.setContextId(correlationId);
1449
+
1450
+ this.logger.info('Email job started', {
1451
+ props: {
1452
+ jobId: job.id,
1453
+ to: job.data.to,
1454
+ correlationId,
1455
+ },
1456
+ });
1457
+
1458
+ try {
1459
+ await this.emailService.send(job.data);
1460
+
1461
+ const duration = Date.now() - start;
1462
+ this.logger.info('Email sent successfully', {
1463
+ props: {
1464
+ jobId: job.id,
1465
+ durationMs: duration,
1466
+ },
1467
+ });
1468
+ } catch (error) {
1469
+ this.logger.error(error, {
1470
+ props: {
1471
+ jobId: job.id,
1472
+ correlationId,
1473
+ durationMs: Date.now() - start,
1474
+ },
1475
+ });
1476
+ throw error; // Re-throw for Bull retry mechanism
1477
+ }
1478
+ }
1479
+ }
1480
+ ```
1481
+
1482
+ **Enqueuing jobs with correlationId:**
1483
+
1484
+ ```typescript
1485
+ @Injectable()
1486
+ export class EmailService {
1487
+ constructor(
1488
+ private readonly emailQueue: Queue,
1489
+ private readonly contextStorage: ContextStorageService,
1490
+ ) {}
1491
+
1492
+ async sendEmail(data: EmailData) {
1493
+ // Get current correlationId from CLS (if in HTTP context)
1494
+ const correlationId = this.contextStorage.getContextId();
1495
+
1496
+ // Enqueue job with correlationId
1497
+ await this.emailQueue.add('send', {
1498
+ ...data,
1499
+ correlationId, // Propagate correlationId to job
1500
+ });
1501
+ }
1502
+ }
1503
+ ```
1504
+
1505
+ ### CRON / Background Jobs - Complete Example
1506
+
1507
+ **cleanup.scheduler.ts:**
1508
+
1509
+ ```typescript
1510
+ import { Injectable } from '@nestjs/common';
1511
+ import { Cron, CronExpression } from '@nestjs/schedule';
1512
+ import { LoggerService, ContextStorageService } from '@infineit/winston-logger';
1513
+
1514
+ @Injectable()
1515
+ export class CleanupScheduler {
1516
+ constructor(
1517
+ private readonly logger: LoggerService,
1518
+ private readonly contextStorage: ContextStorageService,
1519
+ private readonly cleanupService: CleanupService,
1520
+ ) {}
1521
+
1522
+ @Cron(CronExpression.EVERY_HOUR)
1523
+ async hourlyCleanup() {
1524
+ // Generate unique correlationId for this job
1525
+ const correlationId = `cleanup-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1526
+ this.contextStorage.setContextId(correlationId);
1527
+
1528
+ this.logger.info('Hourly cleanup job started', {
1529
+ props: { correlationId },
1530
+ });
1531
+
1532
+ try {
1533
+ const deleted = await this.cleanupService.deleteOldRecords();
1534
+
1535
+ this.logger.info('Hourly cleanup completed', {
1536
+ props: {
1537
+ correlationId,
1538
+ deletedCount: deleted,
1539
+ },
1540
+ });
1541
+ } catch (error) {
1542
+ this.logger.error(error, {
1543
+ props: { correlationId },
1544
+ });
1545
+ // Don't throw - CRON jobs should fail silently to prevent crashes
1546
+ }
1547
+ }
1548
+
1549
+ @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT)
1550
+ async dailyReport() {
1551
+ const correlationId = `daily-report-${Date.now()}`;
1552
+ this.contextStorage.setContextId(correlationId);
1553
+
1554
+ this.logger.info('Daily report generation started', {
1555
+ props: { correlationId },
1556
+ });
1557
+
1558
+ try {
1559
+ await this.reportService.generateDailyReport();
1560
+ this.logger.info('Daily report generated', {
1561
+ props: { correlationId },
1562
+ });
1563
+ } catch (error) {
1564
+ this.logger.error(error, {
1565
+ props: { correlationId },
1566
+ });
1567
+ }
1568
+ }
1569
+ }
1570
+ ```
1571
+
1572
+ ### End-to-End Correlation ID Flow Example
1573
+
1574
+ **Scenario: Sales → Kafka → Document → HTTP → Common Service**
1575
+
1576
+ **1. HTTP Entry (Sales Service):**
1577
+
1578
+ ```typescript
1579
+ // sales.controller.ts
1580
+ export class SalesController {
1581
+ constructor(
1582
+ private readonly logger: LoggerService,
1583
+ private readonly contextStorage: ContextStorageService,
1584
+ private readonly kafkaProducer: KafkaProducer,
1585
+ ) {}
1586
+
1587
+ @Post('orders')
1588
+ async createOrder(@Body() orderData: CreateOrderDto) {
1589
+ // Correlation ID set by ClsModule middleware from HTTP header
1590
+ // Header: x-correlation-id: abc-123-def-456
1591
+
1592
+ this.logger.info('Order creation request received', {
1593
+ props: { userId: orderData.userId },
1594
+ });
1595
+ // Log: { correlationId: 'abc-123-def-456', ... }
1596
+
1597
+ const order = await this.orderService.create(orderData);
1598
+
1599
+ // Extract correlationId from CLS before Kafka publish
1600
+ const correlationId = this.contextStorage.getContextId(); // 'abc-123-def-456'
1601
+
1602
+ // Publish to Kafka with correlationId
1603
+ await this.kafkaProducer.send({
1604
+ topic: 'order.created',
1605
+ messages: [{
1606
+ value: JSON.stringify({
1607
+ ...order,
1608
+ correlationId, // Propagate: 'abc-123-def-456'
1609
+ }),
1610
+ }],
1611
+ });
1612
+
1613
+ return order;
1614
+ }
1615
+ ```
1616
+
1617
+ **2. Kafka Consumer (Document Service):**
1618
+
1619
+ ```typescript
1620
+ // document.consumer.ts
1621
+ @KafkaListener('order.created')
1622
+ async handleOrderCreated(message: OrderCreatedEvent) {
1623
+ // Extract correlationId from message
1624
+ const correlationId = message.correlationId; // 'abc-123-def-456'
1625
+
1626
+ // CRITICAL: Set in CLS at handler start
1627
+ this.contextStorage.setContextId(correlationId);
1628
+
1629
+ this.logger.info('Processing order document', {
1630
+ props: { orderId: message.id },
1631
+ });
1632
+ // Log: { correlationId: 'abc-123-def-456', ... }
1633
+
1634
+ try {
1635
+ await this.documentService.createInvoice(message);
1636
+
1637
+ // Make HTTP call to Common Service with correlationId
1638
+ await this.httpClient.post('http://common-service/invoices', {
1639
+ orderId: message.id,
1640
+ correlationId, // Propagate: 'abc-123-def-456'
1641
+ }, {
1642
+ headers: {
1643
+ 'x-correlation-id': correlationId, // Propagate in header
1644
+ },
1645
+ });
1646
+ } catch (error) {
1647
+ this.logger.error(error, {
1648
+ props: { orderId: message.id },
1649
+ });
1650
+ }
1651
+ }
1652
+ ```
1653
+
1654
+ **3. HTTP Handler (Common Service):**
1655
+
1656
+ ```typescript
1657
+ // common.controller.ts
1658
+ @Post('invoices')
1659
+ async createInvoice(@Body() data: CreateInvoiceDto, @Headers() headers: Headers) {
1660
+ // ClsModule middleware extracts correlationId from header
1661
+ // Header: x-correlation-id: abc-123-def-456
1662
+
1663
+ this.logger.info('Invoice creation request received', {
1664
+ props: { orderId: data.orderId },
1665
+ });
1666
+ // Log: { correlationId: 'abc-123-def-456', ... }
1667
+
1668
+ const invoice = await this.invoiceService.create(data);
1669
+
1670
+ this.logger.info('Invoice created', {
1671
+ props: { invoiceId: invoice.id, orderId: data.orderId },
1672
+ });
1673
+ // Log: { correlationId: 'abc-123-def-456', ... }
1674
+
1675
+ return invoice;
1676
+ }
1677
+ ```
1678
+
1679
+ **4. Common Service (app.module.ts):**
1680
+
1681
+ ```typescript
1682
+ import { ClsModule } from 'nestjs-cls';
1683
+ import { v4 as uuidv4 } from 'uuid';
1684
+ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
1685
+
1686
+ @Module({
1687
+ imports: [
1688
+ // ⚠️ CRITICAL: ClsModule MUST be imported FIRST
1689
+ ClsModule.forRoot({
1690
+ global: true,
1691
+ middleware: {
1692
+ mount: true,
1693
+ generateId: true,
1694
+ idGenerator: (req: Request) => {
1695
+ // Extract from header (propagated from Document Service)
1696
+ return (req.headers['x-correlation-id'] as string) || uuidv4();
1697
+ },
1698
+ },
1699
+ }),
1700
+ ContextModule, // Uses existing ClsModule configuration
1701
+ LoggerModule.forRoot(),
1702
+ ],
1703
+ })
1704
+ export class AppModule {}
1705
+ ```
1706
+
1707
+ **Flow Summary:**
1708
+
1709
+ ```
1710
+ HTTP Request → Sales Service
1711
+ Header: x-correlation-id: abc-123-def-456
1712
+ CLS: abc-123-def-456
1713
+ Log: { correlationId: 'abc-123-def-456' }
1714
+
1715
+ ↓ Kafka Message
1716
+
1717
+ Kafka Topic: order.created
1718
+ Payload: { ..., correlationId: 'abc-123-def-456' }
1719
+
1720
+ ↓ Consumer
1721
+
1722
+ Document Service
1723
+ CLS: abc-123-def-456 (set at handler start)
1724
+ Log: { correlationId: 'abc-123-def-456' }
1725
+
1726
+ ↓ HTTP Request
1727
+
1728
+ HTTP Request → Common Service
1729
+ Header: x-correlation-id: abc-123-def-456
1730
+ CLS: abc-123-def-456 (extracted by middleware)
1731
+ Log: { correlationId: 'abc-123-def-456' }
1732
+ ```
1733
+
1734
+ **Key Points:**
1735
+
1736
+ 1. ✅ **HTTP → Kafka**: Extract from CLS, include in message payload
1737
+ 2. ✅ **Kafka → Consumer**: Extract from message, set in CLS at handler start
1738
+ 3. ✅ **Consumer → HTTP**: Extract from CLS, include in HTTP header
1739
+ 4. ✅ **HTTP → Service**: CLS middleware extracts from header automatically
1740
+ 5. ✅ **All logs**: Same correlationId throughout the flow
1741
+
1742
+ ---
1743
+
1744
+ ## API Reference
1745
+
1746
+ ### LoggerService
1747
+
1748
+ Main logging service injected into your classes.
1749
+
1750
+ #### Methods
1751
+
1752
+ ```typescript
1753
+ // Generic log method
1754
+ log(level: LogLevel, message: string | Error, data?: LogData, profile?: string): void
1755
+
1756
+ // Convenience methods
1757
+ debug(message: string, data?: LogData, profile?: string): void
1758
+ info(message: string, data?: LogData, profile?: string): void
1759
+ warn(message: string | Error, data?: LogData, profile?: string): void
1760
+ error(message: string | Error, data?: LogData, profile?: string): void
1761
+ fatal(message: string | Error, data?: LogData, profile?: string): void
1762
+ emergency(message: string | Error, data?: LogData, profile?: string): void
1763
+
1764
+ // Profile (currently not implemented in transport architecture)
1765
+ startProfile(id: string): void
1766
+ ```
1767
+
1768
+ #### LogLevel
1769
+
1770
+ ```typescript
1771
+ enum LogLevel {
1772
+ Emergency = 'emergency', // One or more systems are unusable
1773
+ Fatal = 'fatal', // A person must take an action immediately
1774
+ Error = 'error', // Error events are likely to cause problems
1775
+ Warn = 'warn', // Warning events might cause problems
1776
+ Info = 'info', // Routine information
1777
+ Debug = 'debug', // Debug or trace information
1778
+ }
1779
+ ```
1780
+
1781
+ #### LogData
1782
+
1783
+ ```typescript
1784
+ interface LogData {
1785
+ organization?: string; // Override config organization
1786
+ context?: string; // Override config context
1787
+ app?: string; // Override config app
1788
+ sourceClass?: string; // Override auto-detected class name
1789
+ correlationId?: string; // Override CLS correlation ID
1790
+ error?: SerializedError; // Error (must be serialized)
1791
+ props?: Record<string, any>; // Custom properties
1792
+ durationMs?: number; // Duration in milliseconds
1793
+ }
1794
+ ```
1795
+
1796
+ ### ContextStorageService
1797
+
1798
+ Service for managing CLS context (correlation ID).
1799
+
1800
+ #### Methods
1801
+
1802
+ ```typescript
1803
+ // Get correlation ID from CLS
1804
+ getContextId(): string | undefined
1805
+
1806
+ // Set correlation ID in CLS
1807
+ setContextId(id: string): void
1808
+
1809
+ // Get any value from CLS
1810
+ get<T>(key: string): T | undefined
1811
+
1812
+ // Set any value in CLS
1813
+ set<T>(key: string, value: T): void
1814
+ ```
1815
+
1816
+ ---
1817
+
1818
+ ## Migration Guide (Legacy app.useLogger Users)
1819
+
1820
+ ### ⚠️ Deprecated: app.useLogger() and app.flushLogs()
1821
+
1822
+ If you're migrating from an older version of `@infineit/winston-logger` that used `app.useLogger()` and `app.flushLogs()`, this section is for you.
1823
+
1824
+ **Status**: These methods have been **removed** and are **no longer available**.
1825
+
1826
+ ### Why Was It Removed?
1827
+
1828
+ The `app.useLogger()` and `app.flushLogs()` methods were removed for the following architectural reasons:
1829
+
1830
+ 1. **HTTP Assumption Violation**: These methods assumed HTTP runtime, making the logger unusable in Kafka consumers, Bull workers, CRON jobs, and CLI tasks.
1831
+
1832
+ 2. **Middleware Auto-Mounting**: The old pattern auto-mounted middleware, violating the library's core principle that applications must configure middleware themselves.
1833
+
1834
+ 3. **Transport Architecture**: The new architecture uses a pluggable `LoggerTransport[]` system where Winston is one transport among many, not the only transport.
1835
+
1836
+ 4. **Fire-and-Forget Guarantee**: The new architecture ensures all logging is truly fire-and-forget without requiring explicit `flushLogs()` calls.
1837
+
1838
+ ### Old Deprecated Usage ❌
1839
+
1840
+ ```typescript
1841
+ // ❌ DEPRECATED - This no longer works
774
1842
  import { NestFactory } from '@nestjs/core';
1843
+ import { LoggerModule } from '@infineit/winston-logger';
1844
+
1845
+ async function bootstrap() {
1846
+ const app = await NestFactory.create(AppModule);
1847
+
1848
+ // ❌ DEPRECATED: app.useLogger() is removed
1849
+ app.useLogger(app.get(LoggerModule));
1850
+
1851
+ await app.listen(3000);
1852
+
1853
+ // ❌ DEPRECATED: app.flushLogs() is removed
1854
+ app.flushLogs();
1855
+ }
1856
+
1857
+ bootstrap();
1858
+ ```
1859
+
1860
+ ### New Correct Usage ✅
1861
+
1862
+ ```typescript
1863
+ // ✅ CORRECT: Use LoggerService injection
1864
+ import { NestFactory } from '@nestjs/core';
1865
+ import { ClsModule } from 'nestjs-cls';
1866
+ import { v4 as uuidv4 } from 'uuid';
775
1867
  import { ContextModule, LoggerModule } from '@infineit/winston-logger';
776
1868
 
777
1869
  @Module({
778
1870
  imports: [
779
- ContextModule,
1871
+ // ⚠️ CRITICAL: ClsModule MUST be imported BEFORE ContextModule
1872
+ ClsModule.forRoot({
1873
+ global: true,
1874
+ middleware: {
1875
+ mount: true,
1876
+ generateId: true,
1877
+ idGenerator: (req: Request) =>
1878
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
1879
+ },
1880
+ }),
1881
+ ContextModule, // Uses existing ClsModule
780
1882
  LoggerModule.forRoot(),
781
1883
  ],
782
1884
  })
@@ -935,14 +2037,22 @@ async function bootstrap() {
935
2037
  ```typescript
936
2038
  import { NestFactory } from '@nestjs/core';
937
2039
  import { ClsModule } from 'nestjs-cls';
2040
+ import { v4 as uuidv4 } from 'uuid';
938
2041
  import { ContextModule, LoggerModule } from '@infineit/winston-logger';
939
2042
 
940
2043
  @Module({
941
2044
  imports: [
2045
+ // ⚠️ CRITICAL: ClsModule MUST be imported FIRST
942
2046
  ClsModule.forRoot({
943
- middleware: { mount: true, generateId: true },
2047
+ global: true,
2048
+ middleware: {
2049
+ mount: true,
2050
+ generateId: true,
2051
+ idGenerator: (req: Request) =>
2052
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
2053
+ },
944
2054
  }),
945
- ContextModule, // ✅ Add
2055
+ ContextModule, // ✅ Uses existing ClsModule
946
2056
  LoggerModule.forRoot(), // ✅ Add
947
2057
  ],
948
2058
  })
@@ -960,10 +2070,11 @@ async function bootstrap() {
960
2070
 
961
2071
  If you encounter issues during migration:
962
2072
 
963
- 1. **Check module imports**: Ensure `ContextModule` and `LoggerModule.forRoot()` are in your `AppModule`
964
- 2. **Check CLS setup**: For HTTP services, ensure `ClsModule` is configured
965
- 3. **Check injection**: Ensure `LoggerService` is injected via constructor
966
- 4. **Review examples**: See the "Usage in Different Contexts" section above
2073
+ 1. **Check module imports**: Ensure `ClsModule.forRoot()` is imported **BEFORE** `ContextModule`, then `LoggerModule.forRoot()` in your `AppModule`
2074
+ 2. **Check CLS setup**: For HTTP services, ensure `ClsModule.forRoot()` is configured with `middleware: { mount: true, generateId: true }`
2075
+ 3. **Check import order**: `ClsModule` must come before `ContextModule` to avoid `HttpAdapterHost` dependency errors
2076
+ 4. **Check injection**: Ensure `LoggerService` is injected via constructor
2077
+ 5. **Review examples**: See the "Usage in Different Contexts" section above
967
2078
 
968
2079
  ---
969
2080
 
@@ -1131,15 +2242,17 @@ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
1131
2242
  @Module({
1132
2243
  imports: [
1133
2244
  ConfigModule.forRoot({ isGlobal: true }),
2245
+ // ⚠️ CRITICAL: ClsModule MUST be imported FIRST
1134
2246
  ClsModule.forRoot({
2247
+ global: true,
1135
2248
  middleware: {
1136
2249
  mount: true,
1137
2250
  generateId: true,
1138
2251
  idGenerator: (req: Request) =>
1139
- req.headers['x-correlation-id'] as string || uuidv4(),
2252
+ (req.headers['x-correlation-id'] as string) || uuidv4(),
1140
2253
  },
1141
2254
  }),
1142
- ContextModule,
2255
+ ContextModule, // Uses existing ClsModule configuration
1143
2256
  LoggerModule.forRoot({
1144
2257
  nodeEnv: process.env.NODE_ENV,
1145
2258
  organization: 'my-org',
@@ -1149,7 +2262,6 @@ import { ContextModule, LoggerModule } from '@infineit/winston-logger';
1149
2262
  ],
1150
2263
  })
1151
2264
  export class AppModule {}
1152
- ```
1153
2265
 
1154
2266
  ```typescript
1155
2267
  import { Injectable } from '@nestjs/common';