@tsed/platform-log-request 8.0.1 → 8.0.2

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,16 +2,17 @@
2
2
  "name": "@tsed/platform-log-request",
3
3
  "description": "Log request module for Ts.ED Framework (Express, Koa, Serverless)",
4
4
  "type": "module",
5
- "version": "8.0.1",
5
+ "version": "8.0.2",
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
- "default": "./lib/cjs/index.js"
15
+ "default": "./lib/esm/index.js"
15
16
  }
16
17
  },
17
18
  "scripts": {
@@ -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.2",
30
+ "@tsed/di": "8.0.2",
31
+ "@tsed/platform-middlewares": "8.0.2",
32
+ "@tsed/platform-params": "8.0.2",
33
+ "@tsed/typescript": "8.0.2",
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.2",
40
+ "@tsed/platform-middlewares": "8.0.2",
41
+ "@tsed/platform-params": "8.0.2"
41
42
  },
42
43
  "peerDependenciesMeta": {
43
44
  "@tsed/di": {
@@ -0,0 +1,28 @@
1
+ import type {DIContext} from "@tsed/di";
2
+
3
+ export type AlterLogCallback = (
4
+ level: "debug" | "info" | "warn" | "error" | "all",
5
+ obj: Record<string, unknown>,
6
+ ctx: DIContext
7
+ ) => Record<string, unknown>;
8
+
9
+ export interface PlatformLogRequestSettings {
10
+ /**
11
+ * Log all incoming request. By default, is true and print the configured `logger.requestFields`.
12
+ */
13
+ logRequest?: boolean;
14
+ /**
15
+ * A function to alter the log object before it's logged.
16
+ */
17
+ alterLog?: AlterLogCallback;
18
+ /**
19
+ * A function to log the server response.
20
+ */
21
+ onLogResponse?: ($ctx: DIContext) => void;
22
+ }
23
+
24
+ declare global {
25
+ namespace TsED {
26
+ interface LoggerConfiguration extends Partial<PlatformLogRequestSettings> {}
27
+ }
28
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./domain/PlatformLogRequestSettings.js";
5
+ export * from "./services/PlatformLogRequestFactory.js";
6
+ export * from "./utils/defaultAlterLog.js";
7
+ export * from "./utils/defaultLogResponse.js";
@@ -0,0 +1,139 @@
1
+ import "../domain/PlatformLogRequestSettings.js";
2
+
3
+ import {DITest} from "@tsed/di";
4
+
5
+ import {PlatformLogRequestFactory} from "./PlatformLogRequestFactory.js";
6
+
7
+ describe("PlatformLogRequestFactory", () => {
8
+ describe("when logger is enabled", () => {
9
+ beforeEach(() =>
10
+ DITest.create({
11
+ logger: {}
12
+ })
13
+ );
14
+ afterEach(() => DITest.reset());
15
+
16
+ it("should create logger factory", async () => {
17
+ const factory = await DITest.invoke<PlatformLogRequestFactory>(PlatformLogRequestFactory);
18
+
19
+ expect(factory).toEqual({
20
+ alterLog: expect.any(Function),
21
+ onLogResponse: expect.any(Function)
22
+ });
23
+
24
+ const ctx: any = {
25
+ request: {
26
+ method: "GET"
27
+ },
28
+ logger: {
29
+ alterLog: vi.fn(),
30
+ info: vi.fn()
31
+ }
32
+ };
33
+
34
+ vi.spyOn(factory!, "alterLog").mockReturnValue({});
35
+ vi.spyOn(factory!, "onLogResponse").mockReturnValue(undefined);
36
+
37
+ DITest.injector.getProvider(PlatformLogRequestFactory)?.hooks?.$onRequest(factory!, ctx);
38
+
39
+ expect(ctx.logger.alterLog).toHaveBeenCalledWith(expect.any(Function));
40
+
41
+ ctx.logger.alterLog.mock.calls[0][0]({test: "test"}, "info");
42
+
43
+ expect(ctx.logStarted).toBe(true);
44
+ expect(factory!.alterLog).toHaveBeenCalledWith(
45
+ "info",
46
+ {
47
+ test: "test"
48
+ },
49
+ ctx
50
+ );
51
+
52
+ DITest.injector.getProvider(PlatformLogRequestFactory)?.hooks?.$onResponse(factory!, ctx);
53
+
54
+ expect(factory!.onLogResponse).toHaveBeenCalledWith(ctx);
55
+ });
56
+ });
57
+ describe("when logger is enabled with options", () => {
58
+ beforeEach(() =>
59
+ DITest.create({
60
+ logger: {
61
+ logRequest: true,
62
+ alterLog: vi.fn().mockReturnValue({}),
63
+ onLogResponse: vi.fn()
64
+ }
65
+ })
66
+ );
67
+ afterEach(() => DITest.reset());
68
+
69
+ it("should create logger factory", async () => {
70
+ const factory = await DITest.invoke<PlatformLogRequestFactory>(PlatformLogRequestFactory);
71
+
72
+ expect(factory).toEqual({
73
+ alterLog: expect.any(Function),
74
+ onLogResponse: expect.any(Function)
75
+ });
76
+
77
+ const ctx: any = {
78
+ request: {
79
+ method: "GET"
80
+ },
81
+ logger: {
82
+ alterLog: vi.fn(),
83
+ info: vi.fn()
84
+ }
85
+ };
86
+
87
+ DITest.injector.getProvider(PlatformLogRequestFactory)?.hooks?.$onRequest(factory!, ctx);
88
+
89
+ expect(ctx.logger.alterLog).toHaveBeenCalledWith(expect.any(Function));
90
+
91
+ ctx.logger.alterLog.mock.calls[0][0]({test: "test"}, "info");
92
+
93
+ expect(ctx.logStarted).toBe(true);
94
+ expect(factory!.alterLog).toHaveBeenCalledWith(
95
+ "info",
96
+ {
97
+ test: "test"
98
+ },
99
+ ctx
100
+ );
101
+
102
+ DITest.injector.getProvider(PlatformLogRequestFactory)?.hooks?.$onResponse(factory!, ctx);
103
+
104
+ expect(factory!.onLogResponse).toHaveBeenCalledWith(ctx);
105
+ });
106
+ });
107
+ describe("when logger is disabled", () => {
108
+ beforeEach(() =>
109
+ DITest.create({
110
+ logger: {
111
+ logRequest: false
112
+ }
113
+ })
114
+ );
115
+ afterEach(() => DITest.reset());
116
+
117
+ it("should create logger factory", async () => {
118
+ const factory = await DITest.invoke<PlatformLogRequestFactory>(PlatformLogRequestFactory);
119
+
120
+ expect(factory).toEqual(null);
121
+
122
+ const ctx: any = {
123
+ request: {
124
+ method: "GET"
125
+ },
126
+ logger: {
127
+ alterLog: vi.fn(),
128
+ info: vi.fn()
129
+ }
130
+ };
131
+
132
+ DITest.injector.getProvider(PlatformLogRequestFactory)?.hooks?.$onRequest(factory!, ctx);
133
+
134
+ expect(ctx.logger.alterLog).not.toHaveBeenCalled();
135
+
136
+ DITest.injector.getProvider(PlatformLogRequestFactory)?.hooks?.$onResponse(factory!, ctx);
137
+ });
138
+ });
139
+ });
@@ -0,0 +1,38 @@
1
+ import {type BaseContext, constant, injectable} from "@tsed/di";
2
+
3
+ import {defaultAlterLog} from "../utils/defaultAlterLog.js";
4
+ import {defaultLogResponse} from "../utils/defaultLogResponse.js";
5
+
6
+ function factory() {
7
+ const {
8
+ logRequest = true,
9
+ alterLog = defaultAlterLog,
10
+ onLogResponse = defaultLogResponse
11
+ } = constant<TsED.LoggerConfiguration>("logger", {});
12
+
13
+ return logRequest
14
+ ? {
15
+ alterLog,
16
+ onLogResponse
17
+ }
18
+ : null;
19
+ }
20
+
21
+ export type PlatformLogRequestFactory = ReturnType<typeof factory>;
22
+
23
+ export const PlatformLogRequestFactory = injectable(Symbol.for("PLATFORM:LOGGER:REQUEST"))
24
+ .factory(factory)
25
+ .hooks({
26
+ $onRequest(instance: ReturnType<typeof factory>, $ctx: BaseContext) {
27
+ if (instance) {
28
+ $ctx.logger.alterLog((obj: any, level) => instance.alterLog(level, obj, $ctx));
29
+ $ctx.logStarted = true;
30
+ }
31
+ },
32
+ $onResponse(instance: ReturnType<typeof factory>, $ctx: BaseContext) {
33
+ if (instance && $ctx.logStarted) {
34
+ instance.onLogResponse($ctx);
35
+ }
36
+ }
37
+ })
38
+ .token();
@@ -0,0 +1,66 @@
1
+ import {defaultAlterLog} from "./defaultAlterLog.js";
2
+
3
+ describe("defaultAlterLog", () => {
4
+ it("should return the expected log object", () => {
5
+ expect(
6
+ defaultAlterLog("info", {test: "test"}, {
7
+ request: {
8
+ method: "GET",
9
+ url: "url",
10
+ headers: {
11
+ "x-forwarded-for": "value"
12
+ },
13
+ route: "route"
14
+ }
15
+ } as never)
16
+ ).toEqual({
17
+ method: "GET",
18
+ route: "route",
19
+ test: "test",
20
+ url: "url"
21
+ });
22
+ });
23
+ it("should return the expected log object (without route)", () => {
24
+ expect(
25
+ defaultAlterLog("info", {test: "test"}, {
26
+ request: {
27
+ method: "GET",
28
+ url: "url",
29
+ headers: {
30
+ "x-forwarded-for": "value"
31
+ }
32
+ }
33
+ } as never)
34
+ ).toEqual({
35
+ method: "GET",
36
+ route: "url",
37
+ test: "test",
38
+ url: "url"
39
+ });
40
+ });
41
+ it("should return the expected log object with extras info", () => {
42
+ expect(
43
+ defaultAlterLog("error", {test: "test"}, {
44
+ request: {
45
+ method: "GET",
46
+ url: "url",
47
+ headers: {
48
+ "x-forwarded-for": "value"
49
+ },
50
+ route: "route"
51
+ }
52
+ } as never)
53
+ ).toEqual({
54
+ method: "GET",
55
+ route: "route",
56
+ test: "test",
57
+ url: "url",
58
+ body: undefined,
59
+ headers: {
60
+ "x-forwarded-for": "value"
61
+ },
62
+ params: undefined,
63
+ query: undefined
64
+ });
65
+ });
66
+ });
@@ -0,0 +1,22 @@
1
+ import type {DIContext} from "@tsed/di";
2
+
3
+ export function defaultAlterLog(level: string, obj: Record<string, unknown>, ctx: DIContext) {
4
+ const minimalLog = {
5
+ method: ctx.request.method,
6
+ url: ctx.request.url,
7
+ route: ctx.request.route || ctx.request.url,
8
+ ...obj
9
+ };
10
+
11
+ if (level === "info") {
12
+ return minimalLog;
13
+ }
14
+
15
+ return {
16
+ ...minimalLog,
17
+ headers: ctx.request.headers,
18
+ body: ctx.request.body,
19
+ query: ctx.request.query,
20
+ params: ctx.request.params
21
+ };
22
+ }
@@ -0,0 +1,98 @@
1
+ import {defaultLogResponse} from "./defaultLogResponse.js";
2
+
3
+ describe("defaultLogEnd", () => {
4
+ it("should log the request end", () => {
5
+ // GIVEN
6
+ const $ctx = {
7
+ response: {
8
+ statusCode: 200
9
+ },
10
+ logger: {
11
+ info: vi.fn()
12
+ }
13
+ };
14
+
15
+ // WHEN
16
+ defaultLogResponse($ctx as any);
17
+
18
+ // THEN
19
+ expect($ctx.logger.info).toHaveBeenCalledWith({
20
+ event: "request.end",
21
+ status: 200,
22
+ status_code: "200",
23
+ state: "OK"
24
+ });
25
+ });
26
+ it("should log the request end with error", () => {
27
+ // GIVEN
28
+ const $ctx = {
29
+ response: {
30
+ statusCode: 500
31
+ },
32
+ error: {
33
+ name: "name",
34
+ message: "message",
35
+ errors: "errors",
36
+ stack: "stack",
37
+ body: "body",
38
+ headers: "headers"
39
+ },
40
+ logger: {
41
+ error: vi.fn()
42
+ }
43
+ };
44
+
45
+ // WHEN
46
+ defaultLogResponse($ctx as any);
47
+
48
+ // THEN
49
+ expect($ctx.logger.error).toHaveBeenCalledWith({
50
+ event: "request.end",
51
+ status: 500,
52
+ status_code: "500",
53
+ state: "KO",
54
+ error_name: "name",
55
+ error_message: "message",
56
+ error_errors: "errors",
57
+ error_stack: "stack",
58
+ error_body: "body",
59
+ error_headers: "headers"
60
+ });
61
+ });
62
+ it("should log the request end with error code", () => {
63
+ // GIVEN
64
+ const $ctx = {
65
+ response: {
66
+ statusCode: 500
67
+ },
68
+ error: {
69
+ code: "code",
70
+ message: "message",
71
+ errors: "errors",
72
+ stack: "stack",
73
+ body: "body",
74
+ headers: "headers"
75
+ },
76
+ logger: {
77
+ error: vi.fn()
78
+ }
79
+ };
80
+
81
+ // WHEN
82
+ defaultLogResponse($ctx as any);
83
+
84
+ // THEN
85
+ expect($ctx.logger.error).toHaveBeenCalledWith({
86
+ event: "request.end",
87
+ status: 500,
88
+ status_code: "500",
89
+ state: "KO",
90
+ error_name: "code",
91
+ error_message: "message",
92
+ error_errors: "errors",
93
+ error_stack: "stack",
94
+ error_body: "body",
95
+ error_headers: "headers"
96
+ });
97
+ });
98
+ });
@@ -0,0 +1,30 @@
1
+ import {cleanObject} from "@tsed/core";
2
+ import type {BaseContext} from "@tsed/di";
3
+
4
+ export function defaultLogResponse($ctx: BaseContext) {
5
+ if ($ctx.response.statusCode >= 400) {
6
+ const error = $ctx.error as any | undefined;
7
+
8
+ $ctx.logger.error({
9
+ event: "request.end",
10
+ status: $ctx.response.statusCode,
11
+ status_code: String($ctx.response.statusCode),
12
+ state: "KO",
13
+ ...cleanObject({
14
+ error_name: error?.name || error?.code,
15
+ error_message: error?.message,
16
+ error_errors: error?.errors,
17
+ error_stack: error?.stack,
18
+ error_body: error?.body,
19
+ error_headers: error?.headers
20
+ })
21
+ });
22
+ } else {
23
+ $ctx.logger.info({
24
+ event: "request.end",
25
+ status: $ctx.response.statusCode,
26
+ status_code: String($ctx.response.statusCode),
27
+ state: "OK"
28
+ });
29
+ }
30
+ }