@adatechnology/logger 0.0.8 → 0.0.10
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 +2 -17
- package/agents/skills/SKILL.md +15 -5
- package/dist/index.d.ts +36 -8
- package/dist/index.js +272 -173
- package/package.json +1 -1
- package/src/context/async-context.service.ts +1 -1
- package/src/implementations/winston/winston.logger.module.ts +231 -176
- package/src/implementations/winston/winston.logger.provider.ts +47 -48
- package/src/index.ts +14 -2
- package/src/interceptors/exclude-http-logging.decorator.ts +6 -0
- package/src/interceptors/http-logging.interceptor.ts +40 -4
- package/src/logger.config.ts +6 -0
- package/src/logger.interface.ts +24 -8
- package/src/logger.module.ts +13 -4
- package/src/logger.provider.ts +19 -30
- package/src/logger.token.ts +1 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DynamicModule, Module, Provider } from "@nestjs/common";
|
|
2
2
|
import { createLogger, format, transports, LoggerOptions } from "winston";
|
|
3
|
-
import { inspect } from "util";
|
|
3
|
+
import { inspect } from "node:util";
|
|
4
4
|
import { WinstonLoggerProvider } from "./winston.logger.provider";
|
|
5
5
|
import {
|
|
6
6
|
WINSTON_LOGGER,
|
|
@@ -11,6 +11,227 @@ import type { LoggerConfig } from "../../logger.config";
|
|
|
11
11
|
import { DEFAULT_LOG_LEVEL } from "./winston.logger.constants";
|
|
12
12
|
import { buildDefaultObfuscator } from "../../obfuscator";
|
|
13
13
|
|
|
14
|
+
type WritableLogInfo = Record<string, unknown> & {
|
|
15
|
+
level?: unknown;
|
|
16
|
+
message?: unknown;
|
|
17
|
+
timestamp?: unknown;
|
|
18
|
+
requestId?: unknown;
|
|
19
|
+
context?: unknown;
|
|
20
|
+
source?: unknown;
|
|
21
|
+
appName?: unknown;
|
|
22
|
+
appVersion?: unknown;
|
|
23
|
+
lib?: unknown;
|
|
24
|
+
libVersion?: unknown;
|
|
25
|
+
libMethod?: unknown;
|
|
26
|
+
stack?: unknown;
|
|
27
|
+
meta?: unknown;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type MetaLogContext = {
|
|
31
|
+
className?: unknown;
|
|
32
|
+
methodName?: unknown;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function asString(value: unknown): string | undefined {
|
|
36
|
+
if (typeof value === "string") return value;
|
|
37
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
38
|
+
return String(value);
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function colorizeText(
|
|
44
|
+
useColors: boolean,
|
|
45
|
+
color: string,
|
|
46
|
+
reset: string,
|
|
47
|
+
text: string,
|
|
48
|
+
): string {
|
|
49
|
+
return useColors ? `${color}${text}${reset}` : text;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fillInfoFromMeta(info: WritableLogInfo): void {
|
|
53
|
+
if (!info.meta || typeof info.meta !== "object") return;
|
|
54
|
+
|
|
55
|
+
const meta = info.meta as Record<string, unknown>;
|
|
56
|
+
const metadataKeys = [
|
|
57
|
+
"requestId",
|
|
58
|
+
"context",
|
|
59
|
+
"source",
|
|
60
|
+
"lib",
|
|
61
|
+
"libVersion",
|
|
62
|
+
"libMethod",
|
|
63
|
+
"appName",
|
|
64
|
+
"appVersion",
|
|
65
|
+
] as const;
|
|
66
|
+
|
|
67
|
+
for (const key of metadataKeys) {
|
|
68
|
+
if (meta[key] && !info[key]) {
|
|
69
|
+
info[key] = meta[key];
|
|
70
|
+
delete meta[key];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const logContext = meta.logContext as MetaLogContext | undefined;
|
|
75
|
+
if (!logContext || info.source) return;
|
|
76
|
+
|
|
77
|
+
const className = asString(logContext.className);
|
|
78
|
+
const methodName = asString(logContext.methodName);
|
|
79
|
+
|
|
80
|
+
if (className && methodName) {
|
|
81
|
+
info.source = `${className}.${methodName}`;
|
|
82
|
+
} else if (className) {
|
|
83
|
+
info.source = className;
|
|
84
|
+
} else if (methodName) {
|
|
85
|
+
info.source = methodName;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function applyDefaultInfoValues(
|
|
90
|
+
info: WritableLogInfo,
|
|
91
|
+
config?: LoggerConfig,
|
|
92
|
+
): void {
|
|
93
|
+
info.requestId = info.requestId || "no-request-id";
|
|
94
|
+
info.context = info.context || config?.context || "App";
|
|
95
|
+
info.appName = info.appName || config?.appName;
|
|
96
|
+
info.appVersion = info.appVersion || config?.appVersion;
|
|
97
|
+
info.lib = info.lib || config?.lib;
|
|
98
|
+
info.libVersion = info.libVersion || config?.libVersion;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildMethodDisplays(params: {
|
|
102
|
+
context: string;
|
|
103
|
+
source?: string;
|
|
104
|
+
lib?: string;
|
|
105
|
+
libMethod?: string;
|
|
106
|
+
colorize: (text: string) => string;
|
|
107
|
+
}): { sourceDisplay: string; libMethodDisplay: string } {
|
|
108
|
+
const { context, source, lib, libMethod, colorize } = params;
|
|
109
|
+
|
|
110
|
+
let sourceDisplay = "";
|
|
111
|
+
let libMethodDisplay = "";
|
|
112
|
+
|
|
113
|
+
if (source) {
|
|
114
|
+
sourceDisplay = `[${colorize(source)}]`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (lib) {
|
|
118
|
+
const methodPath = libMethod ? `${context}.${libMethod}` : context;
|
|
119
|
+
libMethodDisplay = `[${colorize(methodPath)}]`;
|
|
120
|
+
if (source === context || source === methodPath) {
|
|
121
|
+
sourceDisplay = "";
|
|
122
|
+
}
|
|
123
|
+
return { sourceDisplay, libMethodDisplay };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!source) {
|
|
127
|
+
libMethodDisplay = `[${colorize(context)}]`;
|
|
128
|
+
return { sourceDisplay, libMethodDisplay };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (source.startsWith(`${context}.`) || source === context) {
|
|
132
|
+
libMethodDisplay = `[${colorize(source)}]`;
|
|
133
|
+
sourceDisplay = "";
|
|
134
|
+
return { sourceDisplay, libMethodDisplay };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
libMethodDisplay = `[${colorize(context)}]`;
|
|
138
|
+
sourceDisplay = `[${colorize(source)}]`;
|
|
139
|
+
return { sourceDisplay, libMethodDisplay };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatDevelopmentLog(
|
|
143
|
+
infoInput: WritableLogInfo,
|
|
144
|
+
useColors: boolean,
|
|
145
|
+
levelColorizer: ReturnType<typeof format.colorize>,
|
|
146
|
+
): string {
|
|
147
|
+
const info = infoInput;
|
|
148
|
+
const level = asString(info.level) ?? "info";
|
|
149
|
+
const message = asString(info.message) ?? "";
|
|
150
|
+
const timestamp = asString(info.timestamp) ?? "";
|
|
151
|
+
const requestId = asString(info.requestId) ?? "no-request-id";
|
|
152
|
+
const context = asString(info.context) ?? "App";
|
|
153
|
+
const source = asString(info.source);
|
|
154
|
+
const appName = asString(info.appName);
|
|
155
|
+
const appVersion = asString(info.appVersion);
|
|
156
|
+
const lib = asString(info.lib);
|
|
157
|
+
const libMethod = asString(info.libMethod);
|
|
158
|
+
const libVersion = asString(info.libVersion);
|
|
159
|
+
const stack = asString(info.stack);
|
|
160
|
+
|
|
161
|
+
const meta =
|
|
162
|
+
info.meta && typeof info.meta === "object"
|
|
163
|
+
? (info.meta as Record<string, unknown>)
|
|
164
|
+
: undefined;
|
|
165
|
+
|
|
166
|
+
const colors = {
|
|
167
|
+
reset: "\x1b[0m",
|
|
168
|
+
gray: "\x1b[90m",
|
|
169
|
+
cyan: "\x1b[36m",
|
|
170
|
+
magenta: "\x1b[35m",
|
|
171
|
+
yellow: "\x1b[33m",
|
|
172
|
+
red: "\x1b[31m",
|
|
173
|
+
green: "\x1b[32m",
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const coloredLevel = useColors
|
|
177
|
+
? levelColorizer.colorize(level, level.toUpperCase())
|
|
178
|
+
: level.toUpperCase();
|
|
179
|
+
|
|
180
|
+
const coloredTime = colorizeText(
|
|
181
|
+
useColors,
|
|
182
|
+
colors.gray,
|
|
183
|
+
colors.reset,
|
|
184
|
+
timestamp,
|
|
185
|
+
);
|
|
186
|
+
const coloredRequestId = colorizeText(
|
|
187
|
+
useColors,
|
|
188
|
+
colors.cyan,
|
|
189
|
+
colors.reset,
|
|
190
|
+
requestId,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
let appDisplay = "";
|
|
194
|
+
if (appName) {
|
|
195
|
+
const appText = appVersion ? `${appName}@${appVersion}` : appName;
|
|
196
|
+
const appLabel = `App-${appText}`;
|
|
197
|
+
appDisplay = `[${colorizeText(useColors, colors.green, colors.reset, appLabel)}]`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let libDisplay = "";
|
|
201
|
+
if (lib) {
|
|
202
|
+
const libText = libVersion ? `${lib}:${libVersion}` : lib;
|
|
203
|
+
libDisplay = `[${colorizeText(useColors, colors.yellow, colors.reset, libText)}]`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const { sourceDisplay, libMethodDisplay } = buildMethodDisplays({
|
|
207
|
+
context,
|
|
208
|
+
source,
|
|
209
|
+
lib,
|
|
210
|
+
libMethod,
|
|
211
|
+
colorize: (text) =>
|
|
212
|
+
colorizeText(useColors, colors.magenta, colors.reset, text),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
let output = `${appDisplay}${libDisplay}[${coloredRequestId}][${coloredTime}]${sourceDisplay}${libMethodDisplay}[${coloredLevel}] - ${message}`;
|
|
216
|
+
|
|
217
|
+
if (meta && typeof meta === "object" && Object.keys(meta).length > 0) {
|
|
218
|
+
const inspectedMeta = inspect(meta, {
|
|
219
|
+
colors: useColors,
|
|
220
|
+
depth: null,
|
|
221
|
+
compact: true,
|
|
222
|
+
sorted: true,
|
|
223
|
+
breakLength: Infinity,
|
|
224
|
+
});
|
|
225
|
+
output += ` - ${inspectedMeta}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (stack) {
|
|
229
|
+
output += `\n${colorizeText(useColors, colors.red, colors.reset, stack)}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return output;
|
|
233
|
+
}
|
|
234
|
+
|
|
14
235
|
@Module({})
|
|
15
236
|
export class WinstonImplementationModule {
|
|
16
237
|
static forRoot(config?: LoggerConfig): DynamicModule {
|
|
@@ -91,181 +312,17 @@ export class WinstonImplementationModule {
|
|
|
91
312
|
|
|
92
313
|
// Standard fields we want to ensure are in the log object
|
|
93
314
|
const standardFields = format((info) => {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
info.requestId = meta.requestId;
|
|
99
|
-
delete meta.requestId;
|
|
100
|
-
}
|
|
101
|
-
if (meta.context && !info.context) {
|
|
102
|
-
info.context = meta.context;
|
|
103
|
-
delete meta.context;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Extract source (used for className.methodName)
|
|
107
|
-
if (meta.source && !info.source) {
|
|
108
|
-
info.source = meta.source;
|
|
109
|
-
delete meta.source;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Support lib identification from metadata
|
|
113
|
-
if (meta.lib && !info.lib) {
|
|
114
|
-
info.lib = meta.lib;
|
|
115
|
-
delete meta.lib;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Support lib version identification from metadata
|
|
119
|
-
if (meta.libVersion && !info.libVersion) {
|
|
120
|
-
info.libVersion = meta.libVersion;
|
|
121
|
-
delete meta.libVersion;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Support lib method identification
|
|
125
|
-
if (meta.libMethod && !info.libMethod) {
|
|
126
|
-
info.libMethod = meta.libMethod;
|
|
127
|
-
delete meta.libMethod;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Support app identification from metadata
|
|
131
|
-
if (meta.appName && !info.appName) {
|
|
132
|
-
info.appName = meta.appName;
|
|
133
|
-
delete meta.appName;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Support app version identification from metadata
|
|
137
|
-
if (meta.appVersion && !info.appVersion) {
|
|
138
|
-
info.appVersion = meta.appVersion;
|
|
139
|
-
delete meta.appVersion;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Support logContext object from some providers
|
|
143
|
-
if (meta.logContext && !info.source) {
|
|
144
|
-
const lc = meta.logContext;
|
|
145
|
-
if (lc.className && lc.methodName) {
|
|
146
|
-
info.source = `${lc.className}.${lc.methodName}`;
|
|
147
|
-
} else if (lc.className) {
|
|
148
|
-
info.source = lc.className;
|
|
149
|
-
} else if (lc.methodName) {
|
|
150
|
-
info.source = lc.methodName;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Default values
|
|
156
|
-
info.requestId = info.requestId || "no-request-id";
|
|
157
|
-
info.context = info.context || config?.context || "App";
|
|
158
|
-
info.appName = info.appName || config?.appName;
|
|
159
|
-
info.appVersion = info.appVersion || config?.appVersion;
|
|
160
|
-
info.lib = info.lib || config?.lib;
|
|
161
|
-
info.libVersion = info.libVersion || config?.libVersion;
|
|
162
|
-
|
|
163
|
-
return info;
|
|
315
|
+
const writableInfo = info as WritableLogInfo;
|
|
316
|
+
fillInfoFromMeta(writableInfo);
|
|
317
|
+
applyDefaultInfoValues(writableInfo, config);
|
|
318
|
+
return writableInfo;
|
|
164
319
|
});
|
|
165
320
|
|
|
166
321
|
const levelColorizer = format.colorize();
|
|
167
322
|
|
|
168
323
|
// Custom format for development (colorful and intuitive)
|
|
169
|
-
const developmentFormat = format.printf(
|
|
170
|
-
(info
|
|
171
|
-
const {
|
|
172
|
-
level, message, timestamp, requestId, context, source,
|
|
173
|
-
meta, stack, appName, appVersion, lib, libMethod, libVersion
|
|
174
|
-
} = info;
|
|
175
|
-
|
|
176
|
-
// Colors (using ANSI codes for precision)
|
|
177
|
-
const colors = {
|
|
178
|
-
reset: "\x1b[0m",
|
|
179
|
-
gray: "\x1b[90m",
|
|
180
|
-
cyan: "\x1b[36m",
|
|
181
|
-
magenta: "\x1b[35m",
|
|
182
|
-
yellow: "\x1b[33m",
|
|
183
|
-
red: "\x1b[31m",
|
|
184
|
-
green: "\x1b[32m",
|
|
185
|
-
bold: "\x1b[1m",
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const colorize = (color: string, text: string) =>
|
|
189
|
-
useColors ? `${color}${text}${colors.reset}` : text;
|
|
190
|
-
|
|
191
|
-
// No trailing space for the level itself, but keep it uppercase
|
|
192
|
-
const coloredLevel = useColors
|
|
193
|
-
? levelColorizer.colorize(level, level.toUpperCase())
|
|
194
|
-
: level.toUpperCase();
|
|
195
|
-
|
|
196
|
-
const coloredTime = colorize(colors.gray, timestamp);
|
|
197
|
-
const coloredRequestId = colorize(colors.cyan, requestId);
|
|
198
|
-
|
|
199
|
-
// App identification: [App-example@0.0.3]
|
|
200
|
-
let appDisplay = "";
|
|
201
|
-
if (appName) {
|
|
202
|
-
const appText = appVersion ? `${appName}@${appVersion}` : appName;
|
|
203
|
-
appDisplay = `[${colorize(colors.green, `App-${appText}`)}]`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Lib identification: [@adatechnology/http-client:0.0.2]
|
|
207
|
-
let libDisplay = "";
|
|
208
|
-
if (lib) {
|
|
209
|
-
const libText = libVersion ? `${lib}:${libVersion}` : lib;
|
|
210
|
-
libDisplay = `[${colorize(colors.yellow, libText)}]`;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const mag = colors.magenta;
|
|
214
|
-
|
|
215
|
-
// Context formatting logic:
|
|
216
|
-
// [Source] (Magenta) - From the caller (e.g., HttpClientController.listPokemon)
|
|
217
|
-
// [Context.Method] (Magenta) - From the lib itself (e.g., HttpRedisClient.get)
|
|
218
|
-
let sourceDisplay = "";
|
|
219
|
-
let libMethodDisplay = "";
|
|
220
|
-
|
|
221
|
-
if (source) {
|
|
222
|
-
sourceDisplay = `[${colorize(mag, source)}]`;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (lib) {
|
|
226
|
-
// Inside a library log
|
|
227
|
-
const methodPath = libMethod ? `${context}.${libMethod}` : context;
|
|
228
|
-
libMethodDisplay = `[${colorize(mag, methodPath)}]`;
|
|
229
|
-
|
|
230
|
-
// If source is the same as context or methodPath, we can omit it to avoid duplication
|
|
231
|
-
if (source === context || source === methodPath) {
|
|
232
|
-
sourceDisplay = "";
|
|
233
|
-
}
|
|
234
|
-
} else if (!source) {
|
|
235
|
-
// App-only log without source: use context
|
|
236
|
-
libMethodDisplay = `[${colorize(mag, context)}]`;
|
|
237
|
-
} else if (source.startsWith(`${context}.`) || source === context) {
|
|
238
|
-
// App-only log where source is more specific than context: use source only
|
|
239
|
-
libMethodDisplay = `[${colorize(mag, source)}]`;
|
|
240
|
-
sourceDisplay = "";
|
|
241
|
-
} else {
|
|
242
|
-
// App-only log with different context and source
|
|
243
|
-
libMethodDisplay = `[${colorize(mag, context)}]`;
|
|
244
|
-
sourceDisplay = `[${colorize(mag, source)}]`;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Header line: [App][Lib][requestId][timestamp][Source][LibMethod][LEVEL]
|
|
248
|
-
let output = `${appDisplay}${libDisplay}[${coloredRequestId}][${coloredTime}]${sourceDisplay}${libMethodDisplay}[${coloredLevel}] - ${message}`;
|
|
249
|
-
|
|
250
|
-
// Meta data (Pretty printed if not empty)
|
|
251
|
-
if (meta && typeof meta === "object" && Object.keys(meta).length > 0) {
|
|
252
|
-
const inspectedMeta = inspect(meta, {
|
|
253
|
-
colors: useColors,
|
|
254
|
-
depth: null,
|
|
255
|
-
compact: true,
|
|
256
|
-
sorted: true,
|
|
257
|
-
breakLength: Infinity,
|
|
258
|
-
});
|
|
259
|
-
output += ` - ${inspectedMeta}`;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Error stack trace
|
|
263
|
-
if (stack) {
|
|
264
|
-
output += `\n${colorize(colors.red, stack)}`;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return output;
|
|
268
|
-
}
|
|
324
|
+
const developmentFormat = format.printf((info) =>
|
|
325
|
+
formatDevelopmentLog(info as WritableLogInfo, useColors, levelColorizer),
|
|
269
326
|
);
|
|
270
327
|
|
|
271
328
|
const formats = [
|
|
@@ -294,11 +351,9 @@ export class WinstonImplementationModule {
|
|
|
294
351
|
transports: [consoleTransport],
|
|
295
352
|
};
|
|
296
353
|
|
|
297
|
-
const mergedOptions: LoggerOptions =
|
|
298
|
-
{}
|
|
299
|
-
defaultOptions
|
|
300
|
-
config?.loggerOptions || {},
|
|
301
|
-
);
|
|
354
|
+
const mergedOptions: LoggerOptions = config?.loggerOptions
|
|
355
|
+
? { ...defaultOptions, ...config.loggerOptions }
|
|
356
|
+
: defaultOptions;
|
|
302
357
|
|
|
303
358
|
return createLogger(mergedOptions);
|
|
304
359
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { Injectable, Inject } from "@nestjs/common";
|
|
2
2
|
import { Logger as WinstonLoggerType } from "winston";
|
|
3
3
|
import {
|
|
4
|
-
type
|
|
4
|
+
type DebugParams,
|
|
5
|
+
type DebugResult,
|
|
6
|
+
type ErrorParams,
|
|
7
|
+
type ErrorResult,
|
|
8
|
+
type InfoParams,
|
|
9
|
+
type InfoResult,
|
|
5
10
|
type LoggerProviderInterface,
|
|
6
11
|
LoggerLevel,
|
|
7
|
-
type
|
|
12
|
+
type WarnParams,
|
|
13
|
+
type WarnResult,
|
|
14
|
+
type WriteLogParams,
|
|
15
|
+
type WriteLogResult,
|
|
8
16
|
} from "../../logger.interface";
|
|
9
17
|
import type { Obfuscator } from "./winston.logger.types";
|
|
10
18
|
import { getContext } from "../../context/async-context.service";
|
|
@@ -20,76 +28,67 @@ export class WinstonLoggerProvider implements LoggerProviderInterface {
|
|
|
20
28
|
@Inject(WINSTON_OBFUSCATOR) private readonly obfuscator?: Obfuscator,
|
|
21
29
|
) {}
|
|
22
30
|
|
|
23
|
-
debug(payload:
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
31
|
+
debug(payload: DebugParams): DebugResult {
|
|
32
|
+
this.log({
|
|
33
|
+
level: LoggerLevel.DEBUG,
|
|
34
|
+
payload: {
|
|
35
|
+
...payload,
|
|
36
|
+
context: payload.context || this.context,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
info(payload:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
info(payload: InfoParams): InfoResult {
|
|
42
|
+
this.log({
|
|
43
|
+
level: LoggerLevel.INFO,
|
|
44
|
+
payload: {
|
|
45
|
+
...payload,
|
|
46
|
+
context: payload.context || this.context,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
33
49
|
}
|
|
34
50
|
|
|
35
|
-
warn(payload:
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
warn(payload: WarnParams): WarnResult {
|
|
52
|
+
this.log({
|
|
53
|
+
level: LoggerLevel.WARN,
|
|
54
|
+
payload: {
|
|
55
|
+
...payload,
|
|
56
|
+
context: payload.context || this.context,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
39
59
|
}
|
|
40
60
|
|
|
41
|
-
error(payload:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
61
|
+
error(payload: ErrorParams): ErrorResult {
|
|
62
|
+
this.log({
|
|
63
|
+
level: LoggerLevel.ERROR,
|
|
64
|
+
payload: {
|
|
65
|
+
...payload,
|
|
66
|
+
context: payload.context || this.context,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
45
69
|
}
|
|
46
70
|
|
|
47
71
|
setContext(context: string): void {
|
|
48
72
|
this.context = context;
|
|
49
73
|
}
|
|
50
74
|
|
|
51
|
-
private
|
|
52
|
-
level: LoggerLevel,
|
|
53
|
-
messageOrPayload: string | LogPayload,
|
|
54
|
-
meta?: Record<string, unknown>,
|
|
55
|
-
context?: string,
|
|
56
|
-
): void {
|
|
57
|
-
let payload: LogPayload;
|
|
58
|
-
|
|
59
|
-
if (typeof messageOrPayload === "string") {
|
|
60
|
-
payload = {
|
|
61
|
-
message: messageOrPayload,
|
|
62
|
-
meta,
|
|
63
|
-
context: context || this.context,
|
|
64
|
-
};
|
|
65
|
-
} else {
|
|
66
|
-
payload = {
|
|
67
|
-
...messageOrPayload,
|
|
68
|
-
context: messageOrPayload.context || context || this.context,
|
|
69
|
-
meta: { ...messageOrPayload.meta, ...meta },
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
this.log({ level, payload });
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
private log(params: LogParams) {
|
|
75
|
+
private log(params: WriteLogParams): WriteLogResult {
|
|
77
76
|
const { level, payload } = params;
|
|
78
77
|
// Extract standard fields but keep the rest to pass to Winston
|
|
79
78
|
const { message, context, meta, ...rest } = payload as any;
|
|
80
|
-
|
|
79
|
+
|
|
81
80
|
const messageText = message ?? EMPTY_STRING;
|
|
82
81
|
const messageContext = context ?? this.context;
|
|
83
82
|
const obfuscatedMeta = this.obfuscator ? this.obfuscator(meta) : meta;
|
|
84
83
|
|
|
85
84
|
const requestContext = getContext();
|
|
86
|
-
const requestIdFromContext =
|
|
87
|
-
|
|
85
|
+
const requestIdFromContext = requestContext?.requestId;
|
|
86
|
+
|
|
88
87
|
// Merge everything into a flat info object for Winston
|
|
89
88
|
const logInfo: Record<string, unknown> = {
|
|
90
89
|
...rest,
|
|
91
90
|
context: messageContext,
|
|
92
|
-
requestId: requestIdFromContext ||
|
|
91
|
+
requestId: requestIdFromContext || rest.requestId,
|
|
93
92
|
meta: obfuscatedMeta,
|
|
94
93
|
};
|
|
95
94
|
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
export { LoggerModule } from "./logger.module";
|
|
2
|
-
export { LOGGER_PROVIDER, HTTP_LOGGING_INTERCEPTOR } from "./logger.token";
|
|
2
|
+
export { LOGGER_PROVIDER, LOGGER_CONFIG, HTTP_LOGGING_INTERCEPTOR } from "./logger.token";
|
|
3
3
|
export type {
|
|
4
|
+
DebugParams,
|
|
5
|
+
DebugResult,
|
|
6
|
+
ErrorParams,
|
|
7
|
+
ErrorResult,
|
|
8
|
+
InfoParams,
|
|
9
|
+
InfoResult,
|
|
10
|
+
LogParams,
|
|
11
|
+
LogResult,
|
|
4
12
|
LoggerProviderInterface,
|
|
5
|
-
LogPayload,
|
|
6
13
|
LoggerLevel,
|
|
14
|
+
WarnParams,
|
|
15
|
+
WarnResult,
|
|
16
|
+
WriteLogParams,
|
|
17
|
+
WriteLogResult,
|
|
7
18
|
} from "./logger.interface";
|
|
8
19
|
export { RequestContextMiddleware } from "./middleware/request-context.middleware";
|
|
9
20
|
export { HttpLoggingInterceptor } from "./interceptors/http-logging.interceptor";
|
|
10
21
|
export { HTTP_LOGGING_INTERCEPTOR_CONTEXT } from "./interceptors/http-logging.interceptor.constant";
|
|
22
|
+
export { ExcludeHttpLogging } from "./interceptors/exclude-http-logging.decorator";
|
|
11
23
|
export { getContext, runWithContext } from "./context/async-context.service";
|
|
12
24
|
export type { LoggerConfig } from "./logger.config";
|
|
13
25
|
export { DEFAULT_LOGGER_CONFIG } from "./logger.config";
|
|
@@ -1,16 +1,43 @@
|
|
|
1
|
-
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor } from "@nestjs/common";
|
|
1
|
+
import { CallHandler, ExecutionContext, Inject, Injectable, NestInterceptor, Optional } from "@nestjs/common";
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
2
3
|
import { Observable } from "rxjs";
|
|
3
4
|
import { tap } from "rxjs/operators";
|
|
4
|
-
import { LOGGER_PROVIDER } from "../logger.token";
|
|
5
|
+
import { LOGGER_PROVIDER, LOGGER_CONFIG } from "../logger.token";
|
|
5
6
|
import type { LoggerProviderInterface } from "../logger.interface";
|
|
7
|
+
import type { LoggerConfig } from "../logger.config";
|
|
6
8
|
import { HTTP_LOGGING_INTERCEPTOR_CONTEXT } from "./http-logging.interceptor.constant";
|
|
9
|
+
import { EXCLUDE_HTTP_LOGGING_KEY } from "./exclude-http-logging.decorator";
|
|
7
10
|
|
|
8
11
|
@Injectable()
|
|
9
12
|
export class HttpLoggingInterceptor implements NestInterceptor {
|
|
13
|
+
private readonly excludedPaths: string[];
|
|
14
|
+
|
|
15
|
+
private toErrorMessage(error: unknown): string {
|
|
16
|
+
if (error instanceof Error) return error.message;
|
|
17
|
+
if (typeof error === "string") return error;
|
|
18
|
+
try {
|
|
19
|
+
return JSON.stringify(error);
|
|
20
|
+
} catch {
|
|
21
|
+
return "unknown error";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
10
25
|
constructor(
|
|
11
26
|
@Inject(LOGGER_PROVIDER)
|
|
12
27
|
private readonly logger: LoggerProviderInterface,
|
|
13
|
-
|
|
28
|
+
private readonly reflector: Reflector,
|
|
29
|
+
@Optional() @Inject(LOGGER_CONFIG)
|
|
30
|
+
config?: LoggerConfig,
|
|
31
|
+
) {
|
|
32
|
+
this.excludedPaths = config?.interceptorExcludedPaths ?? [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private isExcluded(url: string): boolean {
|
|
36
|
+
const normalized = url.replace(/^\/v\d+/, "");
|
|
37
|
+
return this.excludedPaths.some(
|
|
38
|
+
(path) => normalized === path || normalized.startsWith(path + "/"),
|
|
39
|
+
);
|
|
40
|
+
}
|
|
14
41
|
|
|
15
42
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
|
16
43
|
const http = context.switchToHttp();
|
|
@@ -18,6 +45,15 @@ export class HttpLoggingInterceptor implements NestInterceptor {
|
|
|
18
45
|
const response = http.getResponse<Record<string, any>>();
|
|
19
46
|
const start = Date.now();
|
|
20
47
|
|
|
48
|
+
const isDecoratorExcluded = this.reflector.getAllAndOverride<boolean>(
|
|
49
|
+
EXCLUDE_HTTP_LOGGING_KEY,
|
|
50
|
+
[context.getHandler(), context.getClass()],
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
if (isDecoratorExcluded || this.isExcluded(request.url)) {
|
|
54
|
+
return next.handle();
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
const { method, url, headers, body, query, params } = request;
|
|
22
58
|
|
|
23
59
|
this.logger.info({
|
|
@@ -73,7 +109,7 @@ export class HttpLoggingInterceptor implements NestInterceptor {
|
|
|
73
109
|
},
|
|
74
110
|
response: {
|
|
75
111
|
durationMs,
|
|
76
|
-
error:
|
|
112
|
+
error: this.toErrorMessage(error),
|
|
77
113
|
},
|
|
78
114
|
},
|
|
79
115
|
});
|
package/src/logger.config.ts
CHANGED
|
@@ -46,6 +46,12 @@ export interface LoggerConfig extends WinstonModuleConfig {
|
|
|
46
46
|
* Versão da biblioteca/módulo que está gerando o log
|
|
47
47
|
*/
|
|
48
48
|
libVersion?: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Rotas excluídas do HttpLoggingInterceptor (ex.: ['/health'])
|
|
52
|
+
* Suporta prefixo exato ou parcial via startsWith
|
|
53
|
+
*/
|
|
54
|
+
interceptorExcludedPaths?: string[];
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
export const DEFAULT_LOGGER_CONFIG: LoggerConfig = {
|