@naturalcycles/js-lib 15.90.0 → 15.92.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/dist/asyncManager.js +3 -2
- package/dist/decorators/asyncMemo.decorator.d.ts +1 -1
- package/dist/decorators/asyncMemo.decorator.js +4 -3
- package/dist/decorators/logMethod.decorator.d.ts +1 -1
- package/dist/decorators/logMethod.decorator.js +8 -3
- package/dist/decorators/memo.decorator.d.ts +1 -1
- package/dist/decorators/memo.decorator.js +3 -2
- package/dist/decorators/memoSimple.decorator.js +8 -1
- package/dist/decorators/memoized.js +3 -2
- package/dist/decorators/memoizedAsync.js +4 -3
- package/dist/error/tryCatch.d.ts +1 -1
- package/dist/error/tryCatch.js +3 -2
- package/dist/http/fetcher.js +18 -17
- package/dist/http/fetcher.model.d.ts +1 -1
- package/dist/log/commonLogger.d.ts +40 -34
- package/dist/log/commonLogger.js +47 -92
- package/dist/promise/pGradualQueue.d.ts +1 -1
- package/dist/promise/pMap.d.ts +2 -2
- package/dist/promise/pMap.js +4 -3
- package/dist/promise/pQueue.d.ts +1 -1
- package/dist/promise/pRetry.d.ts +1 -1
- package/dist/promise/pRetry.js +3 -2
- package/package.json +1 -1
- package/src/asyncManager.ts +3 -2
- package/src/decorators/asyncMemo.decorator.ts +5 -4
- package/src/decorators/logMethod.decorator.ts +9 -4
- package/src/decorators/memo.decorator.ts +4 -3
- package/src/decorators/memoSimple.decorator.ts +2 -1
- package/src/decorators/memoized.ts +3 -2
- package/src/decorators/memoizedAsync.ts +4 -3
- package/src/error/tryCatch.ts +4 -3
- package/src/http/fetcher.model.ts +1 -1
- package/src/http/fetcher.ts +9 -10
- package/src/log/commonLogger.ts +80 -134
- package/src/promise/pGradualQueue.ts +1 -1
- package/src/promise/pMap.ts +10 -5
- package/src/promise/pQueue.ts +1 -1
- package/src/promise/pRetry.ts +4 -3
package/dist/promise/pMap.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ErrorMode } from '../error/errorMode.js';
|
|
2
|
+
import { consoleLogger } from '../log/commonLogger.js';
|
|
2
3
|
import { END, SKIP } from '../types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Forked from https://github.com/sindresorhus/p-map
|
|
@@ -39,7 +40,7 @@ export async function pMap(iterable, mapper, opt = {}) {
|
|
|
39
40
|
const itemsLength = items.length;
|
|
40
41
|
if (itemsLength === 0)
|
|
41
42
|
return []; // short circuit
|
|
42
|
-
const { concurrency = Infinity, errorMode = ErrorMode.THROW_IMMEDIATELY, logger =
|
|
43
|
+
const { concurrency = Infinity, errorMode = ErrorMode.THROW_IMMEDIATELY, logger = consoleLogger, } = opt;
|
|
43
44
|
// Special cases that are able to preserve async stack traces
|
|
44
45
|
// Special case: serial execution
|
|
45
46
|
if (concurrency === 1) {
|
|
@@ -135,7 +136,7 @@ async function pMap1(items, mapper, errorMode, logger) {
|
|
|
135
136
|
}
|
|
136
137
|
else {
|
|
137
138
|
// otherwise, suppress (but still log via logger)
|
|
138
|
-
logger?.error(err);
|
|
139
|
+
logger?.error({ err });
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
}
|
|
@@ -165,7 +166,7 @@ async function pMapAll(items, mapper, errorMode, logger) {
|
|
|
165
166
|
}
|
|
166
167
|
else {
|
|
167
168
|
// otherwise, suppress (but still log via logger)
|
|
168
|
-
logger?.error(r.reason);
|
|
169
|
+
logger?.error({ err: r.reason });
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
if (errors.length) {
|
package/dist/promise/pQueue.d.ts
CHANGED
package/dist/promise/pRetry.d.ts
CHANGED
package/dist/promise/pRetry.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _since } from '../datetime/time.util.js';
|
|
2
2
|
import { _errorDataAppend } from '../error/error.util.js';
|
|
3
|
+
import { consoleLogger } from '../log/commonLogger.js';
|
|
3
4
|
import { pDelay } from './pDelay.js';
|
|
4
5
|
import { pTimeout } from './pTimeout.js';
|
|
5
6
|
/**
|
|
@@ -12,7 +13,7 @@ export function pRetryFn(fn, opt = {}) {
|
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
15
|
export async function pRetry(fn, opt = {}) {
|
|
15
|
-
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger =
|
|
16
|
+
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = consoleLogger, name, timeout, } = opt;
|
|
16
17
|
const fakeError = timeout ? new Error('TimeoutError') : undefined;
|
|
17
18
|
let { logFirstAttempt = false, logRetries = true, logFailures = true, logSuccess = false } = opt;
|
|
18
19
|
if (opt.logAll) {
|
|
@@ -52,7 +53,7 @@ export async function pRetry(fn, opt = {}) {
|
|
|
52
53
|
if (logFailures) {
|
|
53
54
|
// Logger at warn (not error) level, because it's not a fatal error, but a retriable one
|
|
54
55
|
// Fatal one is not logged either, because it's been thrown instead
|
|
55
|
-
logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}
|
|
56
|
+
logger.warn({ msg: `${fname} attempt #${attempt} error in ${_since(started)}`, err });
|
|
56
57
|
}
|
|
57
58
|
if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
|
|
58
59
|
// Give up
|
package/package.json
CHANGED
package/src/asyncManager.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { consoleLogger } from './log/commonLogger.js'
|
|
1
2
|
import type { CommonLogger } from './log/commonLogger.js'
|
|
2
3
|
import type { NumberOfMilliseconds } from './types.js'
|
|
3
4
|
|
|
4
5
|
class AsyncManagerImpl {
|
|
5
|
-
logger: CommonLogger =
|
|
6
|
+
logger: CommonLogger = consoleLogger
|
|
6
7
|
|
|
7
8
|
pendingOps = new Set<Promise<unknown>>()
|
|
8
9
|
|
|
@@ -58,7 +59,7 @@ class AsyncManagerImpl {
|
|
|
58
59
|
if (this.onErrorHooks.length) {
|
|
59
60
|
this.onErrorHooks.forEach(hook => hook(err))
|
|
60
61
|
} else {
|
|
61
|
-
this.logger.error('AsyncManager unhandled rejection
|
|
62
|
+
this.logger.error({ msg: 'AsyncManager unhandled rejection', err })
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _assert, _assertTypeOf } from '../error/assert.js'
|
|
2
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
2
3
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
3
4
|
import type { AnyAsyncFunction, AnyFunction, AnyObject, MaybeParameters } from '../types.js'
|
|
4
5
|
import { _objectAssign, MISS } from '../types.js'
|
|
@@ -20,7 +21,7 @@ export interface AsyncMemoOptions<FN> {
|
|
|
20
21
|
cacheKeyFn?: (args: MaybeParameters<FN>) => any
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
|
-
* Default to `
|
|
24
|
+
* Default to `consoleLogger`
|
|
24
25
|
*/
|
|
25
26
|
logger?: CommonLogger
|
|
26
27
|
}
|
|
@@ -64,7 +65,7 @@ export const _AsyncMemo =
|
|
|
64
65
|
// After it's resolved - it's evicted from the cache and moved to the "proper" `instanceCache`
|
|
65
66
|
const instancePromiseCache = new Map<AnyObject, Map<any, Promise<any>>>()
|
|
66
67
|
|
|
67
|
-
const { logger =
|
|
68
|
+
const { logger = consoleLogger, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt
|
|
68
69
|
|
|
69
70
|
const keyStr = String(key)
|
|
70
71
|
const methodSignature = _getTargetMethodSignature(target, keyStr)
|
|
@@ -109,7 +110,7 @@ export const _AsyncMemo =
|
|
|
109
110
|
},
|
|
110
111
|
async err => {
|
|
111
112
|
// Log the cache error and proceed "as cache Miss"
|
|
112
|
-
logger.error(err)
|
|
113
|
+
logger.error({ err })
|
|
113
114
|
return await onMiss()
|
|
114
115
|
},
|
|
115
116
|
)
|
|
@@ -130,7 +131,7 @@ export const _AsyncMemo =
|
|
|
130
131
|
try {
|
|
131
132
|
await cache!.set(cacheKey, value)
|
|
132
133
|
} catch (err) {
|
|
133
|
-
logger.error(err) // log and ignore the error
|
|
134
|
+
logger.error({ err }) // log and ignore the error
|
|
134
135
|
} finally {
|
|
135
136
|
// Clear the "in-flight" promise cache entry, as we now have a "permanent" cache entry
|
|
136
137
|
promiseCache!.delete(cacheKey)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { _ms } from '../datetime/time.util.js'
|
|
2
2
|
import { _assert } from '../error/assert.js'
|
|
3
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
3
4
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
4
5
|
import { SimpleMovingAverage } from '../math/sma.js'
|
|
5
6
|
import { _stringify } from '../string/stringify.js'
|
|
@@ -53,7 +54,7 @@ export interface LogMethodOptions {
|
|
|
53
54
|
logResultFn?: LogResultFn
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* Defaults to `
|
|
57
|
+
* Defaults to `consoleLogger`
|
|
57
58
|
*/
|
|
58
59
|
logger?: CommonLogger
|
|
59
60
|
}
|
|
@@ -84,7 +85,7 @@ export function _LogMethod(opt: LogMethodOptions = {}): MethodDecorator {
|
|
|
84
85
|
logStart,
|
|
85
86
|
logResult,
|
|
86
87
|
logResultLength = true,
|
|
87
|
-
logger =
|
|
88
|
+
logger = consoleLogger,
|
|
88
89
|
} = opt
|
|
89
90
|
let { logResultFn } = opt
|
|
90
91
|
if (!logResultFn) {
|
|
@@ -156,10 +157,14 @@ function logFinished(
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
if (err !== undefined) {
|
|
159
|
-
t.push('ERROR:'
|
|
160
|
+
t.push('ERROR:')
|
|
160
161
|
} else if (logResultFn) {
|
|
161
162
|
t.push(...logResultFn(res))
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
|
|
165
|
+
const msg = t
|
|
166
|
+
.filter(Boolean)
|
|
167
|
+
.map(part => _stringify(part))
|
|
168
|
+
.join(' ')
|
|
169
|
+
logger.log(err === undefined ? msg : { msg, err })
|
|
165
170
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _assert, _assertTypeOf } from '../error/assert.js'
|
|
2
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
2
3
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
3
4
|
import type { AnyFunction, AnyObject, MaybeParameters } from '../types.js'
|
|
4
5
|
import { _objectAssign } from '../types.js'
|
|
@@ -21,7 +22,7 @@ export interface MemoOptions<FN> {
|
|
|
21
22
|
cacheKeyFn?: (args: MaybeParameters<FN>) => any
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Default to `
|
|
25
|
+
* Default to `consoleLogger`
|
|
25
26
|
*/
|
|
26
27
|
logger?: CommonLogger
|
|
27
28
|
}
|
|
@@ -78,7 +79,7 @@ export const _Memo =
|
|
|
78
79
|
const instanceCache = new Map<AnyObject, MemoCache>()
|
|
79
80
|
|
|
80
81
|
const {
|
|
81
|
-
logger =
|
|
82
|
+
logger = consoleLogger,
|
|
82
83
|
cacheFactory = () => new MapMemoCache(),
|
|
83
84
|
cacheKeyFn = jsonMemoSerializer,
|
|
84
85
|
} = opt
|
|
@@ -107,7 +108,7 @@ export const _Memo =
|
|
|
107
108
|
try {
|
|
108
109
|
cache.set(cacheKey, value)
|
|
109
110
|
} catch (err) {
|
|
110
|
-
logger.error(err)
|
|
111
|
+
logger.error({ err })
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
return value
|
|
@@ -11,6 +11,7 @@ Otherwise resorts to JSON.stringify.
|
|
|
11
11
|
Benchmark shows similar perf for ObjectCache and MapCache.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
14
15
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
15
16
|
import { _getTargetMethodSignature } from './decorator.util.js'
|
|
16
17
|
import type { MemoCache } from './memo.util.js'
|
|
@@ -55,7 +56,7 @@ export const memoSimple =
|
|
|
55
56
|
*/
|
|
56
57
|
const cache: MemoCache = new MapMemoCache()
|
|
57
58
|
|
|
58
|
-
const { logger =
|
|
59
|
+
const { logger = consoleLogger } = opt
|
|
59
60
|
const keyStr = String(key)
|
|
60
61
|
const methodSignature = _getTargetMethodSignature(target, keyStr)
|
|
61
62
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
1
2
|
import type { AnyFunction, MaybeParameters } from '../types.js'
|
|
2
3
|
import type { MemoOptions } from './memo.decorator.js'
|
|
3
4
|
import type { MemoCache } from './memo.util.js'
|
|
@@ -17,7 +18,7 @@ export function _memoized<FN extends AnyFunction>(
|
|
|
17
18
|
opt: MemoOptions<FN> = {},
|
|
18
19
|
): FN & MemoizedFunction {
|
|
19
20
|
const {
|
|
20
|
-
logger =
|
|
21
|
+
logger = consoleLogger,
|
|
21
22
|
cacheFactory = () => new MapMemoCache(),
|
|
22
23
|
cacheKeyFn = jsonMemoSerializer,
|
|
23
24
|
} = opt
|
|
@@ -37,7 +38,7 @@ export function _memoized<FN extends AnyFunction>(
|
|
|
37
38
|
try {
|
|
38
39
|
cache.set(cacheKey, value)
|
|
39
40
|
} catch (err) {
|
|
40
|
-
logger.error(err)
|
|
41
|
+
logger.error({ err })
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
return value
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
1
2
|
import type { AnyAsyncFunction, MaybeParameters } from '../types.js'
|
|
2
3
|
import { MISS } from '../types.js'
|
|
3
4
|
import type { AsyncMemoOptions } from './asyncMemo.decorator.js'
|
|
@@ -12,7 +13,7 @@ export function _memoizedAsync<FN extends AnyAsyncFunction>(
|
|
|
12
13
|
fn: FN,
|
|
13
14
|
opt: AsyncMemoOptions<FN>,
|
|
14
15
|
): FN & MemoizedAsyncFunction {
|
|
15
|
-
const { logger =
|
|
16
|
+
const { logger = consoleLogger, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt
|
|
16
17
|
|
|
17
18
|
const cache = cacheFactory()
|
|
18
19
|
|
|
@@ -24,7 +25,7 @@ export function _memoizedAsync<FN extends AnyAsyncFunction>(
|
|
|
24
25
|
try {
|
|
25
26
|
value = await cache.get(cacheKey)
|
|
26
27
|
} catch (err) {
|
|
27
|
-
logger.error(err)
|
|
28
|
+
logger.error({ err })
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
if (value !== MISS) {
|
|
@@ -37,7 +38,7 @@ export function _memoizedAsync<FN extends AnyAsyncFunction>(
|
|
|
37
38
|
try {
|
|
38
39
|
await cache.set(cacheKey, value)
|
|
39
40
|
} catch (err) {
|
|
40
|
-
logger.error(err)
|
|
41
|
+
logger.error({ err })
|
|
41
42
|
}
|
|
42
43
|
})()
|
|
43
44
|
|
package/src/error/tryCatch.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _since } from '../datetime/time.util.js'
|
|
2
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
2
3
|
import type { CommonLogger } from '../log/commonLogger.js'
|
|
3
4
|
import type { AnyFunction, UnixTimestampMillis } from '../types.js'
|
|
4
5
|
import { _anyToError } from './error.util.js'
|
|
@@ -21,7 +22,7 @@ export interface TryCatchOptions {
|
|
|
21
22
|
logError?: boolean
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Default to `
|
|
25
|
+
* Default to `consoleLogger`
|
|
25
26
|
*/
|
|
26
27
|
logger?: CommonLogger
|
|
27
28
|
}
|
|
@@ -35,7 +36,7 @@ export interface TryCatchOptions {
|
|
|
35
36
|
* @experimental
|
|
36
37
|
*/
|
|
37
38
|
export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {}): T {
|
|
38
|
-
const { onError, logError = true, logSuccess = false, logger =
|
|
39
|
+
const { onError, logError = true, logSuccess = false, logger = consoleLogger } = opt
|
|
39
40
|
|
|
40
41
|
const fname = fn.name || 'anonymous'
|
|
41
42
|
|
|
@@ -52,7 +53,7 @@ export function _tryCatch<T extends AnyFunction>(fn: T, opt: TryCatchOptions = {
|
|
|
52
53
|
return r
|
|
53
54
|
} catch (err) {
|
|
54
55
|
if (logError) {
|
|
55
|
-
logger.warn(`tryCatch.${fname} error in ${_since(started)}
|
|
56
|
+
logger.warn({ msg: `tryCatch.${fname} error in ${_since(started)}`, err })
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
if (onError) {
|
package/src/http/fetcher.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
HttpRequestError,
|
|
17
17
|
UnexpectedPassError,
|
|
18
18
|
} from '../error/error.util.js'
|
|
19
|
+
import { consoleLogger } from '../log/commonLogger.js'
|
|
19
20
|
import { _clamp } from '../number/number.util.js'
|
|
20
21
|
import {
|
|
21
22
|
_filterFalsyValues,
|
|
@@ -30,7 +31,6 @@ import { pDelaySignal } from '../promise/pDelay.js'
|
|
|
30
31
|
import { pTimeout } from '../promise/pTimeout.js'
|
|
31
32
|
import { _toUrlOrNull } from '../string/index.js'
|
|
32
33
|
import { _jsonParse, _jsonParseIfPossible } from '../string/json.util.js'
|
|
33
|
-
import { _stringify } from '../string/stringify.js'
|
|
34
34
|
import type {
|
|
35
35
|
AnyObject,
|
|
36
36
|
ErrorDataTuple,
|
|
@@ -416,7 +416,7 @@ export class Fetcher {
|
|
|
416
416
|
.join(' '),
|
|
417
417
|
)
|
|
418
418
|
if (req.logRequestBody && req.init.body) {
|
|
419
|
-
logger.log(req.init.body) // todo: check if we can _inspect it
|
|
419
|
+
logger.log({ body: req.init.body }) // todo: check if we can _inspect it
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
|
|
@@ -584,7 +584,7 @@ export class Fetcher {
|
|
|
584
584
|
)
|
|
585
585
|
|
|
586
586
|
if (req.logResponseBody && res.body !== undefined) {
|
|
587
|
-
logger.log(res.body)
|
|
587
|
+
logger.log({ body: res.body })
|
|
588
588
|
}
|
|
589
589
|
}
|
|
590
590
|
}
|
|
@@ -688,8 +688,8 @@ export class Fetcher {
|
|
|
688
688
|
// lastError = retryStatus.retryStopped
|
|
689
689
|
// We need to log the response "anyway" if logResponse is true
|
|
690
690
|
if (res.err && (!retryStatus.retryStopped || res.req.logResponse)) {
|
|
691
|
-
this.cfg.logger.error(
|
|
692
|
-
[
|
|
691
|
+
this.cfg.logger.error({
|
|
692
|
+
msg: [
|
|
693
693
|
' <<',
|
|
694
694
|
res.fetchResponse?.status || 0,
|
|
695
695
|
res.signature,
|
|
@@ -699,10 +699,9 @@ export class Fetcher {
|
|
|
699
699
|
_since(res.req.started),
|
|
700
700
|
]
|
|
701
701
|
.filter(Boolean)
|
|
702
|
-
.join(' ')
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
)
|
|
702
|
+
.join(' '),
|
|
703
|
+
err: res.err.cause || res.err,
|
|
704
|
+
})
|
|
706
705
|
}
|
|
707
706
|
|
|
708
707
|
if (retryStatus.retryStopped) return
|
|
@@ -847,7 +846,7 @@ export class Fetcher {
|
|
|
847
846
|
}
|
|
848
847
|
|
|
849
848
|
private normalizeCfg(cfg: FetcherCfg & FetcherOptions): FetcherNormalizedCfg {
|
|
850
|
-
const { debug = false, logger =
|
|
849
|
+
const { debug = false, logger = consoleLogger } = cfg
|
|
851
850
|
|
|
852
851
|
if (cfg.baseUrl?.endsWith('/')) {
|
|
853
852
|
logger.warn(`Fetcher: baseUrl should not end with slash: ${cfg.baseUrl}`)
|
package/src/log/commonLogger.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { _isPlainObject } from '../is.util.js'
|
|
2
|
-
import type { AnyObject, MutateOptions } from '../types.js'
|
|
3
|
-
|
|
4
1
|
// copy-pasted to avoid weird circular dependency
|
|
5
2
|
const _noop = (..._args: any[]): undefined => undefined
|
|
6
3
|
|
|
@@ -23,23 +20,43 @@ export type CommonLogLevel = 'debug' | 'log' | 'warn' | 'error'
|
|
|
23
20
|
export const commonLogLevelNumber: Record<CommonLogLevel, number> = {
|
|
24
21
|
debug: 10,
|
|
25
22
|
log: 20,
|
|
26
|
-
warn:
|
|
27
|
-
error:
|
|
23
|
+
warn: 30,
|
|
24
|
+
error: 40,
|
|
28
25
|
}
|
|
29
26
|
|
|
30
27
|
/**
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
* Structured log data: a message, an error, and any other fields.
|
|
29
|
+
*/
|
|
30
|
+
export interface LogData {
|
|
31
|
+
msg?: string
|
|
32
|
+
err?: unknown
|
|
33
|
+
[field: string]: unknown
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* What a log call accepts: a string is `{ msg }`, an Error is `{ err }`.
|
|
38
|
+
* A non-Error error value (e.g an `unknown` from a catch) is passed as `{ err }`.
|
|
35
39
|
*/
|
|
36
|
-
export type
|
|
37
|
-
|
|
40
|
+
export type LogInput = string | Error | LogData
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* What a sink receives: the logger's context, the log data and the level.
|
|
44
|
+
*/
|
|
45
|
+
export interface LogEntry extends LogData {
|
|
46
|
+
level: CommonLogLevel
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type LogSink = (entry: LogEntry) => void
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A log call is either one `LogInput`, or a message with the other fields:
|
|
53
|
+
* `logger.log('hey')`, `logger.error(err)`, `logger.warn({ msg, err })`, `logger.warn('failed', { err })`.
|
|
54
|
+
*/
|
|
55
|
+
export type CommonLogArgs = [input: LogInput] | [msg: string, data: LogData]
|
|
56
|
+
|
|
57
|
+
export type CommonLogFunction = (...args: CommonLogArgs) => void
|
|
38
58
|
|
|
39
59
|
/**
|
|
40
|
-
* Interface is inspired/compatible with `console.*`
|
|
41
|
-
* So, `console` is a valid CommonLogger implementation as-is.
|
|
42
|
-
*
|
|
43
60
|
* @experimental
|
|
44
61
|
*/
|
|
45
62
|
export interface CommonLogger {
|
|
@@ -47,63 +64,83 @@ export interface CommonLogger {
|
|
|
47
64
|
log: CommonLogFunction
|
|
48
65
|
warn: CommonLogFunction
|
|
49
66
|
error: CommonLogFunction
|
|
67
|
+
/**
|
|
68
|
+
* Creates a logger that adds `context` to every entry.
|
|
69
|
+
*/
|
|
70
|
+
child: (context: LogData) => CommonLogger
|
|
50
71
|
}
|
|
51
72
|
|
|
52
73
|
/**
|
|
53
|
-
*
|
|
74
|
+
* Creates a CommonLogger that writes every entry to `sink`.
|
|
75
|
+
* `context` is called on every log call, so it can return values that change
|
|
76
|
+
* after the logger is created (e.g a request context whose user is resolved later).
|
|
77
|
+
*/
|
|
78
|
+
export function commonLoggerCreate(sink: LogSink, context?: () => LogData): CommonLogger {
|
|
79
|
+
const emit = (level: CommonLogLevel, [input, data]: CommonLogArgs): void =>
|
|
80
|
+
sink({ ...context?.(), ...toLogData(input, data), level })
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
debug: (...args) => emit('debug', args),
|
|
84
|
+
log: (...args) => emit('log', args),
|
|
85
|
+
warn: (...args) => emit('warn', args),
|
|
86
|
+
error: (...args) => emit('error', args),
|
|
87
|
+
child: childContext => commonLoggerCreate(sink, () => ({ ...context?.(), ...childContext })),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toLogData(input: LogInput, data?: LogData): LogData {
|
|
92
|
+
if (typeof input === 'string') return { ...data, msg: input }
|
|
93
|
+
if (input instanceof Error) return { err: input }
|
|
94
|
+
return input
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* CommonLogger that logs to `console`.
|
|
99
|
+
*/
|
|
100
|
+
export const consoleLogger: CommonLogger = commonLoggerCreate(({ level, msg, err, ...fields }) => {
|
|
101
|
+
const args: unknown[] = []
|
|
102
|
+
if (msg !== undefined) args.push(msg)
|
|
103
|
+
if (err !== undefined) args.push(err)
|
|
104
|
+
if (Object.keys(fields).length) args.push(fields)
|
|
105
|
+
console[level](...args)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* CommonLogger that does nothing (noop).
|
|
54
110
|
*/
|
|
55
111
|
export const commonLoggerNoop: CommonLogger = {
|
|
56
112
|
debug: _noop,
|
|
57
113
|
log: _noop,
|
|
58
114
|
warn: _noop,
|
|
59
115
|
error: _noop,
|
|
116
|
+
child: () => commonLoggerNoop,
|
|
60
117
|
}
|
|
61
118
|
|
|
62
119
|
/**
|
|
63
|
-
* Creates a
|
|
120
|
+
* Creates a CommonLogger that is "limited" to the specified CommonLogLevel.
|
|
64
121
|
*/
|
|
65
122
|
export function createCommonLoggerAtLevel(
|
|
66
|
-
logger: CommonLogger =
|
|
123
|
+
logger: CommonLogger = consoleLogger,
|
|
67
124
|
minLevel: CommonLogLevel = 'log',
|
|
68
|
-
opt: MutateOptions = {},
|
|
69
125
|
): CommonLogger {
|
|
70
126
|
const level = commonLogLevelNumber[minLevel]
|
|
71
|
-
if (opt.mutate) {
|
|
72
|
-
if (level > commonLogLevelNumber['debug']) {
|
|
73
|
-
logger.debug = _noop
|
|
74
|
-
if (level > commonLogLevelNumber['log']) {
|
|
75
|
-
logger.log = _noop
|
|
76
|
-
if (level > commonLogLevelNumber['warn']) {
|
|
77
|
-
logger.warn = _noop
|
|
78
|
-
if (level > commonLogLevelNumber['error']) {
|
|
79
|
-
logger.error = _noop
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return logger
|
|
85
|
-
}
|
|
86
127
|
|
|
87
128
|
if (level <= commonLogLevelNumber['debug']) {
|
|
88
129
|
// All levels are kept
|
|
89
130
|
return logger
|
|
90
131
|
}
|
|
91
132
|
|
|
92
|
-
if (level > commonLogLevelNumber['error']) {
|
|
93
|
-
// "Log nothing" logger
|
|
94
|
-
return commonLoggerNoop
|
|
95
|
-
}
|
|
96
|
-
|
|
97
133
|
return {
|
|
98
134
|
debug: _noop, // otherwise it is "log everything" logger (same logger as input)
|
|
99
|
-
log: level <= commonLogLevelNumber['log'] ? logger.log
|
|
100
|
-
warn: level <= commonLogLevelNumber['warn'] ? logger.warn
|
|
101
|
-
error:
|
|
135
|
+
log: level <= commonLogLevelNumber['log'] ? (...args) => logger.log(...args) : _noop,
|
|
136
|
+
warn: level <= commonLogLevelNumber['warn'] ? (...args) => logger.warn(...args) : _noop,
|
|
137
|
+
error: (...args) => logger.error(...args),
|
|
138
|
+
child: context => createCommonLoggerAtLevel(logger.child(context), minLevel),
|
|
102
139
|
}
|
|
103
140
|
}
|
|
104
141
|
|
|
105
142
|
/**
|
|
106
|
-
* Creates a "proxy" CommonLogger that pipes log
|
|
143
|
+
* Creates a "proxy" CommonLogger that pipes log entries to all provided sub-loggers.
|
|
107
144
|
*/
|
|
108
145
|
export function commonLoggerPipe(loggers: CommonLogger[]): CommonLogger {
|
|
109
146
|
return {
|
|
@@ -111,97 +148,6 @@ export function commonLoggerPipe(loggers: CommonLogger[]): CommonLogger {
|
|
|
111
148
|
log: (...args) => loggers.forEach(logger => logger.log(...args)),
|
|
112
149
|
warn: (...args) => loggers.forEach(logger => logger.warn(...args)),
|
|
113
150
|
error: (...args) => loggers.forEach(logger => logger.error(...args)),
|
|
151
|
+
child: context => commonLoggerPipe(loggers.map(logger => logger.child(context))),
|
|
114
152
|
}
|
|
115
153
|
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Creates a "child" CommonLogger with prefix (one or multiple).
|
|
119
|
-
*/
|
|
120
|
-
export function commonLoggerPrefix(logger: CommonLogger, ...prefixes: any[]): CommonLogger {
|
|
121
|
-
return {
|
|
122
|
-
debug: (...args) => logger.debug(...prefixes, ...args),
|
|
123
|
-
log: (...args) => logger.log(...prefixes, ...args),
|
|
124
|
-
warn: (...args) => logger.warn(...prefixes, ...args),
|
|
125
|
-
error: (...args) => logger.error(...prefixes, ...args),
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export type CommonLogSink = CommonLogger | CommonLogWithLevelFunction
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* A plain object is copied once, at creation.
|
|
133
|
-
* A function is called on every log call, for context that changes after the logger is created
|
|
134
|
-
* (e.g a request context whose user is resolved later).
|
|
135
|
-
*/
|
|
136
|
-
export type CommonLogContext = AnyObject | (() => AnyObject)
|
|
137
|
-
|
|
138
|
-
export interface CommonLoggerWithContext extends CommonLogger {
|
|
139
|
-
child: (context: CommonLogContext) => CommonLoggerWithContext
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Creates a CommonLogger from a single function that takes `level` and `args`.
|
|
144
|
-
*
|
|
145
|
-
* With a context, every call emits one object: plain-object args merged in, first Error as `err`,
|
|
146
|
-
* the rest joined into `msg`.
|
|
147
|
-
*/
|
|
148
|
-
export function commonLoggerCreate(sink: CommonLogSink): CommonLogger
|
|
149
|
-
export function commonLoggerCreate(
|
|
150
|
-
sink: CommonLogSink,
|
|
151
|
-
context: CommonLogContext,
|
|
152
|
-
): CommonLoggerWithContext
|
|
153
|
-
export function commonLoggerCreate(sink: CommonLogSink, context?: CommonLogContext): CommonLogger {
|
|
154
|
-
const fn: CommonLogWithLevelFunction =
|
|
155
|
-
typeof sink === 'function' ? sink : (level, args) => sink[level](...args)
|
|
156
|
-
|
|
157
|
-
if (!context) {
|
|
158
|
-
return {
|
|
159
|
-
debug: (...args) => fn('debug', args),
|
|
160
|
-
log: (...args) => fn('log', args),
|
|
161
|
-
warn: (...args) => fn('warn', args),
|
|
162
|
-
error: (...args) => fn('error', args),
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const getContext = contextGetter(context)
|
|
167
|
-
|
|
168
|
-
const logger: CommonLoggerWithContext = {
|
|
169
|
-
debug: (...args) => fn('debug', [toLogEntry(getContext(), args)]),
|
|
170
|
-
log: (...args) => fn('log', [toLogEntry(getContext(), args)]),
|
|
171
|
-
warn: (...args) => fn('warn', [toLogEntry(getContext(), args)]),
|
|
172
|
-
error: (...args) => fn('error', [toLogEntry(getContext(), args)]),
|
|
173
|
-
child: c => {
|
|
174
|
-
const getChild = contextGetter(c)
|
|
175
|
-
return commonLoggerCreate(fn, () => ({ ...getContext(), ...getChild() }))
|
|
176
|
-
},
|
|
177
|
-
}
|
|
178
|
-
return logger
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function contextGetter(context: CommonLogContext): () => AnyObject {
|
|
182
|
-
// AnyObject also admits functions, so typeof does not narrow the union
|
|
183
|
-
if (typeof context === 'function') return context as () => AnyObject
|
|
184
|
-
const copy = { ...context }
|
|
185
|
-
return () => copy
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function toLogEntry(context: AnyObject, args: any[]): AnyObject {
|
|
189
|
-
const entry: AnyObject = { ...context }
|
|
190
|
-
let err: Error | undefined
|
|
191
|
-
let msg: string | undefined
|
|
192
|
-
|
|
193
|
-
for (const arg of args) {
|
|
194
|
-
if (_isPlainObject(arg)) {
|
|
195
|
-
Object.assign(entry, arg)
|
|
196
|
-
} else if (!err && arg instanceof Error) {
|
|
197
|
-
err = arg
|
|
198
|
-
} else {
|
|
199
|
-
const s = String(arg)
|
|
200
|
-
msg = msg === undefined ? s : `${msg} ${s}`
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (err) entry['err'] = err
|
|
205
|
-
if (msg !== undefined) entry['msg'] = msg
|
|
206
|
-
return entry
|
|
207
|
-
}
|