@globalart/nestjs-logger 1.0.4 โ†’ 1.0.6

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,7 +1,8 @@
1
- export declare const LOGGER_CONFIG_TOKEN = "LOGGER_CONFIG";
2
- export declare const LOGGER_CONTEXT_METADATA = "LOGGER_CONTEXT";
3
- export declare const LOGGER_METADATA_METADATA = "LOGGER_METADATA";
4
- export declare const LOGGER_EXCLUDE_METADATA = "LOGGER_EXCLUDE";
1
+ export declare const LOGGER_CONFIG_TOKEN: unique symbol;
2
+ export declare const LOGGER_SERVICE_TOKEN: unique symbol;
3
+ export declare const LOGGER_CONTEXT_METADATA: unique symbol;
4
+ export declare const LOGGER_METADATA_METADATA: unique symbol;
5
+ export declare const LOGGER_EXCLUDE_METADATA: unique symbol;
5
6
  export declare const DEFAULT_SENSITIVE_FIELDS: readonly ["password", "pass", "token", "accessToken", "refreshToken", "secret", "key", "apiKey", "authorization", "auth", "credential", "credentials"];
6
7
  export declare const COLORS: {
7
8
  readonly reset: "\u001B[0m";
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_LOGGER_CONFIG = exports.COLORS = exports.DEFAULT_SENSITIVE_FIELDS = exports.LOGGER_EXCLUDE_METADATA = exports.LOGGER_METADATA_METADATA = exports.LOGGER_CONTEXT_METADATA = exports.LOGGER_CONFIG_TOKEN = void 0;
4
- exports.LOGGER_CONFIG_TOKEN = "LOGGER_CONFIG";
5
- exports.LOGGER_CONTEXT_METADATA = "LOGGER_CONTEXT";
6
- exports.LOGGER_METADATA_METADATA = "LOGGER_METADATA";
7
- exports.LOGGER_EXCLUDE_METADATA = "LOGGER_EXCLUDE";
3
+ exports.DEFAULT_LOGGER_CONFIG = exports.COLORS = exports.DEFAULT_SENSITIVE_FIELDS = exports.LOGGER_EXCLUDE_METADATA = exports.LOGGER_METADATA_METADATA = exports.LOGGER_CONTEXT_METADATA = exports.LOGGER_SERVICE_TOKEN = exports.LOGGER_CONFIG_TOKEN = void 0;
4
+ exports.LOGGER_CONFIG_TOKEN = Symbol("LOGGER_CONFIG");
5
+ exports.LOGGER_SERVICE_TOKEN = Symbol("LOGGER_SERVICE");
6
+ exports.LOGGER_CONTEXT_METADATA = Symbol("LOGGER_CONTEXT");
7
+ exports.LOGGER_METADATA_METADATA = Symbol("LOGGER_METADATA");
8
+ exports.LOGGER_EXCLUDE_METADATA = Symbol("LOGGER_EXCLUDE");
8
9
  exports.DEFAULT_SENSITIVE_FIELDS = [
9
10
  "password",
10
11
  "pass",
@@ -18,5 +18,6 @@ export declare class HttpLoggerInterceptor implements NestInterceptor {
18
18
  private createLogEntry;
19
19
  private getClientIp;
20
20
  private sanitizeHeaders;
21
- private shouldExcludeUrl;
21
+ private getRequestMethod;
22
+ private shouldExcludeRequest;
22
23
  }
@@ -32,7 +32,8 @@ let HttpLoggerInterceptor = class HttpLoggerInterceptor {
32
32
  intercept(context, next) {
33
33
  const request = context.switchToHttp().getRequest();
34
34
  const response = context.switchToHttp().getResponse();
35
- if (this.shouldExcludeUrl(request.url)) {
35
+ const requestMethod = this.getRequestMethod(request.method);
36
+ if (this.shouldExcludeRequest(requestMethod, request.url)) {
36
37
  return next.handle();
37
38
  }
38
39
  const isExcluded = this.reflector.getAllAndOverride(constants_1.LOGGER_EXCLUDE_METADATA, [context.getHandler(), context.getClass()]);
@@ -130,14 +131,37 @@ let HttpLoggerInterceptor = class HttpLoggerInterceptor {
130
131
  }
131
132
  return this.dataSanitizer.sanitize(sanitized);
132
133
  }
133
- shouldExcludeUrl(url) {
134
- return this.config.exclude.some((excludeUrl) => {
135
- if (excludeUrl.includes("*")) {
136
- const pattern = excludeUrl.replace(/\*/g, ".*");
134
+ getRequestMethod(method) {
135
+ switch (method.toUpperCase()) {
136
+ case "GET":
137
+ return common_1.RequestMethod.GET;
138
+ case "POST":
139
+ return common_1.RequestMethod.POST;
140
+ case "PUT":
141
+ return common_1.RequestMethod.PUT;
142
+ case "DELETE":
143
+ return common_1.RequestMethod.DELETE;
144
+ case "PATCH":
145
+ return common_1.RequestMethod.PATCH;
146
+ case "OPTIONS":
147
+ return common_1.RequestMethod.OPTIONS;
148
+ case "HEAD":
149
+ return common_1.RequestMethod.HEAD;
150
+ default:
151
+ return common_1.RequestMethod.ALL;
152
+ }
153
+ }
154
+ shouldExcludeRequest(method, path) {
155
+ return this.config.exclude.some((excludeOption) => {
156
+ if (excludeOption.method !== method) {
157
+ return false;
158
+ }
159
+ if (excludeOption.path.includes("*")) {
160
+ const pattern = excludeOption.path.replace(/\*/g, ".*");
137
161
  const regex = new RegExp(`^${pattern}$`);
138
- return regex.test(url);
162
+ return regex.test(path);
139
163
  }
140
- return url === excludeUrl;
164
+ return path === excludeOption.path;
141
165
  });
142
166
  }
143
167
  };
@@ -0,0 +1 @@
1
+ export declare const InjectLogger: () => PropertyDecorator & ParameterDecorator;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InjectLogger = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const constants_1 = require("../constants");
6
+ const InjectLogger = () => (0, common_1.Inject)(constants_1.LOGGER_SERVICE_TOKEN);
7
+ exports.InjectLogger = InjectLogger;
@@ -1,4 +1,5 @@
1
1
  import { DynamicModule } from "@nestjs/common";
2
+ import { ExcludeOption } from "../types";
2
3
  export interface LoggerModuleOptions {
3
4
  level?: "error" | "warn" | "info" | "debug" | "verbose";
4
5
  timestamp?: boolean;
@@ -6,7 +7,7 @@ export interface LoggerModuleOptions {
6
7
  context?: string;
7
8
  format?: "json" | "text" | "pino";
8
9
  sensitiveFields?: string[];
9
- exclude?: string[];
10
+ exclude?: ExcludeOption[];
10
11
  }
11
12
  export interface LoggerModuleAsyncOptions {
12
13
  useFactory: (...args: any[]) => LoggerModuleOptions | Promise<LoggerModuleOptions>;
@@ -25,7 +25,7 @@ let LoggerModule = LoggerModule_1 = class LoggerModule {
25
25
  return {
26
26
  module: LoggerModule_1,
27
27
  providers,
28
- exports: [logger_service_1.LoggerService, http_logger_interceptor_1.HttpLoggerInterceptor],
28
+ exports: [constants_1.LOGGER_SERVICE_TOKEN, http_logger_interceptor_1.HttpLoggerInterceptor],
29
29
  global: true,
30
30
  };
31
31
  }
@@ -42,7 +42,7 @@ let LoggerModule = LoggerModule_1 = class LoggerModule {
42
42
  return {
43
43
  module: LoggerModule_1,
44
44
  providers,
45
- exports: [logger_service_1.LoggerService, http_logger_interceptor_1.HttpLoggerInterceptor],
45
+ exports: [constants_1.LOGGER_SERVICE_TOKEN, http_logger_interceptor_1.HttpLoggerInterceptor],
46
46
  global: true,
47
47
  };
48
48
  }
@@ -75,7 +75,7 @@ let LoggerModule = LoggerModule_1 = class LoggerModule {
75
75
  inject: [constants_1.LOGGER_CONFIG_TOKEN],
76
76
  },
77
77
  {
78
- provide: logger_service_1.LoggerService,
78
+ provide: constants_1.LOGGER_SERVICE_TOKEN,
79
79
  useFactory: (config, formatterFactory, writer, contextResolver) => {
80
80
  const formatter = formatterFactory.create(config.format, {
81
81
  colors: config.colors,
@@ -98,7 +98,7 @@ let LoggerModule = LoggerModule_1 = class LoggerModule {
98
98
  return new http_logger_interceptor_1.HttpLoggerInterceptor(logger, dataSanitizer, requestIdGenerator, config, reflector);
99
99
  },
100
100
  inject: [
101
- logger_service_1.LoggerService,
101
+ constants_1.LOGGER_SERVICE_TOKEN,
102
102
  data_sanitizer_1.DataSanitizer,
103
103
  request_id_generator_1.RequestIdGenerator,
104
104
  constants_1.LOGGER_CONFIG_TOKEN,
@@ -1,12 +1,13 @@
1
+ import { LOGGER_CONTEXT_METADATA, LOGGER_METADATA_METADATA, LOGGER_EXCLUDE_METADATA } from "../constants";
1
2
  /**
2
3
  * Decorator to set logging context for a class or method
3
4
  */
4
- export declare const LogContext: (context: string) => import("@nestjs/common").CustomDecorator<string>;
5
+ export declare const LogContext: (context: string) => import("@nestjs/common").CustomDecorator<typeof LOGGER_CONTEXT_METADATA>;
5
6
  /**
6
7
  * Decorator to add metadata to logs
7
8
  */
8
- export declare const LogMetadata: (metadata: Record<string, unknown>) => import("@nestjs/common").CustomDecorator<string>;
9
+ export declare const LogMetadata: (metadata: Record<string, unknown>) => import("@nestjs/common").CustomDecorator<typeof LOGGER_METADATA_METADATA>;
9
10
  /**
10
11
  * Decorator to exclude logging for a controller or method
11
12
  */
12
- export declare const ExcludeLogging: () => import("@nestjs/common").CustomDecorator<string>;
13
+ export declare const ExcludeLogging: () => import("@nestjs/common").CustomDecorator<typeof LOGGER_EXCLUDE_METADATA>;
@@ -9,4 +9,6 @@ export declare abstract class BaseFormatter implements ILogFormatter {
9
9
  protected formatTimestamp(timestamp: Date): string;
10
10
  protected colorize(text: string, color: keyof typeof COLORS): string;
11
11
  protected getColorForLevel(level: string | number): keyof typeof COLORS;
12
+ private getColorForStringLevel;
13
+ private getColorForNumericLevel;
12
14
  }
@@ -16,22 +16,21 @@ class BaseFormatter {
16
16
  }
17
17
  getColorForLevel(level) {
18
18
  if (typeof level === "string") {
19
- switch (level) {
20
- case "error":
21
- return "red";
22
- case "warn":
23
- return "yellow";
24
- case "info":
25
- return "green";
26
- case "debug":
27
- return "blue";
28
- case "verbose":
29
- return "magenta";
30
- default:
31
- return "gray";
32
- }
19
+ return this.getColorForStringLevel(level);
33
20
  }
34
- // ะ”ะปั ั‡ะธัะปะพะฒั‹ั… ัƒั€ะพะฒะฝะตะน (Pino)
21
+ return this.getColorForNumericLevel(level);
22
+ }
23
+ getColorForStringLevel(level) {
24
+ const colorMap = {
25
+ error: "red",
26
+ warn: "yellow",
27
+ info: "green",
28
+ debug: "blue",
29
+ verbose: "magenta",
30
+ };
31
+ return colorMap[level] || "gray";
32
+ }
33
+ getColorForNumericLevel(level) {
35
34
  if (level >= 50)
36
35
  return "red";
37
36
  if (level >= 40)
@@ -3,4 +3,6 @@ import { LogEntry, HttpRequestLogEntry } from "../types";
3
3
  export declare class JsonFormatter extends BaseFormatter {
4
4
  format(entry: LogEntry): string;
5
5
  formatHttpRequest(entry: HttpRequestLogEntry): string;
6
+ private buildLogObject;
7
+ private addOptionalFields;
6
8
  }
@@ -11,23 +11,35 @@ const common_1 = require("@nestjs/common");
11
11
  const base_formatter_1 = require("./base-formatter");
12
12
  let JsonFormatter = class JsonFormatter extends base_formatter_1.BaseFormatter {
13
13
  format(entry) {
14
- const logObject = {
14
+ const logObject = this.buildLogObject(entry);
15
+ return JSON.stringify(logObject);
16
+ }
17
+ formatHttpRequest(entry) {
18
+ const jsonString = JSON.stringify(entry);
19
+ return this.options.colors
20
+ ? this.colorize(jsonString, this.getColorForLevel(entry.level))
21
+ : jsonString;
22
+ }
23
+ buildLogObject(entry) {
24
+ const baseObject = {
15
25
  timestamp: this.formatTimestamp(entry.timestamp),
16
26
  level: entry.level,
17
27
  message: entry.message,
18
- ...(entry.context && { context: entry.context }),
19
- ...(entry.metadata && { metadata: entry.metadata }),
20
- ...(entry.trace && { trace: entry.trace }),
21
28
  };
22
- return JSON.stringify(logObject);
29
+ return this.addOptionalFields(baseObject, entry);
23
30
  }
24
- formatHttpRequest(entry) {
25
- const jsonString = JSON.stringify(entry);
26
- if (this.options.colors) {
27
- const color = this.getColorForLevel(entry.level);
28
- return this.colorize(jsonString, color);
31
+ addOptionalFields(baseObject, entry) {
32
+ const result = { ...baseObject };
33
+ if (entry.context) {
34
+ result.context = entry.context;
35
+ }
36
+ if (entry.metadata) {
37
+ result.metadata = entry.metadata;
38
+ }
39
+ if (entry.trace) {
40
+ result.trace = entry.trace;
29
41
  }
30
- return jsonString;
42
+ return result;
31
43
  }
32
44
  };
33
45
  exports.JsonFormatter = JsonFormatter;
@@ -15,11 +15,9 @@ let PinoFormatter = class PinoFormatter extends base_formatter_1.BaseFormatter {
15
15
  }
16
16
  formatHttpRequest(entry) {
17
17
  const jsonString = JSON.stringify(entry);
18
- if (this.options.colors) {
19
- const color = this.getColorForLevel(entry.level);
20
- return this.colorize(jsonString, color);
21
- }
22
- return jsonString;
18
+ return this.options.colors
19
+ ? this.colorize(jsonString, this.getColorForLevel(entry.level))
20
+ : jsonString;
23
21
  }
24
22
  };
25
23
  exports.PinoFormatter = PinoFormatter;
@@ -3,4 +3,12 @@ import { LogEntry, HttpRequestLogEntry } from "../types";
3
3
  export declare class TextFormatter extends BaseFormatter {
4
4
  format(entry: LogEntry): string;
5
5
  formatHttpRequest(entry: HttpRequestLogEntry): string;
6
+ private buildLogParts;
7
+ private addTimestamp;
8
+ private addLevel;
9
+ private addContext;
10
+ private addMessage;
11
+ private addMetadata;
12
+ private hasMetadata;
13
+ private addTrace;
6
14
  }
@@ -11,32 +11,54 @@ const common_1 = require("@nestjs/common");
11
11
  const base_formatter_1 = require("./base-formatter");
12
12
  let TextFormatter = class TextFormatter extends base_formatter_1.BaseFormatter {
13
13
  format(entry) {
14
+ const parts = this.buildLogParts(entry);
15
+ const result = parts.join(" ");
16
+ return entry.trace ? this.addTrace(result, entry.trace) : result;
17
+ }
18
+ formatHttpRequest(entry) {
19
+ return JSON.stringify(entry);
20
+ }
21
+ buildLogParts(entry) {
14
22
  const parts = [];
23
+ this.addTimestamp(parts, entry);
24
+ this.addLevel(parts, entry);
25
+ this.addContext(parts, entry);
26
+ this.addMessage(parts, entry);
27
+ this.addMetadata(parts, entry);
28
+ return parts;
29
+ }
30
+ addTimestamp(parts, entry) {
15
31
  if (this.options.timestamp) {
16
32
  const timestamp = this.colorize(`[${this.formatTimestamp(entry.timestamp)}]`, "gray");
17
33
  parts.push(timestamp);
18
34
  }
35
+ }
36
+ addLevel(parts, entry) {
19
37
  const level = this.colorize(`[${entry.level.toUpperCase()}]`, this.getColorForLevel(entry.level));
20
38
  parts.push(level);
39
+ }
40
+ addContext(parts, entry) {
21
41
  if (entry.context) {
22
42
  const context = this.colorize(`[${entry.context}]`, "cyan");
23
43
  parts.push(context);
24
44
  }
45
+ }
46
+ addMessage(parts, entry) {
25
47
  const message = this.colorize(entry.message, "bright");
26
48
  parts.push(message);
27
- if (entry.metadata && Object.keys(entry.metadata).length > 0) {
49
+ }
50
+ addMetadata(parts, entry) {
51
+ if (this.hasMetadata(entry)) {
28
52
  const metadata = this.colorize(JSON.stringify(entry.metadata), "gray");
29
53
  parts.push(metadata);
30
54
  }
31
- let result = parts.join(" ");
32
- if (entry.trace) {
33
- const trace = this.colorize(entry.trace, "red");
34
- result += `\n${trace}`;
35
- }
36
- return result;
37
55
  }
38
- formatHttpRequest(entry) {
39
- return JSON.stringify(entry);
56
+ hasMetadata(entry) {
57
+ return Boolean(entry.metadata && Object.keys(entry.metadata).length > 0);
58
+ }
59
+ addTrace(result, trace) {
60
+ const coloredTrace = this.colorize(trace, "red");
61
+ return `${result}\n${coloredTrace}`;
40
62
  }
41
63
  };
42
64
  exports.TextFormatter = TextFormatter;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { LoggerModule, LoggerModuleOptions, LoggerModuleAsyncOptions, } from "./core/logger.module";
2
+ export { InjectLogger } from "./core/logger.di-tokens";
2
3
  export { LoggerService } from "./core/logger.service";
3
4
  export { HttpLoggerInterceptor } from "./core/http-logger.interceptor";
4
5
  export { LogContext, LogMetadata, ExcludeLogging } from "./decorators";
package/dist/index.js CHANGED
@@ -14,10 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.ExcludeLogging = exports.LogMetadata = exports.LogContext = exports.HttpLoggerInterceptor = exports.LoggerService = exports.LoggerModule = void 0;
17
+ exports.ExcludeLogging = exports.LogMetadata = exports.LogContext = exports.HttpLoggerInterceptor = exports.LoggerService = exports.InjectLogger = exports.LoggerModule = void 0;
18
18
  // Core
19
19
  var logger_module_1 = require("./core/logger.module");
20
20
  Object.defineProperty(exports, "LoggerModule", { enumerable: true, get: function () { return logger_module_1.LoggerModule; } });
21
+ var logger_di_tokens_1 = require("./core/logger.di-tokens");
22
+ Object.defineProperty(exports, "InjectLogger", { enumerable: true, get: function () { return logger_di_tokens_1.InjectLogger; } });
21
23
  var logger_service_1 = require("./core/logger.service");
22
24
  Object.defineProperty(exports, "LoggerService", { enumerable: true, get: function () { return logger_service_1.LoggerService; } });
23
25
  var http_logger_interceptor_1 = require("./core/http-logger.interceptor");
@@ -1,3 +1,4 @@
1
+ import { RequestMethod } from "@nestjs/common";
1
2
  export type LogLevel = "error" | "warn" | "info" | "debug" | "verbose";
2
3
  export type LogFormat = "json" | "text" | "pino";
3
4
  export declare const LOG_LEVELS: Record<LogLevel, number>;
@@ -35,6 +36,10 @@ export interface HttpResponse {
35
36
  readonly statusCode: number;
36
37
  readonly headers: Record<string, string>;
37
38
  }
39
+ export interface ExcludeOption {
40
+ method: RequestMethod;
41
+ path: string;
42
+ }
38
43
  export interface LoggerConfiguration {
39
44
  readonly level: LogLevel;
40
45
  readonly timestamp: boolean;
@@ -42,7 +47,7 @@ export interface LoggerConfiguration {
42
47
  readonly context?: string;
43
48
  readonly format: LogFormat;
44
49
  readonly sensitiveFields: readonly string[];
45
- readonly exclude: readonly string[];
50
+ readonly exclude: readonly ExcludeOption[];
46
51
  }
47
52
  export interface FormatterOptions {
48
53
  readonly colors: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@globalart/nestjs-logger",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "A advanced logger for NestJS",
5
5
  "author": {
6
6
  "name": "GlobalArt, Inc"
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "@nestjs/common": "^11.1.3",
44
44
  "@nestjs/core": "^11.1.3",
45
- "@nestjs/microservices": "11.1.5",
45
+ "@nestjs/microservices": "11.1.6",
46
46
  "@nestjs/swagger": "^11.2.0",
47
47
  "@nestjs/testing": "^11.1.3",
48
48
  "@nestjs/typeorm": "^11.0.0"
package/README.md DELETED
@@ -1,364 +0,0 @@
1
- # @globalart/nestjs-logger
2
-
3
- A professional logging module for NestJS with clean architecture, strict typing, and extensibility.
4
-
5
- ## ๐Ÿš€ Features
6
-
7
- - **Clean Architecture** - separation of concerns, SOLID principles
8
- - **Strict Typing** - full TypeScript type safety
9
- - **Performance** - optimized architecture with minimal allocations
10
- - **Extensibility** - easy to add new formatters and transports
11
- - **Testability** - dependency injection, easy mocking
12
- - **Automatic Context** - class detection from call stack
13
- - **HTTP Logging** - detailed request logging with format consistency
14
- - **Security** - automatic sanitization of sensitive data
15
- - **Colored Output** - beautiful console logs
16
- - **Multiple Formats** - Text, JSON, and Pino support
17
-
18
- ## ๐Ÿ“ฆ Installation
19
-
20
- ```bash
21
- npm install @globalart/nestjs-logger
22
- ```
23
-
24
- ## ๐ŸŽฏ Quick Start
25
-
26
- ```typescript
27
- import { Module } from '@nestjs/common';
28
- import { LoggerModule } from '@globalart/nestjs-logger';
29
-
30
- @Module({
31
- imports: [
32
- LoggerModule.forRoot({
33
- level: 'info',
34
- timestamp: true,
35
- colors: true,
36
- format: 'text',
37
- }),
38
- ],
39
- })
40
- export class AppModule {}
41
- ```
42
-
43
- ## ๐Ÿ”ง Configuration
44
-
45
- ### Synchronous Configuration
46
-
47
- ```typescript
48
- LoggerModule.forRoot({
49
- level: 'debug',
50
- timestamp: true,
51
- colors: true,
52
- format: 'pino',
53
- sensitiveFields: ['password', 'secret'],
54
- exclude: ['/health', '/metrics'],
55
- })
56
- ```
57
-
58
- ### Asynchronous Configuration
59
-
60
- ```typescript
61
- LoggerModule.forRootAsync({
62
- useFactory: (configService: ConfigService) => ({
63
- level: configService.get('LOG_LEVEL', 'info'),
64
- format: configService.get('LOG_FORMAT', 'text'),
65
- colors: !configService.get('PRODUCTION'),
66
- sensitiveFields: configService.get('SENSITIVE_FIELDS', []),
67
- }),
68
- inject: [ConfigService],
69
- })
70
- ```
71
-
72
- ## ๐Ÿ“ Usage
73
-
74
- ### In Services
75
-
76
- ```typescript
77
- import { Injectable } from '@nestjs/common';
78
- import { LoggerService } from '@globalart/nestjs-logger';
79
-
80
- @Injectable()
81
- export class UserService {
82
- constructor(private readonly logger: LoggerService) {}
83
-
84
- async createUser(userData: CreateUserDto) {
85
- this.logger.log({
86
- message: 'Creating new user',
87
- metadata: { userId: userData.email }
88
- });
89
-
90
- try {
91
- const user = await this.userRepository.save(userData);
92
- this.logger.log({
93
- message: 'User created successfully',
94
- metadata: { id: user.id }
95
- });
96
- return user;
97
- } catch (error) {
98
- this.logger.error({
99
- message: 'Failed to create user',
100
- trace: error.stack,
101
- metadata: { email: userData.email }
102
- });
103
- throw error;
104
- }
105
- }
106
- }
107
- ```
108
-
109
- ### HTTP Logging
110
-
111
- ```typescript
112
- import { Controller, UseInterceptors } from '@nestjs/common';
113
- import { HttpLoggerInterceptor, LogContext } from '@globalart/nestjs-logger';
114
-
115
- @Controller('users')
116
- @UseInterceptors(HttpLoggerInterceptor)
117
- @LogContext('UserController')
118
- export class UserController {
119
- // All HTTP requests will be automatically logged
120
- }
121
- ```
122
-
123
- ### Global HTTP Logging
124
-
125
- ```typescript
126
- import { Module } from '@nestjs/common';
127
- import { APP_INTERCEPTOR } from '@nestjs/core';
128
- import { LoggerModule, HttpLoggerInterceptor } from '@globalart/nestjs-logger';
129
-
130
- @Module({
131
- imports: [LoggerModule.forRoot()],
132
- providers: [
133
- {
134
- provide: APP_INTERCEPTOR,
135
- useClass: HttpLoggerInterceptor,
136
- },
137
- ],
138
- })
139
- export class AppModule {}
140
- ```
141
-
142
- ## ๐ŸŽจ Output Formats
143
-
144
- ### Text Format (Default)
145
- ```
146
- [2024-01-15T10:30:45.123Z] [INFO] [UserService] Creating new user {"userId":"123"}
147
- [2024-01-15T10:30:45.335Z] [INFO] [HttpLogger] GET /users - 200 (12ms)
148
- ```
149
-
150
- ### JSON Format
151
- ```json
152
- {
153
- "timestamp": "2024-01-15T10:30:45.123Z",
154
- "level": "info",
155
- "message": "Creating new user",
156
- "context": "UserService",
157
- "metadata": {"userId": "123"}
158
- }
159
- {
160
- "timestamp": "2024-01-15T10:30:45.335Z",
161
- "level": "info",
162
- "message": "GET /users - 200 (12ms)",
163
- "context": "HttpLogger",
164
- "metadata": {
165
- "requestId": "req-123",
166
- "method": "GET",
167
- "url": "/users",
168
- "statusCode": 200,
169
- "responseTime": 12,
170
- "remoteAddress": "127.0.0.1"
171
- }
172
- }
173
- ```
174
-
175
- ### Pino Format
176
- ```json
177
- {"level":30,"time":1642247445123,"pid":1234,"hostname":"app-server","req":{"id":"req-123","method":"GET","url":"/users","query":{},"params":{},"headers":{},"remoteAddress":"127.0.0.1"},"res":{"statusCode":200,"headers":{}},"responseTime":12,"msg":"request completed"}
178
- ```
179
-
180
- ## ๐ŸŽฏ API Reference
181
-
182
- ### LoggerService
183
-
184
- | Method | Description |
185
- |--------|-------------|
186
- | `log(options: LogOptions)` | Information message |
187
- | `error(options: LogOptions)` | Error with trace |
188
- | `warn(options: LogOptions)` | Warning message |
189
- | `debug(options: LogOptions)` | Debug information |
190
- | `verbose(options: LogOptions)` | Verbose logging |
191
- | `setContext(context: string)` | Set context |
192
- | `logHttpRequest(entry: HttpRequestLogEntry)` | Log HTTP request (Pino format only) |
193
-
194
- ### LogOptions Interface
195
-
196
- ```typescript
197
- interface LogOptions {
198
- message: string;
199
- context?: string;
200
- metadata?: Record<string, unknown>;
201
- trace?: string;
202
- }
203
- ```
204
-
205
- ### Decorators
206
-
207
- - `@LogContext(context: string)` - Set context for class/method
208
- - `@LogMetadata(metadata: Record<string, unknown>)` - Add metadata to logs
209
- - `@ExcludeLogging()` - Exclude logging for controller/method
210
-
211
- ### Configuration Options
212
-
213
- | Option | Type | Default | Description |
214
- |--------|------|---------|-------------|
215
- | `level` | `LogLevel` | `'info'` | Logging level |
216
- | `timestamp` | `boolean` | `true` | Show timestamp |
217
- | `colors` | `boolean` | `true` | Colored output |
218
- | `format` | `LogFormat` | `'text'` | Output format |
219
- | `context` | `string` | `undefined` | Default context |
220
- | `sensitiveFields` | `string[]` | `[...]` | Fields to sanitize |
221
- | `exclude` | `string[]` | `[]` | URLs to exclude from HTTP logging |
222
-
223
- ## ๐Ÿ—๏ธ Architecture
224
-
225
- The package is built on Clean Architecture principles:
226
-
227
- ### Core Components
228
-
229
- - **LoggerModule** - Main module with configuration
230
- - **LoggerService** - Primary logging service
231
- - **HttpLoggerInterceptor** - HTTP request logging interceptor
232
-
233
- ### Formatters
234
-
235
- - **TextFormatter** - Human-readable text output
236
- - **JsonFormatter** - Structured JSON output
237
- - **PinoFormatter** - Pino-compatible JSON output
238
-
239
- ### Utilities
240
-
241
- - **ContextResolver** - Automatic context detection
242
- - **DataSanitizer** - Sensitive data sanitization
243
- - **RequestIdGenerator** - Unique request ID generation
244
-
245
- ### Writers
246
-
247
- - **ConsoleWriter** - Console output (extensible for other transports)
248
-
249
- ### Contracts & Types
250
-
251
- - **ILogger** - Logger interface
252
- - **ILogFormatter** - Formatter interface
253
- - **ILogWriter** - Writer interface
254
- - **LogEntry** - Standard log entry structure
255
- - **HttpRequestLogEntry** - HTTP-specific log entry structure
256
-
257
- ## ๐Ÿ”’ Security
258
-
259
- Automatic sanitization of sensitive fields:
260
- - `password`, `pass`
261
- - `token`, `accessToken`, `refreshToken`
262
- - `secret`, `key`, `apiKey`
263
- - `authorization`, `auth`
264
- - `credential`, `credentials`
265
-
266
- ## ๐Ÿงช Testing
267
-
268
- ```typescript
269
- import { LoggerService } from '@globalart/nestjs-logger';
270
-
271
- describe('UserService', () => {
272
- let service: UserService;
273
- let logger: jest.Mocked<LoggerService>;
274
-
275
- beforeEach(() => {
276
- logger = {
277
- log: jest.fn(),
278
- error: jest.fn(),
279
- warn: jest.fn(),
280
- debug: jest.fn(),
281
- verbose: jest.fn(),
282
- setContext: jest.fn(),
283
- logHttpRequest: jest.fn(),
284
- } as any;
285
-
286
- service = new UserService(logger);
287
- });
288
-
289
- it('should log user creation', () => {
290
- service.createUser(userData);
291
- expect(logger.log).toHaveBeenCalledWith({
292
- message: 'Creating new user',
293
- metadata: { userId: userData.email }
294
- });
295
- });
296
- });
297
- ```
298
-
299
- ## ๐Ÿ”„ Migration from v1
300
-
301
- ```typescript
302
- // v1 (old API)
303
- import { LoggerModule, LoggerInterceptor } from '@globalart/nestjs-logger';
304
-
305
- // v2 (new API)
306
- import { LoggerModule, HttpLoggerInterceptor } from '@globalart/nestjs-logger';
307
-
308
- // Legacy exports available for compatibility:
309
- import { LegacyLoggerModule, LoggerInterceptor } from '@globalart/nestjs-logger';
310
- ```
311
-
312
- ## ๐Ÿ“ˆ Performance
313
-
314
- - Minimal allocations in hot paths
315
- - Lazy formatter initialization
316
- - Optimized context resolution
317
- - Efficient data sanitization
318
- - Format-specific HTTP logging optimization
319
-
320
- ## ๐ŸŽจ Customization
321
-
322
- ### Custom Formatter
323
-
324
- ```typescript
325
- import { Injectable } from '@nestjs/common';
326
- import { BaseFormatter } from '@globalart/nestjs-logger';
327
-
328
- @Injectable()
329
- export class CustomFormatter extends BaseFormatter {
330
- format(entry: LogEntry): string {
331
- return `[${entry.level.toUpperCase()}] ${entry.message}`;
332
- }
333
-
334
- formatHttpRequest(entry: HttpRequestLogEntry): string {
335
- return JSON.stringify(entry);
336
- }
337
- }
338
- ```
339
-
340
- ### Custom Writer
341
-
342
- ```typescript
343
- import { Injectable } from '@nestjs/common';
344
- import { ILogWriter } from '@globalart/nestjs-logger';
345
-
346
- @Injectable()
347
- export class FileWriter implements ILogWriter {
348
- write(formattedLog: string): void {
349
- // Write to file
350
- }
351
- }
352
- ```
353
-
354
- ## ๐Ÿค Contributing
355
-
356
- 1. Fork the repository
357
- 2. Create a feature branch
358
- 3. Make your changes
359
- 4. Add tests
360
- 5. Create a Pull Request
361
-
362
- ## ๐Ÿ“„ License
363
-
364
- MIT License