@naturalcycles/js-lib 15.89.0 → 15.91.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 +35 -28
- package/dist/log/commonLogger.js +51 -86
- 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 +2 -2
- 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 +78 -118
- 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/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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/js-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.91.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"tslib": "^2"
|
|
7
7
|
},
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
-
"@naturalcycles/dev-lib": "
|
|
17
|
+
"@naturalcycles/dev-lib": "20.64.0",
|
|
18
18
|
"@types/node": "^26",
|
|
19
19
|
"@types/semver": "^7",
|
|
20
20
|
"typescript": "^7",
|
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,37 @@ 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
|
-
* It is expected that logged arguments are separated by "space", like console.log does.
|
|
33
|
-
*
|
|
34
|
-
* @experimental
|
|
28
|
+
* Structured log data: a message, an error, and any other fields.
|
|
35
29
|
*/
|
|
36
|
-
export
|
|
37
|
-
|
|
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 }`.
|
|
39
|
+
*/
|
|
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
|
+
export type CommonLogFunction = (input: LogInput) => void
|
|
38
52
|
|
|
39
53
|
/**
|
|
40
|
-
* Interface is inspired/compatible with `console.*`
|
|
41
|
-
* So, `console` is a valid CommonLogger implementation as-is.
|
|
42
|
-
*
|
|
43
54
|
* @experimental
|
|
44
55
|
*/
|
|
45
56
|
export interface CommonLogger {
|
|
@@ -47,141 +58,90 @@ export interface CommonLogger {
|
|
|
47
58
|
log: CommonLogFunction
|
|
48
59
|
warn: CommonLogFunction
|
|
49
60
|
error: CommonLogFunction
|
|
61
|
+
/**
|
|
62
|
+
* Creates a logger that adds `context` to every entry.
|
|
63
|
+
*/
|
|
64
|
+
child: (context: LogData) => CommonLogger
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
/**
|
|
53
|
-
*
|
|
68
|
+
* Creates a CommonLogger that writes every entry to `sink`.
|
|
69
|
+
* `context` is called on every log call, so it can return values that change
|
|
70
|
+
* after the logger is created (e.g a request context whose user is resolved later).
|
|
71
|
+
*/
|
|
72
|
+
export function commonLoggerCreate(sink: LogSink, context?: () => LogData): CommonLogger {
|
|
73
|
+
const emit = (level: CommonLogLevel, input: LogInput): void =>
|
|
74
|
+
sink({ ...context?.(), ...toLogData(input), level })
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
debug: input => emit('debug', input),
|
|
78
|
+
log: input => emit('log', input),
|
|
79
|
+
warn: input => emit('warn', input),
|
|
80
|
+
error: input => emit('error', input),
|
|
81
|
+
child: childContext => commonLoggerCreate(sink, () => ({ ...context?.(), ...childContext })),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function toLogData(input: LogInput): LogData {
|
|
86
|
+
if (typeof input === 'string') return { msg: input }
|
|
87
|
+
if (input instanceof Error) return { err: input }
|
|
88
|
+
return input
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* CommonLogger that logs to `console`.
|
|
93
|
+
*/
|
|
94
|
+
export const consoleLogger: CommonLogger = commonLoggerCreate(({ level, msg, err, ...fields }) => {
|
|
95
|
+
const args: unknown[] = []
|
|
96
|
+
if (msg !== undefined) args.push(msg)
|
|
97
|
+
if (err !== undefined) args.push(err)
|
|
98
|
+
if (Object.keys(fields).length) args.push(fields)
|
|
99
|
+
console[level](...args)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* CommonLogger that does nothing (noop).
|
|
54
104
|
*/
|
|
55
105
|
export const commonLoggerNoop: CommonLogger = {
|
|
56
106
|
debug: _noop,
|
|
57
107
|
log: _noop,
|
|
58
108
|
warn: _noop,
|
|
59
109
|
error: _noop,
|
|
110
|
+
child: () => commonLoggerNoop,
|
|
60
111
|
}
|
|
61
112
|
|
|
62
113
|
/**
|
|
63
|
-
* Creates a
|
|
114
|
+
* Creates a CommonLogger that is "limited" to the specified CommonLogLevel.
|
|
64
115
|
*/
|
|
65
116
|
export function createCommonLoggerAtLevel(
|
|
66
|
-
logger: CommonLogger =
|
|
117
|
+
logger: CommonLogger = consoleLogger,
|
|
67
118
|
minLevel: CommonLogLevel = 'log',
|
|
68
|
-
opt: MutateOptions = {},
|
|
69
119
|
): CommonLogger {
|
|
70
120
|
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
121
|
|
|
87
122
|
if (level <= commonLogLevelNumber['debug']) {
|
|
88
123
|
// All levels are kept
|
|
89
124
|
return logger
|
|
90
125
|
}
|
|
91
126
|
|
|
92
|
-
if (level > commonLogLevelNumber['error']) {
|
|
93
|
-
// "Log nothing" logger
|
|
94
|
-
return commonLoggerNoop
|
|
95
|
-
}
|
|
96
|
-
|
|
97
127
|
return {
|
|
98
128
|
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: logger.error
|
|
129
|
+
log: level <= commonLogLevelNumber['log'] ? input => logger.log(input) : _noop,
|
|
130
|
+
warn: level <= commonLogLevelNumber['warn'] ? input => logger.warn(input) : _noop,
|
|
131
|
+
error: input => logger.error(input),
|
|
132
|
+
child: context => createCommonLoggerAtLevel(logger.child(context), minLevel),
|
|
102
133
|
}
|
|
103
134
|
}
|
|
104
135
|
|
|
105
136
|
/**
|
|
106
|
-
* Creates a "proxy" CommonLogger that pipes log
|
|
137
|
+
* Creates a "proxy" CommonLogger that pipes log entries to all provided sub-loggers.
|
|
107
138
|
*/
|
|
108
139
|
export function commonLoggerPipe(loggers: CommonLogger[]): CommonLogger {
|
|
109
140
|
return {
|
|
110
|
-
debug:
|
|
111
|
-
log:
|
|
112
|
-
warn:
|
|
113
|
-
error:
|
|
114
|
-
|
|
115
|
-
}
|
|
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
|
-
export interface CommonLoggerWithContext extends CommonLogger {
|
|
132
|
-
child: (context: AnyObject) => CommonLoggerWithContext
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Creates a CommonLogger from a single function that takes `level` and `args`.
|
|
137
|
-
*
|
|
138
|
-
* With a context, every call emits one object: plain-object args merged in, first Error as `err`,
|
|
139
|
-
* the rest joined into `msg`.
|
|
140
|
-
*/
|
|
141
|
-
export function commonLoggerCreate(sink: CommonLogSink): CommonLogger
|
|
142
|
-
export function commonLoggerCreate(sink: CommonLogSink, context: AnyObject): CommonLoggerWithContext
|
|
143
|
-
export function commonLoggerCreate(sink: CommonLogSink, context?: AnyObject): CommonLogger {
|
|
144
|
-
const fn: CommonLogWithLevelFunction =
|
|
145
|
-
typeof sink === 'function' ? sink : (level, args) => sink[level](...args)
|
|
146
|
-
|
|
147
|
-
if (!context) {
|
|
148
|
-
return {
|
|
149
|
-
debug: (...args) => fn('debug', args),
|
|
150
|
-
log: (...args) => fn('log', args),
|
|
151
|
-
warn: (...args) => fn('warn', args),
|
|
152
|
-
error: (...args) => fn('error', args),
|
|
153
|
-
}
|
|
141
|
+
debug: input => loggers.forEach(logger => logger.debug(input)),
|
|
142
|
+
log: input => loggers.forEach(logger => logger.log(input)),
|
|
143
|
+
warn: input => loggers.forEach(logger => logger.warn(input)),
|
|
144
|
+
error: input => loggers.forEach(logger => logger.error(input)),
|
|
145
|
+
child: context => commonLoggerPipe(loggers.map(logger => logger.child(context))),
|
|
154
146
|
}
|
|
155
|
-
|
|
156
|
-
const ctx: AnyObject = { ...context }
|
|
157
|
-
|
|
158
|
-
const logger: CommonLoggerWithContext = {
|
|
159
|
-
debug: (...args) => fn('debug', [toLogEntry(ctx, args)]),
|
|
160
|
-
log: (...args) => fn('log', [toLogEntry(ctx, args)]),
|
|
161
|
-
warn: (...args) => fn('warn', [toLogEntry(ctx, args)]),
|
|
162
|
-
error: (...args) => fn('error', [toLogEntry(ctx, args)]),
|
|
163
|
-
child: c => commonLoggerCreate(fn, { ...ctx, ...c }),
|
|
164
|
-
}
|
|
165
|
-
return logger
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function toLogEntry(context: AnyObject, args: any[]): AnyObject {
|
|
169
|
-
const entry: AnyObject = { ...context }
|
|
170
|
-
let err: Error | undefined
|
|
171
|
-
let msg: string | undefined
|
|
172
|
-
|
|
173
|
-
for (const arg of args) {
|
|
174
|
-
if (_isPlainObject(arg)) {
|
|
175
|
-
Object.assign(entry, arg)
|
|
176
|
-
} else if (!err && arg instanceof Error) {
|
|
177
|
-
err = arg
|
|
178
|
-
} else {
|
|
179
|
-
const s = String(arg)
|
|
180
|
-
msg = msg === undefined ? s : `${msg} ${s}`
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (err) entry['err'] = err
|
|
185
|
-
if (msg !== undefined) entry['msg'] = msg
|
|
186
|
-
return entry
|
|
187
147
|
}
|