Skip to content

Commit f3f5058

Browse files
committed
Capture logs from NestJS loggers
1 parent c1c7dfe commit f3f5058

File tree

16 files changed

+147
-51
lines changed

16 files changed

+147
-51
lines changed

src/common/console.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { AsyncLocalStorage } from "async_hooks";
2+
import { format } from "util";
3+
4+
import { LogRecord } from "./requestLogger.js";
5+
6+
type LogLevel = "log" | "warn" | "error" | "info" | "debug";
7+
8+
let isPatched = false;
9+
let globalLogsContext: AsyncLocalStorage<LogRecord[]>;
10+
11+
export function patchConsole(logsContext: AsyncLocalStorage<LogRecord[]>) {
12+
globalLogsContext = logsContext;
13+
14+
if (isPatched) {
15+
return;
16+
}
17+
18+
const logMethods: LogLevel[] = ["log", "warn", "error", "info", "debug"];
19+
logMethods.forEach((method) => {
20+
const originalMethod = console[method];
21+
console[method] = function (...args: any[]) {
22+
captureLog(method, args);
23+
return originalMethod.apply(console, args);
24+
};
25+
});
26+
27+
isPatched = true;
28+
}
29+
30+
function captureLog(level: LogLevel, args: any[]) {
31+
const logs = globalLogsContext?.getStore();
32+
if (logs) {
33+
logs.push({
34+
timestamp: Date.now() / 1000,
35+
level,
36+
message: format(...args),
37+
});
38+
}
39+
}

src/common/consoleCapture.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/common/requestLogger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export type Response = {
8888

8989
export type LogRecord = {
9090
timestamp: number;
91+
logger?: string;
9192
level: string;
9293
message: string;
9394
};
@@ -393,6 +394,7 @@ export default class RequestLogger {
393394
if (logs && logs.length > 0) {
394395
item.logs = logs.map((log) => ({
395396
timestamp: log.timestamp,
397+
logger: log.logger,
396398
level: log.level,
397399
message: truncateLogMessage(log.message),
398400
}));

src/express/middleware.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { performance } from "perf_hooks";
33

44
import { AsyncLocalStorage } from "async_hooks";
55
import { ApitallyClient } from "../common/client.js";
6-
import { patchConsole } from "../common/consoleCapture.js";
6+
import { patchConsole } from "../common/console.js";
77
import { consumerFromStringOrObject } from "../common/consumerRegistry.js";
88
import { parseContentLength } from "../common/headers.js";
99
import { getPackageVersion } from "../common/packageVersions.js";
@@ -57,6 +57,14 @@ function getMiddleware(app: Express | Router, client: ApitallyClient) {
5757

5858
if (client.requestLogger.config.captureLogs) {
5959
patchConsole(logsContext);
60+
61+
import("../nestjs/logger.js")
62+
.then(({ patchNestLogger }) => {
63+
patchNestLogger(logsContext);
64+
})
65+
.catch(() => {
66+
// ignore
67+
});
6068
}
6169

6270
return (req: Request, res: Response, next: NextFunction) => {

src/fastify/plugin.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import type {
88
import fp from "fastify-plugin";
99

1010
import { ApitallyClient } from "../common/client.js";
11-
import { patchConsole } from "../common/consoleCapture.js";
11+
import { patchConsole } from "../common/console.js";
1212
import { consumerFromStringOrObject } from "../common/consumerRegistry.js";
1313
import { parseContentLength } from "../common/headers.js";
1414
import { getPackageVersion } from "../common/packageVersions.js";
@@ -54,6 +54,14 @@ const apitallyPlugin: FastifyPluginAsync<ApitallyConfig> = async (
5454
if (client.requestLogger.config.captureLogs) {
5555
patchConsole(logsContext);
5656
patchPino(fastify.log, logsContext, filterLogs);
57+
58+
try {
59+
const { patchNestLogger } = await import("../nestjs/logger.js");
60+
patchNestLogger(logsContext);
61+
console.log("NestJS logger patched");
62+
} catch (error) {
63+
// ignore
64+
}
5765
}
5866

5967
fastify.decorateRequest("apitallyConsumer", null);
@@ -224,11 +232,15 @@ const apitallyPlugin: FastifyPluginAsync<ApitallyConfig> = async (
224232
function getAppInfo(routes: PathInfo[], appVersion?: string) {
225233
const versions = [["nodejs", process.version.replace(/^v/, "")]];
226234
const fastifyVersion = getPackageVersion("fastify");
235+
const pinoVersion = getPackageVersion("pino");
227236
const nestjsVersion = getPackageVersion("@nestjs/core");
228237
const apitallyVersion = getPackageVersion("../..");
229238
if (fastifyVersion) {
230239
versions.push(["fastify", fastifyVersion]);
231240
}
241+
if (pinoVersion) {
242+
versions.push(["pino", pinoVersion]);
243+
}
232244
if (nestjsVersion) {
233245
versions.push(["nestjs", nestjsVersion]);
234246
}

src/hono/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { performance } from "perf_hooks";
44

55
import { AsyncLocalStorage } from "async_hooks";
66
import { ApitallyClient } from "../common/client.js";
7-
import { patchConsole } from "../common/consoleCapture.js";
7+
import { patchConsole } from "../common/console.js";
88
import { consumerFromStringOrObject } from "../common/consumerRegistry.js";
99
import { parseContentLength } from "../common/headers.js";
1010
import { convertHeaders, LogRecord } from "../common/requestLogger.js";

src/koa/middleware.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Koa from "koa";
22

33
import { AsyncLocalStorage } from "async_hooks";
44
import { ApitallyClient } from "../common/client.js";
5-
import { patchConsole } from "../common/consoleCapture.js";
5+
import { patchConsole } from "../common/console.js";
66
import { consumerFromStringOrObject } from "../common/consumerRegistry.js";
77
import { getPackageVersion } from "../common/packageVersions.js";
88
import {

src/nestjs/logger.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Logger } from "@nestjs/common";
2+
import { AsyncLocalStorage } from "async_hooks";
3+
import { format } from "util";
4+
5+
import { LogRecord } from "../common/requestLogger.js";
6+
7+
type LogLevel = "log" | "error" | "warn" | "debug" | "verbose" | "fatal";
8+
9+
let isPatched = false;
10+
let globalLogsContext: AsyncLocalStorage<LogRecord[]>;
11+
12+
export function patchNestLogger(logsContext: AsyncLocalStorage<LogRecord[]>) {
13+
globalLogsContext = logsContext;
14+
15+
if (isPatched) {
16+
return;
17+
}
18+
19+
const logMethods: LogLevel[] = [
20+
"log",
21+
"error",
22+
"warn",
23+
"debug",
24+
"verbose",
25+
"fatal",
26+
];
27+
28+
// Patch static methods
29+
logMethods.forEach((method) => {
30+
const originalMethod = Logger[method];
31+
Logger[method] = function (message: any, ...args: any[]) {
32+
captureLog(method, [message, ...args]);
33+
return originalMethod.apply(Logger, [message, ...args]);
34+
};
35+
});
36+
37+
// Patch prototype methods to affect all instances (new and existing)
38+
logMethods.forEach((method) => {
39+
const originalMethod = Logger.prototype[method];
40+
Logger.prototype[method] = function (message: any, ...args: any[]) {
41+
captureLog(method, [message, ...args], this.context);
42+
return originalMethod.apply(this, [message, ...args]);
43+
};
44+
});
45+
46+
isPatched = true;
47+
}
48+
49+
function captureLog(level: LogLevel, args: any[], context?: string) {
50+
const logs = globalLogsContext?.getStore();
51+
if (logs) {
52+
logs.push({
53+
timestamp: Date.now() / 1000,
54+
logger: context,
55+
level,
56+
message: format(...args),
57+
});
58+
}
59+
}

tests/nestjs-express/app.controller.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
Get,
77
Header,
88
Injectable,
9+
Logger,
910
Param,
1011
ParseIntPipe,
1112
Post,
@@ -47,9 +48,12 @@ export class HelloBodyDTO {
4748
@Controller()
4849
@UseGuards(AuthGuard)
4950
export class AppController {
51+
private readonly logger = new Logger(AppController.name);
52+
5053
@Get("/hello")
5154
@Header("Content-Type", "text/plain")
5255
getHello(@Query() { name, age }: HelloQueryDTO) {
56+
this.logger.log("Saying hello", { name, age });
5357
return `Hello ${name}! You are ${age} years old!`;
5458
}
5559

tests/nestjs-express/app.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Module } from "@nestjs/common";
2+
23
import { AppController } from "./app.controller.js";
34

45
@Module({

0 commit comments

Comments
 (0)