@infineit/winston-logger 1.0.29 → 1.0.31
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 +1229 -115
- package/context/infrastructure/nestjs/contextModule.js +2 -4
- package/context/infrastructure/nestjs/contextModule.js.map +1 -1
- package/index.d.ts +14 -5
- package/index.js +20 -5
- package/index.js.map +1 -1
- package/logger/domain/log.d.ts +3 -1
- package/logger/domain/loggerService.d.ts +7 -4
- package/logger/domain/loggerService.js +140 -31
- package/logger/domain/loggerService.js.map +1 -1
- package/logger/domain/loggerTransport.d.ts +5 -0
- package/logger/domain/loggerTransport.js +5 -0
- package/logger/domain/loggerTransport.js.map +1 -0
- package/logger/domain/normalizedLog.d.ts +23 -0
- package/logger/domain/normalizedLog.js +37 -0
- package/logger/domain/normalizedLog.js.map +1 -0
- package/logger/infrastructure/nestjs/loggerModule.d.ts +12 -9
- package/logger/infrastructure/nestjs/loggerModule.js +72 -137
- package/logger/infrastructure/nestjs/loggerModule.js.map +1 -1
- package/logger/infrastructure/nestjs/nestjsLoggerServiceAdapter.js +61 -11
- package/logger/infrastructure/nestjs/nestjsLoggerServiceAdapter.js.map +1 -1
- package/logger/infrastructure/winston/transports/fileTransport.d.ts +1 -1
- package/logger/infrastructure/winston/transports/fileTransport.js +5 -2
- package/logger/infrastructure/winston/transports/fileTransport.js.map +1 -1
- package/logger/infrastructure/winston/winstonLogger.js +78 -27
- package/logger/infrastructure/winston/winstonLogger.js.map +1 -1
- package/logger/infrastructure/winston/winstonTransportAdapter.d.ts +10 -0
- package/logger/infrastructure/winston/winstonTransportAdapter.js +128 -0
- package/logger/infrastructure/winston/winstonTransportAdapter.js.map +1 -0
- package/package.json +11 -16
- package/tsconfig.lib.tsbuildinfo +1 -1
- package/logger/infrastructure/winston/transports/prisma-transport.d.ts +0 -11
- package/logger/infrastructure/winston/transports/prisma-transport.js +0 -50
- package/logger/infrastructure/winston/transports/prisma-transport.js.map +0 -1
- package/logger/levelFilter.d.ts +0 -2
- package/logger/levelFilter.js +0 -47
- package/logger/levelFilter.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,169 +1,1283 @@
|
|
|
1
|
-
|
|
2
|
-
<div align="center">
|
|
3
|
-
<h3 align="center">NestJS Logger</h3>
|
|
1
|
+
# @infineit/winston-logger
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
A Nest.js production-ready logger implementation.
|
|
7
|
-
</p>
|
|
8
|
-
</div>
|
|
3
|
+
Enterprise-level logging library for NestJS applications with support for multiple contexts (HTTP, Kafka, Bull/BullMQ, CRON, CLI) and flexible transport architecture.
|
|
9
4
|
|
|
5
|
+
## Features
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
- ✅ **Multi-Context Support**: Works in HTTP services, Kafka consumers, Bull/BullMQ workers, CRON jobs, and CLI tasks
|
|
8
|
+
- ✅ **Transport Architecture**: Pluggable transport system with Winston as one transport among many
|
|
9
|
+
- ✅ **Log Normalization**: Automatic normalization and error serialization before transport
|
|
10
|
+
- ✅ **CLS Integration**: Correlation ID support via AsyncLocalStorage (CLS)
|
|
11
|
+
- ✅ **Fire-and-Forget**: Non-blocking, failure-safe logging that never breaks business logic
|
|
12
|
+
- ✅ **Zero Dependencies**: No database, HTTP framework, or ORM dependencies
|
|
13
|
+
- ✅ **Type-Safe**: Full TypeScript support with comprehensive type definitions
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## Table of Contents
|
|
14
16
|
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Quick Start](#quick-start)
|
|
19
|
+
- [Architecture Overview](#architecture-overview)
|
|
20
|
+
- [Configuration](#configuration)
|
|
21
|
+
- [Usage in Different Contexts](#usage-in-different-contexts)
|
|
22
|
+
- [Transport Architecture](#transport-architecture)
|
|
23
|
+
- [Log Normalization](#log-normalization)
|
|
24
|
+
- [Correlation ID (CLS)](#correlation-id-cls)
|
|
25
|
+
- [API Reference](#api-reference)
|
|
26
|
+
- [Migration Guide (Legacy app.useLogger Users)](#migration-guide-legacy-appuselogger-users)
|
|
27
|
+
- [Best Practices](#best-practices)
|
|
28
|
+
- [Forbidden Actions](#forbidden-actions)
|
|
29
|
+
- [Examples](#examples)
|
|
15
30
|
|
|
16
|
-
|
|
31
|
+
---
|
|
17
32
|
|
|
18
|
-
|
|
19
|
-
- **Configurable**: Provides options to customize logger configuration.
|
|
20
|
-
- **Lightweight**: Minimal dependencies and overhead.
|
|
33
|
+
## Installation
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
```bash
|
|
36
|
+
npm install @infineit/winston-logger
|
|
37
|
+
```
|
|
23
38
|
|
|
24
|
-
|
|
25
|
-
* Decoupled log transporters
|
|
26
|
-
* Log levels
|
|
27
|
-
* Logging Rules
|
|
28
|
-
* Log formatters
|
|
39
|
+
### Peer Dependencies
|
|
29
40
|
|
|
30
|
-
|
|
41
|
+
This package requires the following peer dependencies (usually already installed in NestJS projects):
|
|
31
42
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
- `@nestjs/common` 11.1.9
|
|
44
|
+
- `@nestjs/config` 4.2.0
|
|
45
|
+
- `@nestjs/core` 11.1.9
|
|
46
|
+
- `nestjs-cls` 5.0.1
|
|
36
47
|
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
yarn add @infineit/winston-logger
|
|
40
|
-
```
|
|
48
|
+
---
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
## Quick Start
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
- Winston
|
|
46
|
-
- morgon
|
|
47
|
-
- prisma
|
|
52
|
+
### 1. Import Modules
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
```typescript
|
|
55
|
+
import { Module } from '@nestjs/common';
|
|
56
|
+
import { ConfigModule } from '@nestjs/config';
|
|
57
|
+
import { ContextModule, LoggerModule } from '@infineit/winston-logger';
|
|
50
58
|
|
|
51
|
-
|
|
59
|
+
@Module({
|
|
60
|
+
imports: [
|
|
61
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
62
|
+
ContextModule, // Required for CLS (correlation ID support)
|
|
63
|
+
LoggerModule.forRoot(), // Configure logger
|
|
64
|
+
],
|
|
65
|
+
})
|
|
66
|
+
export class AppModule {}
|
|
67
|
+
```
|
|
52
68
|
|
|
53
|
-
|
|
69
|
+
### 2. Use LoggerService
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
```typescript
|
|
72
|
+
import { Injectable } from '@nestjs/common';
|
|
73
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
57
74
|
|
|
58
|
-
|
|
75
|
+
@Injectable()
|
|
76
|
+
export class MyService {
|
|
77
|
+
constructor(private readonly logger: LoggerService) {}
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
doSomething() {
|
|
80
|
+
this.logger.info('Processing started', {
|
|
81
|
+
userId: 123,
|
|
82
|
+
action: 'process',
|
|
83
|
+
});
|
|
62
84
|
|
|
63
|
-
|
|
85
|
+
try {
|
|
86
|
+
// Your business logic
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.logger.error(error, {
|
|
89
|
+
userId: 123,
|
|
90
|
+
action: 'process',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
64
96
|
|
|
65
|
-
|
|
66
|
-
import { ContextModule, LoggerModule } from '@infineit/winston-logger';
|
|
97
|
+
---
|
|
67
98
|
|
|
68
|
-
|
|
69
|
-
imports: [
|
|
70
|
-
ContextModule,
|
|
71
|
-
LoggerModule.forRoot(PrismaService),
|
|
72
|
-
]
|
|
73
|
-
})
|
|
74
|
-
```
|
|
99
|
+
## Architecture Overview
|
|
75
100
|
|
|
76
|
-
|
|
101
|
+
### LoggerService → LoggerTransport[] Flow
|
|
77
102
|
|
|
78
|
-
|
|
79
|
-
|
|
103
|
+
```
|
|
104
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
105
|
+
│ Application Code │
|
|
106
|
+
│ (HTTP Services, Kafka Consumers, Bull Workers, etc.) │
|
|
107
|
+
└────────────────────┬────────────────────────────────────────┘
|
|
108
|
+
│
|
|
109
|
+
▼
|
|
110
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
111
|
+
│ LoggerService │
|
|
112
|
+
│ • Normalizes logs │
|
|
113
|
+
│ • Serializes errors │
|
|
114
|
+
│ • Retrieves correlationId from CLS │
|
|
115
|
+
│ • Sends to all transports (fire-and-forget) │
|
|
116
|
+
└────────────────────┬────────────────────────────────────────┘
|
|
117
|
+
│
|
|
118
|
+
▼
|
|
119
|
+
┌───────────────────────┐
|
|
120
|
+
│ NormalizedLog │
|
|
121
|
+
│ • timestamp │
|
|
122
|
+
│ • level │
|
|
123
|
+
│ • message │
|
|
124
|
+
│ • error (serialized)│
|
|
125
|
+
│ • correlationId │
|
|
126
|
+
│ • ... │
|
|
127
|
+
└───────────────────────┘
|
|
128
|
+
│
|
|
129
|
+
▼
|
|
130
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
131
|
+
│ LoggerTransport[] (Array) │
|
|
132
|
+
└────────────────────┬────────────────────────────────────────┘
|
|
133
|
+
│
|
|
134
|
+
┌────────────┼────────────┐
|
|
135
|
+
│ │ │
|
|
136
|
+
▼ ▼ ▼
|
|
137
|
+
┌──────────────┐ ┌──────────┐ ┌──────────┐
|
|
138
|
+
│WinstonTransport│ │ Custom │ │ Custom │
|
|
139
|
+
│ Adapter │ │ Transport│ │ Transport│
|
|
140
|
+
│ │ │ │ │ │
|
|
141
|
+
│ • Console │ │ • Kafka │ │ • HTTP │
|
|
142
|
+
│ • File │ │ • Database│ │ • etc. │
|
|
143
|
+
│ • Slack │ │ │ │ │
|
|
144
|
+
└──────────────┘ └──────────┘ └──────────┘
|
|
145
|
+
```
|
|
80
146
|
|
|
81
|
-
|
|
147
|
+
### Key Components
|
|
82
148
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
});
|
|
89
|
-
```
|
|
149
|
+
1. **LoggerService**: Main logging service that normalizes logs and sends them to transports
|
|
150
|
+
2. **LoggerTransport**: Interface for all log transports (Winston, Kafka, HTTP, Database, etc.)
|
|
151
|
+
3. **WinstonTransportAdapter**: Wraps Winston as one transport among many
|
|
152
|
+
4. **ContextModule**: Provides CLS (AsyncLocalStorage) for correlation ID management
|
|
153
|
+
5. **NormalizedLog**: Standardized log format sent to all transports
|
|
90
154
|
|
|
91
|
-
|
|
155
|
+
---
|
|
92
156
|
|
|
93
157
|
## Configuration
|
|
94
158
|
|
|
95
|
-
|
|
159
|
+
### LoggerModule Configuration
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { LoggerModule, LoggerModuleConfig } from '@infineit/winston-logger';
|
|
163
|
+
|
|
164
|
+
const config: LoggerModuleConfig = {
|
|
165
|
+
nodeEnv: 'production', // 'production' | 'testing' | 'development'
|
|
166
|
+
slack_webhook: 'https://...', // Slack webhook URL (optional)
|
|
167
|
+
console_print: true, // Enable console output (boolean or 'true'/'false')
|
|
168
|
+
log_in_file: true, // Enable file logging (boolean or 'true'/'false')
|
|
169
|
+
organization: 'my-org', // Organization name (optional)
|
|
170
|
+
context: 'user-service', // Bounded context name (optional)
|
|
171
|
+
app: 'api-gateway', // Application name (optional)
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
@Module({
|
|
175
|
+
imports: [
|
|
176
|
+
LoggerModule.forRoot(config),
|
|
177
|
+
],
|
|
178
|
+
})
|
|
179
|
+
export class AppModule {}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Configuration via Environment Variables
|
|
183
|
+
|
|
184
|
+
You can also configure via `@nestjs/config`:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// .env
|
|
188
|
+
NODE_ENV=production
|
|
189
|
+
SLACK_WEBHOOK=https://hooks.slack.com/services/...
|
|
190
|
+
CONSOLE_PRINT=true
|
|
191
|
+
LOG_IN_FILE=true
|
|
192
|
+
LOGGER_ORGANIZATION=my-org
|
|
193
|
+
LOGGER_CONTEXT=user-service
|
|
194
|
+
LOGGER_APP=api-gateway
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// logger.config.ts
|
|
199
|
+
import { registerAs } from '@nestjs/config';
|
|
200
|
+
|
|
201
|
+
export default registerAs('logger', () => ({
|
|
202
|
+
nodeEnv: process.env.NODE_ENV,
|
|
203
|
+
slack_webhook: process.env.SLACK_WEBHOOK,
|
|
204
|
+
console_print: process.env.CONSOLE_PRINT,
|
|
205
|
+
log_in_file: process.env.LOG_IN_FILE,
|
|
206
|
+
organization: process.env.LOGGER_ORGANIZATION,
|
|
207
|
+
context: process.env.LOGGER_CONTEXT,
|
|
208
|
+
app: process.env.LOGGER_APP,
|
|
209
|
+
}));
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
@Module({
|
|
214
|
+
imports: [
|
|
215
|
+
ConfigModule.forRoot({
|
|
216
|
+
isGlobal: true,
|
|
217
|
+
load: [loggerConfig],
|
|
218
|
+
}),
|
|
219
|
+
LoggerModule.forRoot(), // Will read from ConfigService
|
|
220
|
+
],
|
|
221
|
+
})
|
|
222
|
+
export class AppModule {}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Configuration Options
|
|
226
|
+
|
|
227
|
+
| Option | Type | Required | Default | Description |
|
|
228
|
+
|--------|------|----------|---------|-------------|
|
|
229
|
+
| `nodeEnv` | `string` | No | `undefined` | Environment: `'production'`, `'testing'`, or `'development'` |
|
|
230
|
+
| `slack_webhook` | `string` | No | `undefined` | Slack webhook URL for fatal error notifications |
|
|
231
|
+
| `console_print` | `boolean \| string` | No | `false` | Enable console output (`true`/`false` or `'true'`/`'false'`) |
|
|
232
|
+
| `log_in_file` | `boolean \| string` | No | `false` | Enable file logging (`true`/`false` or `'true'`/`'false'`) |
|
|
233
|
+
| `organization` | `string` | No | `undefined` | Organization or project name |
|
|
234
|
+
| `context` | `string` | No | `undefined` | Bounded context name |
|
|
235
|
+
| `app` | `string` | No | `undefined` | Application or microservice name |
|
|
236
|
+
|
|
237
|
+
### Default Behavior
|
|
238
|
+
|
|
239
|
+
- **Development/Testing**: Console and file logging enabled by default
|
|
240
|
+
- **Production**: Console and file logging disabled by default (unless explicitly enabled)
|
|
241
|
+
- **Slack**: Only enabled in production/testing when `slack_webhook` is provided
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Usage in Different Contexts
|
|
246
|
+
|
|
247
|
+
### HTTP Services
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { Injectable } from '@nestjs/common';
|
|
251
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
252
|
+
|
|
253
|
+
@Injectable()
|
|
254
|
+
export class UserController {
|
|
255
|
+
constructor(private readonly logger: LoggerService) {}
|
|
256
|
+
|
|
257
|
+
@Get('/users/:id')
|
|
258
|
+
async getUser(@Param('id') id: string) {
|
|
259
|
+
// Correlation ID is automatically retrieved from CLS
|
|
260
|
+
// (if CLS middleware is configured in your app)
|
|
261
|
+
this.logger.info('Fetching user', { userId: id });
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
const user = await this.userService.findById(id);
|
|
265
|
+
return user;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
this.logger.error(error, { userId: id });
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Important**: For HTTP services, you must configure CLS middleware manually:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import { ClsModule } from 'nestjs-cls';
|
|
278
|
+
|
|
279
|
+
@Module({
|
|
280
|
+
imports: [
|
|
281
|
+
ClsModule.forRoot({
|
|
282
|
+
middleware: {
|
|
283
|
+
mount: true, // Enable middleware
|
|
284
|
+
generateId: true, // Auto-generate correlation ID
|
|
285
|
+
idGenerator: (req: Request) => req.headers['x-correlation-id'] || uuidv4(),
|
|
286
|
+
},
|
|
287
|
+
}),
|
|
288
|
+
ContextModule, // Provides CLS service
|
|
289
|
+
LoggerModule.forRoot(),
|
|
290
|
+
],
|
|
291
|
+
})
|
|
292
|
+
export class AppModule {}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Kafka Consumers
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
import { Injectable } from '@nestjs/common';
|
|
299
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
300
|
+
import { ContextStorageService } from '@infineit/winston-logger';
|
|
301
|
+
|
|
302
|
+
@Injectable()
|
|
303
|
+
export class OrderConsumer {
|
|
304
|
+
constructor(
|
|
305
|
+
private readonly logger: LoggerService,
|
|
306
|
+
private readonly contextStorage: ContextStorageService,
|
|
307
|
+
) {}
|
|
308
|
+
|
|
309
|
+
@KafkaListener('order.created')
|
|
310
|
+
async handleOrderCreated(message: OrderCreatedEvent) {
|
|
311
|
+
// Set correlation ID from Kafka message
|
|
312
|
+
this.contextStorage.setContextId(message.correlationId || message.id);
|
|
313
|
+
|
|
314
|
+
this.logger.info('Processing order', {
|
|
315
|
+
orderId: message.id,
|
|
316
|
+
userId: message.userId,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
await this.processOrder(message);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
this.logger.error(error, {
|
|
323
|
+
orderId: message.id,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Bull/BullMQ Workers
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import { Injectable } from '@nestjs/common';
|
|
334
|
+
import { Processor, Process } from '@nestjs/bull';
|
|
335
|
+
import { Job } from 'bull';
|
|
336
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
337
|
+
import { ContextStorageService } from '@infineit/winston-logger';
|
|
338
|
+
|
|
339
|
+
@Processor('email')
|
|
340
|
+
export class EmailProcessor {
|
|
341
|
+
constructor(
|
|
342
|
+
private readonly logger: LoggerService,
|
|
343
|
+
private readonly contextStorage: ContextStorageService,
|
|
344
|
+
) {}
|
|
345
|
+
|
|
346
|
+
@Process('send')
|
|
347
|
+
async handleSendEmail(job: Job<EmailJob>) {
|
|
348
|
+
// Set correlation ID from job
|
|
349
|
+
this.contextStorage.setContextId(job.id.toString());
|
|
350
|
+
|
|
351
|
+
this.logger.info('Sending email', {
|
|
352
|
+
jobId: job.id,
|
|
353
|
+
email: job.data.to,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
await this.emailService.send(job.data);
|
|
358
|
+
this.logger.info('Email sent', {
|
|
359
|
+
jobId: job.id,
|
|
360
|
+
durationMs: Date.now() - job.timestamp,
|
|
361
|
+
});
|
|
362
|
+
} catch (error) {
|
|
363
|
+
this.logger.error(error, {
|
|
364
|
+
jobId: job.id,
|
|
365
|
+
});
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### CRON Jobs
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { Injectable } from '@nestjs/common';
|
|
376
|
+
import { Cron, CronExpression } from '@nestjs/schedule';
|
|
377
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
378
|
+
import { ContextStorageService } from '@infineit/winston-logger';
|
|
379
|
+
|
|
380
|
+
@Injectable()
|
|
381
|
+
export class CleanupService {
|
|
382
|
+
constructor(
|
|
383
|
+
private readonly logger: LoggerService,
|
|
384
|
+
private readonly contextStorage: ContextStorageService,
|
|
385
|
+
) {}
|
|
386
|
+
|
|
387
|
+
@Cron(CronExpression.EVERY_HOUR)
|
|
388
|
+
async cleanup() {
|
|
389
|
+
// Set correlation ID for this job
|
|
390
|
+
this.contextStorage.setContextId(`cleanup-${Date.now()}`);
|
|
391
|
+
|
|
392
|
+
this.logger.info('Starting cleanup job');
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
const deleted = await this.repository.deleteOldRecords();
|
|
396
|
+
this.logger.info('Cleanup completed', {
|
|
397
|
+
deletedCount: deleted,
|
|
398
|
+
});
|
|
399
|
+
} catch (error) {
|
|
400
|
+
this.logger.error(error);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### CLI Tasks
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { Injectable } from '@nestjs/common';
|
|
410
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
411
|
+
import { ContextStorageService } from '@infineit/winston-logger';
|
|
412
|
+
|
|
413
|
+
@Injectable()
|
|
414
|
+
export class MigrationService {
|
|
415
|
+
constructor(
|
|
416
|
+
private readonly logger: LoggerService,
|
|
417
|
+
private readonly contextStorage: ContextStorageService,
|
|
418
|
+
) {}
|
|
419
|
+
|
|
420
|
+
async runMigration() {
|
|
421
|
+
// Set correlation ID for this task
|
|
422
|
+
this.contextStorage.setContextId(`migration-${Date.now()}`);
|
|
423
|
+
|
|
424
|
+
this.logger.info('Starting migration');
|
|
425
|
+
|
|
426
|
+
try {
|
|
427
|
+
await this.migrate();
|
|
428
|
+
this.logger.info('Migration completed');
|
|
429
|
+
} catch (error) {
|
|
430
|
+
this.logger.error(error);
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## Transport Architecture
|
|
440
|
+
|
|
441
|
+
### Built-in Transports
|
|
442
|
+
|
|
443
|
+
The library includes `WinstonTransportAdapter` which wraps Winston and provides:
|
|
444
|
+
|
|
445
|
+
- **ConsoleTransport**: Colorized console output
|
|
446
|
+
- **FileTransport**: Daily rotating file logs (`logs/log-YYYY-MM-DD-HH.log`)
|
|
447
|
+
- **SlackTransport**: Fatal error notifications to Slack
|
|
448
|
+
|
|
449
|
+
### Custom Transports
|
|
450
|
+
|
|
451
|
+
You can create custom transports by implementing the `LoggerTransport` interface:
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
import { Injectable } from '@nestjs/common';
|
|
455
|
+
import { LoggerTransport, NormalizedLog } from '@infineit/winston-logger';
|
|
456
|
+
|
|
457
|
+
@Injectable()
|
|
458
|
+
export class KafkaTransport implements LoggerTransport {
|
|
459
|
+
constructor(private readonly kafkaProducer: KafkaProducer) {}
|
|
460
|
+
|
|
461
|
+
log(normalizedLog: NormalizedLog): void {
|
|
462
|
+
try {
|
|
463
|
+
// Send to Kafka (fire-and-forget)
|
|
464
|
+
this.kafkaProducer.send({
|
|
465
|
+
topic: 'logs',
|
|
466
|
+
messages: [{
|
|
467
|
+
value: JSON.stringify(normalizedLog),
|
|
468
|
+
}],
|
|
469
|
+
});
|
|
470
|
+
} catch (error) {
|
|
471
|
+
// Swallow errors - never break business logic
|
|
472
|
+
console.error('KafkaTransport error (swallowed):', error);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### Registering Custom Transports
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import { Provider } from '@nestjs/common';
|
|
482
|
+
import { LoggerModule } from '@infineit/winston-logger';
|
|
483
|
+
import { KafkaTransport } from './kafka-transport';
|
|
484
|
+
|
|
485
|
+
const customTransports: Provider[] = [
|
|
486
|
+
{
|
|
487
|
+
provide: KafkaTransport,
|
|
488
|
+
useClass: KafkaTransport,
|
|
489
|
+
},
|
|
490
|
+
];
|
|
491
|
+
|
|
492
|
+
@Module({
|
|
493
|
+
imports: [
|
|
494
|
+
LoggerModule.forRoot(config, customTransports),
|
|
495
|
+
],
|
|
496
|
+
})
|
|
497
|
+
export class AppModule {}
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Transport Requirements
|
|
501
|
+
|
|
502
|
+
All transports must:
|
|
503
|
+
|
|
504
|
+
1. ✅ Implement `LoggerTransport` interface
|
|
505
|
+
2. ✅ Accept `NormalizedLog` (never raw `Error` objects)
|
|
506
|
+
3. ✅ Be fire-and-forget (non-blocking)
|
|
507
|
+
4. ✅ Never throw errors
|
|
508
|
+
5. ✅ Handle failures gracefully
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Log Normalization
|
|
513
|
+
|
|
514
|
+
### Automatic Normalization
|
|
515
|
+
|
|
516
|
+
All logs are automatically normalized before being sent to transports:
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
// You can pass Error objects or LogData with errors
|
|
520
|
+
this.logger.error(new Error('Something went wrong'), {
|
|
521
|
+
userId: 123,
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// LoggerService automatically:
|
|
525
|
+
// 1. Serializes the Error to SerializedError
|
|
526
|
+
// 2. Creates NormalizedLog with all required fields
|
|
527
|
+
// 3. Sends to all transports
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### NormalizedLog Structure
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
interface NormalizedLog {
|
|
534
|
+
timestamp: number; // Unix timestamp in milliseconds
|
|
535
|
+
level: LogLevel; // 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'emergency'
|
|
536
|
+
message: string; // Log message
|
|
537
|
+
organization?: string; // From config
|
|
538
|
+
context?: string; // From config
|
|
539
|
+
app?: string; // From config
|
|
540
|
+
sourceClass?: string; // Auto-detected from class name
|
|
541
|
+
correlationId?: string; // From CLS (can be undefined)
|
|
542
|
+
error?: SerializedError; // Serialized error (never raw Error)
|
|
543
|
+
props?: Record<string, any>; // Custom properties
|
|
544
|
+
durationMs?: number; // Duration in milliseconds
|
|
545
|
+
label?: string; // Auto-generated: `${organization}.${context}.${app}`
|
|
546
|
+
stack?: string; // Error stack trace (if error present)
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
### Error Serialization
|
|
551
|
+
|
|
552
|
+
Errors are automatically serialized using `serializeError()`:
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
import { serializeError } from '@infineit/winston-logger';
|
|
556
|
+
|
|
557
|
+
const error = new Error('Test error');
|
|
558
|
+
error.code = 'ERR_TEST';
|
|
559
|
+
|
|
560
|
+
const serialized = serializeError(error);
|
|
561
|
+
// {
|
|
562
|
+
// name: 'Error',
|
|
563
|
+
// message: 'Test error',
|
|
564
|
+
// stack: 'Error: Test error\n at ...',
|
|
565
|
+
// code: 'ERR_TEST'
|
|
566
|
+
// }
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
**Important**: If you pass errors in `LogData`, they must already be serialized:
|
|
570
|
+
|
|
571
|
+
```typescript
|
|
572
|
+
import { serializeError } from '@infineit/winston-logger';
|
|
573
|
+
|
|
574
|
+
const error = new Error('Test');
|
|
575
|
+
this.logger.info('Message', {
|
|
576
|
+
error: serializeError(error), // Must serialize manually if in LogData
|
|
577
|
+
});
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Correlation ID (CLS)
|
|
583
|
+
|
|
584
|
+
### Automatic Retrieval
|
|
585
|
+
|
|
586
|
+
`LoggerService` automatically retrieves correlation ID from CLS:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
// If CLS is configured, correlationId is automatically included
|
|
590
|
+
this.logger.info('Message');
|
|
591
|
+
// Log includes: { correlationId: '...' } (from CLS)
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Manual Setting (Non-HTTP Contexts)
|
|
595
|
+
|
|
596
|
+
For non-HTTP contexts (Kafka, Bull, CRON, CLI), set correlation ID manually:
|
|
597
|
+
|
|
598
|
+
```typescript
|
|
599
|
+
import { ContextStorageService } from '@infineit/winston-logger';
|
|
600
|
+
|
|
601
|
+
constructor(
|
|
602
|
+
private readonly logger: LoggerService,
|
|
603
|
+
private readonly contextStorage: ContextStorageService,
|
|
604
|
+
) {}
|
|
605
|
+
|
|
606
|
+
async processMessage(message: Message) {
|
|
607
|
+
// Set correlation ID from message
|
|
608
|
+
this.contextStorage.setContextId(message.correlationId);
|
|
609
|
+
|
|
610
|
+
this.logger.info('Processing message');
|
|
611
|
+
// Log includes: { correlationId: message.correlationId }
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Optional Correlation ID
|
|
616
|
+
|
|
617
|
+
Correlation ID is **optional** - `undefined` is valid:
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
// If no correlation ID is set, logs will have:
|
|
621
|
+
// { correlationId: undefined }
|
|
622
|
+
// This is perfectly valid and expected in some contexts
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
### HTTP Context Setup
|
|
626
|
+
|
|
627
|
+
For HTTP services, configure CLS middleware:
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
import { ClsModule } from 'nestjs-cls';
|
|
631
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
632
|
+
|
|
633
|
+
@Module({
|
|
634
|
+
imports: [
|
|
635
|
+
ClsModule.forRoot({
|
|
636
|
+
middleware: {
|
|
637
|
+
mount: true,
|
|
638
|
+
generateId: true,
|
|
639
|
+
idGenerator: (req: Request) => {
|
|
640
|
+
// Use existing correlation ID from header, or generate new one
|
|
641
|
+
return req.headers['x-correlation-id'] as string || uuidv4();
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
}),
|
|
645
|
+
ContextModule, // Provides CLS service
|
|
646
|
+
LoggerModule.forRoot(),
|
|
647
|
+
],
|
|
648
|
+
})
|
|
649
|
+
export class AppModule {}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## API Reference
|
|
655
|
+
|
|
656
|
+
### LoggerService
|
|
657
|
+
|
|
658
|
+
Main logging service injected into your classes.
|
|
659
|
+
|
|
660
|
+
#### Methods
|
|
661
|
+
|
|
662
|
+
```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
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
#### LogLevel
|
|
96
679
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- `LOGGER_SLACK_INC_WEBHOOK_URL`: Slack url, eg. `https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX`.
|
|
680
|
+
```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
|
+
}
|
|
689
|
+
```
|
|
108
690
|
|
|
109
|
-
|
|
691
|
+
#### LogData
|
|
110
692
|
|
|
111
|
-
|
|
693
|
+
```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
|
+
}
|
|
704
|
+
```
|
|
112
705
|
|
|
113
|
-
###
|
|
706
|
+
### ContextStorageService
|
|
114
707
|
|
|
115
|
-
|
|
708
|
+
Service for managing CLS context (correlation ID).
|
|
116
709
|
|
|
117
|
-
|
|
710
|
+
#### Methods
|
|
118
711
|
|
|
119
|
-
|
|
712
|
+
```typescript
|
|
713
|
+
// Get correlation ID from CLS
|
|
714
|
+
getContextId(): string | undefined
|
|
120
715
|
|
|
121
|
-
|
|
716
|
+
// Set correlation ID in CLS
|
|
717
|
+
setContextId(id: string): void
|
|
122
718
|
|
|
123
|
-
|
|
719
|
+
// Get any value from CLS
|
|
720
|
+
get<T>(key: string): T | undefined
|
|
124
721
|
|
|
125
|
-
|
|
722
|
+
// Set any value in CLS
|
|
723
|
+
set<T>(key: string, value: T): void
|
|
724
|
+
```
|
|
126
725
|
|
|
127
|
-
|
|
726
|
+
---
|
|
128
727
|
|
|
129
|
-
|
|
728
|
+
## Migration Guide (Legacy app.useLogger Users)
|
|
130
729
|
|
|
131
|
-
|
|
730
|
+
### ⚠️ Deprecated: app.useLogger() and app.flushLogs()
|
|
132
731
|
|
|
133
|
-
|
|
134
|
-
model winstonlog {
|
|
135
|
-
id_log String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
|
136
|
-
level String @db.VarChar(80)
|
|
137
|
-
message String @db.Text
|
|
138
|
-
context String? @db.VarChar(255)
|
|
139
|
-
correlationId String? @db.Uuid
|
|
140
|
-
sourceClass String? @db.VarChar(255)
|
|
141
|
-
props Json?
|
|
142
|
-
organization String? @db.VarChar(40)
|
|
143
|
-
app String? @db.VarChar(40)
|
|
144
|
-
durationMs Decimal? @default(0) @db.Decimal(10, 4)
|
|
145
|
-
stack String? @db.Text
|
|
146
|
-
label String? @db.VarChar(40)
|
|
147
|
-
timestamp DateTime @default(now()) @db.Timestamptz(6)
|
|
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.
|
|
148
733
|
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
## Inspiration
|
|
734
|
+
**Status**: These methods have been **removed** and are **no longer available**.
|
|
153
735
|
|
|
154
|
-
|
|
736
|
+
### Why Was It Removed?
|
|
155
737
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
738
|
+
The `app.useLogger()` and `app.flushLogs()` methods were removed for the following architectural reasons:
|
|
739
|
+
|
|
740
|
+
1. **HTTP Assumption Violation**: These methods assumed HTTP runtime, making the logger unusable in Kafka consumers, Bull workers, CRON jobs, and CLI tasks.
|
|
741
|
+
|
|
742
|
+
2. **Middleware Auto-Mounting**: The old pattern auto-mounted middleware, violating the library's core principle that applications must configure middleware themselves.
|
|
743
|
+
|
|
744
|
+
3. **Transport Architecture**: The new architecture uses a pluggable `LoggerTransport[]` system where Winston is one transport among many, not the only transport.
|
|
745
|
+
|
|
746
|
+
4. **Fire-and-Forget Guarantee**: The new architecture ensures all logging is truly fire-and-forget without requiring explicit `flushLogs()` calls.
|
|
747
|
+
|
|
748
|
+
### Old Deprecated Usage ❌
|
|
749
|
+
|
|
750
|
+
```typescript
|
|
751
|
+
// ❌ DEPRECATED - This no longer works
|
|
752
|
+
import { NestFactory } from '@nestjs/core';
|
|
753
|
+
import { LoggerModule } from '@infineit/winston-logger';
|
|
754
|
+
|
|
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();
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
bootstrap();
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
### New Correct Usage ✅
|
|
771
|
+
|
|
772
|
+
```typescript
|
|
773
|
+
// ✅ CORRECT: Use LoggerService injection
|
|
774
|
+
import { NestFactory } from '@nestjs/core';
|
|
775
|
+
import { ContextModule, LoggerModule } from '@infineit/winston-logger';
|
|
776
|
+
|
|
777
|
+
@Module({
|
|
778
|
+
imports: [
|
|
779
|
+
ContextModule,
|
|
780
|
+
LoggerModule.forRoot(),
|
|
781
|
+
],
|
|
782
|
+
})
|
|
783
|
+
export class AppModule {}
|
|
784
|
+
|
|
785
|
+
async function bootstrap() {
|
|
786
|
+
const app = await NestFactory.create(AppModule);
|
|
787
|
+
|
|
788
|
+
// ✅ No app.useLogger() needed - LoggerService is automatically available
|
|
789
|
+
// ✅ No app.flushLogs() needed - logging is fire-and-forget
|
|
790
|
+
|
|
791
|
+
await app.listen(3000);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
bootstrap();
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### Using LoggerService in Your Code
|
|
798
|
+
|
|
799
|
+
```typescript
|
|
800
|
+
// ✅ CORRECT: Inject LoggerService
|
|
801
|
+
import { Injectable } from '@nestjs/common';
|
|
802
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
803
|
+
|
|
804
|
+
@Injectable()
|
|
805
|
+
export class MyService {
|
|
806
|
+
constructor(private readonly logger: LoggerService) {}
|
|
807
|
+
|
|
808
|
+
doSomething() {
|
|
809
|
+
// ✅ Logging is automatic and fire-and-forget
|
|
810
|
+
this.logger.info('Processing started');
|
|
811
|
+
|
|
812
|
+
// ✅ No need to flush - logs are sent immediately
|
|
813
|
+
// ✅ No need to await - logging is non-blocking
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### Comparison Table
|
|
819
|
+
|
|
820
|
+
| Feature | Old (Deprecated) ❌ | New (Current) ✅ |
|
|
821
|
+
|---------|---------------------|------------------|
|
|
822
|
+
| **Setup** | `app.useLogger(...)` | `LoggerModule.forRoot()` in imports |
|
|
823
|
+
| **Flush** | `app.flushLogs()` required | Not needed (fire-and-forget) |
|
|
824
|
+
| **Usage** | Direct logger access | Inject `LoggerService` |
|
|
825
|
+
| **HTTP Only** | Yes (assumed HTTP runtime) | No (works in all contexts) |
|
|
826
|
+
| **Middleware** | Auto-mounted | Manual configuration |
|
|
827
|
+
| **Transports** | Winston only | Pluggable `LoggerTransport[]` |
|
|
828
|
+
| **Blocking** | Could block with `flushLogs()` | Always non-blocking |
|
|
829
|
+
| **Context Support** | HTTP only | HTTP, Kafka, Bull, CRON, CLI |
|
|
830
|
+
|
|
831
|
+
### Key Changes Summary
|
|
832
|
+
|
|
833
|
+
#### ❌ What's Removed
|
|
834
|
+
|
|
835
|
+
- `app.useLogger()` - **No longer exists**
|
|
836
|
+
- `app.flushLogs()` - **No longer exists**
|
|
837
|
+
- HTTP-only assumptions - **Removed**
|
|
838
|
+
- Auto-mounted middleware - **Removed**
|
|
839
|
+
|
|
840
|
+
#### ✅ What's New
|
|
841
|
+
|
|
842
|
+
- **LoggerService injection** - Inject `LoggerService` into your classes
|
|
843
|
+
- **Multi-context support** - Works in HTTP, Kafka, Bull, CRON, CLI
|
|
844
|
+
- **Transport architecture** - Pluggable transports via `LoggerTransport[]`
|
|
845
|
+
- **Automatic normalization** - Logs are normalized before transport
|
|
846
|
+
- **CLS integration** - Correlation ID support via AsyncLocalStorage
|
|
847
|
+
|
|
848
|
+
### Migration Steps
|
|
849
|
+
|
|
850
|
+
1. **Remove `app.useLogger()` calls**
|
|
851
|
+
```typescript
|
|
852
|
+
// ❌ Remove this
|
|
853
|
+
app.useLogger(app.get(LoggerModule));
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
2. **Remove `app.flushLogs()` calls**
|
|
857
|
+
```typescript
|
|
858
|
+
// ❌ Remove this
|
|
859
|
+
app.flushLogs();
|
|
860
|
+
```
|
|
861
|
+
|
|
862
|
+
3. **Add LoggerModule to imports**
|
|
863
|
+
```typescript
|
|
864
|
+
@Module({
|
|
865
|
+
imports: [
|
|
866
|
+
ContextModule, // Required for CLS
|
|
867
|
+
LoggerModule.forRoot(), // Add this
|
|
868
|
+
],
|
|
869
|
+
})
|
|
870
|
+
export class AppModule {}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
4. **Inject LoggerService instead of direct logger access**
|
|
874
|
+
```typescript
|
|
875
|
+
// ❌ Old way (if you had direct logger access)
|
|
876
|
+
// logger.info('message');
|
|
877
|
+
|
|
878
|
+
// ✅ New way
|
|
879
|
+
constructor(private readonly logger: LoggerService) {}
|
|
880
|
+
this.logger.info('message');
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
5. **Configure CLS middleware (for HTTP services)**
|
|
884
|
+
```typescript
|
|
885
|
+
// ✅ Add CLS middleware configuration
|
|
886
|
+
ClsModule.forRoot({
|
|
887
|
+
middleware: {
|
|
888
|
+
mount: true,
|
|
889
|
+
generateId: true,
|
|
890
|
+
},
|
|
891
|
+
}),
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
### Reassurance: Nothing Breaks
|
|
895
|
+
|
|
896
|
+
**Good news**: Your existing logging calls will continue to work! The only changes needed are:
|
|
897
|
+
|
|
898
|
+
1. ✅ Remove `app.useLogger()` and `app.flushLogs()` calls
|
|
899
|
+
2. ✅ Add `LoggerModule.forRoot()` to your module imports
|
|
900
|
+
3. ✅ Inject `LoggerService` where you need logging
|
|
901
|
+
|
|
902
|
+
**Logging behavior**:
|
|
903
|
+
- ✅ Still fire-and-forget (no `flushLogs()` needed)
|
|
904
|
+
- ✅ Still non-blocking
|
|
905
|
+
- ✅ Still failure-safe (errors are swallowed)
|
|
906
|
+
- ✅ Still works in all contexts (HTTP, Kafka, Bull, etc.)
|
|
907
|
+
|
|
908
|
+
**No breaking changes to logging API**:
|
|
909
|
+
- ✅ Same log levels: `debug`, `info`, `warn`, `error`, `fatal`, `emergency`
|
|
910
|
+
- ✅ Same method signatures
|
|
911
|
+
- ✅ Same `LogData` structure
|
|
912
|
+
- ✅ Same error handling
|
|
913
|
+
|
|
914
|
+
### Example: Complete Migration
|
|
915
|
+
|
|
916
|
+
**Before (Deprecated)**:
|
|
917
|
+
```typescript
|
|
918
|
+
import { NestFactory } from '@nestjs/core';
|
|
919
|
+
import { LoggerModule } from '@infineit/winston-logger';
|
|
920
|
+
|
|
921
|
+
@Module({
|
|
922
|
+
imports: [LoggerModule],
|
|
923
|
+
})
|
|
924
|
+
export class AppModule {}
|
|
925
|
+
|
|
926
|
+
async function bootstrap() {
|
|
927
|
+
const app = await NestFactory.create(AppModule);
|
|
928
|
+
app.useLogger(app.get(LoggerModule)); // ❌ Remove
|
|
929
|
+
await app.listen(3000);
|
|
930
|
+
app.flushLogs(); // ❌ Remove
|
|
931
|
+
}
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
**After (Current)**:
|
|
935
|
+
```typescript
|
|
936
|
+
import { NestFactory } from '@nestjs/core';
|
|
937
|
+
import { ClsModule } from 'nestjs-cls';
|
|
938
|
+
import { ContextModule, LoggerModule } from '@infineit/winston-logger';
|
|
939
|
+
|
|
940
|
+
@Module({
|
|
941
|
+
imports: [
|
|
942
|
+
ClsModule.forRoot({
|
|
943
|
+
middleware: { mount: true, generateId: true },
|
|
944
|
+
}),
|
|
945
|
+
ContextModule, // ✅ Add
|
|
946
|
+
LoggerModule.forRoot(), // ✅ Add
|
|
947
|
+
],
|
|
948
|
+
})
|
|
949
|
+
export class AppModule {}
|
|
950
|
+
|
|
951
|
+
async function bootstrap() {
|
|
952
|
+
const app = await NestFactory.create(AppModule);
|
|
953
|
+
// ✅ No app.useLogger() needed
|
|
954
|
+
await app.listen(3000);
|
|
955
|
+
// ✅ No app.flushLogs() needed
|
|
956
|
+
}
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
### Need Help?
|
|
960
|
+
|
|
961
|
+
If you encounter issues during migration:
|
|
962
|
+
|
|
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
|
|
967
|
+
|
|
968
|
+
---
|
|
969
|
+
|
|
970
|
+
## Best Practices
|
|
971
|
+
|
|
972
|
+
### 1. Fire-and-Forget Logging
|
|
973
|
+
|
|
974
|
+
✅ **DO**: Log without awaiting
|
|
975
|
+
```typescript
|
|
976
|
+
this.logger.info('Processing started');
|
|
977
|
+
// Continue with business logic immediately
|
|
978
|
+
```
|
|
979
|
+
|
|
980
|
+
❌ **DON'T**: Try to await logging (methods return `void`)
|
|
981
|
+
```typescript
|
|
982
|
+
// This won't work - logger methods return void
|
|
983
|
+
await this.logger.info('Message'); // ❌
|
|
984
|
+
```
|
|
985
|
+
|
|
986
|
+
### 2. Error Handling
|
|
987
|
+
|
|
988
|
+
✅ **DO**: Pass Error objects directly
|
|
989
|
+
```typescript
|
|
990
|
+
try {
|
|
991
|
+
// ...
|
|
992
|
+
} catch (error) {
|
|
993
|
+
this.logger.error(error, { userId: 123 });
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
✅ **DO**: Include context in error logs
|
|
998
|
+
```typescript
|
|
999
|
+
this.logger.error(error, {
|
|
1000
|
+
userId: 123,
|
|
1001
|
+
orderId: 456,
|
|
1002
|
+
action: 'process-payment',
|
|
1003
|
+
});
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
### 3. Structured Logging
|
|
1007
|
+
|
|
1008
|
+
✅ **DO**: Use `props` for structured data
|
|
1009
|
+
```typescript
|
|
1010
|
+
this.logger.info('User logged in', {
|
|
1011
|
+
props: {
|
|
1012
|
+
userId: 123,
|
|
1013
|
+
email: 'user@example.com',
|
|
1014
|
+
ipAddress: '192.168.1.1',
|
|
1015
|
+
},
|
|
1016
|
+
});
|
|
1017
|
+
```
|
|
1018
|
+
|
|
1019
|
+
### 4. Correlation ID
|
|
1020
|
+
|
|
1021
|
+
✅ **DO**: Set correlation ID in non-HTTP contexts
|
|
1022
|
+
```typescript
|
|
1023
|
+
// Kafka consumer
|
|
1024
|
+
this.contextStorage.setContextId(message.correlationId);
|
|
1025
|
+
|
|
1026
|
+
// Bull worker
|
|
1027
|
+
this.contextStorage.setContextId(job.id.toString());
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
✅ **DO**: Use correlation ID for request tracing
|
|
1031
|
+
```typescript
|
|
1032
|
+
// All logs in the same request/job will have the same correlationId
|
|
1033
|
+
this.logger.info('Step 1');
|
|
1034
|
+
this.logger.info('Step 2');
|
|
1035
|
+
// Both logs have the same correlationId
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### 5. Performance Logging
|
|
1039
|
+
|
|
1040
|
+
✅ **DO**: Include duration for performance monitoring
|
|
1041
|
+
```typescript
|
|
1042
|
+
const start = Date.now();
|
|
1043
|
+
await this.processData();
|
|
1044
|
+
this.logger.info('Processing completed', {
|
|
1045
|
+
durationMs: Date.now() - start,
|
|
1046
|
+
});
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
---
|
|
1050
|
+
|
|
1051
|
+
## Forbidden Actions
|
|
1052
|
+
|
|
1053
|
+
### ❌ DO NOT: Add Database Dependencies
|
|
1054
|
+
|
|
1055
|
+
```typescript
|
|
1056
|
+
// ❌ FORBIDDEN: Do not add Prisma, TypeORM, Mongoose, etc.
|
|
1057
|
+
import { PrismaClient } from '@prisma/client';
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
**Why**: The logger must work in all contexts without database assumptions.
|
|
1061
|
+
|
|
1062
|
+
### ❌ DO NOT: Access HTTP Objects
|
|
1063
|
+
|
|
1064
|
+
```typescript
|
|
1065
|
+
// ❌ FORBIDDEN: Do not inject HttpAdapterHost, Request, Response
|
|
1066
|
+
constructor(
|
|
1067
|
+
private readonly httpAdapter: HttpAdapterHost, // ❌
|
|
1068
|
+
) {}
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
**Why**: The logger must work in non-HTTP contexts (Kafka, Bull, CRON, CLI).
|
|
1072
|
+
|
|
1073
|
+
### ❌ DO NOT: Auto-Mount Middleware
|
|
1074
|
+
|
|
1075
|
+
```typescript
|
|
1076
|
+
// ❌ FORBIDDEN: Do not implement NestModule or configure middleware
|
|
1077
|
+
export class LoggerModule implements NestModule { // ❌
|
|
1078
|
+
configure(consumer: MiddlewareConsumer) { // ❌
|
|
1079
|
+
consumer.apply(...).forRoutes('*');
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
```
|
|
1083
|
+
|
|
1084
|
+
**Why**: Applications must configure middleware themselves for flexibility.
|
|
1085
|
+
|
|
1086
|
+
### ❌ DO NOT: Generate Correlation ID
|
|
1087
|
+
|
|
1088
|
+
```typescript
|
|
1089
|
+
// ❌ FORBIDDEN: Do not generate correlation ID in logger
|
|
1090
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
1091
|
+
const correlationId = uuidv4(); // ❌
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
**Why**: Correlation ID must come from CLS or be set by the application.
|
|
1095
|
+
|
|
1096
|
+
### ❌ DO NOT: Throw Errors
|
|
1097
|
+
|
|
1098
|
+
```typescript
|
|
1099
|
+
// ❌ FORBIDDEN: Do not throw errors in logging code
|
|
1100
|
+
if (!transport) {
|
|
1101
|
+
throw new Error('Transport not found'); // ❌
|
|
1102
|
+
}
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
**Why**: Logging failures must never break business logic. All errors are swallowed.
|
|
1106
|
+
|
|
1107
|
+
### ❌ DO NOT: Use Async/Await in Logging
|
|
1108
|
+
|
|
1109
|
+
```typescript
|
|
1110
|
+
// ❌ FORBIDDEN: Do not use async/await in logging
|
|
1111
|
+
async log(...) { // ❌
|
|
1112
|
+
await this.transport.send(...);
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
**Why**: Logging must be fire-and-forget and non-blocking.
|
|
1117
|
+
|
|
1118
|
+
---
|
|
1119
|
+
|
|
1120
|
+
## Examples
|
|
1121
|
+
|
|
1122
|
+
### Complete HTTP Service Example
|
|
1123
|
+
|
|
1124
|
+
```typescript
|
|
1125
|
+
import { Module } from '@nestjs/common';
|
|
1126
|
+
import { ConfigModule } from '@nestjs/config';
|
|
1127
|
+
import { ClsModule } from 'nestjs-cls';
|
|
1128
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
1129
|
+
import { ContextModule, LoggerModule } from '@infineit/winston-logger';
|
|
1130
|
+
|
|
1131
|
+
@Module({
|
|
1132
|
+
imports: [
|
|
1133
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
1134
|
+
ClsModule.forRoot({
|
|
1135
|
+
middleware: {
|
|
1136
|
+
mount: true,
|
|
1137
|
+
generateId: true,
|
|
1138
|
+
idGenerator: (req: Request) =>
|
|
1139
|
+
req.headers['x-correlation-id'] as string || uuidv4(),
|
|
1140
|
+
},
|
|
1141
|
+
}),
|
|
1142
|
+
ContextModule,
|
|
1143
|
+
LoggerModule.forRoot({
|
|
1144
|
+
nodeEnv: process.env.NODE_ENV,
|
|
1145
|
+
organization: 'my-org',
|
|
1146
|
+
context: 'user-service',
|
|
1147
|
+
app: 'api',
|
|
1148
|
+
}),
|
|
1149
|
+
],
|
|
1150
|
+
})
|
|
1151
|
+
export class AppModule {}
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
```typescript
|
|
1155
|
+
import { Injectable } from '@nestjs/common';
|
|
1156
|
+
import { LoggerService } from '@infineit/winston-logger';
|
|
1157
|
+
|
|
1158
|
+
@Injectable()
|
|
1159
|
+
export class UserService {
|
|
1160
|
+
constructor(private readonly logger: LoggerService) {}
|
|
1161
|
+
|
|
1162
|
+
async findById(id: string) {
|
|
1163
|
+
this.logger.info('Fetching user', { userId: id });
|
|
1164
|
+
|
|
1165
|
+
try {
|
|
1166
|
+
const user = await this.repository.findById(id);
|
|
1167
|
+
this.logger.info('User found', { userId: id });
|
|
1168
|
+
return user;
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
this.logger.error(error, { userId: id });
|
|
1171
|
+
throw error;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
### Complete Kafka Consumer Example
|
|
1178
|
+
|
|
1179
|
+
```typescript
|
|
1180
|
+
import { Injectable } from '@nestjs/common';
|
|
1181
|
+
import { KafkaListener } from '@nestjs/microservices';
|
|
1182
|
+
import { LoggerService, ContextStorageService } from '@infineit/winston-logger';
|
|
1183
|
+
|
|
1184
|
+
@Injectable()
|
|
1185
|
+
export class OrderConsumer {
|
|
1186
|
+
constructor(
|
|
1187
|
+
private readonly logger: LoggerService,
|
|
1188
|
+
private readonly contextStorage: ContextStorageService,
|
|
1189
|
+
) {}
|
|
1190
|
+
|
|
1191
|
+
@KafkaListener('order.created')
|
|
1192
|
+
async handleOrderCreated(message: OrderCreatedEvent) {
|
|
1193
|
+
// Set correlation ID from message
|
|
1194
|
+
this.contextStorage.setContextId(message.correlationId || message.id);
|
|
1195
|
+
|
|
1196
|
+
const start = Date.now();
|
|
1197
|
+
this.logger.info('Processing order', {
|
|
1198
|
+
orderId: message.id,
|
|
1199
|
+
userId: message.userId,
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
try {
|
|
1203
|
+
await this.processOrder(message);
|
|
1204
|
+
|
|
1205
|
+
this.logger.info('Order processed', {
|
|
1206
|
+
orderId: message.id,
|
|
1207
|
+
durationMs: Date.now() - start,
|
|
1208
|
+
});
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
this.logger.error(error, {
|
|
1211
|
+
orderId: message.id,
|
|
1212
|
+
durationMs: Date.now() - start,
|
|
1213
|
+
});
|
|
1214
|
+
throw error;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
### Custom Transport Example
|
|
1221
|
+
|
|
1222
|
+
```typescript
|
|
1223
|
+
import { Injectable } from '@nestjs/common';
|
|
1224
|
+
import { LoggerTransport, NormalizedLog } from '@infineit/winston-logger';
|
|
1225
|
+
import { KafkaProducer } from './kafka-producer';
|
|
1226
|
+
|
|
1227
|
+
@Injectable()
|
|
1228
|
+
export class KafkaLogTransport implements LoggerTransport {
|
|
1229
|
+
constructor(private readonly kafkaProducer: KafkaProducer) {}
|
|
1230
|
+
|
|
1231
|
+
log(normalizedLog: NormalizedLog): void {
|
|
1232
|
+
try {
|
|
1233
|
+
// Fire-and-forget: send to Kafka without awaiting
|
|
1234
|
+
this.kafkaProducer.send({
|
|
1235
|
+
topic: 'application-logs',
|
|
1236
|
+
messages: [{
|
|
1237
|
+
key: normalizedLog.correlationId || 'no-correlation-id',
|
|
1238
|
+
value: JSON.stringify(normalizedLog),
|
|
1239
|
+
timestamp: normalizedLog.timestamp.toString(),
|
|
1240
|
+
}],
|
|
1241
|
+
});
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
// Swallow errors - never break business logic
|
|
1244
|
+
console.error('KafkaLogTransport error (swallowed):', error);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
```typescript
|
|
1251
|
+
import { Provider } from '@nestjs/common';
|
|
1252
|
+
import { LoggerModule } from '@infineit/winston-logger';
|
|
1253
|
+
import { KafkaLogTransport } from './kafka-log-transport';
|
|
1254
|
+
|
|
1255
|
+
const customTransports: Provider[] = [
|
|
1256
|
+
{
|
|
1257
|
+
provide: KafkaLogTransport,
|
|
1258
|
+
useClass: KafkaLogTransport,
|
|
1259
|
+
},
|
|
1260
|
+
];
|
|
1261
|
+
|
|
1262
|
+
@Module({
|
|
1263
|
+
imports: [
|
|
1264
|
+
LoggerModule.forRoot(config, customTransports),
|
|
1265
|
+
],
|
|
1266
|
+
})
|
|
1267
|
+
export class AppModule {}
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
---
|
|
159
1271
|
|
|
160
1272
|
## License
|
|
161
1273
|
|
|
162
|
-
|
|
1274
|
+
MIT
|
|
163
1275
|
|
|
164
1276
|
## Author
|
|
165
1277
|
|
|
166
|
-
Dharmesh Patel
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
1278
|
+
Dharmesh Patel
|
|
1279
|
+
|
|
1280
|
+
## Repository
|
|
1281
|
+
|
|
1282
|
+
https://github.com/dharmesh-r-patel/nestjs-logger
|
|
1283
|
+
|