@tsed/platform-log-middleware 8.0.1 → 8.0.3

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.
package/package.json CHANGED
@@ -2,13 +2,14 @@
2
2
  "name": "@tsed/platform-log-middleware",
3
3
  "description": "Log request middleware module for Ts.ED Framework (deprecated)",
4
4
  "type": "module",
5
- "version": "8.0.1",
5
+ "version": "8.0.3",
6
6
  "source": "./src/index.ts",
7
7
  "main": "./lib/esm/index.js",
8
8
  "module": "./lib/esm/index.js",
9
9
  "typings": "./lib/types/index.d.ts",
10
10
  "exports": {
11
11
  ".": {
12
+ "@tsed/source": "./src/index.ts",
12
13
  "types": "./lib/types/index.d.ts",
13
14
  "import": "./lib/esm/index.js",
14
15
  "default": "./lib/esm/index.js"
@@ -25,19 +26,19 @@
25
26
  "tslib": "2.7.0"
26
27
  },
27
28
  "devDependencies": {
28
- "@tsed/barrels": "8.0.1",
29
- "@tsed/di": "8.0.1",
30
- "@tsed/platform-middlewares": "8.0.1",
31
- "@tsed/platform-params": "8.0.1",
32
- "@tsed/typescript": "8.0.1",
29
+ "@tsed/barrels": "8.0.3",
30
+ "@tsed/di": "8.0.3",
31
+ "@tsed/platform-middlewares": "8.0.3",
32
+ "@tsed/platform-params": "8.0.3",
33
+ "@tsed/typescript": "8.0.3",
33
34
  "eslint": "9.12.0",
34
35
  "typescript": "5.4.5",
35
36
  "vitest": "2.1.2"
36
37
  },
37
38
  "peerDependencies": {
38
- "@tsed/di": "8.0.1",
39
- "@tsed/platform-middlewares": "8.0.1",
40
- "@tsed/platform-params": "8.0.1"
39
+ "@tsed/di": "8.0.3",
40
+ "@tsed/platform-middlewares": "8.0.3",
41
+ "@tsed/platform-params": "8.0.3"
41
42
  },
42
43
  "peerDependenciesMeta": {
43
44
  "@tsed/di": {
@@ -0,0 +1,26 @@
1
+ export type LoggerRequestFields = ("reqId" | "method" | "url" | "headers" | "body" | "query" | "params" | "duration" | string)[];
2
+
3
+ export interface PlatformLogMiddlewareSettings {
4
+ /**
5
+ * Fields displayed when a request is logged. Possible values: `reqId`, `method`, `url`, `headers`, `body`, `query`,`params`, `duration`.
6
+ */
7
+ requestFields?: LoggerRequestFields;
8
+ /**
9
+ * Log all incoming request. By default, is true and print the configured `logger.requestFields`.
10
+ */
11
+ logRequest?: boolean;
12
+ /**
13
+ * Log start of all incoming request. By default, is false
14
+ */
15
+ logStart?: boolean;
16
+ /**
17
+ * Log end of all incoming request. By default, is true
18
+ */
19
+ logEnd?: boolean;
20
+ }
21
+
22
+ declare global {
23
+ namespace TsED {
24
+ interface LoggerConfiguration extends Partial<PlatformLogMiddlewareSettings> {}
25
+ }
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./domain/PlatformLogMiddlewareSettings.js";
5
+ export * from "./middlewares/PlatformLogMiddleware.js";
@@ -0,0 +1,291 @@
1
+ import "../domain/PlatformLogMiddlewareSettings.js";
2
+
3
+ import {PlatformTest} from "@tsed/platform-http/testing";
4
+ import {PlatformHandlerMetadata} from "@tsed/platform-router";
5
+
6
+ import {PlatformLogMiddleware} from "./PlatformLogMiddleware.js";
7
+
8
+ async function createMiddlewareFixture({statusCode = 200, error}: {statusCode?: number; error?: any} = {}) {
9
+ const middleware = await PlatformTest.invoke<PlatformLogMiddleware>(PlatformLogMiddleware);
10
+
11
+ const ctx = PlatformTest.createRequestContext({
12
+ event: {
13
+ request: PlatformTest.createRequest({
14
+ method: "GET",
15
+ url: "url",
16
+ originalUrl: undefined
17
+ }),
18
+ response: PlatformTest.createResponse({
19
+ statusCode,
20
+ error
21
+ })
22
+ },
23
+ endpoint: new Map(),
24
+ logger: PlatformTest.injector.logger
25
+ });
26
+
27
+ ctx.error = error;
28
+
29
+ ctx.handlerMetadata = new PlatformHandlerMetadata({
30
+ handler: () => {}
31
+ });
32
+
33
+ vi.spyOn(PlatformTest.injector.logger, "info").mockResolvedValue(undefined);
34
+ vi.spyOn(PlatformTest.injector.logger, "warn").mockResolvedValue(undefined);
35
+ vi.spyOn(PlatformTest.injector.logger, "trace").mockResolvedValue(undefined);
36
+ vi.spyOn(PlatformTest.injector.logger, "debug").mockResolvedValue(undefined);
37
+ vi.spyOn(PlatformTest.injector.logger, "error").mockResolvedValue(undefined);
38
+
39
+ ctx.logger.maxStackSize = 0;
40
+ ctx.data = "test";
41
+
42
+ ctx.response.getRes().on = vi.fn();
43
+
44
+ return {request: ctx.request.raw, ctx, middleware};
45
+ }
46
+
47
+ describe("PlatformLogMiddleware", () => {
48
+ describe("use()", () => {
49
+ describe("when no debug, logRequest", () => {
50
+ beforeEach(() =>
51
+ PlatformTest.create({
52
+ logger: {
53
+ debug: false,
54
+ logRequest: true
55
+ }
56
+ })
57
+ );
58
+ afterEach(() => PlatformTest.reset());
59
+ it("should configure request and create context logger", async () => {
60
+ // GIVEN
61
+ const {request, ctx, middleware} = await createMiddlewareFixture();
62
+ request.originalUrl = "/originalUrl";
63
+ ctx.handlerMetadata.path = "/:id";
64
+
65
+ // WHEN
66
+ middleware.use(ctx);
67
+
68
+ // THEN
69
+ middleware.onLogEnd(request.$ctx);
70
+
71
+ // THEN
72
+ expect(PlatformTest.injector.logger.info).toHaveBeenCalledWith(
73
+ expect.objectContaining({
74
+ event: "request.start",
75
+ method: "GET",
76
+ reqId: "id",
77
+ url: "/originalUrl",
78
+ route: "/:id"
79
+ })
80
+ );
81
+ expect(PlatformTest.injector.logger.info).toHaveBeenCalledWith(
82
+ expect.objectContaining({
83
+ event: "request.end",
84
+ method: "GET",
85
+ reqId: "id",
86
+ url: "/originalUrl",
87
+ route: "/:id",
88
+ status: 200,
89
+ state: "OK"
90
+ })
91
+ );
92
+ expect(PlatformTest.injector.logger.info).toHaveBeenCalledWith(
93
+ expect.objectContaining({
94
+ duration: expect.any(Number)
95
+ })
96
+ );
97
+ expect(PlatformTest.injector.logger.info).toHaveBeenCalledWith(expect.objectContaining({time: expect.any(Date)}));
98
+ });
99
+ });
100
+ describe("when debug, logRequest", () => {
101
+ beforeEach(() =>
102
+ PlatformTest.create({
103
+ logger: {
104
+ level: "debug",
105
+ logRequest: true
106
+ }
107
+ })
108
+ );
109
+ afterEach(() => PlatformTest.reset());
110
+
111
+ it("should configure request and create context logger (debug, logRequest)", async () => {
112
+ // GIVEN
113
+ const {ctx, middleware} = await createMiddlewareFixture();
114
+
115
+ // WHEN
116
+ middleware.use(ctx);
117
+
118
+ // THEN
119
+ middleware.onLogEnd(ctx as any);
120
+
121
+ // THEN
122
+ expect(PlatformTest.injector.logger.debug).toHaveBeenCalledWith(
123
+ expect.objectContaining({
124
+ event: "request.start",
125
+ method: "GET",
126
+ reqId: "id",
127
+ url: "url"
128
+ })
129
+ );
130
+ expect(PlatformTest.injector.logger.debug).toHaveBeenCalledWith(
131
+ expect.objectContaining({
132
+ event: "request.end",
133
+ method: "GET",
134
+ reqId: "id",
135
+ url: "url",
136
+ status: 200,
137
+ data: "test",
138
+ state: "OK"
139
+ })
140
+ );
141
+ });
142
+ });
143
+ describe("when no debug, logRequest, logEnd", () => {
144
+ beforeEach(() =>
145
+ PlatformTest.create({
146
+ logger: {debug: false, logRequest: true, logEnd: false}
147
+ })
148
+ );
149
+ afterEach(() => PlatformTest.reset());
150
+ it("should configure request and create context logger ()", async () => {
151
+ // GIVEN
152
+ const {request, ctx, middleware} = await createMiddlewareFixture();
153
+ request.originalUrl = "originalUrl";
154
+
155
+ // WHEN
156
+ middleware.use(ctx);
157
+
158
+ // THEN
159
+ middleware.onLogEnd(request.$ctx as any);
160
+
161
+ // THEN
162
+ expect(PlatformTest.injector.logger.info).toHaveBeenCalledWith(
163
+ expect.objectContaining({
164
+ event: "request.start"
165
+ })
166
+ );
167
+ });
168
+ });
169
+ describe("when no debug, logRequest, logStart", () => {
170
+ beforeEach(() =>
171
+ PlatformTest.create({
172
+ logger: {
173
+ debug: false,
174
+ logRequest: true,
175
+ logStart: false
176
+ }
177
+ })
178
+ );
179
+ afterEach(() => PlatformTest.reset());
180
+ it("should configure request and create context logger (no debug, logRequest, logStart)", async () => {
181
+ // GIVEN
182
+ const {ctx, middleware} = await createMiddlewareFixture();
183
+
184
+ // WHEN
185
+ middleware.use(ctx);
186
+
187
+ // THEN
188
+ vi.mocked(ctx.response.getRes().on).mock.calls[0][1]();
189
+ // middleware.onLogEnd(request.$ctx as any);
190
+
191
+ // THEN
192
+ expect(PlatformTest.injector.logger.info).toHaveBeenCalledWith(
193
+ expect.objectContaining({
194
+ event: "request.end",
195
+ method: "GET",
196
+ reqId: "id",
197
+ url: "url",
198
+ status: 200
199
+ })
200
+ );
201
+ });
202
+ it("should configure request and create context logger (error)", async () => {
203
+ // GIVEN
204
+ const {ctx, middleware} = await createMiddlewareFixture({
205
+ statusCode: 400,
206
+ error: new Error("Test")
207
+ });
208
+
209
+ // WHEN
210
+ middleware.use(ctx);
211
+
212
+ // THEN
213
+ vi.mocked(ctx.response.getRes().on).mock.calls[0][1]();
214
+ // middleware.onLogEnd(request.$ctx as any);
215
+
216
+ // THEN
217
+ expect(PlatformTest.injector.logger.error).toHaveBeenCalledWith(
218
+ expect.objectContaining({
219
+ event: "request.end",
220
+ method: "GET",
221
+ reqId: "id",
222
+ url: "url",
223
+ status: 400,
224
+ error_name: "Error",
225
+ error_message: "Test"
226
+ })
227
+ );
228
+ });
229
+ it("should configure request and create context logger (error.code)", async () => {
230
+ // GIVEN
231
+ const error: any = {
232
+ code: "CODE",
233
+ message: "message"
234
+ };
235
+ const {ctx, middleware} = await createMiddlewareFixture({
236
+ statusCode: 400,
237
+ error
238
+ });
239
+
240
+ // WHEN
241
+ middleware.use(ctx);
242
+
243
+ // THEN
244
+ vi.mocked(ctx.response.getRes().on).mock.calls[0][1]();
245
+ // middleware.onLogEnd(request.$ctx as any);
246
+
247
+ // THEN
248
+ expect(PlatformTest.injector.logger.error).toHaveBeenCalledWith(
249
+ expect.objectContaining({
250
+ event: "request.end",
251
+ method: "GET",
252
+ reqId: "id",
253
+ url: "url",
254
+ status: 400,
255
+ error_name: "CODE",
256
+ error_message: "message"
257
+ })
258
+ );
259
+ });
260
+ });
261
+ describe("when no debug, log error", () => {
262
+ beforeEach(() =>
263
+ PlatformTest.create({
264
+ logger: {debug: false, logRequest: true}
265
+ })
266
+ );
267
+ afterEach(() => PlatformTest.reset());
268
+ it("should log error", async () => {
269
+ // GIVEN
270
+ const {request, ctx, middleware} = await createMiddlewareFixture();
271
+ request.originalUrl = "originalUrl";
272
+ // WHEN
273
+ middleware.use(ctx);
274
+
275
+ // THEN
276
+ ctx.logger.error({
277
+ event: "event"
278
+ });
279
+ // THEN
280
+ expect(PlatformTest.injector.logger.error).toHaveBeenCalledWith(
281
+ expect.objectContaining({
282
+ event: "event",
283
+ method: "GET",
284
+ reqId: "id",
285
+ url: "originalUrl"
286
+ })
287
+ );
288
+ });
289
+ });
290
+ });
291
+ });
@@ -0,0 +1,160 @@
1
+ import {cleanObject} from "@tsed/core";
2
+ import {Constant} from "@tsed/di";
3
+ import {Middleware, MiddlewareMethods} from "@tsed/platform-middlewares";
4
+ import {Context} from "@tsed/platform-params";
5
+
6
+ import type {LoggerRequestFields} from "../domain/PlatformLogMiddlewareSettings.js";
7
+
8
+ /**
9
+ * @middleware
10
+ * @platform
11
+ */
12
+ @Middleware()
13
+ export class PlatformLogMiddleware implements MiddlewareMethods {
14
+ @Constant("logger.requestFields", ["reqId", "method", "url", "duration", "route"])
15
+ protected requestFields: LoggerRequestFields;
16
+
17
+ @Constant("logger.logRequest", true)
18
+ protected logRequest: boolean;
19
+
20
+ @Constant("logger.logStart", true)
21
+ protected logStart: boolean;
22
+
23
+ @Constant("logger.logEnd", true)
24
+ protected logEnd: boolean;
25
+
26
+ @Constant("logger.level")
27
+ protected logLevel: string;
28
+
29
+ get settings() {
30
+ return this;
31
+ }
32
+
33
+ /**
34
+ * Handle the request.
35
+ */
36
+ public use(@Context() ctx: Context): void {
37
+ this.configureRequest(ctx);
38
+ this.onLogStart(ctx);
39
+
40
+ ctx.response.onEnd(() => this.onLogEnd(ctx));
41
+ }
42
+
43
+ /**
44
+ * Called when the `$onResponse` is called by Ts.ED (through Express.end).
45
+ */
46
+ onLogEnd(ctx: Context) {
47
+ const {logRequest, logEnd, logLevel} = this.settings;
48
+ const started = ctx.logStarted;
49
+
50
+ if (logEnd && started) {
51
+ if (ctx.response.statusCode >= 400) {
52
+ const error = ctx.error as any | undefined;
53
+ ctx.logger.error({
54
+ event: "request.end",
55
+ status: ctx.response.statusCode,
56
+ status_code: String(ctx.response.statusCode),
57
+ state: "KO",
58
+ ...cleanObject({
59
+ error_name: error?.name || error?.code,
60
+ error_message: error?.message,
61
+ error_errors: error?.errors,
62
+ error_stack: error?.stack,
63
+ error_body: error?.body,
64
+ error_headers: error?.headers
65
+ })
66
+ });
67
+ } else {
68
+ if (logLevel === "debug") {
69
+ ctx.logger.debug({
70
+ event: "request.end",
71
+ status: ctx.response.statusCode,
72
+ status_code: String(ctx.response.statusCode),
73
+ data: ctx.data,
74
+ state: "OK"
75
+ });
76
+ } else if (logRequest) {
77
+ ctx.logger.info({
78
+ event: "request.end",
79
+ status: ctx.response.statusCode,
80
+ status_code: String(ctx.response.statusCode),
81
+ state: "OK"
82
+ });
83
+ }
84
+ }
85
+ }
86
+ }
87
+
88
+ /**
89
+ * The separate onLogStart() function will allow developer to overwrite the initial request log.
90
+ * @param ctx
91
+ */
92
+ protected onLogStart(ctx: Context) {
93
+ const {logRequest, logLevel, logStart} = this.settings;
94
+
95
+ ctx.logStarted = true;
96
+
97
+ if (logStart) {
98
+ if (logLevel === "debug") {
99
+ ctx.logger.debug({
100
+ event: "request.start"
101
+ });
102
+ } else if (logRequest) {
103
+ ctx.logger.info({
104
+ event: "request.start"
105
+ });
106
+ }
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Attach all information that will be necessary to log the request. Attach a new `request.log` object.
112
+ */
113
+ protected configureRequest(ctx: Context) {
114
+ ctx.logger.alterLog((obj: any, level, withRequest) => {
115
+ switch (level) {
116
+ case "info":
117
+ return {...this.minimalRequestPicker(ctx), ...obj};
118
+ case "debug":
119
+ return {...this.requestToObject(ctx), ...obj};
120
+ default:
121
+ return {...this.requestToObject(ctx), ...obj};
122
+ }
123
+ });
124
+ }
125
+
126
+ /**
127
+ * Return complete request info.
128
+ * @returns {Object}
129
+ * @param ctx
130
+ */
131
+ protected requestToObject(ctx: Context): any {
132
+ const {request} = ctx;
133
+
134
+ return {
135
+ method: request.method,
136
+ url: request.url,
137
+ route: request.route || request.url,
138
+ headers: request.headers,
139
+ body: request.body,
140
+ query: request.query,
141
+ params: request.params
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Return a filtered request from global configuration.
147
+ * @returns {Object}
148
+ * @param ctx
149
+ */
150
+ protected minimalRequestPicker(ctx: Context): any {
151
+ const {requestFields} = this;
152
+ const info = this.requestToObject(ctx);
153
+
154
+ return requestFields!.reduce((acc: any, key: string) => {
155
+ acc[key] = info[key];
156
+
157
+ return acc;
158
+ }, {});
159
+ }
160
+ }