Skip to content

Commit 29392df

Browse files
authored
Ability to configure and disable accessLogger (#2521)
#2512 #2365
1 parent 9e36bcc commit 29392df

File tree

6 files changed

+113
-25
lines changed

6 files changed

+113
-25
lines changed

CHANGELOG.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,22 @@
44

55
### v22.13.0
66

7+
- Ability to configure and disable access logging:
8+
- New config option: `accessLogger` — the function for producing access logs;
9+
- The default value is the function writing messages similar to `GET: /v1/path` having `debug` severity;
10+
- The option can be assigned with `null` to disable writing of access logs;
11+
- Thanks to the contributions of [@gmorgen1](https://github.com/gmorgen1) and [@crgeary](https://github.com/crgeary);
712
- [@danmichaelo](https://github.com/danmichaelo) fixed a broken link in the Security policy;
813
- Added JSDoc for several types involved into creating Middlewares and producing Endpoints.
914

15+
```ts
16+
import { createConfig } from "express-zod-api";
17+
18+
const config = createConfig({
19+
accessLogger: (request, logger) => logger.info(request.path), // or null to disable
20+
});
21+
```
22+
1023
### v22.12.0
1124

1225
- Featuring HTML forms support (URL Encoded request body):
@@ -1400,7 +1413,7 @@ new Integration({
14001413
- This makes all requests eligible for the assigned parsers and reverts changes made in [v18.5.2](#v1852);
14011414
- Specifying `rawParser` in config is no longer needed to enable the feature.
14021415
- Non-breaking significant changes:
1403-
- Request logging reflects the actual path instead of the configured route, and it's placed in front of parsing:
1416+
- Access logging reflects the actual path instead of the configured route, and it's placed in front of parsing:
14041417
- The severity of those messaged reduced from `info` to `debug`;
14051418
- The debug messages from uploader are enabled by default when the logger level is set to `debug`;
14061419
- How to migrate confidently:

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ Therefore, many basic tasks can be accomplished faster and easier, in particular
8888

8989
These people contributed to the improvement of the framework by reporting bugs, making changes and suggesting ideas:
9090

91+
[<img src="https://github.com/gmorgen1.png" alt="@gmorgen1" width="50px" />](https://github.com/gmorgen1)
92+
[<img src="https://github.com/crgeary.png" alt="@crgeary" width="50px" />](https://github.com/crgeary)
9193
[<img src="https://github.com/danmichaelo.png" alt="@danmichaelo" width="50px" />](https://github.com/danmichaelo)
9294
[<img src="https://github.com/james10424.png" alt="@james10424" width="50px" />](https://github.com/james10424)
9395
[<img src="https://github.com/APTy.png" alt="@APTy" width="50px" />](https://github.com/APTy)

express-zod-api/src/config-type.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type ChildLoggerProvider = (params: {
3030
parent: ActualLogger;
3131
}) => ActualLogger | Promise<ActualLogger>;
3232

33+
type LogAccess = (request: Request, logger: ActualLogger) => void;
34+
3335
export interface CommonConfig {
3436
/**
3537
* @desc Enables cross-origin resource sharing.
@@ -62,6 +64,12 @@ export interface CommonConfig {
6264
* @example ({ parent }) => parent.child({ requestId: uuid() })
6365
* */
6466
childLoggerProvider?: ChildLoggerProvider;
67+
/**
68+
* @desc The function for producing access logs
69+
* @default ({ method, path }, logger) => logger.debug(`${method}: ${path}`)
70+
* @example null — disables the feature
71+
* */
72+
accessLogger?: null | LogAccess;
6573
/**
6674
* @desc You can disable the startup logo.
6775
* @default true

express-zod-api/src/server-helpers.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,22 +121,30 @@ export const moveRaw: RequestHandler = (req, {}, next) => {
121121
next();
122122
};
123123

124-
/** @since v19 prints the actual path of the request, not a configured route, severity decreased to debug level */
124+
/** @since v22.13 the access logging can be customized and disabled */
125125
export const createLoggingMiddleware =
126126
({
127127
logger: parent,
128-
config,
128+
config: {
129+
childLoggerProvider,
130+
accessLogger = ({ method, path }, logger) =>
131+
logger.debug(`${method}: ${path}`),
132+
},
129133
}: {
130134
logger: ActualLogger;
131135
config: CommonConfig;
132136
}): RequestHandler =>
133137
async (request, response, next) => {
134-
const logger =
135-
(await config.childLoggerProvider?.({ request, parent })) || parent;
136-
logger.debug(`${request.method}: ${request.path}`);
137-
if (request.res)
138-
(request as EquippedRequest).res!.locals[metaSymbol] = { logger };
139-
next();
138+
try {
139+
const logger =
140+
(await childLoggerProvider?.({ request, parent })) || parent;
141+
accessLogger?.(request, logger);
142+
if (request.res)
143+
(request as EquippedRequest).res!.locals[metaSymbol] = { logger };
144+
next();
145+
} catch (error) {
146+
next(error); // @todo remove in v23 that is express 5 only
147+
}
140148
};
141149

142150
export const makeGetLogger =
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Server helpers > createLoggingMiddleware > should handle errors in accessLogger 1`] = `
4+
[
5+
[
6+
AssertionError({
7+
"message": "Something went wrong",
8+
}),
9+
],
10+
]
11+
`;
12+
13+
exports[`Server helpers > createLoggingMiddleware > should handle errors in childLoggerProvider 1`] = `
14+
[
15+
[
16+
AssertionError({
17+
"message": "Something went wrong",
18+
}),
19+
],
20+
]
21+
`;

express-zod-api/tests/server-helpers.spec.ts

Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { fail } from "node:assert/strict";
12
import { fileUploadMock } from "./express-mock";
23
import { metaSymbol } from "../src/metadata";
34
import {
@@ -243,26 +244,61 @@ describe("Server helpers", () => {
243244
});
244245

245246
describe("createLoggingMiddleware", () => {
246-
const logger = makeLoggerMock();
247-
const child = makeLoggerMock({ isChild: true });
248-
test.each([undefined, () => child, async () => child])(
249-
"should make RequestHandler writing logger to res.locals %#",
250-
async (childLoggerProvider) => {
251-
const config = { childLoggerProvider } as CommonConfig;
247+
describe("should make RequestHandler writing logger to res.locals", () => {
248+
const logger = makeLoggerMock();
249+
const child = makeLoggerMock({ isChild: true });
250+
test.each([undefined, () => child, async () => child])(
251+
"case %#",
252+
async (childLoggerProvider) => {
253+
const config = { childLoggerProvider } as CommonConfig;
254+
const handler = createLoggingMiddleware({ logger, config });
255+
expect(typeof handler).toBe("function");
256+
const nextMock = vi.fn();
257+
const response = makeResponseMock();
258+
const request = makeRequestMock({ path: "/test" });
259+
request.res = response;
260+
await handler(request, response, nextMock);
261+
expect(nextMock).toHaveBeenCalled();
262+
expect(
263+
(childLoggerProvider ? child : logger)._getLogs().debug.pop(),
264+
).toEqual(["GET: /test"]);
265+
expect(request.res).toHaveProperty("locals", {
266+
[metaSymbol]: { logger: childLoggerProvider ? child : logger },
267+
});
268+
},
269+
);
270+
});
271+
272+
test.each<[CommonConfig["accessLogger"], string[][]]>([
273+
[undefined, [["GET: /test"]]],
274+
[null, []],
275+
[({}, instance) => instance.debug("TEST"), [["TEST"]]],
276+
])(
277+
"access logger can be customized and disabled %#",
278+
async (accessLogger, expected) => {
279+
const config = { accessLogger } as CommonConfig;
280+
const logger = makeLoggerMock();
252281
const handler = createLoggingMiddleware({ logger, config });
253-
expect(typeof handler).toBe("function");
254-
const nextMock = vi.fn();
282+
const request = makeRequestMock({ path: "/test" });
255283
const response = makeResponseMock();
284+
await handler(request, response, vi.fn());
285+
expect(logger._getLogs().debug).toEqual(expected);
286+
},
287+
);
288+
289+
test.each(["childLoggerProvider", "accessLogger"] as const)(
290+
"should handle errors in %s",
291+
async (prop) => {
292+
const config = {
293+
[prop]: () => fail("Something went wrong"),
294+
} as unknown as CommonConfig;
295+
const logger = makeLoggerMock();
296+
const handler = createLoggingMiddleware({ logger, config });
256297
const request = makeRequestMock({ path: "/test" });
257-
request.res = response;
298+
const response = makeResponseMock();
299+
const nextMock = vi.fn();
258300
await handler(request, response, nextMock);
259-
expect(nextMock).toHaveBeenCalled();
260-
expect(
261-
(childLoggerProvider ? child : logger)._getLogs().debug.pop(),
262-
).toEqual(["GET: /test"]);
263-
expect(request.res).toHaveProperty("locals", {
264-
[metaSymbol]: { logger: childLoggerProvider ? child : logger },
265-
});
301+
expect(nextMock.mock.calls).toMatchSnapshot();
266302
},
267303
);
268304
});

0 commit comments

Comments
 (0)