Skip to content

Commit dd395d4

Browse files
committed
Capture console logs in Koa middleware
1 parent 8f9e50c commit dd395d4

File tree

3 files changed

+96
-69
lines changed

3 files changed

+96
-69
lines changed

src/koa/middleware.ts

Lines changed: 85 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import Koa from "koa";
22

3+
import { AsyncLocalStorage } from "async_hooks";
34
import { ApitallyClient } from "../common/client.js";
5+
import { patchConsole } from "../common/consoleCapture.js";
46
import { consumerFromStringOrObject } from "../common/consumerRegistry.js";
57
import { getPackageVersion } from "../common/packageVersions.js";
6-
import { convertBody, convertHeaders } from "../common/requestLogger.js";
8+
import {
9+
convertBody,
10+
convertHeaders,
11+
LogRecord,
12+
} from "../common/requestLogger.js";
713
import {
814
ApitallyConfig,
915
ApitallyConsumer,
@@ -28,87 +34,97 @@ export function useApitally(app: Koa, config: ApitallyConfig) {
2834
}
2935

3036
function getMiddleware(client: ApitallyClient) {
37+
const logsContext = new AsyncLocalStorage<LogRecord[]>();
38+
39+
if (client.requestLogger.config.captureLogs) {
40+
patchConsole(logsContext);
41+
}
42+
3143
return async (ctx: Koa.Context, next: Koa.Next) => {
3244
if (!client.isEnabled() || ctx.request.method.toUpperCase() === "OPTIONS") {
3345
await next();
3446
return;
3547
}
3648

37-
let path: string | undefined;
38-
let statusCode: number | undefined;
39-
let serverError: Error | undefined;
40-
const startTime = performance.now();
41-
try {
42-
await next();
43-
} catch (error: any) {
44-
path = getPath(ctx);
45-
statusCode = error.statusCode || error.status || 500;
46-
if (path && statusCode === 500 && error instanceof Error) {
47-
serverError = error;
48-
client.serverErrorCounter.addServerError({
49-
consumer: getConsumer(ctx)?.identifier,
50-
method: ctx.request.method,
51-
path,
52-
type: error.name,
53-
msg: error.message,
54-
traceback: error.stack || "",
55-
});
56-
}
57-
throw error;
58-
} finally {
59-
const responseTime = performance.now() - startTime;
60-
const consumer = getConsumer(ctx);
61-
client.consumerRegistry.addOrUpdateConsumer(consumer);
62-
if (!path) {
49+
await logsContext.run([], async () => {
50+
let path: string | undefined;
51+
let statusCode: number | undefined;
52+
let serverError: Error | undefined;
53+
const startTime = performance.now();
54+
try {
55+
await next();
56+
} catch (error: any) {
6357
path = getPath(ctx);
64-
}
65-
if (path) {
66-
try {
67-
client.requestCounter.addRequest({
68-
consumer: consumer?.identifier,
58+
statusCode = error.statusCode || error.status || 500;
59+
if (path && statusCode === 500 && error instanceof Error) {
60+
serverError = error;
61+
client.serverErrorCounter.addServerError({
62+
consumer: getConsumer(ctx)?.identifier,
6963
method: ctx.request.method,
7064
path,
71-
statusCode: statusCode || ctx.response.status,
72-
responseTime,
73-
requestSize: ctx.request.length,
74-
responseSize: ctx.response.length,
65+
type: error.name,
66+
msg: error.message,
67+
traceback: error.stack || "",
7568
});
76-
} catch (error) {
77-
client.logger.error(
78-
"Error while logging request in Apitally middleware.",
79-
{ context: ctx, error },
69+
}
70+
throw error;
71+
} finally {
72+
const responseTime = performance.now() - startTime;
73+
const consumer = getConsumer(ctx);
74+
client.consumerRegistry.addOrUpdateConsumer(consumer);
75+
if (!path) {
76+
path = getPath(ctx);
77+
}
78+
if (path) {
79+
try {
80+
client.requestCounter.addRequest({
81+
consumer: consumer?.identifier,
82+
method: ctx.request.method,
83+
path,
84+
statusCode: statusCode || ctx.response.status,
85+
responseTime,
86+
requestSize: ctx.request.length,
87+
responseSize: ctx.response.length,
88+
});
89+
} catch (error) {
90+
client.logger.error(
91+
"Error while logging request in Apitally middleware.",
92+
{ context: ctx, error },
93+
);
94+
}
95+
}
96+
if (client.requestLogger.enabled) {
97+
const logs = logsContext.getStore();
98+
client.requestLogger.logRequest(
99+
{
100+
timestamp: Date.now() / 1000,
101+
method: ctx.request.method,
102+
path,
103+
url: ctx.request.href,
104+
headers: convertHeaders(ctx.request.headers),
105+
size: ctx.request.length,
106+
consumer: consumer?.identifier,
107+
body: convertBody(
108+
ctx.request.body,
109+
ctx.request.get("content-type"),
110+
),
111+
},
112+
{
113+
statusCode: statusCode || ctx.response.status,
114+
responseTime: responseTime / 1000,
115+
headers: convertHeaders(ctx.response.headers),
116+
size: ctx.response.length,
117+
body: convertBody(
118+
ctx.response.body,
119+
ctx.response.get("content-type"),
120+
),
121+
},
122+
serverError,
123+
logs,
80124
);
81125
}
82126
}
83-
if (client.requestLogger.enabled) {
84-
client.requestLogger.logRequest(
85-
{
86-
timestamp: Date.now() / 1000,
87-
method: ctx.request.method,
88-
path,
89-
url: ctx.request.href,
90-
headers: convertHeaders(ctx.request.headers),
91-
size: ctx.request.length,
92-
consumer: consumer?.identifier,
93-
body: convertBody(
94-
ctx.request.body,
95-
ctx.request.get("content-type"),
96-
),
97-
},
98-
{
99-
statusCode: statusCode || ctx.response.status,
100-
responseTime: responseTime / 1000,
101-
headers: convertHeaders(ctx.response.headers),
102-
size: ctx.response.length,
103-
body: convertBody(
104-
ctx.response.body,
105-
ctx.response.get("content-type"),
106-
),
107-
},
108-
serverError,
109-
);
110-
}
111-
}
127+
});
112128
};
113129
}
114130

tests/koa/app.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,12 @@ testCases.forEach(({ name, router, getApp }) => {
9797
]);
9898
expect(call[1].body).toBeInstanceOf(Buffer);
9999
expect(call[1].body!.toString()).toMatch(/^Hello John!/);
100+
expect(call[3]).toBeDefined();
101+
expect(call[3]).toHaveLength(2);
102+
expect(call[3]![0].level).toBe("log");
103+
expect(call[3]![0].message).toBe("Test 1");
104+
expect(call[3]![1].level).toBe("warn");
105+
expect(call[3]![1].message).toBe("Test 2");
100106
spy.mockReset();
101107

102108
await appTest.post("/hello").send({ name: "John", age: 20 }).expect(200);

tests/koa/app.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const requestLoggingConfig = {
1313
logRequestBody: true,
1414
logResponseHeaders: true,
1515
logResponseBody: true,
16+
captureLogs: true,
1617
};
1718

1819
export const getAppWithKoaRouter = () => {
@@ -28,6 +29,8 @@ export const getAppWithKoaRouter = () => {
2829

2930
router.get("/hello", async (ctx) => {
3031
setConsumer(ctx, "test");
32+
console.log("Test 1");
33+
console.warn("Test 2");
3134
ctx.body = `Hello ${ctx.query.name}! You are ${ctx.query.age} years old!`;
3235
});
3336
router.get("/hello/:id", async (ctx) => {
@@ -62,6 +65,8 @@ export const getAppWithKoaRoute = () => {
6265
app.use(
6366
route.get("/hello", async (ctx) => {
6467
setConsumer(ctx, "test");
68+
console.log("Test 1");
69+
console.warn("Test 2");
6570
ctx.body = `Hello ${ctx.query.name}! You are ${ctx.query.age} years old!`;
6671
}),
6772
);

0 commit comments

Comments
 (0)