@ogcio/nextjs-logging-wrapper 14.2.5
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/README.md +61 -0
- package/package.json +38 -0
- package/src/client-logger.ts +38 -0
- package/src/common-logger.ts +17 -0
- package/src/server-logger.ts +69 -0
- package/src/shared.ts +60 -0
- package/tsconfig.json +11 -0
- package/tsconfig.prod.json +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# NextJs Logging Wrapper
|
|
2
|
+
|
|
3
|
+
This logging wrapper goal is to standardize the records written by our NextJs services.
|
|
4
|
+
|
|
5
|
+
## How to
|
|
6
|
+
|
|
7
|
+
To use this package three steps are needed:
|
|
8
|
+
|
|
9
|
+
- add `"nextjs-logging-wrapper": "*",` to your dependencies
|
|
10
|
+
- based on the needs, you can use one of the two available methods:
|
|
11
|
+
- `getServerLogger()`, that must be used to log entries on the server side
|
|
12
|
+
- `getClientLogger()`, used to log entries on the client side
|
|
13
|
+
|
|
14
|
+
_Server example_
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
import { getServerLogger } from "nextjs-logging-wrapper";
|
|
18
|
+
.....
|
|
19
|
+
"use server"
|
|
20
|
+
.....
|
|
21
|
+
getServerLogger().debug("Welcome to the server side!");
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
_Client example_
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
"use client"
|
|
28
|
+
import { getClientLogger } from "nextjs-logging-wrapper";
|
|
29
|
+
.....
|
|
30
|
+
getClientLogger().debug("Welcome to the client side!");
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Log entries format
|
|
34
|
+
|
|
35
|
+
### Client Side
|
|
36
|
+
|
|
37
|
+
The entry for the client logger will be something similar to
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
{"level":30,"level_name":"INFO","timestamp":1719322306071,"request":{"path":"/en/the-page","params":{"locale":"en"},"query_params":{}},"message":"Welcome to the client side"}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Server Side
|
|
44
|
+
|
|
45
|
+
The entry for the server logger will be something similar to
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
{"level":30,"level_name":"INFO","hostname":"SSalvatico-ITMAC24","timestamp":1719323762474,"request_id":"50572f1b-a789-459d-9770-c525b69a221e","request":{"scheme":"http","method":"GET","path":"/en/the-backend-route","hostname":"localhost:3002","language":"en","user_agent":"the user agent"},"message":"Welcome to the server side"}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Minimum Log Level
|
|
52
|
+
|
|
53
|
+
The minimum log level is decided, in order of priority, by:
|
|
54
|
+
|
|
55
|
+
- optional `minimumLevel` parameter passed when the logger instance is created
|
|
56
|
+
- `LOG_LEVEL` env variable
|
|
57
|
+
- otherwise `debug` is set as default value
|
|
58
|
+
|
|
59
|
+
## Docker
|
|
60
|
+
|
|
61
|
+
Remember to copy and build the package to the container in your Dockerfile!
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ogcio/nextjs-logging-wrapper",
|
|
3
|
+
"version": "14.2.5",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./client-logger": "./dist/client-logger.js",
|
|
8
|
+
"./common-logger": "./dist/common-logger.js",
|
|
9
|
+
"./server-logger": "./dist/server-logger.js"
|
|
10
|
+
},
|
|
11
|
+
"typesVersions": {
|
|
12
|
+
"*": {
|
|
13
|
+
"client-logger": ["./dist/client-logger.d.ts"],
|
|
14
|
+
"common-logger": ["./dist/common-logger.d.ts"],
|
|
15
|
+
"server-logger": ["./dist/server-logger.d.ts"]
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "rm -rf dist && tsc -w -p tsconfig.prod.json",
|
|
20
|
+
"build": "rm -rf dist && tsc -p tsconfig.prod.json",
|
|
21
|
+
"test": "echo \"Error: no test specified\" && exit 0",
|
|
22
|
+
"lint": "eslint . --ext .ts",
|
|
23
|
+
"lint:fix": "eslint . --ext .ts --fix"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [],
|
|
26
|
+
"author": "samuele.salvatico@nearform.com",
|
|
27
|
+
"license": "ISC",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tsconfig/node20": "^20.1.4",
|
|
30
|
+
"@types/node": "^22.10.2",
|
|
31
|
+
"typescript": "^5.7.2"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"next": "^14.2.5",
|
|
35
|
+
"pino": "^9.5.0",
|
|
36
|
+
"@ogcio/shared-errors": "^1.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useParams, usePathname, useSearchParams } from "next/navigation.js";
|
|
2
|
+
import type { Level, Logger } from "pino";
|
|
3
|
+
import { getPinoInstance } from "./shared.js";
|
|
4
|
+
|
|
5
|
+
export const REDACTED_PATHS = [];
|
|
6
|
+
|
|
7
|
+
let logger: Logger;
|
|
8
|
+
export const getClientLogger = (minimumLevel?: Level): Logger => {
|
|
9
|
+
if (!logger) {
|
|
10
|
+
logger = getPinoInstance({
|
|
11
|
+
minimumLevel,
|
|
12
|
+
loggingContext: getLoggingContext(),
|
|
13
|
+
pathsToRedact: REDACTED_PATHS,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return logger;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
interface ClientLoggingRequest {
|
|
21
|
+
params: { [x: string]: string };
|
|
22
|
+
path: string;
|
|
23
|
+
query_params: { [x: string]: string };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getLoggingContext = (): {
|
|
27
|
+
[x: string]: unknown;
|
|
28
|
+
request: ClientLoggingRequest;
|
|
29
|
+
} => {
|
|
30
|
+
const search: { [x: string]: string } = {};
|
|
31
|
+
useSearchParams().forEach((value, key) => {
|
|
32
|
+
search[key] = value;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
request: { path: usePathname(), params: useParams(), query_params: search },
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Level, Logger } from "pino";
|
|
2
|
+
import { getPinoInstance } from "./shared.js";
|
|
3
|
+
|
|
4
|
+
export const REDACTED_PATHS = [];
|
|
5
|
+
|
|
6
|
+
let logger: Logger;
|
|
7
|
+
export const getCommonLogger = (minimumLevel?: Level): Logger => {
|
|
8
|
+
if (!logger) {
|
|
9
|
+
logger = getPinoInstance({
|
|
10
|
+
minimumLevel,
|
|
11
|
+
loggingContext: { request: {} },
|
|
12
|
+
pathsToRedact: REDACTED_PATHS,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return logger;
|
|
17
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { hostname } from "os";
|
|
2
|
+
import { REQUEST_ID_HEADER } from "@ogcio/shared-errors";
|
|
3
|
+
import { headers } from "next/headers.js";
|
|
4
|
+
import type { Level, Logger } from "pino";
|
|
5
|
+
import { REQUEST_ID_LOG_LABEL, getPinoInstance } from "./shared.js";
|
|
6
|
+
|
|
7
|
+
export interface LoggingRequest {
|
|
8
|
+
scheme: string | undefined;
|
|
9
|
+
method: string | undefined;
|
|
10
|
+
path: string | undefined;
|
|
11
|
+
hostname: string | undefined;
|
|
12
|
+
query_params: unknown | undefined;
|
|
13
|
+
[key: string]: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const REDACTED_PATHS = [];
|
|
17
|
+
|
|
18
|
+
let logger: Logger;
|
|
19
|
+
export const getServerLogger = (minimumLevel?: Level): Logger => {
|
|
20
|
+
if (!logger) {
|
|
21
|
+
logger = getPinoInstance({
|
|
22
|
+
minimumLevel,
|
|
23
|
+
loggingContext: getLoggingContext(),
|
|
24
|
+
baseProperties: { hostname: hostname() },
|
|
25
|
+
pathsToRedact: REDACTED_PATHS,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return logger;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getRequestInfo = (mappedHeaders: {
|
|
33
|
+
[x: string]: unknown;
|
|
34
|
+
}): LoggingRequest => {
|
|
35
|
+
const getHeaderAsStringIfSet = (headerName: string): string | undefined =>
|
|
36
|
+
mappedHeaders[headerName] && typeof mappedHeaders[headerName] === "string"
|
|
37
|
+
? (mappedHeaders[headerName] as string)
|
|
38
|
+
: undefined;
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
scheme: getHeaderAsStringIfSet("x-forwarded-proto"),
|
|
42
|
+
method: "GET",
|
|
43
|
+
path: getHeaderAsStringIfSet("x-pathname"),
|
|
44
|
+
hostname: getHeaderAsStringIfSet("host"),
|
|
45
|
+
language: getHeaderAsStringIfSet("x-next-intl-locale"),
|
|
46
|
+
query_params: undefined,
|
|
47
|
+
user_agent: getHeaderAsStringIfSet("user-agent"),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const getLoggingContext = (): {
|
|
52
|
+
[x: string]: unknown;
|
|
53
|
+
request: LoggingRequest;
|
|
54
|
+
} => {
|
|
55
|
+
const requestHeaders = headers();
|
|
56
|
+
const requestId = requestHeaders.has(REQUEST_ID_HEADER)
|
|
57
|
+
? (requestHeaders.get(REQUEST_ID_HEADER) as string)
|
|
58
|
+
: crypto.randomUUID();
|
|
59
|
+
|
|
60
|
+
const outputHeaders: { [x: string]: unknown } = {};
|
|
61
|
+
requestHeaders.forEach((value, key) => {
|
|
62
|
+
outputHeaders[key] = value;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
[REQUEST_ID_LOG_LABEL]: requestId,
|
|
67
|
+
request: getRequestInfo(outputHeaders),
|
|
68
|
+
};
|
|
69
|
+
};
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Level,
|
|
3
|
+
type Logger,
|
|
4
|
+
type LoggerOptions,
|
|
5
|
+
levels,
|
|
6
|
+
pino,
|
|
7
|
+
} from "pino";
|
|
8
|
+
|
|
9
|
+
export const MESSAGE_KEY = "message";
|
|
10
|
+
|
|
11
|
+
export const REQUEST_ID_LOG_LABEL = "request_id";
|
|
12
|
+
|
|
13
|
+
export const REDACTED_VALUE = "[redacted]";
|
|
14
|
+
|
|
15
|
+
export const getPinoInstance = <T>(params: {
|
|
16
|
+
pathsToRedact: string[];
|
|
17
|
+
baseProperties?: { [x: string]: string };
|
|
18
|
+
loggingContext: {
|
|
19
|
+
[x: string]: unknown;
|
|
20
|
+
request: T;
|
|
21
|
+
};
|
|
22
|
+
minimumLevel?: Level;
|
|
23
|
+
}): Logger => {
|
|
24
|
+
const toCheckLevel = params.minimumLevel ?? process.env.LOG_LEVEL;
|
|
25
|
+
const level = isValidLevel(toCheckLevel) ? toCheckLevel : "debug";
|
|
26
|
+
|
|
27
|
+
return pino(getLoggerConfiguration({ ...params, minimumLevel: level }));
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isValidLevel = (level: string | undefined): level is Level =>
|
|
31
|
+
level !== undefined && Object.keys(levels.values).includes(level);
|
|
32
|
+
|
|
33
|
+
const getLoggerConfiguration = <T>(params: {
|
|
34
|
+
minimumLevel: Level;
|
|
35
|
+
pathsToRedact: string[];
|
|
36
|
+
baseProperties?: { [x: string]: string };
|
|
37
|
+
loggingContext: {
|
|
38
|
+
[x: string]: unknown;
|
|
39
|
+
request: T;
|
|
40
|
+
};
|
|
41
|
+
}): LoggerOptions => ({
|
|
42
|
+
base: params.baseProperties,
|
|
43
|
+
messageKey: MESSAGE_KEY,
|
|
44
|
+
mixin: () => ({
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
...params.loggingContext,
|
|
47
|
+
}),
|
|
48
|
+
redact: {
|
|
49
|
+
paths: params.pathsToRedact,
|
|
50
|
+
censor: REDACTED_VALUE,
|
|
51
|
+
},
|
|
52
|
+
timestamp: false,
|
|
53
|
+
formatters: {
|
|
54
|
+
level: (name: string, levelVal: number) => ({
|
|
55
|
+
level: levelVal,
|
|
56
|
+
level_name: name.toUpperCase(),
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
level: params.minimumLevel,
|
|
60
|
+
});
|
package/tsconfig.json
ADDED