@darksheep/logger 1.0.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 +31 -0
- package/README.md +191 -0
- package/package.json +44 -0
- package/src/create-logger.js +36 -0
- package/src/formatter.js +14 -0
- package/src/formatters/formatter-console.js +243 -0
- package/src/formatters/formatter-json.js +7 -0
- package/src/index.js +25 -0
- package/src/logger.js +369 -0
- package/src/replacer.js +68 -0
- package/src/replacers/buffers.js +18 -0
- package/src/replacers/error.js +40 -0
- package/src/replacers/http-client-request.js +18 -0
- package/src/replacers/http-incoming-message.js +26 -0
- package/src/replacers/http-server-response.js +16 -0
- package/src/replacers/index.js +8 -0
- package/src/replacers/long-strings.js +11 -0
- package/src/replacers/net-socket.js +21 -0
- package/src/replacers/secrets.js +50 -0
- package/src/stdout-write.js +7 -0
- package/src/utilities/colour.js +152 -0
- package/src/utilities/environment.js +61 -0
- package/src/utilities/json-path.js +17 -0
- package/src/utilities/last-callsite.js +11 -0
- package/src/utilities/log-filters.js +22 -0
- package/src/utilities/log-types.js +46 -0
- package/src/utilities/parse-filters.js +47 -0
- package/src/utilities/parse-log-level.js +32 -0
- package/src/utilities/stacktrace.js +57 -0
- package/types/assert.zod.d.ts +18 -0
- package/types/create-logger.d.ts +5 -0
- package/types/formatter.d.ts +6 -0
- package/types/formatters/formatter-console.d.ts +9 -0
- package/types/formatters/formatter-json.d.ts +5 -0
- package/types/index.d.ts +11 -0
- package/types/logger.d.ts +184 -0
- package/types/logger.test.d.ts +1 -0
- package/types/replacer.d.ts +45 -0
- package/types/replacer.test.d.ts +1 -0
- package/types/replacers/buffers.d.ts +2 -0
- package/types/replacers/buffers.test.d.ts +1 -0
- package/types/replacers/error.d.ts +37 -0
- package/types/replacers/error.test.d.ts +1 -0
- package/types/replacers/http-client-request.d.ts +3 -0
- package/types/replacers/http-client-request.test.d.ts +1 -0
- package/types/replacers/http-incoming-message.d.ts +15 -0
- package/types/replacers/http-server-response.d.ts +3 -0
- package/types/replacers/index.d.ts +8 -0
- package/types/replacers/long-strings.d.ts +2 -0
- package/types/replacers/net-socket.d.ts +3 -0
- package/types/replacers/secrets.d.ts +4 -0
- package/types/stdout-write.d.ts +5 -0
- package/types/utilities/colour.d.ts +76 -0
- package/types/utilities/environment.d.ts +25 -0
- package/types/utilities/json-path.d.ts +5 -0
- package/types/utilities/last-callsite.d.ts +9 -0
- package/types/utilities/log-filters.d.ts +12 -0
- package/types/utilities/log-types.d.ts +126 -0
- package/types/utilities/parse-filters.d.ts +9 -0
- package/types/utilities/parse-log-level.d.ts +7 -0
- package/types/utilities/stacktrace.d.ts +59 -0
package/src/logger.js
ADDED
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { deepmerge } from 'deepmerge-ts';
|
|
3
|
+
|
|
4
|
+
import { environment } from './utilities/environment.js';
|
|
5
|
+
import { getLastCallsite } from './utilities/last-callsite.js';
|
|
6
|
+
import { LogLevels } from './utilities/log-types.js';
|
|
7
|
+
import { checkLogLevel, checkLogFilters } from './utilities/log-filters.js';
|
|
8
|
+
|
|
9
|
+
import { replace } from './replacer.js';
|
|
10
|
+
import { ErrorReplacer } from './replacers/error.js';
|
|
11
|
+
import { stdoutWrite } from './stdout-write.js';
|
|
12
|
+
import { formatter } from './formatter.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} Options
|
|
16
|
+
* @property {typeof replace} [replace] The replacer function to use
|
|
17
|
+
* @property {import('./replacer.js').Replacer<any, any>[]} [replacers] The replacers to use in the replacer
|
|
18
|
+
* @property {typeof formatter} [format] The formatter function to use (eg console or json formatter)
|
|
19
|
+
* @property {typeof stdoutWrite} [write] The place to write the log to
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export class Logger {
|
|
23
|
+
/** @type {AsyncLocalStorage<import('./utilities/log-types.js').LogContext[]>} */
|
|
24
|
+
static storage = new AsyncLocalStorage();
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @template [R=unknown]
|
|
28
|
+
* @overload
|
|
29
|
+
* @param {() => R} callback The function to wrap the context in
|
|
30
|
+
* @returns {R}
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* @template [R=unknown]
|
|
34
|
+
* @overload
|
|
35
|
+
* @param {import('./utilities/log-types.js').LogContext} context Context to bind ot the async context
|
|
36
|
+
* @param {() => R} callback The function to wrap the context in
|
|
37
|
+
* @returns {R}
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* @template [R=unknown]
|
|
41
|
+
* @param {unknown} context Context to bind ot the async context
|
|
42
|
+
* @param {unknown} [callback] The function to wrap the context in
|
|
43
|
+
* @returns {R}
|
|
44
|
+
*/
|
|
45
|
+
static wrap(context, callback) {
|
|
46
|
+
// If there is already some context, keep it around
|
|
47
|
+
const store = Logger.storage.getStore() ?? [];
|
|
48
|
+
|
|
49
|
+
if (typeof context === 'function') {
|
|
50
|
+
return Logger.storage.run(
|
|
51
|
+
[ ...store ],
|
|
52
|
+
/** @type {() => R} */ (context),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Logger.storage.run(
|
|
57
|
+
[ ...store, /** @type {import('./utilities/log-types.js').LogContext} */ (context) ],
|
|
58
|
+
/** @type {() => R} */ (callback),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @template [R=unknown]
|
|
64
|
+
* @param {() => R} callback The function to wrap the context in
|
|
65
|
+
* @returns {R}
|
|
66
|
+
*/
|
|
67
|
+
wrap(callback) {
|
|
68
|
+
return Logger.wrap(this.#context, callback);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {import('./utilities/log-types.js').LogContext} context Context to bind ot the async context
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
75
|
+
static addAsyncContext(context) {
|
|
76
|
+
const store = Logger.storage.getStore();
|
|
77
|
+
|
|
78
|
+
if (store == null) {
|
|
79
|
+
throw new Error('Context not inside Context#wrap');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
store.push(context);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** @type {import('./replacer.js').Replacer[]} */
|
|
86
|
+
replacers = [];
|
|
87
|
+
|
|
88
|
+
/** @type {typeof formatter} */
|
|
89
|
+
#format = formatter;
|
|
90
|
+
/** @type {typeof replace} */
|
|
91
|
+
#replace = replace;
|
|
92
|
+
/** @type {typeof stdoutWrite} */
|
|
93
|
+
#write = stdoutWrite;
|
|
94
|
+
/** @type {import('./utilities/log-types.js').LogContext} */
|
|
95
|
+
#context;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Context to add to the logger
|
|
99
|
+
* @param {Options} [options] Options for the loggers output
|
|
100
|
+
*/
|
|
101
|
+
constructor(context, options) {
|
|
102
|
+
this.#context = context ?? {};
|
|
103
|
+
if (options?.replace != null) {
|
|
104
|
+
this.#replace = options.replace;
|
|
105
|
+
}
|
|
106
|
+
if (options?.replacers != null) {
|
|
107
|
+
this.replacers = options.replacers;
|
|
108
|
+
}
|
|
109
|
+
if (options?.format != null) {
|
|
110
|
+
this.#format = options.format;
|
|
111
|
+
}
|
|
112
|
+
if (options?.write != null) {
|
|
113
|
+
this.#write = options.write;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Write a new log entry
|
|
119
|
+
* @param {import('./utilities/log-types.js').LogLevel} level the log level of the message to log
|
|
120
|
+
* @param {Error | string} message The message to log
|
|
121
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Context to add to the log (merged with this.context, and Logger.contexts)
|
|
122
|
+
* @returns {void}
|
|
123
|
+
*/
|
|
124
|
+
write(level, message, context) {
|
|
125
|
+
/** @type {import('./utilities/log-types.js').LogContext[]} */
|
|
126
|
+
const contexts = [];
|
|
127
|
+
|
|
128
|
+
const asyncContexts = Logger.storage.getStore();
|
|
129
|
+
if (asyncContexts instanceof Array) {
|
|
130
|
+
contexts.push(...asyncContexts);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
contexts.push(this.#context);
|
|
134
|
+
if (context instanceof Object) {
|
|
135
|
+
contexts.push(context);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const fullContext =
|
|
139
|
+
/** @type {import('./utilities/log-types.js').LogContext} */
|
|
140
|
+
(deepmerge(...contexts));
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
checkLogLevel(level) === false ||
|
|
144
|
+
checkLogFilters(fullContext?.channel) === false
|
|
145
|
+
) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @type {import('./utilities/log-types.js').LogEntry} */
|
|
150
|
+
const entry = (
|
|
151
|
+
typeof message === 'string'
|
|
152
|
+
? { ...fullContext, message, level }
|
|
153
|
+
: { ...fullContext, ...ErrorReplacer.replace(message), level }
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
if (environment.includeCallsite === true) {
|
|
157
|
+
entry.source = getLastCallsite();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const normalised =
|
|
161
|
+
/** @type {import('./utilities/log-types.js').LogEntry} */
|
|
162
|
+
(this.#replace(entry, this.replacers));
|
|
163
|
+
|
|
164
|
+
const formatted = this.#format(normalised);
|
|
165
|
+
|
|
166
|
+
return this.#write(formatted);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// =========
|
|
170
|
+
// Level
|
|
171
|
+
// =========
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Write a log with the level 'critical' - A crucial part of the application is not working
|
|
175
|
+
*
|
|
176
|
+
* @param {Error | string} message The message to log
|
|
177
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
178
|
+
* @returns {void}
|
|
179
|
+
*/
|
|
180
|
+
critical(message, context) {
|
|
181
|
+
return this.write(LogLevels.critical, message, context);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Write a log with the level 'error' - A non critical operation fails
|
|
186
|
+
* @param {Error | string} message The message to log
|
|
187
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
error(message, context) {
|
|
191
|
+
return this.write(LogLevels.error, message, context);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Write a log with the level 'warning' - An operation might fail in the future
|
|
196
|
+
* @param {Error | string} message The message to log
|
|
197
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
198
|
+
* @returns {void}
|
|
199
|
+
*/
|
|
200
|
+
warning(message, context) {
|
|
201
|
+
return this.write(LogLevels.warning, message, context);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Write a log with the level 'notice' - Information about events that may be unusual
|
|
206
|
+
* @param {Error | string} message The message to log
|
|
207
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
208
|
+
* @returns {void}
|
|
209
|
+
*/
|
|
210
|
+
notice(message, context) {
|
|
211
|
+
return this.write(LogLevels.notice, message, context);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Write a log with the level 'info' - Information about successful operations
|
|
216
|
+
* @param {Error | string} message The message to log
|
|
217
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
218
|
+
* @returns {void}
|
|
219
|
+
*/
|
|
220
|
+
info(message, context) {
|
|
221
|
+
return this.write(LogLevels.info, message, context);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Write a log with the level 'debug' - Information that is unlikely to help in production
|
|
226
|
+
* @param {Error | string} message The message to log
|
|
227
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
228
|
+
* @returns {void}
|
|
229
|
+
*/
|
|
230
|
+
debug(message, context) {
|
|
231
|
+
return this.write(LogLevels.debug, message, context);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Write a log with the level 'silly' - Information to help resolve complex logic issues
|
|
236
|
+
* @param {Error | string} message The message to log
|
|
237
|
+
* @param {import('./utilities/log-types.js').LogContext} [context] Additional context
|
|
238
|
+
* @returns {void}
|
|
239
|
+
*/
|
|
240
|
+
silly(message, context) {
|
|
241
|
+
return this.write(LogLevels.silly, message, context);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// =========
|
|
245
|
+
// Context
|
|
246
|
+
// =========
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* @param {import('./utilities/log-types.js').LogContext} context The new context
|
|
250
|
+
* @param {import('./utilities/log-types.js').LogLevel} [level] The log level at which we allow this context
|
|
251
|
+
* @returns {Logger}
|
|
252
|
+
*/
|
|
253
|
+
#overrideContext(context, level) {
|
|
254
|
+
if (
|
|
255
|
+
typeof level === 'number' &&
|
|
256
|
+
checkLogLevel(level) === false
|
|
257
|
+
) {
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return new Logger(
|
|
262
|
+
deepmerge(this.#context, context),
|
|
263
|
+
{
|
|
264
|
+
replace: this.#replace,
|
|
265
|
+
replacers: this.replacers,
|
|
266
|
+
format: this.#format,
|
|
267
|
+
write: this.#write,
|
|
268
|
+
},
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Creating a new logger with {context, ...this.context}
|
|
274
|
+
* @overload
|
|
275
|
+
* @param {import('./utilities/log-types.js').LogContext} context The new context
|
|
276
|
+
* @returns {Logger}
|
|
277
|
+
*/
|
|
278
|
+
/**
|
|
279
|
+
* Creating a new logger with {context, ...this.context}
|
|
280
|
+
* @overload
|
|
281
|
+
* @param {import('./utilities/log-types.js').LogContext} context The new context
|
|
282
|
+
* @param {import('./utilities/log-types.js').LogLevel} level The log level at which we allow this context
|
|
283
|
+
* @returns {Logger}
|
|
284
|
+
*/
|
|
285
|
+
/**
|
|
286
|
+
* Creating a new logger with {context, ...this.context}
|
|
287
|
+
* @overload
|
|
288
|
+
* @param {import('./utilities/log-types.js').LogLevel} level The log level at which we allow this context
|
|
289
|
+
* @param {import('./utilities/log-types.js').LogContext} context The new context
|
|
290
|
+
* @returns {Logger}
|
|
291
|
+
*/
|
|
292
|
+
/**
|
|
293
|
+
* Creating a new logger with {context, ...this.context}
|
|
294
|
+
* @param {import('./utilities/log-types.js').LogContext | import('./utilities/log-types.js').LogLevel} contextOrLevel The log level at which we allow this context
|
|
295
|
+
* @param {import('./utilities/log-types.js').LogLevel | import('./utilities/log-types.js').LogContext} [levelOrContext] The new context
|
|
296
|
+
* @returns {Logger}
|
|
297
|
+
*/
|
|
298
|
+
context(contextOrLevel, levelOrContext) {
|
|
299
|
+
if (
|
|
300
|
+
levelOrContext instanceof Object &&
|
|
301
|
+
(
|
|
302
|
+
contextOrLevel == null ||
|
|
303
|
+
typeof contextOrLevel === 'number'
|
|
304
|
+
)
|
|
305
|
+
) {
|
|
306
|
+
return this.#overrideContext(
|
|
307
|
+
levelOrContext,
|
|
308
|
+
contextOrLevel,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (
|
|
313
|
+
contextOrLevel instanceof Object &&
|
|
314
|
+
(
|
|
315
|
+
levelOrContext == null ||
|
|
316
|
+
typeof levelOrContext === 'number'
|
|
317
|
+
)
|
|
318
|
+
) {
|
|
319
|
+
return this.#overrideContext(
|
|
320
|
+
contextOrLevel,
|
|
321
|
+
levelOrContext,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
throw new TypeError('Invalid LogContext in Logger#context()');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Get a value from the context by key
|
|
330
|
+
* @param {string} key The key to get from the context
|
|
331
|
+
* @returns {unknown}
|
|
332
|
+
*/
|
|
333
|
+
get(key) {
|
|
334
|
+
return this.#context[key];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Set a value into a new context by key
|
|
339
|
+
* @param {string} key The key to set
|
|
340
|
+
* @param {unknown} value The value to set
|
|
341
|
+
* @returns {Logger}
|
|
342
|
+
*/
|
|
343
|
+
set(key, value) {
|
|
344
|
+
return this.context({ [key]: value });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Set the channel in the new logger
|
|
349
|
+
* @param {string} channel The channel name
|
|
350
|
+
* @returns {Logger}
|
|
351
|
+
*/
|
|
352
|
+
channel(channel) {
|
|
353
|
+
return this.set('channel', channel);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Set the trail in the new logger
|
|
358
|
+
* @param {string} [trail] The trail id
|
|
359
|
+
* @returns {Logger}
|
|
360
|
+
*/
|
|
361
|
+
trail(trail) {
|
|
362
|
+
trail ??= Math
|
|
363
|
+
.random()
|
|
364
|
+
.toString(16)
|
|
365
|
+
.slice(2, 10);
|
|
366
|
+
|
|
367
|
+
return this.set('trail', trail);
|
|
368
|
+
}
|
|
369
|
+
}
|
package/src/replacer.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* eslint no-invalid-this: 0 */
|
|
2
|
+
|
|
3
|
+
import Traverse from 'traverse';
|
|
4
|
+
import { nodesToPath } from './utilities/json-path.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @template [T=unknown]
|
|
8
|
+
* @template [O=unknown]
|
|
9
|
+
* @typedef {Object} Replacer
|
|
10
|
+
* @property {string} name The name of the replacer
|
|
11
|
+
* @property {(input: unknown, path?: string[]) => boolean} shouldReplace Check to see if input can be replaced
|
|
12
|
+
* @property {(input: T, path?: string[]) => O} replace The replace function if shouldReplace returns true
|
|
13
|
+
* @property {boolean} [stopHere] Should the traverse stop here
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {unknown} object The object we're replacing
|
|
18
|
+
* @param {Replacer[]} replacers The Replacer array to execute
|
|
19
|
+
* @returns {unknown}
|
|
20
|
+
*/
|
|
21
|
+
export function replace(object, replacers = []) {
|
|
22
|
+
/** @type {import('traverse').Traverse<unknown>} */
|
|
23
|
+
const traverse = Traverse(object);
|
|
24
|
+
|
|
25
|
+
return traverse.forEach(function (value) {
|
|
26
|
+
if (value == null) {
|
|
27
|
+
return this.remove();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (this.circular != null) {
|
|
31
|
+
return this.update({ $ref: nodesToPath(this.circular.path) });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const replacer of replacers) {
|
|
35
|
+
if (replacer.shouldReplace(value, this.path)) {
|
|
36
|
+
const replaced = replacer.replace(value, this.path);
|
|
37
|
+
|
|
38
|
+
if (replaced == null) {
|
|
39
|
+
return this.remove(replacer.stopHere);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return this.update(replaced, replacer.stopHere);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof value.toJSON === 'function') {
|
|
47
|
+
try {
|
|
48
|
+
return this.update(value.toJSON());
|
|
49
|
+
} catch { }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
value instanceof Object &&
|
|
54
|
+
Object.getPrototypeOf(value) !== Object.getPrototypeOf({}) &&
|
|
55
|
+
Object.getPrototypeOf(value) !== Object.getPrototypeOf([])
|
|
56
|
+
) {
|
|
57
|
+
return this.update({ $class: value.constructor.name });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (Object.getPrototypeOf(value).constructor === Object) {
|
|
61
|
+
this.update({ ...value });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (Object.getPrototypeOf(value).constructor === Array) {
|
|
65
|
+
this.update([ ...value ]);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Buffer} buffer the buffer to convert to a string
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
function stringBuffer(buffer) {
|
|
6
|
+
return buffer.toString('utf8');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** @type {import('../replacer.js').Replacer<Buffer>} */
|
|
10
|
+
export const BufferReplacer = {
|
|
11
|
+
name: 'Buffer',
|
|
12
|
+
stopHere: true,
|
|
13
|
+
shouldReplace: (input) => input instanceof Buffer,
|
|
14
|
+
replace: (value) => ({
|
|
15
|
+
$class: 'Buffer',
|
|
16
|
+
text: stringBuffer(value),
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { parseStack } from '../utilities/stacktrace.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} BaseError
|
|
5
|
+
* @property {string} type The error type or name
|
|
6
|
+
* @property {string} message The error message
|
|
7
|
+
* @property {import('../utilities/stacktrace.js').Callsite[]} stack The stacktrace of the error
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {BaseError & { [k: string]: unknown }} NormalisedError
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** @type {import('../replacer.js').Replacer<Error, NormalisedError>} */
|
|
15
|
+
export const ErrorReplacer = {
|
|
16
|
+
name: 'Error',
|
|
17
|
+
shouldReplace: (input) => input instanceof Error,
|
|
18
|
+
replace: (value) => {
|
|
19
|
+
/** @type {Partial<NormalisedError>} */
|
|
20
|
+
const error = {
|
|
21
|
+
type: undefined,
|
|
22
|
+
message: undefined,
|
|
23
|
+
stack: undefined,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
for (const key of Object.getOwnPropertyNames(value)) {
|
|
27
|
+
if (Object.hasOwn(value, key)) {
|
|
28
|
+
// @ts-ignore - This is always going to be defined
|
|
29
|
+
error[key] = value[key];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const type = Object.getPrototypeOf(value).constructor.name;
|
|
34
|
+
error.type ??= type;
|
|
35
|
+
error.message = `(${type}) ${value.message}`;
|
|
36
|
+
error.stack = parseStack(value.stack);
|
|
37
|
+
|
|
38
|
+
return /** @type {NormalisedError} */ (error);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ClientRequest } from 'node:http';
|
|
2
|
+
|
|
3
|
+
/** @type {import('../replacer.js').Replacer<ClientRequest>} */
|
|
4
|
+
export const HttpClientRequestReplacer = {
|
|
5
|
+
name: 'HttpClientRequest',
|
|
6
|
+
shouldReplace: (input) => input instanceof ClientRequest,
|
|
7
|
+
replace: (request) => ({
|
|
8
|
+
method: request.method,
|
|
9
|
+
protocol: request.protocol,
|
|
10
|
+
host: request.host ?? request.getHeaders()?.host,
|
|
11
|
+
url: request.path,
|
|
12
|
+
|
|
13
|
+
headers: { ...request.getHeaders() },
|
|
14
|
+
headersSent: request.headersSent,
|
|
15
|
+
writableEnded: request.writableEnded,
|
|
16
|
+
destroyed: request.destroyed,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { IncomingMessage } from 'node:http';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @type {import('../replacer.js').Replacer<
|
|
5
|
+
* IncomingMessage & {
|
|
6
|
+
* hostname?: string,
|
|
7
|
+
* originalUrl?: string,
|
|
8
|
+
* req?: import('node:http').ClientRequest
|
|
9
|
+
* }
|
|
10
|
+
* >}
|
|
11
|
+
*/
|
|
12
|
+
export const HttpIncomingMessageReplacer = {
|
|
13
|
+
name: 'HttpIncomingMessage',
|
|
14
|
+
shouldReplace: (input) => input instanceof IncomingMessage,
|
|
15
|
+
replace: (input) => ({
|
|
16
|
+
httpVersion: input.httpVersion,
|
|
17
|
+
method: input.method,
|
|
18
|
+
host: input?.hostname ?? input?.req?.host,
|
|
19
|
+
url: input.originalUrl ?? input?.req?.path ?? input.url,
|
|
20
|
+
|
|
21
|
+
headers: input.headers,
|
|
22
|
+
complete: input.complete,
|
|
23
|
+
destroyed: input.destroyed,
|
|
24
|
+
})
|
|
25
|
+
,
|
|
26
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ServerResponse, STATUS_CODES } from 'node:http';
|
|
2
|
+
|
|
3
|
+
/** @type {import('../replacer.js').Replacer<ServerResponse>} */
|
|
4
|
+
export const HttpServerResponseReplacer = {
|
|
5
|
+
name: 'HttpServerResponse',
|
|
6
|
+
shouldReplace: (input) => input instanceof ServerResponse,
|
|
7
|
+
replace: (response) => ({
|
|
8
|
+
statusCode: response.statusCode,
|
|
9
|
+
statusMessage: response.statusMessage ?? STATUS_CODES[response.statusCode],
|
|
10
|
+
|
|
11
|
+
headers: response.getHeaders(),
|
|
12
|
+
headersSent: response.headersSent,
|
|
13
|
+
writableEnded: response.writableEnded,
|
|
14
|
+
destroyed: response.destroyed,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BufferReplacer } from './buffers.js';
|
|
2
|
+
export { ErrorReplacer } from './error.js';
|
|
3
|
+
export { HttpClientRequestReplacer } from './http-client-request.js';
|
|
4
|
+
export { HttpIncomingMessageReplacer } from './http-incoming-message.js';
|
|
5
|
+
export { HttpServerResponseReplacer } from './http-server-response.js';
|
|
6
|
+
export { LongStringReplacer } from './long-strings.js';
|
|
7
|
+
export { SecretDelete, SecretObscure } from './secrets.js';
|
|
8
|
+
export { NetSocketReplacer } from './net-socket.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { environment } from '../utilities/environment.js';
|
|
2
|
+
|
|
3
|
+
/** @type {import('../replacer.js').Replacer<string>} */
|
|
4
|
+
export const LongStringReplacer = {
|
|
5
|
+
name: 'LongString',
|
|
6
|
+
shouldReplace: (input) => (
|
|
7
|
+
typeof input === 'string' &&
|
|
8
|
+
input.length > environment.stringMaxLength
|
|
9
|
+
),
|
|
10
|
+
replace: (value) => `${value.slice(0, environment.stringMaxLength)} ... (truncated)`,
|
|
11
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Socket } from 'node:net';
|
|
2
|
+
|
|
3
|
+
/** @type {import('../replacer.js').Replacer<Socket>} */
|
|
4
|
+
export const NetSocketReplacer = {
|
|
5
|
+
name: 'NetSocket',
|
|
6
|
+
shouldReplace: (input) => input instanceof Socket,
|
|
7
|
+
replace: (socket) => ({
|
|
8
|
+
bytesRead: socket.bytesRead,
|
|
9
|
+
bytesWritten: socket.bytesWritten,
|
|
10
|
+
|
|
11
|
+
connecting: socket.connecting,
|
|
12
|
+
pending: socket.pending,
|
|
13
|
+
destroyed: socket.destroyed,
|
|
14
|
+
local: typeof socket.localAddress === 'string' && typeof socket.localPort === 'number'
|
|
15
|
+
? `${socket.localAddress}:${socket.localPort}`
|
|
16
|
+
: null,
|
|
17
|
+
remote: typeof socket.remoteAddress === 'string' && typeof socket.remotePort === 'number'
|
|
18
|
+
? `${socket.remoteAddress}:${socket.remotePort}`
|
|
19
|
+
: null,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { environment } from '../utilities/environment.js';
|
|
2
|
+
import { nodesToPath } from '../utilities/json-path.js';
|
|
3
|
+
|
|
4
|
+
/** @type {import('../replacer.js').Replacer} */
|
|
5
|
+
export const SecretDelete = {
|
|
6
|
+
name: 'SecretDelete',
|
|
7
|
+
stopHere: true,
|
|
8
|
+
shouldReplace: (_, original) => {
|
|
9
|
+
if (original == null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const path = original.join('.');
|
|
14
|
+
for (const filter of environment.secretFilters.blocked) {
|
|
15
|
+
if (filter.test(path)) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return false;
|
|
21
|
+
},
|
|
22
|
+
replace: () => null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** @type {import('../replacer.js').Replacer} */
|
|
26
|
+
export const SecretObscure = {
|
|
27
|
+
name: 'SecretObscure',
|
|
28
|
+
stopHere: true,
|
|
29
|
+
shouldReplace: (_, original) => {
|
|
30
|
+
if (original == null) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const path = original.join('.');
|
|
35
|
+
for (const filter of environment.secretFilters.allowed) {
|
|
36
|
+
if (filter.test(path)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return false;
|
|
42
|
+
},
|
|
43
|
+
replace: (_, path) => {
|
|
44
|
+
if (path == null) {
|
|
45
|
+
return `[secret]`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `[secret ${nodesToPath(path)}]`;
|
|
49
|
+
},
|
|
50
|
+
};
|