@globalart/nestjs-logger 1.0.3 → 1.0.5

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.
@@ -14,8 +14,10 @@ export declare class HttpLoggerInterceptor implements NestInterceptor {
14
14
  private readonly pid;
15
15
  constructor(logger: LoggerService, dataSanitizer: IDataSanitizer, requestIdGenerator: IRequestIdGenerator, config: LoggerConfiguration, reflector: Reflector);
16
16
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown>;
17
+ private createHttpLogEntry;
17
18
  private createLogEntry;
18
19
  private getClientIp;
19
20
  private sanitizeHeaders;
20
- private shouldExcludeUrl;
21
+ private getRequestMethod;
22
+ private shouldExcludeRequest;
21
23
  }
@@ -32,11 +32,10 @@ let HttpLoggerInterceptor = class HttpLoggerInterceptor {
32
32
  intercept(context, next) {
33
33
  const request = context.switchToHttp().getRequest();
34
34
  const response = context.switchToHttp().getResponse();
35
- // Проверяем, нужно ли исключить этот URL из логирования
36
- if (this.shouldExcludeUrl(request.url)) {
35
+ const requestMethod = this.getRequestMethod(request.method);
36
+ if (this.shouldExcludeRequest(requestMethod, request.url)) {
37
37
  return next.handle();
38
38
  }
39
- // Проверяем, есть ли декоратор ExcludeLogging на контроллере или методе
40
39
  const isExcluded = this.reflector.getAllAndOverride(constants_1.LOGGER_EXCLUDE_METADATA, [context.getHandler(), context.getClass()]);
41
40
  if (isExcluded) {
42
41
  return next.handle();
@@ -44,17 +43,27 @@ let HttpLoggerInterceptor = class HttpLoggerInterceptor {
44
43
  const requestId = this.requestIdGenerator.generate();
45
44
  const startTime = Date.now();
46
45
  return next.handle().pipe((0, operators_1.tap)(() => {
47
- const entry = this.createLogEntry(request, response, requestId, startTime, 30, // INFO level
48
- "request completed");
49
- this.logger.logHttpRequest(entry);
46
+ if (this.config.format === "pino") {
47
+ const entry = this.createHttpLogEntry(request, response, requestId, startTime, 30, "request completed");
48
+ this.logger.logHttpRequest(entry);
49
+ }
50
+ else {
51
+ const entry = this.createLogEntry(request, response, requestId, startTime);
52
+ this.logger.log(entry);
53
+ }
50
54
  }), (0, operators_1.catchError)((error) => {
51
- const entry = this.createLogEntry(request, response, requestId, startTime, 50, // ERROR level
52
- "request failed");
53
- this.logger.logHttpRequest(entry);
55
+ if (this.config.format === "pino") {
56
+ const entry = this.createHttpLogEntry(request, response, requestId, startTime, 50, "request failed");
57
+ this.logger.logHttpRequest(entry);
58
+ }
59
+ else {
60
+ const entry = this.createLogEntry(request, response, requestId, startTime);
61
+ this.logger.error(entry);
62
+ }
54
63
  throw error;
55
64
  }));
56
65
  }
57
- createLogEntry(request, response, requestId, startTime, level, message) {
66
+ createHttpLogEntry(request, response, requestId, startTime, level, message) {
58
67
  const responseTime = Date.now() - startTime;
59
68
  const ip = this.getClientIp(request);
60
69
  const httpRequest = {
@@ -83,6 +92,23 @@ let HttpLoggerInterceptor = class HttpLoggerInterceptor {
83
92
  msg: message,
84
93
  };
85
94
  }
95
+ createLogEntry(request, response, requestId, startTime) {
96
+ const responseTime = Date.now() - startTime;
97
+ const ip = this.getClientIp(request);
98
+ return {
99
+ message: `${request.method} ${request.url} - ${response.statusCode || 500} (${responseTime}ms)`,
100
+ context: "HttpLogger",
101
+ metadata: {
102
+ requestId,
103
+ method: request.method,
104
+ url: request.url,
105
+ statusCode: response.statusCode || 500,
106
+ responseTime,
107
+ remoteAddress: ip,
108
+ userAgent: request.headers["user-agent"],
109
+ },
110
+ };
111
+ }
86
112
  getClientIp(request) {
87
113
  return (request.headers["x-forwarded-for"] ||
88
114
  request.headers["x-real-ip"] ||
@@ -105,16 +131,37 @@ let HttpLoggerInterceptor = class HttpLoggerInterceptor {
105
131
  }
106
132
  return this.dataSanitizer.sanitize(sanitized);
107
133
  }
108
- shouldExcludeUrl(url) {
109
- return this.config.exclude.some((excludeUrl) => {
110
- // Поддержка простых паттернов с * (wildcard)
111
- if (excludeUrl.includes("*")) {
112
- const pattern = excludeUrl.replace(/\*/g, ".*");
134
+ getRequestMethod(method) {
135
+ switch (method.toUpperCase()) {
136
+ case "GET":
137
+ return common_1.RequestMethod.GET;
138
+ case "POST":
139
+ return common_1.RequestMethod.POST;
140
+ case "PUT":
141
+ return common_1.RequestMethod.PUT;
142
+ case "DELETE":
143
+ return common_1.RequestMethod.DELETE;
144
+ case "PATCH":
145
+ return common_1.RequestMethod.PATCH;
146
+ case "OPTIONS":
147
+ return common_1.RequestMethod.OPTIONS;
148
+ case "HEAD":
149
+ return common_1.RequestMethod.HEAD;
150
+ default:
151
+ return common_1.RequestMethod.ALL;
152
+ }
153
+ }
154
+ shouldExcludeRequest(method, path) {
155
+ return this.config.exclude.some((excludeOption) => {
156
+ if (excludeOption.method !== method) {
157
+ return false;
158
+ }
159
+ if (excludeOption.path.includes("*")) {
160
+ const pattern = excludeOption.path.replace(/\*/g, ".*");
113
161
  const regex = new RegExp(`^${pattern}$`);
114
- return regex.test(url);
162
+ return regex.test(path);
115
163
  }
116
- // Точное совпадение
117
- return url === excludeUrl;
164
+ return path === excludeOption.path;
118
165
  });
119
166
  }
120
167
  };
@@ -1,4 +1,5 @@
1
1
  import { DynamicModule } from "@nestjs/common";
2
+ import { ExcludeOption } from "../types";
2
3
  export interface LoggerModuleOptions {
3
4
  level?: "error" | "warn" | "info" | "debug" | "verbose";
4
5
  timestamp?: boolean;
@@ -6,7 +7,7 @@ export interface LoggerModuleOptions {
6
7
  context?: string;
7
8
  format?: "json" | "text" | "pino";
8
9
  sensitiveFields?: string[];
9
- exclude?: string[];
10
+ exclude?: ExcludeOption[];
10
11
  }
11
12
  export interface LoggerModuleAsyncOptions {
12
13
  useFactory: (...args: any[]) => LoggerModuleOptions | Promise<LoggerModuleOptions>;
@@ -9,4 +9,6 @@ export declare abstract class BaseFormatter implements ILogFormatter {
9
9
  protected formatTimestamp(timestamp: Date): string;
10
10
  protected colorize(text: string, color: keyof typeof COLORS): string;
11
11
  protected getColorForLevel(level: string | number): keyof typeof COLORS;
12
+ private getColorForStringLevel;
13
+ private getColorForNumericLevel;
12
14
  }
@@ -16,22 +16,21 @@ class BaseFormatter {
16
16
  }
17
17
  getColorForLevel(level) {
18
18
  if (typeof level === "string") {
19
- switch (level) {
20
- case "error":
21
- return "red";
22
- case "warn":
23
- return "yellow";
24
- case "info":
25
- return "green";
26
- case "debug":
27
- return "blue";
28
- case "verbose":
29
- return "magenta";
30
- default:
31
- return "gray";
32
- }
19
+ return this.getColorForStringLevel(level);
33
20
  }
34
- // Для числовых уровней (Pino)
21
+ return this.getColorForNumericLevel(level);
22
+ }
23
+ getColorForStringLevel(level) {
24
+ const colorMap = {
25
+ error: "red",
26
+ warn: "yellow",
27
+ info: "green",
28
+ debug: "blue",
29
+ verbose: "magenta",
30
+ };
31
+ return colorMap[level] || "gray";
32
+ }
33
+ getColorForNumericLevel(level) {
35
34
  if (level >= 50)
36
35
  return "red";
37
36
  if (level >= 40)
@@ -3,4 +3,6 @@ import { LogEntry, HttpRequestLogEntry } from "../types";
3
3
  export declare class JsonFormatter extends BaseFormatter {
4
4
  format(entry: LogEntry): string;
5
5
  formatHttpRequest(entry: HttpRequestLogEntry): string;
6
+ private buildLogObject;
7
+ private addOptionalFields;
6
8
  }
@@ -11,23 +11,35 @@ const common_1 = require("@nestjs/common");
11
11
  const base_formatter_1 = require("./base-formatter");
12
12
  let JsonFormatter = class JsonFormatter extends base_formatter_1.BaseFormatter {
13
13
  format(entry) {
14
- const logObject = {
14
+ const logObject = this.buildLogObject(entry);
15
+ return JSON.stringify(logObject);
16
+ }
17
+ formatHttpRequest(entry) {
18
+ const jsonString = JSON.stringify(entry);
19
+ return this.options.colors
20
+ ? this.colorize(jsonString, this.getColorForLevel(entry.level))
21
+ : jsonString;
22
+ }
23
+ buildLogObject(entry) {
24
+ const baseObject = {
15
25
  timestamp: this.formatTimestamp(entry.timestamp),
16
26
  level: entry.level,
17
27
  message: entry.message,
18
- ...(entry.context && { context: entry.context }),
19
- ...(entry.metadata && { metadata: entry.metadata }),
20
- ...(entry.trace && { trace: entry.trace }),
21
28
  };
22
- return JSON.stringify(logObject);
29
+ return this.addOptionalFields(baseObject, entry);
23
30
  }
24
- formatHttpRequest(entry) {
25
- const jsonString = JSON.stringify(entry);
26
- if (this.options.colors) {
27
- const color = this.getColorForLevel(entry.level);
28
- return this.colorize(jsonString, color);
31
+ addOptionalFields(baseObject, entry) {
32
+ const result = { ...baseObject };
33
+ if (entry.context) {
34
+ result.context = entry.context;
35
+ }
36
+ if (entry.metadata) {
37
+ result.metadata = entry.metadata;
38
+ }
39
+ if (entry.trace) {
40
+ result.trace = entry.trace;
29
41
  }
30
- return jsonString;
42
+ return result;
31
43
  }
32
44
  };
33
45
  exports.JsonFormatter = JsonFormatter;
@@ -15,11 +15,9 @@ let PinoFormatter = class PinoFormatter extends base_formatter_1.BaseFormatter {
15
15
  }
16
16
  formatHttpRequest(entry) {
17
17
  const jsonString = JSON.stringify(entry);
18
- if (this.options.colors) {
19
- const color = this.getColorForLevel(entry.level);
20
- return this.colorize(jsonString, color);
21
- }
22
- return jsonString;
18
+ return this.options.colors
19
+ ? this.colorize(jsonString, this.getColorForLevel(entry.level))
20
+ : jsonString;
23
21
  }
24
22
  };
25
23
  exports.PinoFormatter = PinoFormatter;
@@ -3,4 +3,12 @@ import { LogEntry, HttpRequestLogEntry } from "../types";
3
3
  export declare class TextFormatter extends BaseFormatter {
4
4
  format(entry: LogEntry): string;
5
5
  formatHttpRequest(entry: HttpRequestLogEntry): string;
6
+ private buildLogParts;
7
+ private addTimestamp;
8
+ private addLevel;
9
+ private addContext;
10
+ private addMessage;
11
+ private addMetadata;
12
+ private hasMetadata;
13
+ private addTrace;
6
14
  }
@@ -11,32 +11,54 @@ const common_1 = require("@nestjs/common");
11
11
  const base_formatter_1 = require("./base-formatter");
12
12
  let TextFormatter = class TextFormatter extends base_formatter_1.BaseFormatter {
13
13
  format(entry) {
14
+ const parts = this.buildLogParts(entry);
15
+ const result = parts.join(" ");
16
+ return entry.trace ? this.addTrace(result, entry.trace) : result;
17
+ }
18
+ formatHttpRequest(entry) {
19
+ return JSON.stringify(entry);
20
+ }
21
+ buildLogParts(entry) {
14
22
  const parts = [];
23
+ this.addTimestamp(parts, entry);
24
+ this.addLevel(parts, entry);
25
+ this.addContext(parts, entry);
26
+ this.addMessage(parts, entry);
27
+ this.addMetadata(parts, entry);
28
+ return parts;
29
+ }
30
+ addTimestamp(parts, entry) {
15
31
  if (this.options.timestamp) {
16
32
  const timestamp = this.colorize(`[${this.formatTimestamp(entry.timestamp)}]`, "gray");
17
33
  parts.push(timestamp);
18
34
  }
35
+ }
36
+ addLevel(parts, entry) {
19
37
  const level = this.colorize(`[${entry.level.toUpperCase()}]`, this.getColorForLevel(entry.level));
20
38
  parts.push(level);
39
+ }
40
+ addContext(parts, entry) {
21
41
  if (entry.context) {
22
42
  const context = this.colorize(`[${entry.context}]`, "cyan");
23
43
  parts.push(context);
24
44
  }
45
+ }
46
+ addMessage(parts, entry) {
25
47
  const message = this.colorize(entry.message, "bright");
26
48
  parts.push(message);
27
- if (entry.metadata && Object.keys(entry.metadata).length > 0) {
49
+ }
50
+ addMetadata(parts, entry) {
51
+ if (this.hasMetadata(entry)) {
28
52
  const metadata = this.colorize(JSON.stringify(entry.metadata), "gray");
29
53
  parts.push(metadata);
30
54
  }
31
- let result = parts.join(" ");
32
- if (entry.trace) {
33
- const trace = this.colorize(entry.trace, "red");
34
- result += `\n${trace}`;
35
- }
36
- return result;
37
55
  }
38
- formatHttpRequest(entry) {
39
- return JSON.stringify(entry);
56
+ hasMetadata(entry) {
57
+ return Boolean(entry.metadata && Object.keys(entry.metadata).length > 0);
58
+ }
59
+ addTrace(result, trace) {
60
+ const coloredTrace = this.colorize(trace, "red");
61
+ return `${result}\n${coloredTrace}`;
40
62
  }
41
63
  };
42
64
  exports.TextFormatter = TextFormatter;
@@ -1,3 +1,4 @@
1
+ import { RequestMethod } from "@nestjs/common";
1
2
  export type LogLevel = "error" | "warn" | "info" | "debug" | "verbose";
2
3
  export type LogFormat = "json" | "text" | "pino";
3
4
  export declare const LOG_LEVELS: Record<LogLevel, number>;
@@ -35,6 +36,10 @@ export interface HttpResponse {
35
36
  readonly statusCode: number;
36
37
  readonly headers: Record<string, string>;
37
38
  }
39
+ export interface ExcludeOption {
40
+ method: RequestMethod;
41
+ path: string;
42
+ }
38
43
  export interface LoggerConfiguration {
39
44
  readonly level: LogLevel;
40
45
  readonly timestamp: boolean;
@@ -42,7 +47,7 @@ export interface LoggerConfiguration {
42
47
  readonly context?: string;
43
48
  readonly format: LogFormat;
44
49
  readonly sensitiveFields: readonly string[];
45
- readonly exclude: readonly string[];
50
+ readonly exclude: readonly ExcludeOption[];
46
51
  }
47
52
  export interface FormatterOptions {
48
53
  readonly colors: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalart/nestjs-logger",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "A advanced logger for NestJS",
5
5
  "author": {
6
6
  "name": "GlobalArt, Inc"
@@ -34,6 +34,7 @@
34
34
  "test:cov": "jest --coverage --passWithNoTests",
35
35
  "coveralls": "yarn run test:cov --coverageReporters=text-lcov | coveralls",
36
36
  "build": "rm -rf ./dist && tsc",
37
+ "build:watch": "tsc --watch",
37
38
  "prepublishOnly": "npm run build",
38
39
  "publish:dev": "npm publish --access public --tag dev",
39
40
  "publish:npm": "release-it"
@@ -41,7 +42,7 @@
41
42
  "dependencies": {
42
43
  "@nestjs/common": "^11.1.3",
43
44
  "@nestjs/core": "^11.1.3",
44
- "@nestjs/microservices": "11.1.5",
45
+ "@nestjs/microservices": "11.1.6",
45
46
  "@nestjs/swagger": "^11.2.0",
46
47
  "@nestjs/testing": "^11.1.3",
47
48
  "@nestjs/typeorm": "^11.0.0"
package/README.md DELETED
@@ -1,263 +0,0 @@
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