@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.
@@ -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,3 @@
1
+ export const WINSTON_LOGGER = "WINSTON_LOGGER";
2
+ export const WINSTON_RAW = "WINSTON_RAW";
3
+ export const WINSTON_OBFUSCATOR = "WINSTON_OBFUSCATOR";
@@ -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
+ }