@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.
- package/dist/core/http-logger.interceptor.d.ts +3 -1
- package/dist/core/http-logger.interceptor.js +65 -18
- package/dist/core/logger.module.d.ts +2 -1
- package/dist/formatters/base-formatter.d.ts +2 -0
- package/dist/formatters/base-formatter.js +14 -15
- package/dist/formatters/json-formatter.d.ts +2 -0
- package/dist/formatters/json-formatter.js +23 -11
- package/dist/formatters/pino-formatter.js +3 -5
- package/dist/formatters/text-formatter.d.ts +8 -0
- package/dist/formatters/text-formatter.js +31 -9
- package/dist/types/index.d.ts +6 -1
- package/package.json +3 -2
- package/README.md +0 -263
|
@@ -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
|
|
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
|
-
|
|
36
|
-
if (this.
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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(
|
|
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?:
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
@@ -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
|
|
29
|
+
return this.addOptionalFields(baseObject, entry);
|
|
23
30
|
}
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
return
|
|
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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
+
"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.
|
|
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
|