@jnpt/nest-logging 0.0.0-stage → 1.1.0

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,37 @@
1
+ import { type Config, type ConfigInput, type RequestInfo, type ResponseInfo } from './types';
2
+ export interface Writer {
3
+ write(chunk: string): unknown;
4
+ }
5
+ export declare function configureDefaults(config?: ConfigInput | null): void;
6
+ export declare function getDefaultConfig(): Config;
7
+ export declare class AppLogger {
8
+ private readonly name;
9
+ private readonly ownConfig?;
10
+ private output;
11
+ private maskingFor?;
12
+ private maskingUtil?;
13
+ constructor(name: string, config?: ConfigInput | null);
14
+ get config(): Config;
15
+ private get masking();
16
+ maskUrl(url: string): string;
17
+ setOutput(w: Writer): void;
18
+ debug(message: string, ...extra: unknown[]): void;
19
+ info(message: string, ...extra: unknown[]): void;
20
+ warn(message: string, ...extra: unknown[]): void;
21
+ warnError(message: string, err: unknown, ...extra: unknown[]): void;
22
+ error(message: string, ...extra: unknown[]): void;
23
+ errorWithErr(message: string, err: unknown, ...extra: unknown[]): void;
24
+ logIncomingHttp(method: string, uri: string, statusCode: number, durationMs: number, request?: RequestInfo, response?: ResponseInfo, remoteAddr?: string, userAgent?: string): void;
25
+ logOutgoingHttp(method: string, uri: string, statusCode: number, durationMs: number, request?: RequestInfo, response?: ResponseInfo): void;
26
+ private logApplicationIfEnabled;
27
+ private logRequest;
28
+ private baseEntry;
29
+ private buildRequestBody;
30
+ private buildResponseBody;
31
+ private buildErrorInfo;
32
+ private writeLog;
33
+ private isApplicationLoggingEnabled;
34
+ private isRequestLoggingEnabled;
35
+ private isLevelEnabled;
36
+ }
37
+ export declare function mergeExtras(extras: unknown[]): Record<string, unknown> | undefined;
@@ -0,0 +1,258 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppLogger = void 0;
4
+ exports.configureDefaults = configureDefaults;
5
+ exports.getDefaultConfig = getDefaultConfig;
6
+ exports.mergeExtras = mergeExtras;
7
+ const node_worker_threads_1 = require("node:worker_threads");
8
+ const context_1 = require("./context");
9
+ const masking_1 = require("./masking");
10
+ const types_1 = require("./types");
11
+ let globalConfig = (0, types_1.defaultConfig)();
12
+ // configureDefaults sets default configuration for all loggers.
13
+ // Accepts a full or partial config (missing fields use defaultConfig()); undefined/null is ignored.
14
+ function configureDefaults(config) {
15
+ if (config)
16
+ globalConfig = (0, types_1.buildConfig)(config);
17
+ }
18
+ // getDefaultConfig returns the default configuration
19
+ function getDefaultConfig() {
20
+ return globalConfig;
21
+ }
22
+ const threadName = node_worker_threads_1.isMainThread ? 'main' : `worker-${node_worker_threads_1.threadId}`;
23
+ // AppLogger is the main structured logger
24
+ class AppLogger {
25
+ name;
26
+ ownConfig;
27
+ output = process.stdout;
28
+ maskingFor;
29
+ maskingUtil;
30
+ // new AppLogger(name) follows the global default config (configureDefaults);
31
+ // new AppLogger(name, config) uses its own config (partial allowed, merged onto defaultConfig()).
32
+ constructor(name, config) {
33
+ this.name = name;
34
+ if (config)
35
+ this.ownConfig = (0, types_1.buildConfig)(config);
36
+ }
37
+ get config() {
38
+ return this.ownConfig ?? getDefaultConfig();
39
+ }
40
+ get masking() {
41
+ const config = this.config;
42
+ if (this.maskingFor !== config) {
43
+ this.maskingUtil = new masking_1.MaskingUtil(config);
44
+ this.maskingFor = config;
45
+ }
46
+ return this.maskingUtil;
47
+ }
48
+ // maskUrl masks sensitive query parameters in a URL using this logger's maskedFields
49
+ maskUrl(url) {
50
+ return this.masking.maskUrl(url);
51
+ }
52
+ // setOutput sets the output writer (default: process.stdout)
53
+ setOutput(w) {
54
+ this.output = w;
55
+ }
56
+ // debug logs a debug message
57
+ debug(message, ...extra) {
58
+ this.logApplicationIfEnabled('DEBUG', message, undefined, extra);
59
+ }
60
+ // info logs an info message
61
+ info(message, ...extra) {
62
+ this.logApplicationIfEnabled('INFO', message, undefined, extra);
63
+ }
64
+ // warn logs a warning message
65
+ warn(message, ...extra) {
66
+ this.logApplicationIfEnabled('WARN', message, undefined, extra);
67
+ }
68
+ // warnError logs a warning message with error
69
+ warnError(message, err, ...extra) {
70
+ this.logApplicationIfEnabled('WARN', message, err, extra);
71
+ }
72
+ // error logs an error message
73
+ error(message, ...extra) {
74
+ this.logApplicationIfEnabled('ERROR', message, undefined, extra);
75
+ }
76
+ // errorWithErr logs an error message with error
77
+ errorWithErr(message, err, ...extra) {
78
+ this.logApplicationIfEnabled('ERROR', message, err, extra);
79
+ }
80
+ // logIncomingHttp logs incoming request and response as a single entry
81
+ logIncomingHttp(method, uri, statusCode, durationMs, request, response, remoteAddr = '', userAgent = '') {
82
+ if (!this.isRequestLoggingEnabled())
83
+ return;
84
+ this.logRequest(getStatusLevel(statusCode), 'Incoming request', method, uri, statusCode, durationMs, remoteAddr, userAgent, request, response);
85
+ }
86
+ // logOutgoingHttp logs outgoing request and response as a single entry
87
+ logOutgoingHttp(method, uri, statusCode, durationMs, request, response) {
88
+ if (!this.isRequestLoggingEnabled())
89
+ return;
90
+ this.logRequest(getStatusLevel(statusCode), 'Outgoing request', method, this.maskUrl(uri), statusCode, durationMs, '', '', request, response, { direction: 'outgoing' });
91
+ }
92
+ logApplicationIfEnabled(level, message, err, extra) {
93
+ if (!this.isApplicationLoggingEnabled() || !this.isLevelEnabled(level))
94
+ return;
95
+ const entry = this.baseEntry(level, message, types_1.LogTypeApplication);
96
+ entry.request_body = {};
97
+ entry.response_body = {};
98
+ if (err !== undefined && err !== null && this.config.applicationLogging.includeStackTrace) {
99
+ entry.error = this.buildErrorInfo(err);
100
+ }
101
+ entry.extra = mergeExtras(extra);
102
+ this.writeLog(entry);
103
+ }
104
+ logRequest(level, message, method, uri, statusCode, durationMs, remoteAddr, userAgent, request, response, extra) {
105
+ const entry = this.baseEntry(level, message, types_1.LogTypeRequest);
106
+ entry.method = method;
107
+ entry.uri = uri;
108
+ entry.status_code = statusCode;
109
+ entry.duration_ms = durationMs;
110
+ entry.remote_address = remoteAddr || undefined;
111
+ entry.user_agent = userAgent || undefined;
112
+ entry.request_body = request ? this.buildRequestBody(request) : {};
113
+ entry.response_body = response ? this.buildResponseBody(response) : {};
114
+ entry.extra = extra;
115
+ this.writeLog(entry);
116
+ }
117
+ baseEntry(level, message, type) {
118
+ // Property order here is the JSON field order.
119
+ return {
120
+ '@timestamp': (0, types_1.formatTimestamp)(new Date()),
121
+ '@version': '1',
122
+ application: this.config.applicationName,
123
+ message,
124
+ logger_name: this.name,
125
+ thread_name: threadName,
126
+ level,
127
+ level_value: types_1.LevelValue[level] ?? 0,
128
+ type,
129
+ correlation_id: (0, context_1.getCorrelationId)(),
130
+ method: '',
131
+ uri: '',
132
+ status_code: undefined,
133
+ duration_ms: undefined,
134
+ remote_address: undefined,
135
+ user_agent: undefined,
136
+ request_body: undefined,
137
+ response_body: undefined,
138
+ error: undefined,
139
+ extra: undefined,
140
+ };
141
+ }
142
+ buildRequestBody(req) {
143
+ const cfg = this.config.requestLogging;
144
+ const body = {};
145
+ if (cfg.logHeaders && req.headers)
146
+ body.headers = this.masking.maskHeaders(req.headers);
147
+ if (req.queryParams)
148
+ body.query_params = this.masking.maskBody(req.queryParams);
149
+ if (req.pathParams)
150
+ body.path_params = req.pathParams;
151
+ if (cfg.logBody && req.body !== undefined && req.body !== null)
152
+ body.body = this.masking.maskBody(req.body);
153
+ if (req.contentType)
154
+ body.content_type = req.contentType;
155
+ if (req.contentLength && req.contentLength > 0)
156
+ body.content_length = req.contentLength;
157
+ return body;
158
+ }
159
+ buildResponseBody(res) {
160
+ const cfg = this.config.requestLogging;
161
+ const body = {};
162
+ if (cfg.logHeaders && res.headers)
163
+ body.headers = this.masking.maskHeaders(res.headers);
164
+ if (cfg.logResponseBody && res.body !== undefined && res.body !== null)
165
+ body.body = this.masking.maskBody(res.body);
166
+ if (res.contentType)
167
+ body.content_type = res.contentType;
168
+ if (res.contentLength && res.contentLength > 0)
169
+ body.content_length = res.contentLength;
170
+ return body;
171
+ }
172
+ buildErrorInfo(err) {
173
+ const info = {
174
+ class: err instanceof Error ? err.constructor?.name || err.name : typeof err,
175
+ message: err instanceof Error ? err.message : String(err),
176
+ };
177
+ if (this.config.applicationLogging.includeStackTrace) {
178
+ const stack = err instanceof Error && err.stack ? err.stack : new Error().stack;
179
+ info.stack_trace = parseStack(stack, this.config.applicationLogging.maxStackTraceDepth);
180
+ }
181
+ return info;
182
+ }
183
+ writeLog(entry) {
184
+ let data;
185
+ try {
186
+ data = JSON.stringify(entry);
187
+ }
188
+ catch (err) {
189
+ this.output.write(`Failed to marshal log entry: ${err instanceof Error ? err.message : String(err)}\n`);
190
+ return;
191
+ }
192
+ this.output.write(data + '\n');
193
+ }
194
+ isApplicationLoggingEnabled() {
195
+ return this.config.enabled && this.config.applicationLogging.enabled;
196
+ }
197
+ isRequestLoggingEnabled() {
198
+ return this.config.enabled && this.config.requestLogging.enabled;
199
+ }
200
+ // isLevelEnabled checks if the given level should be logged based on minLevel
201
+ isLevelEnabled(level) {
202
+ const minLevelValue = types_1.LevelValue[this.config.minLevel] ?? types_1.LevelValue.DEBUG; // default to DEBUG if invalid
203
+ const levelValue = types_1.LevelValue[level];
204
+ if (levelValue === undefined)
205
+ return true; // log unknown levels
206
+ return levelValue >= minLevelValue;
207
+ }
208
+ }
209
+ exports.AppLogger = AppLogger;
210
+ function getStatusLevel(statusCode) {
211
+ if (statusCode >= 500)
212
+ return 'ERROR';
213
+ if (statusCode >= 400)
214
+ return 'WARN';
215
+ return 'INFO';
216
+ }
217
+ // parseStack turns " at fn (file:line:col)" lines into trimmed frames, up to maxDepth
218
+ function parseStack(stack, maxDepth) {
219
+ if (!stack)
220
+ return undefined;
221
+ const frames = stack
222
+ .split('\n')
223
+ .map((line) => line.trim())
224
+ .filter((line) => line.startsWith('at '))
225
+ .map((line) => line.slice(3))
226
+ .slice(0, Math.max(0, maxDepth));
227
+ return frames.length > 0 ? frames : undefined;
228
+ }
229
+ // mergeExtras converts any value (object or class instance) to a plain object and merges them.
230
+ // Values that are not objects after JSON round-trip (arrays, primitives) are stored under "data".
231
+ function mergeExtras(extras) {
232
+ if (extras.length === 0)
233
+ return undefined;
234
+ const result = {};
235
+ for (const extra of extras) {
236
+ if (extra === undefined || extra === null)
237
+ continue;
238
+ if ((0, masking_1.isPlainObject)(extra)) {
239
+ Object.assign(result, extra);
240
+ continue;
241
+ }
242
+ let parsed;
243
+ try {
244
+ const json = JSON.stringify(extra);
245
+ if (json === undefined)
246
+ continue;
247
+ parsed = JSON.parse(json);
248
+ }
249
+ catch {
250
+ continue;
251
+ }
252
+ if ((0, masking_1.isPlainObject)(parsed))
253
+ Object.assign(result, parsed);
254
+ else
255
+ result.data = extra;
256
+ }
257
+ return Object.keys(result).length > 0 ? result : undefined;
258
+ }
@@ -0,0 +1,12 @@
1
+ import type { Config } from './types';
2
+ export declare class MaskingUtil {
3
+ private readonly maskedHeaders;
4
+ private readonly maskedFields;
5
+ private readonly maskValue;
6
+ constructor(config: Config);
7
+ maskHeaders(headers?: Record<string, string>): Record<string, string> | undefined;
8
+ maskUrl(url: string): string;
9
+ maskBody(body: unknown): unknown;
10
+ private maskValueRecursive;
11
+ }
12
+ export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MaskingUtil = void 0;
4
+ exports.isPlainObject = isPlainObject;
5
+ // MaskingUtil provides utilities for masking sensitive data
6
+ class MaskingUtil {
7
+ maskedHeaders;
8
+ maskedFields;
9
+ maskValue;
10
+ constructor(config) {
11
+ this.maskedHeaders = new Set(config.maskedHeaders.map((h) => h.toLowerCase()));
12
+ this.maskedFields = new Set(config.maskedFields.map((f) => f.toLowerCase()));
13
+ this.maskValue = config.maskValue;
14
+ }
15
+ // maskHeaders masks sensitive headers (case-insensitive)
16
+ maskHeaders(headers) {
17
+ if (!headers)
18
+ return undefined;
19
+ const masked = {};
20
+ for (const [key, value] of Object.entries(headers)) {
21
+ masked[key] = this.maskedHeaders.has(key.toLowerCase()) ? this.maskValue : value;
22
+ }
23
+ return masked;
24
+ }
25
+ // maskUrl masks query parameters whose name is a masked field (case-insensitive).
26
+ // Returns the URL unchanged when nothing matches or it cannot be parsed.
27
+ maskUrl(url) {
28
+ let parsed;
29
+ try {
30
+ parsed = new URL(url);
31
+ }
32
+ catch {
33
+ return url;
34
+ }
35
+ let changed = false;
36
+ for (const key of new Set(parsed.searchParams.keys())) {
37
+ if (this.maskedFields.has(key.toLowerCase())) {
38
+ parsed.searchParams.set(key, this.maskValue);
39
+ changed = true;
40
+ }
41
+ }
42
+ return changed ? parsed.toString() : url;
43
+ }
44
+ // maskBody masks sensitive fields in body, recursively through objects and arrays (case-insensitive)
45
+ maskBody(body) {
46
+ if (body === undefined || body === null)
47
+ return body;
48
+ return this.maskValueRecursive(body);
49
+ }
50
+ maskValueRecursive(value) {
51
+ if (Array.isArray(value))
52
+ return value.map((item) => this.maskValueRecursive(item));
53
+ if (isPlainObject(value)) {
54
+ const masked = {};
55
+ for (const [key, v] of Object.entries(value)) {
56
+ masked[key] = this.maskedFields.has(key.toLowerCase()) ? this.maskValue : this.maskValueRecursive(v);
57
+ }
58
+ return masked;
59
+ }
60
+ return value;
61
+ }
62
+ }
63
+ exports.MaskingUtil = MaskingUtil;
64
+ function isPlainObject(value) {
65
+ if (value === null || typeof value !== 'object')
66
+ return false;
67
+ const proto = Object.getPrototypeOf(value);
68
+ return proto === Object.prototype || proto === null;
69
+ }
@@ -0,0 +1,77 @@
1
+ export type LogType = 'application' | 'request';
2
+ export declare const LogTypeApplication: LogType;
3
+ export declare const LogTypeRequest: LogType;
4
+ export interface LogEntry {
5
+ '@timestamp': string;
6
+ '@version': string;
7
+ application: string;
8
+ message: string;
9
+ logger_name: string;
10
+ thread_name: string;
11
+ level: string;
12
+ level_value: number;
13
+ type: LogType;
14
+ correlation_id: string;
15
+ method: string;
16
+ uri: string;
17
+ status_code?: number;
18
+ duration_ms?: number;
19
+ remote_address?: string;
20
+ user_agent?: string;
21
+ request_body: unknown;
22
+ response_body: unknown;
23
+ error?: ErrorInfo;
24
+ extra?: Record<string, unknown>;
25
+ }
26
+ export interface ErrorInfo {
27
+ class: string;
28
+ message: string;
29
+ stack_trace?: string[];
30
+ root_cause?: string;
31
+ }
32
+ export interface RequestInfo {
33
+ headers?: Record<string, string>;
34
+ queryParams?: Record<string, string>;
35
+ pathParams?: Record<string, string>;
36
+ body?: unknown;
37
+ contentType?: string;
38
+ contentLength?: number;
39
+ }
40
+ export interface ResponseInfo {
41
+ headers?: Record<string, string>;
42
+ body?: unknown;
43
+ contentType?: string;
44
+ contentLength?: number;
45
+ }
46
+ export interface Config {
47
+ applicationName: string;
48
+ enabled: boolean;
49
+ minLevel: string;
50
+ requestLogging: RequestLoggingConfig;
51
+ applicationLogging: ApplicationLoggingConfig;
52
+ maskedHeaders: string[];
53
+ maskedFields: string[];
54
+ maskValue: string;
55
+ }
56
+ export interface RequestLoggingConfig {
57
+ enabled: boolean;
58
+ logHeaders: boolean;
59
+ logBody: boolean;
60
+ logResponseBody: boolean;
61
+ maxBodySize: number;
62
+ excludePatterns: string[];
63
+ includePatterns: string[];
64
+ }
65
+ export interface ApplicationLoggingConfig {
66
+ enabled: boolean;
67
+ includeStackTrace: boolean;
68
+ maxStackTraceDepth: number;
69
+ }
70
+ export type ConfigInput = Partial<Omit<Config, 'requestLogging' | 'applicationLogging'>> & {
71
+ requestLogging?: Partial<RequestLoggingConfig>;
72
+ applicationLogging?: Partial<ApplicationLoggingConfig>;
73
+ };
74
+ export declare function defaultConfig(): Config;
75
+ export declare function buildConfig(input?: ConfigInput): Config;
76
+ export declare const LevelValue: Readonly<Record<string, number>>;
77
+ export declare function formatTimestamp(d: Date): string;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LevelValue = exports.LogTypeRequest = exports.LogTypeApplication = void 0;
4
+ exports.defaultConfig = defaultConfig;
5
+ exports.buildConfig = buildConfig;
6
+ exports.formatTimestamp = formatTimestamp;
7
+ exports.LogTypeApplication = 'application';
8
+ exports.LogTypeRequest = 'request';
9
+ // defaultConfig returns default configuration
10
+ function defaultConfig() {
11
+ return {
12
+ applicationName: 'application',
13
+ enabled: true,
14
+ minLevel: 'DEBUG',
15
+ requestLogging: {
16
+ enabled: true,
17
+ logHeaders: true,
18
+ logBody: true,
19
+ logResponseBody: true,
20
+ maxBodySize: 10240,
21
+ excludePatterns: ['/health', '/metrics', '/favicon.ico'],
22
+ includePatterns: [],
23
+ },
24
+ applicationLogging: {
25
+ enabled: true,
26
+ includeStackTrace: true,
27
+ maxStackTraceDepth: 50,
28
+ },
29
+ maskedHeaders: ['Authorization', 'X-Api-Key', 'Cookie', 'Set-Cookie'],
30
+ maskedFields: ['password', 'secret', 'token', 'creditCard', 'ssn'],
31
+ maskValue: '***MASKED***',
32
+ };
33
+ }
34
+ // buildConfig merges a partial config onto defaultConfig()
35
+ function buildConfig(input) {
36
+ const base = defaultConfig();
37
+ if (!input)
38
+ return base;
39
+ return {
40
+ ...base,
41
+ ...input,
42
+ requestLogging: { ...base.requestLogging, ...input.requestLogging },
43
+ applicationLogging: { ...base.applicationLogging, ...input.applicationLogging },
44
+ };
45
+ }
46
+ // LevelValue maps log level to numeric value
47
+ exports.LevelValue = {
48
+ TRACE: 5000,
49
+ DEBUG: 10000,
50
+ INFO: 20000,
51
+ WARN: 30000,
52
+ ERROR: 40000,
53
+ };
54
+ // formatTimestamp formats time to ISO 8601 with local timezone, e.g. 2006-01-02T15:04:05.000+07:00
55
+ function formatTimestamp(d) {
56
+ const pad = (n, w = 2) => String(Math.abs(n)).padStart(w, '0');
57
+ const offset = -d.getTimezoneOffset();
58
+ const sign = offset >= 0 ? '+' : '-';
59
+ return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +
60
+ `T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}` +
61
+ `${sign}${pad(Math.trunc(offset / 60))}:${pad(offset % 60)}`);
62
+ }
@@ -0,0 +1,15 @@
1
+ import type { DynamicModule, MiddlewareConsumer, NestModule } from '@nestjs/common';
2
+ import type { ConfigInput } from './logger/types';
3
+ interface HttpAdapterHostLike {
4
+ httpAdapter?: {
5
+ getType?(): string;
6
+ getInstance?(): unknown;
7
+ };
8
+ }
9
+ export declare class LoggingModule implements NestModule {
10
+ private readonly adapterHost?;
11
+ constructor(adapterHost?: HttpAdapterHostLike | undefined);
12
+ static forRoot(config?: ConfigInput | null): DynamicModule;
13
+ configure(consumer: MiddlewareConsumer): void;
14
+ }
15
+ export {};
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoggingModule = void 0;
4
+ const fastify_logging_1 = require("./fastify/fastify-logging");
5
+ const logger_1 = require("./logger/logger");
6
+ const logging_middleware_1 = require("./middleware/logging.middleware");
7
+ // LoggingModule.forRoot(config?) sets the global default config and logs every incoming request:
8
+ // Express apps get LoggingMiddleware on all routes, Fastify apps (FastifyAdapter) get Fastify hooks.
9
+ // @nestjs/core is only loaded lazily (no static runtime import), so it works with both
10
+ // CommonJS (Nest <= 11) and ESM (Nest 12+) applications.
11
+ class LoggingModule {
12
+ adapterHost;
13
+ constructor(adapterHost) {
14
+ this.adapterHost = adapterHost;
15
+ }
16
+ static forRoot(config) {
17
+ (0, logger_1.configureDefaults)(config);
18
+ injectHttpAdapterHost();
19
+ return { module: LoggingModule };
20
+ }
21
+ configure(consumer) {
22
+ const adapter = this.adapterHost?.httpAdapter;
23
+ if (adapter?.getType?.() === 'fastify') {
24
+ (0, fastify_logging_1.registerFastifyLogging)(adapter.getInstance(), new logger_1.AppLogger('LoggingMiddleware'));
25
+ return;
26
+ }
27
+ consumer.apply(logging_middleware_1.LoggingMiddleware).forRoutes('*');
28
+ }
29
+ }
30
+ exports.LoggingModule = LoggingModule;
31
+ let injected = false;
32
+ // Declares the constructor dependency (what @Inject(HttpAdapterHost) would emit).
33
+ function injectHttpAdapterHost() {
34
+ if (injected)
35
+ return;
36
+ injected = true;
37
+ const reflect = Reflect;
38
+ try {
39
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
40
+ const { HttpAdapterHost } = require('@nestjs/core');
41
+ reflect.defineMetadata?.('design:paramtypes', [HttpAdapterHost], LoggingModule);
42
+ }
43
+ catch {
44
+ // @nestjs/core not resolvable: fall back to the Express middleware only
45
+ }
46
+ }
@@ -0,0 +1,18 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { NestMiddleware } from '@nestjs/common';
3
+ import { AppLogger } from '../logger/logger';
4
+ import { type ConfigInput } from '../logger/types';
5
+ export interface HttpRequest extends IncomingMessage {
6
+ originalUrl?: string;
7
+ ip?: string;
8
+ params?: Record<string, unknown>;
9
+ body?: unknown;
10
+ rawBody?: Buffer;
11
+ }
12
+ export type HttpResponse = ServerResponse;
13
+ export type NextFunction = (err?: unknown) => void;
14
+ export declare function createLoggingMiddleware(log: AppLogger, config?: ConfigInput | null): (req: HttpRequest, res: HttpResponse, next: NextFunction) => void;
15
+ export declare class LoggingMiddleware implements NestMiddleware {
16
+ private readonly handler;
17
+ use(req: HttpRequest, res: HttpResponse, next: NextFunction): void;
18
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoggingMiddleware = void 0;
4
+ exports.createLoggingMiddleware = createLoggingMiddleware;
5
+ const context_1 = require("../logger/context");
6
+ const logger_1 = require("../logger/logger");
7
+ const types_1 = require("../logger/types");
8
+ const http_1 = require("../internal/http");
9
+ const incoming_1 = require("../internal/incoming");
10
+ // createLoggingMiddleware returns an Express middleware that logs every incoming
11
+ // request + response as a single "request" entry and sets up the correlation ID.
12
+ // If config is omitted, the global default config (configureDefaults) is used.
13
+ // For Fastify use createFastifyLoggingPlugin (LoggingModule picks the right one automatically).
14
+ function createLoggingMiddleware(log, config) {
15
+ const ownConfig = config ? (0, types_1.buildConfig)(config) : undefined;
16
+ return function loggingMiddleware(req, res, next) {
17
+ const cfg = ownConfig ?? (0, logger_1.getDefaultConfig)();
18
+ const url = req.originalUrl ?? req.url ?? '/';
19
+ // Skip excluded patterns
20
+ if ((0, http_1.shouldSkip)((0, incoming_1.splitUrl)(url).path, cfg)) {
21
+ next();
22
+ return;
23
+ }
24
+ // Setup correlation ID
25
+ const correlationId = (0, incoming_1.firstHeader)(req.headers['x-correlation-id']) || (0, context_1.generateCorrelationId)();
26
+ res.setHeader(context_1.CorrelationIDHeader, correlationId);
27
+ (0, incoming_1.trackIncoming)(log, cfg, correlationId, {
28
+ req,
29
+ res,
30
+ url,
31
+ params: () => req.params,
32
+ body: () => ({ raw: req.rawBody, parsed: req.body }),
33
+ ip: () => req.ip,
34
+ responseHeaders: () => res.getHeaders(),
35
+ });
36
+ // Process request with the correlation ID available to everything downstream
37
+ (0, context_1.withCorrelationId)(correlationId, () => next());
38
+ };
39
+ }
40
+ // LoggingMiddleware is the NestJS (Express) class form; it logs with logger name "LoggingMiddleware"
41
+ // and the global default config. LoggingModule registers it for all routes automatically.
42
+ class LoggingMiddleware {
43
+ handler = createLoggingMiddleware(new logger_1.AppLogger('LoggingMiddleware'));
44
+ use(req, res, next) {
45
+ this.handler(req, res, next);
46
+ }
47
+ }
48
+ exports.LoggingMiddleware = LoggingMiddleware;