@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.
@@ -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
- // 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;
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 = Object.assign(
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 LogPayload,
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 LogParams,
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: 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);
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: 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);
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: 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);
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: 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);
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 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) {
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 = (requestContext as Record<string, unknown> | undefined)?.requestId;
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 || payload.requestId,
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";
@@ -0,0 +1,6 @@
1
+ import { SetMetadata } from "@nestjs/common";
2
+
3
+ export const EXCLUDE_HTTP_LOGGING_KEY = "EXCLUDE_HTTP_LOGGING";
4
+
5
+ export const ExcludeHttpLogging = () =>
6
+ SetMetadata(EXCLUDE_HTTP_LOGGING_KEY, true);
@@ -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: error instanceof Error ? error.message : String(error),
112
+ error: this.toErrorMessage(error),
77
113
  },
78
114
  },
79
115
  });
@@ -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 = {