@adatechnology/logger 0.0.2
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/CHANGELOG.md +7 -0
- package/README.md +151 -0
- package/agents/skills/SKILL.md +47 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +567 -0
- package/package.json +27 -0
- package/src/context/async-context.service.ts +17 -0
- package/src/context/async-context.types.ts +1 -0
- package/src/implementations/winston/winston.logger.constants.ts +1 -0
- package/src/implementations/winston/winston.logger.module.ts +305 -0
- package/src/implementations/winston/winston.logger.provider.ts +98 -0
- package/src/implementations/winston/winston.logger.token.ts +3 -0
- package/src/implementations/winston/winston.logger.types.ts +16 -0
- package/src/index.ts +11 -0
- package/src/logger.config.ts +57 -0
- package/src/logger.constant.ts +32 -0
- package/src/logger.interface.ts +25 -0
- package/src/logger.module.ts +50 -0
- package/src/logger.provider.ts +53 -0
- package/src/logger.token.ts +1 -0
- package/src/middleware/request-context.middleware.ts +26 -0
- package/src/middleware/request-context.types.ts +11 -0
- package/src/obfuscator.ts +96 -0
- package/src/obfuscator.types.ts +6 -0
- package/src/request-id.constants.ts +7 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsup.json +6 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { DynamicModule, Module, Provider } from "@nestjs/common";
|
|
2
|
+
import { createLogger, format, transports, LoggerOptions } from "winston";
|
|
3
|
+
import { inspect } from "util";
|
|
4
|
+
import { WinstonLoggerProvider } from "./winston.logger.provider";
|
|
5
|
+
import {
|
|
6
|
+
WINSTON_LOGGER,
|
|
7
|
+
WINSTON_RAW,
|
|
8
|
+
WINSTON_OBFUSCATOR,
|
|
9
|
+
} from "./winston.logger.token";
|
|
10
|
+
import type { LoggerConfig } from "../../logger.config";
|
|
11
|
+
import { DEFAULT_LOG_LEVEL } from "./winston.logger.constants";
|
|
12
|
+
import { buildDefaultObfuscator } from "../../obfuscator";
|
|
13
|
+
|
|
14
|
+
@Module({})
|
|
15
|
+
export class WinstonImplementationModule {
|
|
16
|
+
static forRoot(config?: LoggerConfig): DynamicModule {
|
|
17
|
+
const providers = this.createProviders(config);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
module: WinstonImplementationModule,
|
|
21
|
+
providers,
|
|
22
|
+
exports: [WINSTON_LOGGER],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static forRootAsync(options: {
|
|
27
|
+
imports?: any[];
|
|
28
|
+
useFactory: (...args: any[]) => Promise<LoggerConfig> | LoggerConfig;
|
|
29
|
+
inject?: any[];
|
|
30
|
+
}): DynamicModule {
|
|
31
|
+
return {
|
|
32
|
+
module: WinstonImplementationModule,
|
|
33
|
+
imports: options.imports || [],
|
|
34
|
+
providers: [
|
|
35
|
+
{
|
|
36
|
+
provide: "LOGGER_CONFIG",
|
|
37
|
+
useFactory: options.useFactory,
|
|
38
|
+
inject: options.inject || [],
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
provide: WINSTON_RAW,
|
|
42
|
+
useFactory: (config: LoggerConfig) => {
|
|
43
|
+
return this.createWinstonLogger(config);
|
|
44
|
+
},
|
|
45
|
+
inject: ["LOGGER_CONFIG"],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
provide: WINSTON_OBFUSCATOR,
|
|
49
|
+
useFactory: (config: LoggerConfig) => {
|
|
50
|
+
return (
|
|
51
|
+
config?.obfuscator ??
|
|
52
|
+
buildDefaultObfuscator(config?.obfuscatorKeys)
|
|
53
|
+
);
|
|
54
|
+
},
|
|
55
|
+
inject: ["LOGGER_CONFIG"],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
provide: WINSTON_LOGGER,
|
|
59
|
+
useClass: WinstonLoggerProvider,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
exports: [WINSTON_LOGGER],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private static createProviders(config?: LoggerConfig): Provider[] {
|
|
67
|
+
const winstonLogger = this.createWinstonLogger(config);
|
|
68
|
+
const obfuscator =
|
|
69
|
+
config?.obfuscator ?? buildDefaultObfuscator(config?.obfuscatorKeys);
|
|
70
|
+
|
|
71
|
+
return [
|
|
72
|
+
{
|
|
73
|
+
provide: WINSTON_RAW,
|
|
74
|
+
useValue: winstonLogger,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
provide: WINSTON_OBFUSCATOR,
|
|
78
|
+
useValue: obfuscator,
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
provide: WINSTON_LOGGER,
|
|
82
|
+
useClass: WinstonLoggerProvider,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private static createWinstonLogger(config?: LoggerConfig) {
|
|
88
|
+
const isProduction =
|
|
89
|
+
config?.isProduction ?? process.env.NODE_ENV === "production";
|
|
90
|
+
const useColors = config?.colorize !== false;
|
|
91
|
+
|
|
92
|
+
// Standard fields we want to ensure are in the log object
|
|
93
|
+
const standardFields = format((info) => {
|
|
94
|
+
// Extract requestId and context from meta if they exist there
|
|
95
|
+
const meta = info.meta as any;
|
|
96
|
+
if (meta) {
|
|
97
|
+
if (meta.requestId && !info.requestId) {
|
|
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;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const levelColorizer = format.colorize();
|
|
167
|
+
|
|
168
|
+
// 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
|
+
}
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const formats = [
|
|
272
|
+
format.timestamp(),
|
|
273
|
+
format.errors({ stack: true }),
|
|
274
|
+
format.splat(),
|
|
275
|
+
standardFields(),
|
|
276
|
+
];
|
|
277
|
+
|
|
278
|
+
if (isProduction) {
|
|
279
|
+
formats.push(format.json());
|
|
280
|
+
} else {
|
|
281
|
+
formats.push(developmentFormat);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const defaultFormat = format.combine(...formats);
|
|
285
|
+
|
|
286
|
+
const consoleTransport = new transports.Console({
|
|
287
|
+
format: defaultFormat,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const defaultOptions: LoggerOptions = {
|
|
291
|
+
level:
|
|
292
|
+
config?.level || (process.env.LOG_LEVEL as any) || DEFAULT_LOG_LEVEL,
|
|
293
|
+
format: defaultFormat,
|
|
294
|
+
transports: [consoleTransport],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
const mergedOptions: LoggerOptions = Object.assign(
|
|
298
|
+
{},
|
|
299
|
+
defaultOptions,
|
|
300
|
+
config?.loggerOptions || {},
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
return createLogger(mergedOptions);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Injectable, Inject } from "@nestjs/common";
|
|
2
|
+
import { Logger as WinstonLoggerType } from "winston";
|
|
3
|
+
import {
|
|
4
|
+
type LogPayload,
|
|
5
|
+
type LoggerProviderInterface,
|
|
6
|
+
LoggerLevel,
|
|
7
|
+
type LogParams,
|
|
8
|
+
} from "../../logger.interface";
|
|
9
|
+
import type { Obfuscator } from "./winston.logger.types";
|
|
10
|
+
import { getContext } from "../../context/async-context.service";
|
|
11
|
+
import { EMPTY_STRING } from "../../logger.constant";
|
|
12
|
+
import { WINSTON_RAW, WINSTON_OBFUSCATOR } from "./winston.logger.token";
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class WinstonLoggerProvider implements LoggerProviderInterface {
|
|
16
|
+
private context?: string;
|
|
17
|
+
|
|
18
|
+
constructor(
|
|
19
|
+
@Inject(WINSTON_RAW) private readonly logger: WinstonLoggerType,
|
|
20
|
+
@Inject(WINSTON_OBFUSCATOR) private readonly obfuscator?: Obfuscator,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
debug(payload: LogPayload): void;
|
|
24
|
+
debug(message: string, meta?: Record<string, unknown>, context?: string): void;
|
|
25
|
+
debug(messageOrPayload: string | LogPayload, meta?: Record<string, unknown>, context?: string): void {
|
|
26
|
+
this.handleLog(LoggerLevel.DEBUG, messageOrPayload, meta, context);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
info(payload: LogPayload): void;
|
|
30
|
+
info(message: string, meta?: Record<string, unknown>, context?: string): void;
|
|
31
|
+
info(messageOrPayload: string | LogPayload, meta?: Record<string, unknown>, context?: string): void {
|
|
32
|
+
this.handleLog(LoggerLevel.INFO, messageOrPayload, meta, context);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
warn(payload: LogPayload): void;
|
|
36
|
+
warn(message: string, meta?: Record<string, unknown>, context?: string): void;
|
|
37
|
+
warn(messageOrPayload: string | LogPayload, meta?: Record<string, unknown>, context?: string): void {
|
|
38
|
+
this.handleLog(LoggerLevel.WARN, messageOrPayload, meta, context);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
error(payload: LogPayload): void;
|
|
42
|
+
error(message: string, meta?: Record<string, unknown>, context?: string): void;
|
|
43
|
+
error(messageOrPayload: string | LogPayload, meta?: Record<string, unknown>, context?: string): void {
|
|
44
|
+
this.handleLog(LoggerLevel.ERROR, messageOrPayload, meta, context);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setContext(context: string): void {
|
|
48
|
+
this.context = context;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private handleLog(
|
|
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) {
|
|
77
|
+
const { level, payload } = params;
|
|
78
|
+
// Extract standard fields but keep the rest to pass to Winston
|
|
79
|
+
const { message, context, meta, ...rest } = payload as any;
|
|
80
|
+
|
|
81
|
+
const messageText = message ?? EMPTY_STRING;
|
|
82
|
+
const messageContext = context ?? this.context;
|
|
83
|
+
const obfuscatedMeta = this.obfuscator ? this.obfuscator(meta) : meta;
|
|
84
|
+
|
|
85
|
+
const requestContext = getContext();
|
|
86
|
+
const requestIdFromContext = (requestContext as Record<string, unknown> | undefined)?.requestId;
|
|
87
|
+
|
|
88
|
+
// Merge everything into a flat info object for Winston
|
|
89
|
+
const logInfo: Record<string, unknown> = {
|
|
90
|
+
...rest,
|
|
91
|
+
context: messageContext,
|
|
92
|
+
requestId: requestIdFromContext || payload.requestId,
|
|
93
|
+
meta: obfuscatedMeta,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
this.logger.log(level as unknown as string, messageText, logInfo);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { LoggerOptions } from "winston";
|
|
2
|
+
|
|
3
|
+
export interface ObfuscatorKey {
|
|
4
|
+
key: string;
|
|
5
|
+
obfuscator: Obfuscator;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface WinstonModuleConfig {
|
|
9
|
+
loggerOptions?: LoggerOptions;
|
|
10
|
+
obfuscator?: Obfuscator;
|
|
11
|
+
obfuscatorKeys?: Array<string | ObfuscatorKey>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Obfuscator = (value: unknown) => unknown;
|
|
15
|
+
|
|
16
|
+
export type SensitiveEntry = string | { key: string; obfuscator: Obfuscator };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { LoggerModule } from "./logger.module";
|
|
2
|
+
export { LOGGER_PROVIDER } from "./logger.token";
|
|
3
|
+
export type {
|
|
4
|
+
LoggerProviderInterface,
|
|
5
|
+
LogPayload,
|
|
6
|
+
LoggerLevel,
|
|
7
|
+
} from "./logger.interface";
|
|
8
|
+
export { RequestContextMiddleware } from "./middleware/request-context.middleware";
|
|
9
|
+
export { getContext, runWithContext } from "./context/async-context.service";
|
|
10
|
+
export type { LoggerConfig } from "./logger.config";
|
|
11
|
+
export { DEFAULT_LOGGER_CONFIG } from "./logger.config";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { WinstonModuleConfig } from "./implementations/winston/winston.logger.types";
|
|
2
|
+
import { LoggerLevel } from "./logger.interface";
|
|
3
|
+
|
|
4
|
+
export interface LoggerConfig extends WinstonModuleConfig {
|
|
5
|
+
/**
|
|
6
|
+
* Define se o provider do logger deve ser request-scoped
|
|
7
|
+
*/
|
|
8
|
+
requestScoped?: boolean;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Nível de log padrão (string compatível com winston)
|
|
12
|
+
*/
|
|
13
|
+
level?: LoggerLevel | string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Contexto padrão para os logs
|
|
17
|
+
*/
|
|
18
|
+
context?: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Define se o log deve ser formatado para produção (ex.: JSON)
|
|
22
|
+
*/
|
|
23
|
+
isProduction?: boolean;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Define se deve colorir a saída (útil para desenvolvimento local)
|
|
27
|
+
*/
|
|
28
|
+
colorize?: boolean;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Nome da aplicação para exibição nos logs
|
|
32
|
+
*/
|
|
33
|
+
appName?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Versão da aplicação para exibição nos logs
|
|
37
|
+
*/
|
|
38
|
+
appVersion?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Identificação da biblioteca/módulo que está gerando o log
|
|
42
|
+
*/
|
|
43
|
+
lib?: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Versão da biblioteca/módulo que está gerando o log
|
|
47
|
+
*/
|
|
48
|
+
libVersion?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const DEFAULT_LOGGER_CONFIG: LoggerConfig = {
|
|
52
|
+
requestScoped: false,
|
|
53
|
+
level: "info",
|
|
54
|
+
colorize: true,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default DEFAULT_LOGGER_CONFIG;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const DEFAULT_SENSITIVE_KEYS = [
|
|
2
|
+
"password",
|
|
3
|
+
"pass",
|
|
4
|
+
"pwd",
|
|
5
|
+
"token",
|
|
6
|
+
"accessToken",
|
|
7
|
+
"refreshToken",
|
|
8
|
+
"authorization",
|
|
9
|
+
"auth",
|
|
10
|
+
"secret",
|
|
11
|
+
"ssn",
|
|
12
|
+
"creditCard",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// constantes de contexto
|
|
16
|
+
export const EMPTY_STRING = "";
|
|
17
|
+
|
|
18
|
+
// máscara padrão usada para valores sensíveis
|
|
19
|
+
// objeto com constantes relacionadas à máscara de valores sensíveis
|
|
20
|
+
export const MASK = {
|
|
21
|
+
REPLACEMENT: "****",
|
|
22
|
+
MIN_LENGTH: 4,
|
|
23
|
+
EDGE_CHARS: 2,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
// exportações legadas para compatibilidade
|
|
27
|
+
export const MASK_REPLACEMENT = MASK.REPLACEMENT;
|
|
28
|
+
export const MASK_MIN_LENGTH = MASK.MIN_LENGTH;
|
|
29
|
+
export const MASK_EDGE_CHARS = MASK.EDGE_CHARS;
|
|
30
|
+
|
|
31
|
+
// separador usado no id fallback (Date.now() + separador + Math.random())
|
|
32
|
+
export const ID_FALLBACK_SEPARATOR = "-";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export enum LoggerLevel {
|
|
2
|
+
DEBUG = "debug",
|
|
3
|
+
INFO = "info",
|
|
4
|
+
WARN = "warn",
|
|
5
|
+
ERROR = "error",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface LogPayload {
|
|
9
|
+
message: string;
|
|
10
|
+
context?: string;
|
|
11
|
+
meta?: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface LoggerProviderInterface {
|
|
15
|
+
debug(payload: LogPayload): void;
|
|
16
|
+
info(payload: LogPayload): void;
|
|
17
|
+
warn(payload: LogPayload): void;
|
|
18
|
+
error(payload: LogPayload): void;
|
|
19
|
+
setContext?(context: string): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type LogParams = {
|
|
23
|
+
level: LoggerLevel;
|
|
24
|
+
payload: LogPayload;
|
|
25
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { DynamicModule, Module, Scope, Provider, Global } from "@nestjs/common";
|
|
2
|
+
import { LoggerProvider } from "./logger.provider";
|
|
3
|
+
import { LOGGER_PROVIDER } from "./logger.token";
|
|
4
|
+
import { WinstonImplementationModule } from "./implementations/winston/winston.logger.module";
|
|
5
|
+
import type { LoggerConfig } from "./logger.config";
|
|
6
|
+
|
|
7
|
+
@Global()
|
|
8
|
+
@Module({})
|
|
9
|
+
export class LoggerModule {
|
|
10
|
+
static forRoot(config?: LoggerConfig): DynamicModule {
|
|
11
|
+
const implModule = WinstonImplementationModule.forRoot(config);
|
|
12
|
+
const provider: Provider = {
|
|
13
|
+
provide: LOGGER_PROVIDER,
|
|
14
|
+
useClass: LoggerProvider,
|
|
15
|
+
};
|
|
16
|
+
if (config && config.requestScoped) {
|
|
17
|
+
provider.scope = Scope.REQUEST;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
module: LoggerModule,
|
|
22
|
+
imports: [implModule],
|
|
23
|
+
providers: [provider],
|
|
24
|
+
exports: [LOGGER_PROVIDER],
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static forRootAsync(options: {
|
|
29
|
+
imports?: any[];
|
|
30
|
+
useFactory: (...args: any[]) => Promise<LoggerConfig> | LoggerConfig;
|
|
31
|
+
inject?: any[];
|
|
32
|
+
}): DynamicModule {
|
|
33
|
+
return {
|
|
34
|
+
module: LoggerModule,
|
|
35
|
+
imports: [
|
|
36
|
+
...(options.imports || []),
|
|
37
|
+
WinstonImplementationModule.forRootAsync(options),
|
|
38
|
+
],
|
|
39
|
+
providers: [
|
|
40
|
+
{
|
|
41
|
+
provide: LOGGER_PROVIDER,
|
|
42
|
+
useClass: LoggerProvider,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
exports: [LOGGER_PROVIDER],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { LOGGER_PROVIDER } from "./logger.token";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Injectable, Inject } from "@nestjs/common";
|
|
2
|
+
import type { LoggerProviderInterface, LogPayload } from "./logger.interface";
|
|
3
|
+
import { WINSTON_LOGGER } from "./implementations/winston/winston.logger.token";
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class LoggerProvider implements LoggerProviderInterface {
|
|
7
|
+
constructor(
|
|
8
|
+
@Inject(WINSTON_LOGGER)
|
|
9
|
+
private readonly implementation: LoggerProviderInterface,
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
debug(payload: LogPayload): void;
|
|
13
|
+
debug(
|
|
14
|
+
message: string,
|
|
15
|
+
meta?: Record<string, unknown>,
|
|
16
|
+
context?: string,
|
|
17
|
+
): void;
|
|
18
|
+
debug(...args: unknown[]): void {
|
|
19
|
+
// @ts-ignore - delegate to implementation which supports overloads
|
|
20
|
+
return this.implementation.debug(...(args as any));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
info(payload: LogPayload): void;
|
|
24
|
+
info(message: string, meta?: Record<string, unknown>, context?: string): void;
|
|
25
|
+
info(...args: unknown[]): void {
|
|
26
|
+
// @ts-ignore
|
|
27
|
+
return this.implementation.info(...(args as any));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
warn(payload: LogPayload): void;
|
|
31
|
+
warn(message: string, meta?: Record<string, unknown>, context?: string): void;
|
|
32
|
+
warn(...args: unknown[]): void {
|
|
33
|
+
// @ts-ignore
|
|
34
|
+
return this.implementation.warn(...(args as any));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
error(payload: LogPayload): void;
|
|
38
|
+
error(
|
|
39
|
+
message: string,
|
|
40
|
+
meta?: Record<string, unknown>,
|
|
41
|
+
context?: string,
|
|
42
|
+
): void;
|
|
43
|
+
error(...args: unknown[]): void {
|
|
44
|
+
// @ts-ignore
|
|
45
|
+
return this.implementation.error(...(args as any));
|
|
46
|
+
}
|
|
47
|
+
setContext?(context: string): void {
|
|
48
|
+
if (typeof this.implementation.setContext === "function") {
|
|
49
|
+
return this.implementation.setContext(context);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const LOGGER_PROVIDER = "LOGGER_PROVIDER";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Injectable, NestMiddleware } from "@nestjs/common";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { asyncLocalStorage } from "../context/async-context.service";
|
|
4
|
+
import { HEADERS_PARAMS } from "../request-id.constants";
|
|
5
|
+
import { ID_FALLBACK_SEPARATOR } from "../logger.constant";
|
|
6
|
+
import type {
|
|
7
|
+
RequestLike,
|
|
8
|
+
ResponseLike,
|
|
9
|
+
NextFunctionLike,
|
|
10
|
+
} from "./request-context.types";
|
|
11
|
+
|
|
12
|
+
@Injectable()
|
|
13
|
+
export class RequestContextMiddleware implements NestMiddleware {
|
|
14
|
+
use(req: RequestLike, _res: ResponseLike, next: NextFunctionLike) {
|
|
15
|
+
const existing =
|
|
16
|
+
(req.headers?.[HEADERS_PARAMS.REQUEST_ID] as string) ||
|
|
17
|
+
(req.headers?.[HEADERS_PARAMS.FALLBACKS[0]] as string);
|
|
18
|
+
const id =
|
|
19
|
+
existing ||
|
|
20
|
+
(typeof randomUUID === "function"
|
|
21
|
+
? randomUUID()
|
|
22
|
+
: `${Date.now()}${ID_FALLBACK_SEPARATOR}${Math.random()}`);
|
|
23
|
+
// Run the rest of the request inside the async local storage context
|
|
24
|
+
asyncLocalStorage.run({ requestId: id }, () => next());
|
|
25
|
+
}
|
|
26
|
+
}
|