@darksheep/logger 1.3.1 → 1.4.0
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/CHANGELOG.md +16 -0
- package/README.md +35 -13
- package/package.json +7 -5
- package/src/create-logger.js +4 -4
- package/src/formatters/formatter-console/stack.js +1 -1
- package/src/formatters/formatter-console.js +2 -2
- package/src/index.js +2 -2
- package/src/replacer.js +18 -23
- package/src/replacers/index.js +1 -1
- package/src/replacers/secrets.js +100 -11
- package/src/utilities/environment.js +25 -3
- package/src/utilities/legacy-secrets.js +36 -0
- package/src/utilities/parse-filters.js +2 -2
- package/src/utilities/resource-usage.js +9 -6
- package/types/create-logger.d.ts +2 -2
- package/types/formatter.d.ts +2 -2
- package/types/formatters/formatter-console/stack.d.ts +15 -11
- package/types/formatters/formatter-console.d.ts +2 -2
- package/types/formatters/formatter-json.d.ts +1 -1
- package/types/index.d.ts +26 -11
- package/types/logger.d.ts +119 -33
- package/types/replacer.d.ts +15 -15
- package/types/replacers/buffers.d.ts +5 -2
- package/types/replacers/error.d.ts +13 -13
- package/types/replacers/http-client-request.d.ts +6 -3
- package/types/replacers/http-incoming-message.d.ts +8 -4
- package/types/replacers/http-server-response.d.ts +6 -3
- package/types/replacers/index.d.ts +8 -8
- package/types/replacers/long-strings.d.ts +5 -2
- package/types/replacers/net-socket.d.ts +6 -3
- package/types/replacers/secrets.d.ts +6 -3
- package/types/stdout-write.d.ts +1 -1
- package/types/utilities/colour.d.ts +30 -30
- package/types/utilities/environment.d.ts +14 -22
- package/types/utilities/json-path.d.ts +1 -1
- package/types/utilities/last-callsite.d.ts +1 -1
- package/types/utilities/legacy-secrets.d.ts +33 -0
- package/types/utilities/log-filters.d.ts +2 -2
- package/types/utilities/log-types.d.ts +30 -4
- package/types/utilities/parse-filters.d.ts +1 -1
- package/types/utilities/parse-log-level.d.ts +5 -2
- package/types/utilities/resource-usage.d.ts +2 -2
- package/types/utilities/stacktrace.d.ts +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0](https://github.com/DarkSheepSoftware/node-packages/compare/logger-v1.3.1...logger-v1.4.0) (2026-09-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🌟 Features
|
|
7
|
+
|
|
8
|
+
* split logger secrets into redact and remove ([#845](https://github.com/DarkSheepSoftware/node-packages/issues/845)) ([924c6d5](https://github.com/DarkSheepSoftware/node-packages/commit/924c6d589e105c22048d3f05fd7151f03a2513dd))
|
|
9
|
+
* **traverse:** add @darksheep/traverse deep object traversal package ([#874](https://github.com/DarkSheepSoftware/node-packages/issues/874)) ([981bea0](https://github.com/DarkSheepSoftware/node-packages/commit/981bea0a3fa08c78152713108feb02dc0ccbdc37))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### 📦 Dependencies
|
|
13
|
+
|
|
14
|
+
* **pkg:** update dependency deepmerge-ts to v7.1.6 ([#938](https://github.com/DarkSheepSoftware/node-packages/issues/938)) ([194912b](https://github.com/DarkSheepSoftware/node-packages/commit/194912bd6af5f4a83c101080c4d5ea76047511c5))
|
|
15
|
+
* **pkg:** update dependency deepmerge-ts to v8 [security] ([ca3b39a](https://github.com/DarkSheepSoftware/node-packages/commit/ca3b39a53603b522d6f89a3230fa2836e02d532c))
|
|
16
|
+
* **pkg:** update dependency deepmerge-ts to v8.0.1 ([#949](https://github.com/DarkSheepSoftware/node-packages/issues/949)) ([0099631](https://github.com/DarkSheepSoftware/node-packages/commit/0099631ca6a8a6f437f75d83d63bb45c8383cead))
|
|
17
|
+
* **pkg:** update dependency deepmerge-ts to v8.0.2 ([#957](https://github.com/DarkSheepSoftware/node-packages/issues/957)) ([4c78b29](https://github.com/DarkSheepSoftware/node-packages/commit/4c78b296fee9d8d73abec141eb2151e6e4903fcf))
|
|
18
|
+
|
|
3
19
|
## [1.3.1](https://github.com/DarkSheepSoftware/node-packages/compare/logger-v1.3.0...logger-v1.3.1) (2026-04-17)
|
|
4
20
|
|
|
5
21
|
|
package/README.md
CHANGED
|
@@ -56,7 +56,8 @@ app.get('/', (request, response) => {
|
|
|
56
56
|
| `NODE_ENV` | The deployment location |
|
|
57
57
|
| `LOG_LEVEL` | Overrides the log level set by the detected environment |
|
|
58
58
|
| `LOG_FILTERS` | This allows you to allow/block specific channels |
|
|
59
|
-
| `
|
|
59
|
+
| `LOG_SECRET_REDACT` | This allows you to obscure secrets in an object |
|
|
60
|
+
| `LOG_SECRET_REMOVE` | This allows you to remove secrets from an object |
|
|
60
61
|
| `LOG_CALLSITES` | Include the callsite of the log |
|
|
61
62
|
|
|
62
63
|
#### `NODE_ENV`
|
|
@@ -108,18 +109,17 @@ The following filter means that ONLY `router` gets logged, except for the `route
|
|
|
108
109
|
LOG_FILTERS="router,-router.ok"
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
#### `
|
|
112
|
+
#### `LOG_SECRET_REDACT`
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
Comma-separated list of key patterns. Matched keys have their values replaced with `[secret ...]`. Prefix a pattern with `-` to exempt it from redacting.
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
Wild cards are permitted.
|
|
116
117
|
|
|
117
118
|
```js
|
|
118
|
-
//
|
|
119
|
+
// LOG_SECRET_REDACT="token"
|
|
119
120
|
|
|
120
121
|
// Given the following context
|
|
121
122
|
const context = {
|
|
122
|
-
password: '1234',
|
|
123
123
|
token: 'asdf',
|
|
124
124
|
};
|
|
125
125
|
|
|
@@ -129,12 +129,8 @@ const output = {
|
|
|
129
129
|
};
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
-
Wild cards are also permitted.
|
|
133
|
-
|
|
134
|
-
For example, using a positive (obscure) wildcard
|
|
135
|
-
|
|
136
132
|
```js
|
|
137
|
-
//
|
|
133
|
+
// LOG_SECRET_REDACT="*.token"
|
|
138
134
|
|
|
139
135
|
// Given the following context
|
|
140
136
|
const context = {
|
|
@@ -161,9 +157,35 @@ const output = {
|
|
|
161
157
|
};
|
|
162
158
|
```
|
|
163
159
|
|
|
164
|
-
|
|
160
|
+
#### `LOG_SECRET_REMOVE`
|
|
161
|
+
|
|
162
|
+
Comma-separated list of key patterns. Matched keys and their values are deleted entirely. Prefix a pattern with `-` to exempt a specific path from removal.
|
|
163
|
+
|
|
164
|
+
Wild cards are permitted.
|
|
165
|
+
|
|
166
|
+
```js
|
|
167
|
+
// LOG_SECRET_REMOVE="response,-response.statusMessage"
|
|
168
|
+
|
|
169
|
+
// Given the following context
|
|
170
|
+
const context = {
|
|
171
|
+
status: 200,
|
|
172
|
+
response: {
|
|
173
|
+
statusMessage: 'OK',
|
|
174
|
+
body: '...',
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// Then the following will be logged
|
|
179
|
+
const output = {
|
|
180
|
+
status: 200,
|
|
181
|
+
response: {
|
|
182
|
+
statusMessage: 'OK',
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
165
187
|
```js
|
|
166
|
-
//
|
|
188
|
+
// LOG_SECRET_REMOVE="*.token"
|
|
167
189
|
|
|
168
190
|
// Given the following context
|
|
169
191
|
const context = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@darksheep/logger",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Logging your stuff to where ever you want it",
|
|
5
5
|
"license": "UNLICENCED",
|
|
6
6
|
"type": "module",
|
|
@@ -22,15 +22,17 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@darksheep/environment": "3.2.0",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
25
|
+
"@darksheep/traverse": "1.0.0",
|
|
26
|
+
"deepmerge-ts": "8.0.2",
|
|
27
|
+
"stacktrace-parser": "0.1.11"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
|
-
"@types/traverse": "~0.6.33",
|
|
31
30
|
"nock": "~14.0.0"
|
|
32
31
|
},
|
|
33
32
|
"engines": {
|
|
34
33
|
"node": "^20.13.1 || >=22.2.0"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
35
37
|
}
|
|
36
38
|
}
|
package/src/create-logger.js
CHANGED
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
HttpServerResponseReplacer,
|
|
8
8
|
LongStringReplacer,
|
|
9
9
|
NetSocketReplacer,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
SecretRedact,
|
|
11
|
+
SecretRemove,
|
|
12
12
|
} from './replacers/index.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -20,8 +20,8 @@ export function createLogger(channel) {
|
|
|
20
20
|
typeof channel === 'string' ? { channel } : {},
|
|
21
21
|
{ replacers: [
|
|
22
22
|
// Always run the secret replacer first.
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
SecretRemove,
|
|
24
|
+
SecretRedact,
|
|
25
25
|
|
|
26
26
|
ErrorReplacer,
|
|
27
27
|
HttpClientRequestReplacer,
|
|
@@ -28,7 +28,7 @@ function flattenChannelLevel(channel, level) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (level != null && LogNames[level] != null) {
|
|
31
|
-
output +=
|
|
31
|
+
output += LogNames[level].toUpperCase();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
output += ': ';
|
|
@@ -77,7 +77,7 @@ function formatError(error, indent = 0) {
|
|
|
77
77
|
error.message,
|
|
78
78
|
{ foreground: 'red', effects: [ 'bold' ] },
|
|
79
79
|
)
|
|
80
|
-
:
|
|
80
|
+
: error.message;
|
|
81
81
|
|
|
82
82
|
const stack = formatStack(error.stack, indent);
|
|
83
83
|
if (stack.trim().length > 0) {
|
package/src/index.js
CHANGED
|
@@ -28,8 +28,8 @@ export {
|
|
|
28
28
|
HttpServerResponseReplacer,
|
|
29
29
|
LongStringReplacer,
|
|
30
30
|
NetSocketReplacer,
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
SecretRedact,
|
|
32
|
+
SecretRemove,
|
|
33
33
|
} from './replacers/index.js';
|
|
34
34
|
export { environment } from './utilities/environment.js';
|
|
35
35
|
export { LogLevels } from './utilities/log-types.js';
|
package/src/replacer.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* @import { TraverseContext } from 'traverse';
|
|
3
|
-
*/
|
|
4
|
-
import Traverse from 'traverse';
|
|
1
|
+
import { createTraverse } from '@darksheep/traverse';
|
|
5
2
|
|
|
6
3
|
import { nodesToPath } from './utilities/json-path.js';
|
|
7
4
|
|
|
@@ -21,34 +18,30 @@ import { nodesToPath } from './utilities/json-path.js';
|
|
|
21
18
|
* @returns {unknown}
|
|
22
19
|
*/
|
|
23
20
|
export function replace(object, replacers = []) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
* @returns {void}
|
|
29
|
-
* @this {TraverseContext}
|
|
30
|
-
*/
|
|
31
|
-
function (value) {
|
|
21
|
+
const walk = createTraverse(
|
|
22
|
+
(context) => {
|
|
23
|
+
const value = context.node;
|
|
24
|
+
|
|
32
25
|
if (value == null) {
|
|
33
|
-
|
|
26
|
+
context.remove();
|
|
34
27
|
return;
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
30
|
+
if (context.circular != null) {
|
|
31
|
+
context.update({ $ref: nodesToPath(context.circular.path) });
|
|
39
32
|
return;
|
|
40
33
|
}
|
|
41
34
|
|
|
42
35
|
for (const replacer of replacers) {
|
|
43
|
-
if (replacer.shouldReplace(value,
|
|
44
|
-
const replaced = replacer.replace(value,
|
|
36
|
+
if (replacer.shouldReplace(value, context.path)) {
|
|
37
|
+
const replaced = replacer.replace(value, context.path);
|
|
45
38
|
|
|
46
39
|
if (replaced == null) {
|
|
47
|
-
|
|
40
|
+
context.remove();
|
|
48
41
|
return;
|
|
49
42
|
}
|
|
50
43
|
|
|
51
|
-
|
|
44
|
+
context.update(replaced, replacer.stopHere);
|
|
52
45
|
return;
|
|
53
46
|
}
|
|
54
47
|
}
|
|
@@ -59,7 +52,7 @@ export function replace(object, replacers = []) {
|
|
|
59
52
|
typeof value.toJSON === 'function'
|
|
60
53
|
) {
|
|
61
54
|
try {
|
|
62
|
-
|
|
55
|
+
context.update(value.toJSON());
|
|
63
56
|
return;
|
|
64
57
|
} catch { }
|
|
65
58
|
}
|
|
@@ -69,20 +62,22 @@ export function replace(object, replacers = []) {
|
|
|
69
62
|
Object.getPrototypeOf(value) !== Object.getPrototypeOf({}) &&
|
|
70
63
|
Object.getPrototypeOf(value) !== Object.getPrototypeOf([])
|
|
71
64
|
) {
|
|
72
|
-
|
|
65
|
+
context.update({ $class: value.constructor.name });
|
|
73
66
|
return;
|
|
74
67
|
}
|
|
75
68
|
|
|
76
69
|
if (Object.getPrototypeOf(value)?.constructor === Object) {
|
|
77
|
-
|
|
70
|
+
context.update({ ...value });
|
|
78
71
|
}
|
|
79
72
|
|
|
80
73
|
if (
|
|
81
74
|
Array.isArray(value) &&
|
|
82
75
|
Object.getPrototypeOf(value).constructor === Array
|
|
83
76
|
) {
|
|
84
|
-
|
|
77
|
+
context.update([ ...value ]);
|
|
85
78
|
}
|
|
86
79
|
},
|
|
87
80
|
);
|
|
81
|
+
|
|
82
|
+
return walk(object);
|
|
88
83
|
}
|
package/src/replacers/index.js
CHANGED
|
@@ -5,4 +5,4 @@ export { HttpIncomingMessageReplacer } from './http-incoming-message.js';
|
|
|
5
5
|
export { HttpServerResponseReplacer } from './http-server-response.js';
|
|
6
6
|
export { LongStringReplacer } from './long-strings.js';
|
|
7
7
|
export { NetSocketReplacer } from './net-socket.js';
|
|
8
|
-
export {
|
|
8
|
+
export { SecretRedact, SecretRemove } from './secrets.js';
|
package/src/replacers/secrets.js
CHANGED
|
@@ -5,30 +5,106 @@
|
|
|
5
5
|
import { environment } from '../utilities/environment.js';
|
|
6
6
|
import { nodesToPath } from '../utilities/json-path.js';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Whether the subtree at `value` actually contains a node whose path is
|
|
10
|
+
* exempted from removal. Used to decide whether to descend into a node we
|
|
11
|
+
* would otherwise remove wholesale: we only keep the container when a real
|
|
12
|
+
* exempted descendant lives beneath it, so no empty container is left behind.
|
|
13
|
+
*
|
|
14
|
+
* Descendant paths are tested against the compiled exemption patterns, so
|
|
15
|
+
* wildcards behave exactly as they do elsewhere.
|
|
16
|
+
*
|
|
17
|
+
* @param {unknown} value - The node whose descendants to inspect.
|
|
18
|
+
* @param {string} path - The dotted path of `value`.
|
|
19
|
+
* @param {RegExp[]} blocked - The compiled exemption patterns.
|
|
20
|
+
* @param {WeakSet<object>} [seen] - Guards against circular references.
|
|
21
|
+
* @returns {boolean}
|
|
22
|
+
*/
|
|
23
|
+
function hasExemptedDescendant(value, path, blocked, seen = new WeakSet()) {
|
|
24
|
+
if (value === null || typeof value !== 'object' || seen.has(value)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
seen.add(value);
|
|
28
|
+
|
|
29
|
+
const record = /** @type {Record<string, unknown>} */ (value);
|
|
30
|
+
for (const [ key, child ] of Object.entries(record)) {
|
|
31
|
+
const childPath = path === '' ? key : `${path}.${key}`;
|
|
32
|
+
|
|
33
|
+
for (const filter of blocked) {
|
|
34
|
+
if (filter.test(childPath)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (hasExemptedDescendant(child, childPath, blocked, seen)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
8
47
|
/** @type {Replacer} */
|
|
9
|
-
export const
|
|
10
|
-
name: '
|
|
48
|
+
export const SecretRemove = {
|
|
49
|
+
name: 'SecretRemove',
|
|
11
50
|
stopHere: true,
|
|
12
|
-
shouldReplace: (
|
|
51
|
+
shouldReplace: (value, original) => {
|
|
13
52
|
if (original == null) {
|
|
14
53
|
return false;
|
|
15
54
|
}
|
|
16
55
|
|
|
17
56
|
const path = original.join('.');
|
|
18
|
-
|
|
57
|
+
|
|
58
|
+
let directlyRemoved = false;
|
|
59
|
+
for (const filter of environment.removeFilters.allowed) {
|
|
19
60
|
if (filter.test(path)) {
|
|
20
|
-
|
|
61
|
+
directlyRemoved = true;
|
|
62
|
+
break;
|
|
21
63
|
}
|
|
22
64
|
}
|
|
23
65
|
|
|
24
|
-
|
|
66
|
+
let ancestorRemoved = false;
|
|
67
|
+
if (directlyRemoved === false) {
|
|
68
|
+
for (let i = 1; i < original.length; i++) {
|
|
69
|
+
const ancestorPath = original.slice(0, i).join('.');
|
|
70
|
+
for (const filter of environment.removeFilters.allowed) {
|
|
71
|
+
if (filter.test(ancestorPath)) {
|
|
72
|
+
ancestorRemoved = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (ancestorRemoved === true) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (directlyRemoved === false && ancestorRemoved === false) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const filter of environment.removeFilters.blocked) {
|
|
87
|
+
if (filter.test(path)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// This node is slated for removal (directly or via an ancestor). Only keep
|
|
93
|
+
// it when a real exempted descendant lives beneath it, so traversal
|
|
94
|
+
// continues into its children; otherwise remove the whole subtree without
|
|
95
|
+
// leaving an empty container behind.
|
|
96
|
+
if (hasExemptedDescendant(value, path, environment.removeFilters.blocked)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
25
101
|
},
|
|
26
102
|
replace: () => null,
|
|
27
103
|
};
|
|
28
104
|
|
|
29
105
|
/** @type {Replacer} */
|
|
30
|
-
export const
|
|
31
|
-
name: '
|
|
106
|
+
export const SecretRedact = {
|
|
107
|
+
name: 'SecretRedact',
|
|
32
108
|
stopHere: true,
|
|
33
109
|
shouldReplace: (_, original) => {
|
|
34
110
|
if (original == null) {
|
|
@@ -36,13 +112,26 @@ export const SecretObscure = {
|
|
|
36
112
|
}
|
|
37
113
|
|
|
38
114
|
const path = original.join('.');
|
|
39
|
-
|
|
115
|
+
|
|
116
|
+
let matched = false;
|
|
117
|
+
for (const filter of environment.redactFilters.allowed) {
|
|
40
118
|
if (filter.test(path)) {
|
|
41
|
-
|
|
119
|
+
matched = true;
|
|
120
|
+
break;
|
|
42
121
|
}
|
|
43
122
|
}
|
|
44
123
|
|
|
45
|
-
|
|
124
|
+
if (matched === false) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const filter of environment.redactFilters.blocked) {
|
|
129
|
+
if (filter.test(path)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return true;
|
|
46
135
|
},
|
|
47
136
|
replace: (_, path) => {
|
|
48
137
|
if (path == null) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getTypedEnv } from '@darksheep/environment';
|
|
2
2
|
|
|
3
|
+
import { resolveSecretFilters } from './legacy-secrets.js';
|
|
3
4
|
import { parseFilters } from './parse-filters.js';
|
|
4
5
|
import { parseLogLevel } from './parse-log-level.js';
|
|
5
6
|
|
|
@@ -10,8 +11,10 @@ const {
|
|
|
10
11
|
LOG_LEVEL,
|
|
11
12
|
LOG_MAX_LENGTH = 1024,
|
|
12
13
|
LOG_MEMORY = false,
|
|
13
|
-
LOG_SECRETS,
|
|
14
14
|
LOG_RELATIVE_TO,
|
|
15
|
+
LOG_SECRETS,
|
|
16
|
+
LOG_SECRET_REDACT,
|
|
17
|
+
LOG_SECRET_REMOVE,
|
|
15
18
|
NODE_ENV = 'production',
|
|
16
19
|
} = getTypedEnv({
|
|
17
20
|
LOG_CALLSITES: '?boolean',
|
|
@@ -20,11 +23,29 @@ const {
|
|
|
20
23
|
LOG_LEVEL: '?string',
|
|
21
24
|
LOG_MAX_LENGTH: '?number',
|
|
22
25
|
LOG_MEMORY: '?boolean',
|
|
23
|
-
LOG_SECRETS: '?string',
|
|
24
26
|
LOG_RELATIVE_TO: '?string',
|
|
27
|
+
LOG_SECRETS: '?string',
|
|
28
|
+
LOG_SECRET_REDACT: '?string',
|
|
29
|
+
LOG_SECRET_REMOVE: '?string',
|
|
25
30
|
NODE_ENV: '?string',
|
|
26
31
|
});
|
|
27
32
|
|
|
33
|
+
// `LOG_SECRETS` is the pre-split control; map it onto the redact/remove vars
|
|
34
|
+
// so existing configs keep working. The split vars win when set.
|
|
35
|
+
if (LOG_SECRETS != null) {
|
|
36
|
+
process.emitWarning(
|
|
37
|
+
'LOG_SECRETS is deprecated; use LOG_SECRET_REDACT (obscure values) and ' +
|
|
38
|
+
'LOG_SECRET_REMOVE (delete keys) instead.',
|
|
39
|
+
{ code: 'DEP_LOG_SECRETS', type: 'DeprecationWarning' },
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const secretFilters = resolveSecretFilters({
|
|
44
|
+
secrets: LOG_SECRETS,
|
|
45
|
+
redact: LOG_SECRET_REDACT,
|
|
46
|
+
remove: LOG_SECRET_REMOVE,
|
|
47
|
+
});
|
|
48
|
+
|
|
28
49
|
export const environment = {
|
|
29
50
|
includeCallsite: LOG_CALLSITES,
|
|
30
51
|
includeCpuUsage: LOG_CPU,
|
|
@@ -34,7 +55,8 @@ export const environment = {
|
|
|
34
55
|
isTesting: NODE_ENV === 'test',
|
|
35
56
|
logFilters: parseFilters(LOG_FILTERS, [ /.*/u ]),
|
|
36
57
|
logLevel: parseLogLevel(LOG_LEVEL, NODE_ENV),
|
|
37
|
-
|
|
58
|
+
redactFilters: secretFilters.redactFilters,
|
|
38
59
|
relativeTo: LOG_RELATIVE_TO,
|
|
60
|
+
removeFilters: secretFilters.removeFilters,
|
|
39
61
|
stringMaxLength: LOG_MAX_LENGTH,
|
|
40
62
|
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { parseFilters } from './parse-filters.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {{ allowed: RegExp[], blocked: RegExp[] }} Filters
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves the redact and remove filters, mapping the legacy `LOG_SECRETS`
|
|
9
|
+
* control onto them when the split vars are not set.
|
|
10
|
+
*
|
|
11
|
+
* In `LOG_SECRETS` a bare pattern obscured (redacted) the value and a
|
|
12
|
+
* `-`-prefixed pattern removed the key -- which is exactly the `allowed` /
|
|
13
|
+
* `blocked` split that `parseFilters` already produces. So the legacy
|
|
14
|
+
* `allowed` patterns become the redact set and the legacy `blocked` patterns
|
|
15
|
+
* become the remove set (neither carried exemptions). Whenever the matching
|
|
16
|
+
* split var is set it wins and the legacy value is ignored for that half.
|
|
17
|
+
*
|
|
18
|
+
* @param {{
|
|
19
|
+
* secrets?: string | undefined;
|
|
20
|
+
* redact?: string | undefined;
|
|
21
|
+
* remove?: string | undefined;
|
|
22
|
+
* }} options - The raw environment values.
|
|
23
|
+
* @returns {{ redactFilters: Filters, removeFilters: Filters }}
|
|
24
|
+
*/
|
|
25
|
+
export function resolveSecretFilters({ secrets, redact, remove }) {
|
|
26
|
+
const legacy = parseFilters(secrets);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
redactFilters: redact == null
|
|
30
|
+
? { allowed: legacy.allowed, blocked: [] }
|
|
31
|
+
: parseFilters(redact),
|
|
32
|
+
removeFilters: remove == null
|
|
33
|
+
? { allowed: legacy.blocked, blocked: [] }
|
|
34
|
+
: parseFilters(remove),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -29,8 +29,8 @@ export function parseFilters(filter = '', fallback = []) {
|
|
|
29
29
|
|
|
30
30
|
let regex = channel
|
|
31
31
|
.replaceAll(/[\s#$()*+,\-.?[\\\]^{|}]/gu, String.raw`\$&`)
|
|
32
|
-
.replaceAll(String.raw`\*`, multiLevelWildcard.source)
|
|
33
|
-
.replaceAll(String.raw`\+`, oneLevelWildcard.source);
|
|
32
|
+
.replaceAll(String.raw`\*`, () => multiLevelWildcard.source)
|
|
33
|
+
.replaceAll(String.raw`\+`, () => oneLevelWildcard.source);
|
|
34
34
|
|
|
35
35
|
// Allow for multi level wildcards at the start of a filter
|
|
36
36
|
if (regex.startsWith(String.raw`.*\.`)) {
|
|
@@ -14,8 +14,11 @@ export function getMemoryUsage() {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/** Previous sample, kept on one object so `getCPUUsage` mutates no bindings. */
|
|
18
|
+
const last = {
|
|
19
|
+
called: performance.now(),
|
|
20
|
+
value: process.cpuUsage(),
|
|
21
|
+
};
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Get the callsite that we think is outside the package.
|
|
@@ -30,12 +33,12 @@ export function getCPUUsage() {
|
|
|
30
33
|
/**
|
|
31
34
|
* Time difference in microseconds.
|
|
32
35
|
*/
|
|
33
|
-
const elapsed = Math.floor(1000 * (currentCalled -
|
|
36
|
+
const elapsed = Math.floor(1000 * (currentCalled - last.called));
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
last.called = currentCalled;
|
|
39
|
+
last.value = process.cpuUsage(last.value);
|
|
37
40
|
|
|
38
|
-
const { system, user } =
|
|
41
|
+
const { system, user } = last.value;
|
|
39
42
|
const total = system + user;
|
|
40
43
|
|
|
41
44
|
return { elapsed, total, system, user };
|
package/types/create-logger.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
1
2
|
/**
|
|
2
3
|
* @param {string} [channel] - The logging channel.
|
|
3
4
|
* @returns {Logger}
|
|
4
5
|
*/
|
|
5
|
-
export function createLogger(channel?: string): Logger;
|
|
6
|
-
import { Logger } from './logger.js';
|
|
6
|
+
export declare function createLogger(channel?: string): Logger;
|
package/types/formatter.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
export type Formatter = (logEntry: import('./utilities/log-types.js').LogEntry) => string;
|
|
1
2
|
/**
|
|
2
3
|
* @typedef {(logEntry: import('./utilities/log-types.js').LogEntry) => string} Formatter
|
|
3
4
|
*/
|
|
4
5
|
/** @type {Formatter} */
|
|
5
|
-
export const formatter: Formatter;
|
|
6
|
-
export type Formatter = (logEntry: import("./utilities/log-types.js").LogEntry) => string;
|
|
6
|
+
export declare const formatter: Formatter;
|
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
* @
|
|
4
|
-
* @returns {string}
|
|
2
|
+
* @import { ColourOptions } from '../../utilities/colour.js';
|
|
3
|
+
* @import { Callsite } from '../../utilities/log-types.js';
|
|
5
4
|
*/
|
|
6
|
-
|
|
5
|
+
import type { ColourOptions } from '../../utilities/colour.js';
|
|
6
|
+
import type { Callsite } from '../../utilities/log-types.js';
|
|
7
7
|
export type HideOption = {
|
|
8
8
|
hide?: boolean;
|
|
9
9
|
};
|
|
10
10
|
export type LineOptions = (HideOption & {
|
|
11
|
-
Line?: Omit<ColourOptions,
|
|
12
|
-
FunctionName?: Omit<ColourOptions,
|
|
13
|
-
FilePath?: Omit<ColourOptions,
|
|
14
|
-
LineNumber?: Omit<ColourOptions,
|
|
15
|
-
ColumnNumber?: Omit<ColourOptions,
|
|
11
|
+
Line?: Omit<ColourOptions, 'reset'>;
|
|
12
|
+
FunctionName?: Omit<ColourOptions, 'reset'> & HideOption;
|
|
13
|
+
FilePath?: Omit<ColourOptions, 'reset'>;
|
|
14
|
+
LineNumber?: Omit<ColourOptions, 'reset'> & HideOption;
|
|
15
|
+
ColumnNumber?: Omit<ColourOptions, 'reset'> & HideOption;
|
|
16
16
|
});
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param {Callsite[] | string | undefined} stack - The stack trace to format.
|
|
19
|
+
* @param {number} indent - The indent to render from.
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatStack(stack: Callsite[] | string | undefined, indent: number): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import type { LogEntry } from '../utilities/log-types.js';
|
|
1
2
|
/**
|
|
2
3
|
* @param {LogEntry} logEntry - The Log entry which we're going to convert to a splatted string.
|
|
3
4
|
* @returns {string}
|
|
4
5
|
*/
|
|
5
|
-
export function formatterConsole(logEntry: LogEntry): string;
|
|
6
|
-
import type { LogEntry } from '../utilities/log-types.js';
|
|
6
|
+
export declare function formatterConsole(logEntry: LogEntry): string;
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* @param {import('../utilities/log-types.js').LogEntry} logEntry - The Log entry which we're going to convert to JSON.
|
|
3
3
|
* @returns {string}
|
|
4
4
|
*/
|
|
5
|
-
export function formatterJson(logEntry: import(
|
|
5
|
+
export declare function formatterJson(logEntry: import('../utilities/log-types.js').LogEntry): string;
|