@naturalcycles/js-lib 15.90.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.
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 +35 -34
  16. package/dist/log/commonLogger.js +51 -96
  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 +2 -2
  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 +78 -138
  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,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: 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.
35
29
  */
36
- export type CommonLogFunction = (...args: any[]) => void
37
- export type CommonLogWithLevelFunction = (level: CommonLogLevel, args: any[]) => void
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,161 +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
- * SimpleLogger that does nothing (noop).
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 "child" logger that is "limited" to the specified CommonLogLevel.
114
+ * Creates a CommonLogger that is "limited" to the specified CommonLogLevel.
64
115
  */
65
116
  export function createCommonLoggerAtLevel(
66
- logger: CommonLogger = console,
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.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)
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 messages to all provided sub-loggers.
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: (...args) => loggers.forEach(logger => logger.debug(...args)),
111
- log: (...args) => loggers.forEach(logger => logger.log(...args)),
112
- warn: (...args) => loggers.forEach(logger => logger.warn(...args)),
113
- error: (...args) => loggers.forEach(logger => logger.error(...args)),
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
- /**
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
- },
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))),
177
146
  }
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
147
  }
@@ -26,7 +26,7 @@ export interface PGradualQueueCfg {
26
26
  errorMode?: ErrorMode
27
27
 
28
28
  /**
29
- * Default to `console`
29
+ * Default to `consoleLogger`
30
30
  */
31
31
  logger?: CommonLogger
32
32
 
@@ -1,4 +1,5 @@
1
1
  import { ErrorMode } from '../error/errorMode.js'
2
+ import { consoleLogger } from '../log/commonLogger.js'
2
3
  import type { CommonLogger } from '../log/commonLogger.js'
3
4
  import { END, SKIP } from '../types.js'
4
5
  import type { AbortableAsyncMapper } from '../types.js'
@@ -17,12 +18,12 @@ export interface PMapOptions {
17
18
  * When set to THROW_AGGREGATED, instead of stopping when a promise rejects, it will wait for all the promises to settle and then reject with an Aggregated error.
18
19
  *
19
20
  * When set to SUPPRESS - will ignore errors and return results from successful operations.
20
- * When in SUPPRESS - errors are still logged via the `logger` (which defaults to console).
21
+ * When in SUPPRESS - errors are still logged via the `logger` (which defaults to consoleLogger).
21
22
  */
22
23
  errorMode?: ErrorMode
23
24
 
24
25
  /**
25
- * Default to `console`.
26
+ * Default to `consoleLogger`.
26
27
  * Pass `null` to skip logging completely.
27
28
  */
28
29
  logger?: CommonLogger | null
@@ -71,7 +72,11 @@ export async function pMap<IN, OUT>(
71
72
  const itemsLength = items.length
72
73
  if (itemsLength === 0) return [] // short circuit
73
74
 
74
- const { concurrency = Infinity, errorMode = ErrorMode.THROW_IMMEDIATELY, logger = console } = opt
75
+ const {
76
+ concurrency = Infinity,
77
+ errorMode = ErrorMode.THROW_IMMEDIATELY,
78
+ logger = consoleLogger,
79
+ } = opt
75
80
 
76
81
  // Special cases that are able to preserve async stack traces
77
82
  // Special case: serial execution
@@ -181,7 +186,7 @@ async function pMap1<IN, OUT>(
181
186
  errors.push(err as Error)
182
187
  } else {
183
188
  // otherwise, suppress (but still log via logger)
184
- logger?.error(err)
189
+ logger?.error({ err })
185
190
  }
186
191
  }
187
192
  }
@@ -219,7 +224,7 @@ async function pMapAll<IN, OUT>(
219
224
  errors.push(r.reason)
220
225
  } else {
221
226
  // otherwise, suppress (but still log via logger)
222
- logger?.error(r.reason)
227
+ logger?.error({ err: r.reason })
223
228
  }
224
229
  }
225
230
 
@@ -18,7 +18,7 @@ export interface PQueueCfg {
18
18
  errorMode?: ErrorMode
19
19
 
20
20
  /**
21
- * Default to `console`
21
+ * Default to `consoleLogger`
22
22
  */
23
23
  logger?: CommonLogger
24
24
 
@@ -1,6 +1,7 @@
1
1
  import { _since } from '../datetime/time.util.js'
2
2
  import type { ErrorData } from '../error/error.model.js'
3
3
  import { _errorDataAppend } from '../error/error.util.js'
4
+ import { consoleLogger } from '../log/commonLogger.js'
4
5
  import type { CommonLogger } from '../log/commonLogger.js'
5
6
  import type { AnyAsyncFunction, UnixTimestampMillis } from '../types.js'
6
7
  import { pDelay } from './pDelay.js'
@@ -84,7 +85,7 @@ export interface PRetryOptions {
84
85
  logNone?: boolean
85
86
 
86
87
  /**
87
- * Default to `console`
88
+ * Default to `consoleLogger`
88
89
  */
89
90
  logger?: CommonLogger
90
91
 
@@ -113,7 +114,7 @@ export async function pRetry<T>(
113
114
  delay: initialDelay = 1000,
114
115
  delayMultiplier = 2,
115
116
  predicate,
116
- logger = console,
117
+ logger = consoleLogger,
117
118
  name,
118
119
  timeout,
119
120
  } = opt
@@ -164,7 +165,7 @@ export async function pRetry<T>(
164
165
  if (logFailures) {
165
166
  // Logger at warn (not error) level, because it's not a fatal error, but a retriable one
166
167
  // Fatal one is not logged either, because it's been thrown instead
167
- logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, err)
168
+ logger.warn({ msg: `${fname} attempt #${attempt} error in ${_since(started)}`, err })
168
169
  }
169
170
 
170
171
  if (attempt >= maxAttempts || (predicate && !predicate(err as Error, attempt, maxAttempts))) {