@globalart/nestjs-logger 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -0
- package/dist/constants/index.d.ts +22 -0
- package/dist/constants/index.js +38 -0
- package/dist/contracts/index.d.ts +26 -0
- package/dist/contracts/index.js +2 -0
- package/dist/core/http-logger.interceptor.d.ts +16 -0
- package/dist/core/http-logger.interceptor.js +97 -0
- package/dist/core/logger.module.d.ts +20 -0
- package/dist/core/logger.module.js +103 -0
- package/dist/core/logger.service.d.ts +19 -0
- package/dist/core/logger.service.js +60 -0
- package/dist/decorators/index.d.ts +8 -0
- package/dist/decorators/index.js +15 -0
- package/dist/factories/formatter.factory.d.ts +5 -0
- package/dist/factories/formatter.factory.js +31 -0
- package/dist/formatters/base-formatter.d.ts +12 -0
- package/dist/formatters/base-formatter.js +44 -0
- package/dist/formatters/json-formatter.d.ts +6 -0
- package/dist/formatters/json-formatter.js +36 -0
- package/dist/formatters/pino-formatter.d.ts +6 -0
- package/dist/formatters/pino-formatter.js +28 -0
- package/dist/formatters/text-formatter.d.ts +6 -0
- package/dist/formatters/text-formatter.js +45 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +31 -0
- package/dist/interceptors/index.d.ts +1 -0
- package/dist/interceptors/index.js +5 -0
- package/dist/interfaces/index.d.ts +59 -0
- package/dist/interfaces/index.js +2 -0
- package/dist/types/index.d.ts +50 -0
- package/dist/types/index.js +18 -0
- package/dist/utils/context-resolver.d.ts +7 -0
- package/dist/utils/context-resolver.js +68 -0
- package/dist/utils/data-sanitizer.d.ts +9 -0
- package/dist/utils/data-sanitizer.js +56 -0
- package/dist/utils/request-id-generator.d.ts +8 -0
- package/dist/utils/request-id-generator.js +34 -0
- package/dist/writers/console-writer.d.ts +4 -0
- package/dist/writers/console-writer.js +19 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# @globalart/nestjs-logger
|
|
2
|
+
|
|
3
|
+
Профессиональный модуль логирования для NestJS с чистой архитектурой, строгой типизацией и расширяемостью.
|
|
4
|
+
|
|
5
|
+
## 🚀 Особенности
|
|
6
|
+
|
|
7
|
+
- **Clean Architecture** - разделение ответственности, SOLID принципы
|
|
8
|
+
- **Строгая типизация** - полная типобезопасность TypeScript
|
|
9
|
+
- **Производительность** - оптимизированная архитектура без лишних аллокаций
|
|
10
|
+
- **Расширяемость** - легко добавлять новые форматтеры и транспорты
|
|
11
|
+
- **Тестируемость** - dependency injection, легкое мокирование
|
|
12
|
+
- **Автоматический контекст** - определение класса из стека вызовов
|
|
13
|
+
- **HTTP логирование** - детальное логирование запросов
|
|
14
|
+
- **Безопасность** - автоматическая санитизация чувствительных данных
|
|
15
|
+
- **Цветной вывод** - красивые логи в консоли
|
|
16
|
+
|
|
17
|
+
## 📦 Установка
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @globalart/nestjs-logger
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 🎯 Быстрый старт
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { Module } from '@nestjs/common';
|
|
27
|
+
import { LoggerModule } from '@globalart/nestjs-logger';
|
|
28
|
+
|
|
29
|
+
@Module({
|
|
30
|
+
imports: [
|
|
31
|
+
LoggerModule.forRoot({
|
|
32
|
+
level: 'info',
|
|
33
|
+
timestamp: true,
|
|
34
|
+
colors: true,
|
|
35
|
+
format: 'text',
|
|
36
|
+
}),
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
export class AppModule {}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 🔧 Конфигурация
|
|
43
|
+
|
|
44
|
+
### Синхронная конфигурация
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
LoggerModule.forRoot({
|
|
48
|
+
level: 'debug',
|
|
49
|
+
timestamp: true,
|
|
50
|
+
colors: true,
|
|
51
|
+
format: 'pino',
|
|
52
|
+
sensitiveFields: ['password', 'secret'],
|
|
53
|
+
})
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Асинхронная конфигурация
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
LoggerModule.forRootAsync({
|
|
60
|
+
useFactory: (configService: ConfigService) => ({
|
|
61
|
+
level: configService.get('LOG_LEVEL', 'info'),
|
|
62
|
+
format: configService.get('LOG_FORMAT', 'text'),
|
|
63
|
+
colors: !configService.get('PRODUCTION'),
|
|
64
|
+
}),
|
|
65
|
+
inject: [ConfigService],
|
|
66
|
+
})
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## 📝 Использование
|
|
70
|
+
|
|
71
|
+
### В сервисах
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { Injectable } from '@nestjs/common';
|
|
75
|
+
import { LoggerService } from '@globalart/nestjs-logger';
|
|
76
|
+
|
|
77
|
+
@Injectable()
|
|
78
|
+
export class UserService {
|
|
79
|
+
constructor(private readonly logger: LoggerService) {}
|
|
80
|
+
|
|
81
|
+
async createUser(userData: CreateUserDto) {
|
|
82
|
+
this.logger.log('Creating new user', undefined, { userId: userData.email });
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const user = await this.userRepository.save(userData);
|
|
86
|
+
this.logger.log('User created successfully', undefined, { id: user.id });
|
|
87
|
+
return user;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
this.logger.error('Failed to create user', error.stack, undefined, {
|
|
90
|
+
email: userData.email
|
|
91
|
+
});
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### HTTP логирование
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { Controller, UseInterceptors } from '@nestjs/common';
|
|
102
|
+
import { HttpLoggerInterceptor, LogContext } from '@globalart/nestjs-logger';
|
|
103
|
+
|
|
104
|
+
@Controller('users')
|
|
105
|
+
@UseInterceptors(HttpLoggerInterceptor)
|
|
106
|
+
@LogContext('UserController')
|
|
107
|
+
export class UserController {
|
|
108
|
+
// Все HTTP запросы будут автоматически логироваться
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Глобальное HTTP логирование
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { Module } from '@nestjs/common';
|
|
116
|
+
import { APP_INTERCEPTOR } from '@nestjs/core';
|
|
117
|
+
import { LoggerModule, HttpLoggerInterceptor } from '@globalart/nestjs-logger';
|
|
118
|
+
|
|
119
|
+
@Module({
|
|
120
|
+
imports: [LoggerModule.forRoot()],
|
|
121
|
+
providers: [
|
|
122
|
+
{
|
|
123
|
+
provide: APP_INTERCEPTOR,
|
|
124
|
+
useClass: HttpLoggerInterceptor,
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
})
|
|
128
|
+
export class AppModule {}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 🎨 Форматы вывода
|
|
132
|
+
|
|
133
|
+
### Text (по умолчанию)
|
|
134
|
+
```
|
|
135
|
+
[2024-01-15T10:30:45.123Z] [INFO] [UserService] Creating new user {"userId":"123"}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### JSON
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"timestamp": "2024-01-15T10:30:45.123Z",
|
|
142
|
+
"level": "info",
|
|
143
|
+
"message": "Creating new user",
|
|
144
|
+
"context": "UserService",
|
|
145
|
+
"metadata": {"userId": "123"}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Pino (HTTP запросы)
|
|
150
|
+
```json
|
|
151
|
+
{"level":30,"time":1642247445123,"pid":1234,"hostname":"app-server","req":{"id":"req-123","method":"GET","url":"/users","query":{},"params":{},"headers":{},"remoteAddress":"127.0.0.1"},"res":{"statusCode":200,"headers":{}},"responseTime":15,"msg":"request completed"}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 🎯 API Reference
|
|
155
|
+
|
|
156
|
+
### LoggerService
|
|
157
|
+
|
|
158
|
+
| Метод | Описание |
|
|
159
|
+
|-------|----------|
|
|
160
|
+
| `log(message, context?, metadata?)` | Информационное сообщение |
|
|
161
|
+
| `error(message, trace?, context?, metadata?)` | Ошибка с трейсом |
|
|
162
|
+
| `warn(message, context?, metadata?)` | Предупреждение |
|
|
163
|
+
| `debug(message, context?, metadata?)` | Отладочная информация |
|
|
164
|
+
| `verbose(message, context?, metadata?)` | Подробное логирование |
|
|
165
|
+
| `setContext(context)` | Установка контекста |
|
|
166
|
+
|
|
167
|
+
### Декораторы
|
|
168
|
+
|
|
169
|
+
- `@LogContext(context)` - Установка контекста для класса/метода
|
|
170
|
+
- `@LogMetadata(metadata)` - Добавление метаданных
|
|
171
|
+
|
|
172
|
+
### Конфигурация
|
|
173
|
+
|
|
174
|
+
| Опция | Тип | По умолчанию | Описание |
|
|
175
|
+
|-------|-----|--------------|----------|
|
|
176
|
+
| `level` | `LogLevel` | `'info'` | Уровень логирования |
|
|
177
|
+
| `timestamp` | `boolean` | `true` | Показывать время |
|
|
178
|
+
| `colors` | `boolean` | `true` | Цветной вывод |
|
|
179
|
+
| `format` | `LogFormat` | `'text'` | Формат вывода |
|
|
180
|
+
| `context` | `string` | `undefined` | Контекст по умолчанию |
|
|
181
|
+
| `sensitiveFields` | `string[]` | `[...]` | Поля для санитизации |
|
|
182
|
+
|
|
183
|
+
## 🏗️ Архитектура
|
|
184
|
+
|
|
185
|
+
Пакет построен на принципах Clean Architecture:
|
|
186
|
+
|
|
187
|
+
- **Types** - строгая типизация
|
|
188
|
+
- **Contracts** - интерфейсы для расширяемости
|
|
189
|
+
- **Core** - основная бизнес-логика
|
|
190
|
+
- **Utils** - вспомогательные утилиты
|
|
191
|
+
- **Formatters** - форматирование логов
|
|
192
|
+
- **Writers** - вывод логов
|
|
193
|
+
|
|
194
|
+
## 🔒 Безопасность
|
|
195
|
+
|
|
196
|
+
Автоматическая санитизация чувствительных полей:
|
|
197
|
+
- `password`, `pass`
|
|
198
|
+
- `token`, `accessToken`, `refreshToken`
|
|
199
|
+
- `secret`, `key`, `apiKey`
|
|
200
|
+
- `authorization`, `auth`
|
|
201
|
+
- `credential`, `credentials`
|
|
202
|
+
|
|
203
|
+
## 🧪 Тестирование
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { LoggerService } from '@globalart/nestjs-logger';
|
|
207
|
+
|
|
208
|
+
describe('UserService', () => {
|
|
209
|
+
let service: UserService;
|
|
210
|
+
let logger: jest.Mocked<LoggerService>;
|
|
211
|
+
|
|
212
|
+
beforeEach(() => {
|
|
213
|
+
logger = {
|
|
214
|
+
log: jest.fn(),
|
|
215
|
+
error: jest.fn(),
|
|
216
|
+
// ... другие методы
|
|
217
|
+
} as any;
|
|
218
|
+
|
|
219
|
+
service = new UserService(logger);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should log user creation', () => {
|
|
223
|
+
service.createUser(userData);
|
|
224
|
+
expect(logger.log).toHaveBeenCalledWith(
|
|
225
|
+
'Creating new user',
|
|
226
|
+
undefined,
|
|
227
|
+
{ userId: userData.email }
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## 🔄 Миграция с v1
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// v1 (старый API)
|
|
237
|
+
import { LoggerModule, LoggerInterceptor } from '@globalart/nestjs-logger';
|
|
238
|
+
|
|
239
|
+
// v2 (новый API)
|
|
240
|
+
import { LoggerModule, HttpLoggerInterceptor } from '@globalart/nestjs-logger';
|
|
241
|
+
|
|
242
|
+
// Для совместимости доступны legacy экспорты:
|
|
243
|
+
import { LegacyLoggerModule, LoggerInterceptor } from '@globalart/nestjs-logger';
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## 📈 Производительность
|
|
247
|
+
|
|
248
|
+
- Минимальные аллокации в горячих путях
|
|
249
|
+
- Ленивая инициализация форматтеров
|
|
250
|
+
- Оптимизированное определение контекста
|
|
251
|
+
- Эффективная санитизация данных
|
|
252
|
+
|
|
253
|
+
## 🤝 Вклад в развитие
|
|
254
|
+
|
|
255
|
+
1. Fork репозитория
|
|
256
|
+
2. Создайте feature branch
|
|
257
|
+
3. Внесите изменения
|
|
258
|
+
4. Добавьте тесты
|
|
259
|
+
5. Создайте Pull Request
|
|
260
|
+
|
|
261
|
+
## 📄 Лицензия
|
|
262
|
+
|
|
263
|
+
MIT License
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const LOGGER_CONFIG_TOKEN = "LOGGER_CONFIG";
|
|
2
|
+
export declare const LOGGER_CONTEXT_METADATA = "LOGGER_CONTEXT";
|
|
3
|
+
export declare const LOGGER_METADATA_METADATA = "LOGGER_METADATA";
|
|
4
|
+
export declare const DEFAULT_SENSITIVE_FIELDS: readonly ["password", "pass", "token", "accessToken", "refreshToken", "secret", "key", "apiKey", "authorization", "auth", "credential", "credentials"];
|
|
5
|
+
export declare const COLORS: {
|
|
6
|
+
readonly reset: "\u001B[0m";
|
|
7
|
+
readonly red: "\u001B[31m";
|
|
8
|
+
readonly green: "\u001B[32m";
|
|
9
|
+
readonly yellow: "\u001B[33m";
|
|
10
|
+
readonly blue: "\u001B[34m";
|
|
11
|
+
readonly magenta: "\u001B[35m";
|
|
12
|
+
readonly cyan: "\u001B[36m";
|
|
13
|
+
readonly gray: "\u001B[90m";
|
|
14
|
+
readonly bright: "\u001B[1m";
|
|
15
|
+
};
|
|
16
|
+
export declare const DEFAULT_LOGGER_CONFIG: {
|
|
17
|
+
readonly level: "info";
|
|
18
|
+
readonly timestamp: true;
|
|
19
|
+
readonly colors: true;
|
|
20
|
+
readonly format: "text";
|
|
21
|
+
readonly sensitiveFields: readonly ["password", "pass", "token", "accessToken", "refreshToken", "secret", "key", "apiKey", "authorization", "auth", "credential", "credentials"];
|
|
22
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_LOGGER_CONFIG = exports.COLORS = exports.DEFAULT_SENSITIVE_FIELDS = exports.LOGGER_METADATA_METADATA = exports.LOGGER_CONTEXT_METADATA = exports.LOGGER_CONFIG_TOKEN = void 0;
|
|
4
|
+
exports.LOGGER_CONFIG_TOKEN = "LOGGER_CONFIG";
|
|
5
|
+
exports.LOGGER_CONTEXT_METADATA = "LOGGER_CONTEXT";
|
|
6
|
+
exports.LOGGER_METADATA_METADATA = "LOGGER_METADATA";
|
|
7
|
+
exports.DEFAULT_SENSITIVE_FIELDS = [
|
|
8
|
+
"password",
|
|
9
|
+
"pass",
|
|
10
|
+
"token",
|
|
11
|
+
"accessToken",
|
|
12
|
+
"refreshToken",
|
|
13
|
+
"secret",
|
|
14
|
+
"key",
|
|
15
|
+
"apiKey",
|
|
16
|
+
"authorization",
|
|
17
|
+
"auth",
|
|
18
|
+
"credential",
|
|
19
|
+
"credentials",
|
|
20
|
+
];
|
|
21
|
+
exports.COLORS = {
|
|
22
|
+
reset: "\x1b[0m",
|
|
23
|
+
red: "\x1b[31m",
|
|
24
|
+
green: "\x1b[32m",
|
|
25
|
+
yellow: "\x1b[33m",
|
|
26
|
+
blue: "\x1b[34m",
|
|
27
|
+
magenta: "\x1b[35m",
|
|
28
|
+
cyan: "\x1b[36m",
|
|
29
|
+
gray: "\x1b[90m",
|
|
30
|
+
bright: "\x1b[1m",
|
|
31
|
+
};
|
|
32
|
+
exports.DEFAULT_LOGGER_CONFIG = {
|
|
33
|
+
level: "info",
|
|
34
|
+
timestamp: true,
|
|
35
|
+
colors: true,
|
|
36
|
+
format: "text",
|
|
37
|
+
sensitiveFields: exports.DEFAULT_SENSITIVE_FIELDS,
|
|
38
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { LogEntry, HttpRequestLogEntry } from "../types";
|
|
2
|
+
export interface ILogger {
|
|
3
|
+
log(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
4
|
+
error(message: string, trace?: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
5
|
+
warn(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
6
|
+
debug(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
7
|
+
verbose(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
8
|
+
setContext(context: string): void;
|
|
9
|
+
logHttpRequest(entry: HttpRequestLogEntry): void;
|
|
10
|
+
}
|
|
11
|
+
export interface ILogFormatter {
|
|
12
|
+
format(entry: LogEntry): string;
|
|
13
|
+
formatHttpRequest(entry: HttpRequestLogEntry): string;
|
|
14
|
+
}
|
|
15
|
+
export interface ILogWriter {
|
|
16
|
+
write(formattedLog: string): void;
|
|
17
|
+
}
|
|
18
|
+
export interface IContextResolver {
|
|
19
|
+
resolve(): string;
|
|
20
|
+
}
|
|
21
|
+
export interface IDataSanitizer {
|
|
22
|
+
sanitize(data: unknown): unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface IRequestIdGenerator {
|
|
25
|
+
generate(): string;
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
|
|
2
|
+
import { Observable } from "rxjs";
|
|
3
|
+
import { LoggerService } from "./logger.service";
|
|
4
|
+
import { IDataSanitizer, IRequestIdGenerator } from "../contracts";
|
|
5
|
+
export declare class HttpLoggerInterceptor implements NestInterceptor {
|
|
6
|
+
private readonly logger;
|
|
7
|
+
private readonly dataSanitizer;
|
|
8
|
+
private readonly requestIdGenerator;
|
|
9
|
+
private readonly hostname;
|
|
10
|
+
private readonly pid;
|
|
11
|
+
constructor(logger: LoggerService, dataSanitizer: IDataSanitizer, requestIdGenerator: IRequestIdGenerator);
|
|
12
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
|
|
13
|
+
private createLogEntry;
|
|
14
|
+
private getClientIp;
|
|
15
|
+
private sanitizeHeaders;
|
|
16
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.HttpLoggerInterceptor = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const operators_1 = require("rxjs/operators");
|
|
15
|
+
const logger_service_1 = require("./logger.service");
|
|
16
|
+
const os_1 = require("os");
|
|
17
|
+
let HttpLoggerInterceptor = class HttpLoggerInterceptor {
|
|
18
|
+
constructor(logger, dataSanitizer, requestIdGenerator) {
|
|
19
|
+
this.logger = logger;
|
|
20
|
+
this.dataSanitizer = dataSanitizer;
|
|
21
|
+
this.requestIdGenerator = requestIdGenerator;
|
|
22
|
+
this.hostname = (0, os_1.hostname)();
|
|
23
|
+
this.pid = process.pid;
|
|
24
|
+
}
|
|
25
|
+
intercept(context, next) {
|
|
26
|
+
const request = context.switchToHttp().getRequest();
|
|
27
|
+
const response = context.switchToHttp().getResponse();
|
|
28
|
+
const requestId = this.requestIdGenerator.generate();
|
|
29
|
+
const startTime = Date.now();
|
|
30
|
+
return next.handle().pipe((0, operators_1.tap)(() => {
|
|
31
|
+
const entry = this.createLogEntry(request, response, requestId, startTime, 30, // INFO level
|
|
32
|
+
"request completed");
|
|
33
|
+
this.logger.logHttpRequest(entry);
|
|
34
|
+
}), (0, operators_1.catchError)((error) => {
|
|
35
|
+
const entry = this.createLogEntry(request, response, requestId, startTime, 50, // ERROR level
|
|
36
|
+
"request failed");
|
|
37
|
+
this.logger.logHttpRequest(entry);
|
|
38
|
+
throw error;
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
createLogEntry(request, response, requestId, startTime, level, message) {
|
|
42
|
+
const responseTime = Date.now() - startTime;
|
|
43
|
+
const ip = this.getClientIp(request);
|
|
44
|
+
const httpRequest = {
|
|
45
|
+
id: requestId,
|
|
46
|
+
method: request.method,
|
|
47
|
+
url: request.url,
|
|
48
|
+
query: request.query || {},
|
|
49
|
+
params: request.params || {},
|
|
50
|
+
headers: this.sanitizeHeaders(request.headers || {}),
|
|
51
|
+
remoteAddress: ip,
|
|
52
|
+
remotePort: request.connection?.remotePort,
|
|
53
|
+
body: this.dataSanitizer.sanitize(request.body),
|
|
54
|
+
};
|
|
55
|
+
const httpResponse = {
|
|
56
|
+
statusCode: response.statusCode || 500,
|
|
57
|
+
headers: this.sanitizeHeaders(response.getHeaders?.() || {}),
|
|
58
|
+
};
|
|
59
|
+
return {
|
|
60
|
+
level,
|
|
61
|
+
time: Date.now(),
|
|
62
|
+
pid: this.pid,
|
|
63
|
+
hostname: this.hostname,
|
|
64
|
+
req: httpRequest,
|
|
65
|
+
res: httpResponse,
|
|
66
|
+
responseTime,
|
|
67
|
+
msg: message,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
getClientIp(request) {
|
|
71
|
+
return (request.headers["x-forwarded-for"] ||
|
|
72
|
+
request.headers["x-real-ip"] ||
|
|
73
|
+
request.connection?.remoteAddress ||
|
|
74
|
+
request.socket?.remoteAddress ||
|
|
75
|
+
"unknown");
|
|
76
|
+
}
|
|
77
|
+
sanitizeHeaders(headers) {
|
|
78
|
+
const sanitized = {};
|
|
79
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
80
|
+
if (typeof value === "string") {
|
|
81
|
+
sanitized[key] = value;
|
|
82
|
+
}
|
|
83
|
+
else if (Array.isArray(value)) {
|
|
84
|
+
sanitized[key] = value.join(", ");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
sanitized[key] = String(value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return this.dataSanitizer.sanitize(sanitized);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
exports.HttpLoggerInterceptor = HttpLoggerInterceptor;
|
|
94
|
+
exports.HttpLoggerInterceptor = HttpLoggerInterceptor = __decorate([
|
|
95
|
+
(0, common_1.Injectable)(),
|
|
96
|
+
__metadata("design:paramtypes", [logger_service_1.LoggerService, Object, Object])
|
|
97
|
+
], HttpLoggerInterceptor);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { DynamicModule } from "@nestjs/common";
|
|
2
|
+
export interface LoggerModuleOptions {
|
|
3
|
+
level?: "error" | "warn" | "info" | "debug" | "verbose";
|
|
4
|
+
timestamp?: boolean;
|
|
5
|
+
colors?: boolean;
|
|
6
|
+
context?: string;
|
|
7
|
+
format?: "json" | "text" | "pino";
|
|
8
|
+
sensitiveFields?: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface LoggerModuleAsyncOptions {
|
|
11
|
+
useFactory: (...args: any[]) => LoggerModuleOptions | Promise<LoggerModuleOptions>;
|
|
12
|
+
inject?: any[];
|
|
13
|
+
}
|
|
14
|
+
export declare class LoggerModule {
|
|
15
|
+
static forRoot(options?: LoggerModuleOptions): DynamicModule;
|
|
16
|
+
static forRootAsync(options: LoggerModuleAsyncOptions): DynamicModule;
|
|
17
|
+
private static createConfiguration;
|
|
18
|
+
private static createProviders;
|
|
19
|
+
private static createCoreProviders;
|
|
20
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var LoggerModule_1;
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.LoggerModule = void 0;
|
|
11
|
+
const common_1 = require("@nestjs/common");
|
|
12
|
+
const logger_service_1 = require("./logger.service");
|
|
13
|
+
const http_logger_interceptor_1 = require("./http-logger.interceptor");
|
|
14
|
+
const formatter_factory_1 = require("../factories/formatter.factory");
|
|
15
|
+
const console_writer_1 = require("../writers/console-writer");
|
|
16
|
+
const context_resolver_1 = require("../utils/context-resolver");
|
|
17
|
+
const data_sanitizer_1 = require("../utils/data-sanitizer");
|
|
18
|
+
const request_id_generator_1 = require("../utils/request-id-generator");
|
|
19
|
+
const constants_1 = require("../constants");
|
|
20
|
+
let LoggerModule = LoggerModule_1 = class LoggerModule {
|
|
21
|
+
static forRoot(options = {}) {
|
|
22
|
+
const config = this.createConfiguration(options);
|
|
23
|
+
const providers = this.createProviders(config);
|
|
24
|
+
return {
|
|
25
|
+
module: LoggerModule_1,
|
|
26
|
+
providers,
|
|
27
|
+
exports: [logger_service_1.LoggerService, http_logger_interceptor_1.HttpLoggerInterceptor],
|
|
28
|
+
global: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
static forRootAsync(options) {
|
|
32
|
+
const configProvider = {
|
|
33
|
+
provide: constants_1.LOGGER_CONFIG_TOKEN,
|
|
34
|
+
useFactory: async (...args) => {
|
|
35
|
+
const userOptions = await options.useFactory(...args);
|
|
36
|
+
return this.createConfiguration(userOptions);
|
|
37
|
+
},
|
|
38
|
+
inject: options.inject || [],
|
|
39
|
+
};
|
|
40
|
+
const providers = [configProvider, ...this.createCoreProviders()];
|
|
41
|
+
return {
|
|
42
|
+
module: LoggerModule_1,
|
|
43
|
+
providers,
|
|
44
|
+
exports: [logger_service_1.LoggerService, http_logger_interceptor_1.HttpLoggerInterceptor],
|
|
45
|
+
global: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
static createConfiguration(options) {
|
|
49
|
+
return {
|
|
50
|
+
...constants_1.DEFAULT_LOGGER_CONFIG,
|
|
51
|
+
...options,
|
|
52
|
+
sensitiveFields: options.sensitiveFields ?? constants_1.DEFAULT_LOGGER_CONFIG.sensitiveFields,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
static createProviders(config) {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
provide: constants_1.LOGGER_CONFIG_TOKEN,
|
|
59
|
+
useValue: config,
|
|
60
|
+
},
|
|
61
|
+
...this.createCoreProviders(),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
static createCoreProviders() {
|
|
65
|
+
return [
|
|
66
|
+
formatter_factory_1.FormatterFactory,
|
|
67
|
+
console_writer_1.ConsoleWriter,
|
|
68
|
+
context_resolver_1.ContextResolver,
|
|
69
|
+
request_id_generator_1.RequestIdGenerator,
|
|
70
|
+
{
|
|
71
|
+
provide: data_sanitizer_1.DataSanitizer,
|
|
72
|
+
useFactory: (config) => new data_sanitizer_1.DataSanitizer(config.sensitiveFields),
|
|
73
|
+
inject: [constants_1.LOGGER_CONFIG_TOKEN],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
provide: logger_service_1.LoggerService,
|
|
77
|
+
useFactory: (config, formatterFactory, writer, contextResolver) => {
|
|
78
|
+
const formatter = formatterFactory.create(config.format, {
|
|
79
|
+
colors: config.colors,
|
|
80
|
+
timestamp: config.timestamp,
|
|
81
|
+
context: config.context,
|
|
82
|
+
});
|
|
83
|
+
return new logger_service_1.LoggerService(config, formatter, writer, contextResolver);
|
|
84
|
+
},
|
|
85
|
+
inject: [
|
|
86
|
+
constants_1.LOGGER_CONFIG_TOKEN,
|
|
87
|
+
formatter_factory_1.FormatterFactory,
|
|
88
|
+
console_writer_1.ConsoleWriter,
|
|
89
|
+
context_resolver_1.ContextResolver,
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
provide: http_logger_interceptor_1.HttpLoggerInterceptor,
|
|
94
|
+
useFactory: (logger, dataSanitizer, requestIdGenerator) => new http_logger_interceptor_1.HttpLoggerInterceptor(logger, dataSanitizer, requestIdGenerator),
|
|
95
|
+
inject: [logger_service_1.LoggerService, data_sanitizer_1.DataSanitizer, request_id_generator_1.RequestIdGenerator],
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
exports.LoggerModule = LoggerModule;
|
|
101
|
+
exports.LoggerModule = LoggerModule = LoggerModule_1 = __decorate([
|
|
102
|
+
(0, common_1.Module)({})
|
|
103
|
+
], LoggerModule);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { LoggerService as NestLoggerService } from "@nestjs/common";
|
|
2
|
+
import { ILogger, ILogFormatter, ILogWriter, IContextResolver } from "../contracts";
|
|
3
|
+
import { HttpRequestLogEntry, LoggerConfiguration } from "../types";
|
|
4
|
+
export declare class LoggerService implements NestLoggerService, ILogger {
|
|
5
|
+
private readonly config;
|
|
6
|
+
private readonly formatter;
|
|
7
|
+
private readonly writer;
|
|
8
|
+
private readonly contextResolver;
|
|
9
|
+
private context?;
|
|
10
|
+
constructor(config: LoggerConfiguration, formatter: ILogFormatter, writer: ILogWriter, contextResolver: IContextResolver);
|
|
11
|
+
setContext(context: string): void;
|
|
12
|
+
log(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
13
|
+
error(message: string, trace?: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
14
|
+
warn(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
15
|
+
debug(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
16
|
+
verbose(message: string, context?: string, metadata?: Record<string, unknown>): void;
|
|
17
|
+
logHttpRequest(entry: HttpRequestLogEntry): void;
|
|
18
|
+
private writeLog;
|
|
19
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.LoggerService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
let LoggerService = class LoggerService {
|
|
15
|
+
constructor(config, formatter, writer, contextResolver) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.formatter = formatter;
|
|
18
|
+
this.writer = writer;
|
|
19
|
+
this.contextResolver = contextResolver;
|
|
20
|
+
}
|
|
21
|
+
setContext(context) {
|
|
22
|
+
this.context = context;
|
|
23
|
+
}
|
|
24
|
+
log(message, context, metadata) {
|
|
25
|
+
this.writeLog("info", message, context, metadata);
|
|
26
|
+
}
|
|
27
|
+
error(message, trace, context, metadata) {
|
|
28
|
+
this.writeLog("error", message, context, metadata, trace);
|
|
29
|
+
}
|
|
30
|
+
warn(message, context, metadata) {
|
|
31
|
+
this.writeLog("warn", message, context, metadata);
|
|
32
|
+
}
|
|
33
|
+
debug(message, context, metadata) {
|
|
34
|
+
this.writeLog("debug", message, context, metadata);
|
|
35
|
+
}
|
|
36
|
+
verbose(message, context, metadata) {
|
|
37
|
+
this.writeLog("verbose", message, context, metadata);
|
|
38
|
+
}
|
|
39
|
+
logHttpRequest(entry) {
|
|
40
|
+
const formatted = this.formatter.formatHttpRequest(entry);
|
|
41
|
+
this.writer.write(formatted);
|
|
42
|
+
}
|
|
43
|
+
writeLog(level, message, context, metadata, trace) {
|
|
44
|
+
const entry = {
|
|
45
|
+
level,
|
|
46
|
+
message,
|
|
47
|
+
timestamp: new Date(),
|
|
48
|
+
context: context ?? this.context ?? this.contextResolver.resolve(),
|
|
49
|
+
metadata,
|
|
50
|
+
trace,
|
|
51
|
+
};
|
|
52
|
+
const formatted = this.formatter.format(entry);
|
|
53
|
+
this.writer.write(formatted);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.LoggerService = LoggerService;
|
|
57
|
+
exports.LoggerService = LoggerService = __decorate([
|
|
58
|
+
(0, common_1.Injectable)(),
|
|
59
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object])
|
|
60
|
+
], LoggerService);
|