@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.
Files changed (38) hide show
  1. package/dist/asyncManager.js +3 -2
  2. package/dist/decorators/asyncMemo.decorator.d.ts +1 -1
  3. package/dist/decorators/asyncMemo.decorator.js +4 -3
  4. package/dist/decorators/logMethod.decorator.d.ts +1 -1
  5. package/dist/decorators/logMethod.decorator.js +8 -3
  6. package/dist/decorators/memo.decorator.d.ts +1 -1
  7. package/dist/decorators/memo.decorator.js +3 -2
  8. package/dist/decorators/memoSimple.decorator.js +8 -1
  9. package/dist/decorators/memoized.js +3 -2
  10. package/dist/decorators/memoizedAsync.js +4 -3
  11. package/dist/error/tryCatch.d.ts +1 -1
  12. package/dist/error/tryCatch.js +3 -2
  13. package/dist/http/fetcher.js +18 -17
  14. package/dist/http/fetcher.model.d.ts +1 -1
  15. package/dist/log/commonLogger.d.ts +40 -34
  16. package/dist/log/commonLogger.js +47 -92
  17. package/dist/promise/pGradualQueue.d.ts +1 -1
  18. package/dist/promise/pMap.d.ts +2 -2
  19. package/dist/promise/pMap.js +4 -3
  20. package/dist/promise/pQueue.d.ts +1 -1
  21. package/dist/promise/pRetry.d.ts +1 -1
  22. package/dist/promise/pRetry.js +3 -2
  23. package/package.json +1 -1
  24. package/src/asyncManager.ts +3 -2
  25. package/src/decorators/asyncMemo.decorator.ts +5 -4
  26. package/src/decorators/logMethod.decorator.ts +9 -4
  27. package/src/decorators/memo.decorator.ts +4 -3
  28. package/src/decorators/memoSimple.decorator.ts +2 -1
  29. package/src/decorators/memoized.ts +3 -2
  30. package/src/decorators/memoizedAsync.ts +4 -3
  31. package/src/error/tryCatch.ts +4 -3
  32. package/src/http/fetcher.model.ts +1 -1
  33. package/src/http/fetcher.ts +9 -10
  34. package/src/log/commonLogger.ts +80 -134
  35. package/src/promise/pGradualQueue.ts +1 -1
  36. package/src/promise/pMap.ts +10 -5
  37. package/src/promise/pQueue.ts +1 -1
  38. package/src/promise/pRetry.ts +4 -3
@@ -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 = console } = opt;
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) {
@@ -12,7 +12,7 @@ export interface PQueueCfg {
12
12
  */
13
13
  errorMode?: ErrorMode;
14
14
  /**
15
- * Default to `console`
15
+ * Default to `consoleLogger`
16
16
  */
17
17
  logger?: CommonLogger;
18
18
  /**
@@ -67,7 +67,7 @@ export interface PRetryOptions {
67
67
  */
68
68
  logNone?: boolean;
69
69
  /**
70
- * Default to `console`
70
+ * Default to `consoleLogger`
71
71
  */
72
72
  logger?: CommonLogger;
73
73
  /**
@@ -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 = console, name, timeout, } = opt;
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)}:`, err);
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.90.0",
4
+ "version": "15.92.0",
5
5
  "dependencies": {
6
6
  "tslib": "^2"
7
7
  },
@@ -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 = console
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:', err)
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 `console`
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 = console, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt
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 `console`
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 = console,
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:', err)
160
+ t.push('ERROR:')
160
161
  } else if (logResultFn) {
161
162
  t.push(...logResultFn(res))
162
163
  }
163
164
 
164
- logger.log(...t.filter(Boolean))
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 `console`
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 = console,
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 = console } = opt
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 = console,
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 = console, cacheFactory, cacheKeyFn = jsonMemoSerializer } = opt
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
 
@@ -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 `console`
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 = console } = opt
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)}:`, err)
56
+ logger.warn({ msg: `tryCatch.${fname} error in ${_since(started)}`, err })
56
57
  }
57
58
 
58
59
  if (onError) {
@@ -162,7 +162,7 @@ export interface FetcherCfg {
162
162
  logWithSearchParams?: boolean
163
163
 
164
164
  /**
165
- * Defaults to `console`.
165
+ * Defaults to `consoleLogger`.
166
166
  */
167
167
  logger?: CommonLogger
168
168
 
@@ -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(' ') + '\n',
703
- // We're stringifying the error here, otherwise Sentry shows it as [object Object]
704
- _stringify(res.err.cause || res.err),
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 = console } = cfg
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}`)
@@ -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: 20,
27
- error: 30,
23
+ warn: 30,
24
+ error: 40,
28
25
  }
29
26
 
30
27
  /**
31
- * Function that takes any number of arguments and logs them all.
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.
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 CommonLogFunction = (...args: any[]) => void
37
- export type CommonLogWithLevelFunction = (level: CommonLogLevel, args: any[]) => void
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
- * SimpleLogger that does nothing (noop).
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 "child" logger that is "limited" to the specified CommonLogLevel.
120
+ * Creates a CommonLogger that is "limited" to the specified CommonLogLevel.
64
121
  */
65
122
  export function createCommonLoggerAtLevel(
66
- logger: CommonLogger = console,
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.bind(logger) : _noop,
100
- warn: level <= commonLogLevelNumber['warn'] ? logger.warn.bind(logger) : _noop,
101
- error: logger.error.bind(logger), // otherwise it's "log nothing" logger (same as noopLogger)
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 messages to all provided sub-loggers.
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
- }