Skip to content

Commit aec54fa

Browse files
authored
fix: use pino-http for logging within controller (#22)
1 parent 6a6b27a commit aec54fa

File tree

5 files changed

+58
-12
lines changed

5 files changed

+58
-12
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
"octokit": "^4.0.2",
6262
"openid-client": "^5.7.0",
6363
"pino": "^9.4.0",
64+
"pino-http": "^10.3.0",
6465
"pino-pretty": "^11.2.2",
6566
"zod": "^3.23.8"
6667
},

src/bootstrap.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import bodyParser from "body-parser";
22
import expressApp, { Router } from "express";
3+
import { randomUUID } from "node:crypto";
4+
import { pinoHttp } from "pino-http";
35

46
import { dispatchControllerFactory } from "./dispatch/controller.js";
57
import { getConfig } from "./utils/config.js";
@@ -31,6 +33,15 @@ interface BootstrapResult {
3133
}
3234

3335
const logger = getLogger("bootstrap");
36+
const loggerMiddleware = pinoHttp({
37+
logger: getLogger("http"),
38+
genReqId: (_, res) => {
39+
const id = randomUUID();
40+
res.setHeader("x-request-id", id);
41+
42+
return id;
43+
},
44+
});
3445

3546
// Main entrypoint to the application, where all the startup logic is defined.
3647
// This function is immediately invoked when the file is imported.
@@ -84,6 +95,9 @@ export const bootstrap = (async (): Promise<BootstrapResult> => {
8495
});
8596
});
8697

98+
// Mount the logger middleware.
99+
app.use(loggerMiddleware);
100+
87101
// Mount the router on the base path.
88102
// By default, the router is mounted on "/".
89103
app.use(config.BASE_PATH, baseRouter);

src/dispatch/controller.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ import { z } from "zod";
33
import { sendWorkflowDispatch } from "./github.js";
44
import { decodeIdToken, getJwtVerifier } from "./id-token.js";
55
import { evaluatePolicyForRequest, getPolicy } from "./policy.js";
6-
import { getLogger } from "../utils/logger.js";
6+
import { getLoggerForRequest } from "../utils/logger.js";
77

88
import type { IdTokenClaims } from "./id-token.js";
99
import type { PolicyInput } from "./policy.js";
10-
import type { RequestHandler, Request } from "express";
11-
12-
const _logger = getLogger("handler/controller");
10+
import type { Request, RequestHandler } from "express";
1311

1412
const bodySchema = z.object({
1513
target: z.object({
@@ -75,14 +73,15 @@ export const dispatchControllerFactory: () => Promise<RequestHandler> =
7573
const policy = await getPolicy();
7674

7775
return async (req, res) => {
78-
_logger.debug({ req }, "Processing request");
76+
const _reqLogger = getLoggerForRequest(req);
77+
_reqLogger.debug({ req }, "Processing request");
7978

8079
// Validate the ID token.
8180
let idToken;
8281
try {
8382
const idTokenValue = await extractIdToken(req);
8483
if (!idTokenValue) {
85-
_logger.warn("Missing ID token");
84+
_reqLogger.warn("Missing ID token");
8685
return res
8786
.status(401)
8887
.header("content-type", responseContentType)
@@ -91,7 +90,7 @@ export const dispatchControllerFactory: () => Promise<RequestHandler> =
9190

9291
idToken = await decodeIdToken(jwtVerifier, idTokenValue);
9392
} catch (e) {
94-
_logger.warn({ error: e }, "Failed to decode ID token");
93+
_reqLogger.warn({ error: e }, "Failed to decode ID token");
9594
return res
9695
.status(400)
9796
.header("content-type", responseContentType)
@@ -126,7 +125,7 @@ export const dispatchControllerFactory: () => Promise<RequestHandler> =
126125
.json({ error: "Request blocked by policy" });
127126
}
128127
} catch (e) {
129-
_logger.warn({ error: e }, "Failed to evaluate policy");
128+
_reqLogger.warn({ error: e }, "Failed to evaluate policy");
130129
return res
131130
.status(401)
132131
.header("content-type", responseContentType)
@@ -140,14 +139,14 @@ export const dispatchControllerFactory: () => Promise<RequestHandler> =
140139
inputs: body.inputs,
141140
});
142141
} catch (e) {
143-
_logger.warn({ error: e }, "Failed to send workflow dispatch");
142+
_reqLogger.warn({ error: e }, "Failed to send workflow dispatch");
144143
return res
145144
.status(500)
146145
.header("content-type", responseContentType)
147146
.json({ error: "Failed to send workflow dispatch" });
148147
}
149148

150-
_logger.info(policyInput, "Workflow dispatch created");
149+
_reqLogger.info(policyInput, "Workflow dispatch created");
151150

152151
return res.status(200).json({
153152
message: "workflow dispatch created",

src/utils/logger.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { pino } from "pino";
22

3+
import type { Request } from "express";
34
import type { Logger } from "pino";
45

56
const _logger = pino({
67
level: process.env.APP_LOG_LEVEL || "info",
7-
redact: ["config.GH_AUTH_APP_PRIVATE_KEY", "config.GH_AUTH_TOKEN"],
8+
redact: [
9+
"config.GH_AUTH_APP_PRIVATE_KEY",
10+
"config.GH_AUTH_TOKEN",
11+
"req.headers.authorization",
12+
],
813
});
914

1015
/**
@@ -19,3 +24,17 @@ export const getLogger = (module?: string): Logger => {
1924

2025
return _logger.child({ module });
2126
};
27+
28+
/**
29+
* Provides the logger for the given request.
30+
*
31+
* @param req The request to get the logger for.
32+
*/
33+
export const getLoggerForRequest = (req: Request): Logger => {
34+
const logInterface = (req as any).log as pino.Logger;
35+
if (!logInterface) {
36+
throw new Error("Request does not have a logger");
37+
}
38+
39+
return logInterface;
40+
};

yarn.lock

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3846,6 +3846,7 @@ __metadata:
38463846
octokit: "npm:^4.0.2"
38473847
openid-client: "npm:^5.7.0"
38483848
pino: "npm:^9.4.0"
3849+
pino-http: "npm:^10.3.0"
38493850
pino-pretty: "npm:^11.2.2"
38503851
prettier: "npm:^3.3.3"
38513852
sort-package-json: "npm:^2.10.1"
@@ -5672,6 +5673,18 @@ __metadata:
56725673
languageName: node
56735674
linkType: hard
56745675

5676+
"pino-http@npm:^10.3.0":
5677+
version: 10.3.0
5678+
resolution: "pino-http@npm:10.3.0"
5679+
dependencies:
5680+
get-caller-file: "npm:^2.0.5"
5681+
pino: "npm:^9.0.0"
5682+
pino-std-serializers: "npm:^7.0.0"
5683+
process-warning: "npm:^4.0.0"
5684+
checksum: 10/495e7d4ea66ab47a3dc951def40eaf36c7f67555a590e6abd6a6f65152ad98a1fe74442d70f3ce8fefca53b455861d6ac7a8f918c56075bbbefa47b63d99394e
5685+
languageName: node
5686+
linkType: hard
5687+
56755688
"pino-pretty@npm:^11.2.2":
56765689
version: 11.2.2
56775690
resolution: "pino-pretty@npm:11.2.2"
@@ -5703,7 +5716,7 @@ __metadata:
57035716
languageName: node
57045717
linkType: hard
57055718

5706-
"pino@npm:^9.4.0":
5719+
"pino@npm:^9.0.0, pino@npm:^9.4.0":
57075720
version: 9.4.0
57085721
resolution: "pino@npm:9.4.0"
57095722
dependencies:

0 commit comments

Comments
 (0)