@knowark/loggarkjs 0.3.10 → 0.4.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/README.md CHANGED
@@ -1,2 +1,52 @@
1
1
  # loggark
2
2
  Utilitarian Logging Library
3
+
4
+ ## Logger output format
5
+
6
+ `Logger` now emits a single JSON object per log line by default (JSON Lines). This format is easier to consume with `journalctl` pipelines (`jq`, `grep`, field selection).
7
+
8
+ - Default: `format: 'json'`
9
+ - Backward compatibility: `format: 'plain'`
10
+ - Context flattening in JSON mode: `flat: true` (default)
11
+
12
+ Example:
13
+
14
+ ```js
15
+ import { Logger } from '@knowark/loggarkjs'
16
+
17
+ const logger = new Logger({ namespace: 'api' })
18
+ logger.info('User login', { userId: 'u-123' })
19
+ ```
20
+
21
+ Outputs:
22
+
23
+ ```json
24
+ {"timestamp":"2025-02-15T00:00:00.000Z","level":"info","namespace":"api","message":"User login","userId":"u-123"}
25
+ ```
26
+
27
+ Additional object parameters are merged into the base log object. If keys collide, later merged values overwrite earlier keys.
28
+
29
+ If you prefer context keys at top level (for simpler `jq` filters), enable flattening:
30
+
31
+ ```js
32
+ const logger = new Logger({
33
+ namespace: 'api',
34
+ context: { correlationId: 'ABCD1234', interactor: 'Informer' },
35
+ flat: true
36
+ })
37
+
38
+ logger.info('User login')
39
+ ```
40
+
41
+ ```json
42
+ {"timestamp":"2025-02-15T00:00:00.000Z","level":"info","namespace":"api","correlationId":"ABCD1234","interactor":"Informer","message":"User login"}
43
+ ```
44
+
45
+ To keep nested context under `context`, disable flattening:
46
+
47
+ ```js
48
+ const logger = new Logger({
49
+ context: { correlationId: 'ABCD1234' },
50
+ flat: false
51
+ })
52
+ ```
package/lib/index.d.ts CHANGED
@@ -4,7 +4,9 @@ export declare class Logger {
4
4
  constructor (dependencies?: {
5
5
  namespace?: string,
6
6
  context?: object,
7
- global?: object
7
+ global?: object,
8
+ format?: 'json' | 'plain',
9
+ flat?: boolean
8
10
  })
9
11
 
10
12
  context?: object | null
@@ -32,4 +34,6 @@ export declare class Translator {
32
34
  t(key: string, options?: object): string
33
35
  }
34
36
 
35
- export declare function t(key: string, options?: object): string
37
+ export declare function t(key: string, options?: object): string
38
+
39
+ export declare function lt(key: string, options?: object): () => string
package/lib/logger.js CHANGED
@@ -5,8 +5,11 @@ export class Logger {
5
5
 
6
6
  #context = {}
7
7
 
8
- constructor ({ namespace = '', context = null, global = globalThis } = {}) {
8
+ constructor ({ namespace = '', context = null, global = globalThis, format = 'json', flat = true } = {}) {
9
9
  this.levels = ['error', 'warn', 'info', 'debug']
10
+ this.namespace = namespace
11
+ this.format = format
12
+ this.flat = flat
10
13
  this.logvar = [namespace, 'LOGLEVEL'].filter(
11
14
  Boolean).join('_').toUpperCase().replaceAll(' ', '_')
12
15
  this.global = global
@@ -24,28 +27,91 @@ export class Logger {
24
27
  }
25
28
 
26
29
  log (...args) {
27
- if (this.logindex >= 0) log(this.global, 'log', this.context, args)
30
+ if (this.logindex >= 0) log(this.global, 'log', this.context, args, this.namespace, this.format, this.flat)
28
31
  }
29
32
 
30
33
  error (...args) {
31
- if (this.logindex >= 0) log(this.global, 'error', this.context, args)
34
+ if (this.logindex >= 0) log(this.global, 'error', this.context, args, this.namespace, this.format, this.flat)
32
35
  }
33
36
 
34
37
  warn (...args) {
35
- if (this.logindex >= 1) log(this.global, 'warn', this.context, args)
38
+ if (this.logindex >= 1) log(this.global, 'warn', this.context, args, this.namespace, this.format, this.flat)
36
39
  }
37
40
 
38
41
  info (...args) {
39
- if (this.logindex >= 2) log(this.global, 'info', this.context, args)
42
+ if (this.logindex >= 2) log(this.global, 'info', this.context, args, this.namespace, this.format, this.flat)
40
43
  }
41
44
 
42
45
  debug (...args) {
43
- if (this.logindex >= 3) log(this.global, 'debug', this.context, args)
46
+ if (this.logindex >= 3) log(this.global, 'debug', this.context, args, this.namespace, this.format, this.flat)
44
47
  }
45
48
  }
46
49
 
47
- function log (global, level, context, args) {
48
- return global.console[level](
49
- `${new global.Date().toISOString()} [${level.toUpperCase()}]`,
50
- ...[context ? JSON.stringify(context) : false, ...args].filter(Boolean))
51
- }
50
+ function parseArgs (args) {
51
+ const payload = {}
52
+
53
+ args.forEach((value, index) => {
54
+ if (index === 0 && typeof value === 'string') {
55
+ payload.message = value
56
+ return
57
+ }
58
+
59
+ Object.assign(payload, toLogFields(value, index))
60
+ })
61
+
62
+ return payload
63
+ }
64
+
65
+ function toLogFields (value, index) {
66
+ if (value instanceof Error) return toSerializableError(value)
67
+ if (value && typeof value === 'object' && !Array.isArray(value)) return value
68
+ return { [`arg${index}`]: value }
69
+ }
70
+
71
+ function toSerializableError (error) {
72
+ return {
73
+ name: error.name,
74
+ message: error.message,
75
+ stack: error.stack,
76
+ ...error
77
+ }
78
+ }
79
+
80
+ function stringifyJson (payload) {
81
+ const seen = new WeakSet()
82
+ return JSON.stringify(payload, (_key, value) => {
83
+ if (typeof value === 'bigint') return value.toString()
84
+ if (typeof value === 'function') return `[Function ${value.name || 'anonymous'}]`
85
+ if (typeof value === 'symbol') return value.toString()
86
+ if (value instanceof Error) return toSerializableError(value)
87
+
88
+ if (value && typeof value === 'object') {
89
+ if (seen.has(value)) return '[Circular]'
90
+ seen.add(value)
91
+ }
92
+
93
+ return value
94
+ })
95
+ }
96
+
97
+ function log (global, level, context, args, namespace, format, flat) {
98
+ const timestamp = new global.Date().toISOString()
99
+
100
+ if (format === 'plain') {
101
+ const message = [`${timestamp} [${level.toUpperCase()}]`]
102
+ if (context) message.push(stringifyJson(context))
103
+ message.push(...args)
104
+ return global.console[level](...message)
105
+ }
106
+
107
+ const record = {
108
+ ...(flat && context ? context : {}),
109
+ timestamp,
110
+ level,
111
+ ...(namespace ? { namespace } : {}),
112
+ ...(!flat && context ? { context } : {}),
113
+ ...parseArgs(args)
114
+ }
115
+
116
+ return global.console[level](stringifyJson(record))
117
+ }
@@ -1,4 +1,5 @@
1
- import { it, expect } from '@jest/globals'
1
+ import assert from 'node:assert/strict'
2
+ import { it } from 'node:test'
2
3
  import { Logger } from './logger.js'
3
4
 
4
5
  function setup () {
@@ -11,22 +12,29 @@ function setup () {
11
12
  debug (...args) { mockGlobal.debugArgs = args }
12
13
  },
13
14
  Date: class {
14
- toISOString() {
15
+ toISOString () {
15
16
  return new Date('2025-02-15').toISOString()
16
17
  }
17
18
  }
18
19
  }
19
- return {mockGlobal}
20
+
21
+ return { mockGlobal }
22
+ }
23
+
24
+ function parseRecord (args) {
25
+ assert.ok(args)
26
+ assert.strictEqual(args.length, 1)
27
+ return JSON.parse(args[0])
20
28
  }
21
29
 
22
30
  it('can be instantiated', () => {
23
31
  const logger = new Logger()
24
- expect(logger).toBeTruthy()
32
+ assert.ok(logger)
25
33
  })
26
34
 
27
- it('can be instantiated', () => {
35
+ it('returns null context when no static or instance context is available', () => {
28
36
  const logger = new Logger()
29
- expect(logger).toBeTruthy()
37
+ assert.strictEqual(logger.context, null)
30
38
  })
31
39
 
32
40
  it('is disabled by default', () => {
@@ -34,98 +42,353 @@ it('is disabled by default', () => {
34
42
  const logger = new Logger({ global: mockGlobal })
35
43
 
36
44
  logger.log('Logging something...')
37
- expect(mockGlobal.logArgs).toBeFalsy()
45
+ assert.ok(!mockGlobal.logArgs)
38
46
 
39
47
  logger.error('Logging something...')
40
- expect(mockGlobal.errorArgs).toBeFalsy()
48
+ assert.ok(!mockGlobal.errorArgs)
41
49
 
42
50
  logger.warn('Logging something...')
43
- expect(mockGlobal.warnArgs).toBeFalsy()
51
+ assert.ok(!mockGlobal.warnArgs)
44
52
 
45
53
  logger.info('Logging something...')
46
- expect(mockGlobal.infoArgs).toBeFalsy()
54
+ assert.ok(!mockGlobal.infoArgs)
47
55
 
48
56
  logger.debug('Logging something...')
49
- expect(mockGlobal.debugArgs).toBeFalsy()
57
+ assert.ok(!mockGlobal.debugArgs)
50
58
  })
51
59
 
52
- it('adds a prefix to the logging methods according to loglevel', () => {
60
+ it('emits JSON logs by default according to loglevel', () => {
53
61
  const { mockGlobal } = setup()
54
62
  mockGlobal.LOGLEVEL = 'debug'
55
63
  const logger = new Logger({ global: mockGlobal })
56
64
 
57
65
  logger.log('Logging something...')
58
- expect(mockGlobal.logArgs).toEqual(
59
- ['2025-02-15T00:00:00.000Z [LOG]', 'Logging something...'])
66
+ assert.deepStrictEqual(parseRecord(mockGlobal.logArgs), {
67
+ timestamp: '2025-02-15T00:00:00.000Z',
68
+ level: 'log',
69
+ message: 'Logging something...'
70
+ })
60
71
 
61
72
  logger.error('Logging something...')
62
- expect(mockGlobal.errorArgs).toEqual(
63
- ['2025-02-15T00:00:00.000Z [ERROR]', 'Logging something...'])
73
+ assert.deepStrictEqual(parseRecord(mockGlobal.errorArgs), {
74
+ timestamp: '2025-02-15T00:00:00.000Z',
75
+ level: 'error',
76
+ message: 'Logging something...'
77
+ })
64
78
 
65
79
  logger.warn('Logging something...')
66
- expect(mockGlobal.warnArgs).toEqual(
67
- ['2025-02-15T00:00:00.000Z [WARN]', 'Logging something...'])
80
+ assert.deepStrictEqual(parseRecord(mockGlobal.warnArgs), {
81
+ timestamp: '2025-02-15T00:00:00.000Z',
82
+ level: 'warn',
83
+ message: 'Logging something...'
84
+ })
68
85
 
69
86
  logger.info('Logging something...')
70
- expect(mockGlobal.infoArgs).toEqual(
71
- ['2025-02-15T00:00:00.000Z [INFO]', 'Logging something...'])
87
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
88
+ timestamp: '2025-02-15T00:00:00.000Z',
89
+ level: 'info',
90
+ message: 'Logging something...'
91
+ })
72
92
 
73
93
  logger.debug('Logging something...')
74
- expect(mockGlobal.debugArgs).toEqual(
75
- ['2025-02-15T00:00:00.000Z [DEBUG]', 'Logging something...'])
94
+ assert.deepStrictEqual(parseRecord(mockGlobal.debugArgs), {
95
+ timestamp: '2025-02-15T00:00:00.000Z',
96
+ level: 'debug',
97
+ message: 'Logging something...'
98
+ })
99
+ })
100
+
101
+ it('merges object parameters into the base JSON log object', () => {
102
+ const { mockGlobal } = setup()
103
+ mockGlobal.LOGLEVEL = 'info'
104
+ const logger = new Logger({ global: mockGlobal })
105
+
106
+ logger.info('Logging something...', { action: 'create', entity: 'product' })
107
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
108
+ timestamp: '2025-02-15T00:00:00.000Z',
109
+ level: 'info',
110
+ message: 'Logging something...',
111
+ action: 'create',
112
+ entity: 'product'
113
+ })
76
114
  })
77
115
 
78
- it('adds the provided logging context labels to its final output', () => {
116
+ it('can emit JSON logs without message arguments', () => {
117
+ const { mockGlobal } = setup()
118
+ mockGlobal.LOGLEVEL = 'info'
119
+ const logger = new Logger({ global: mockGlobal })
120
+
121
+ logger.info()
122
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
123
+ timestamp: '2025-02-15T00:00:00.000Z',
124
+ level: 'info'
125
+ })
126
+ })
127
+
128
+ it('can emit JSON logs without context when flat is disabled', () => {
129
+ const { mockGlobal } = setup()
130
+ mockGlobal.LOGLEVEL = 'info'
131
+ const logger = new Logger({ global: mockGlobal, flat: false })
132
+
133
+ logger.info('Logging something...')
134
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
135
+ timestamp: '2025-02-15T00:00:00.000Z',
136
+ level: 'info',
137
+ message: 'Logging something...'
138
+ })
139
+ })
140
+
141
+ it('flattens provided logging context labels by default', () => {
79
142
  const { mockGlobal } = setup()
80
143
  mockGlobal.LOGLEVEL = 'debug'
81
144
  const context = { correlationId: 'ABCD1234', interactor: 'Informer' }
82
145
  const logger = new Logger({ global: mockGlobal, context })
83
146
 
84
- logger.log('Logging something...')
85
- expect(mockGlobal.logArgs).toEqual([
86
- '2025-02-15T00:00:00.000Z [LOG]',
87
- JSON.stringify({ correlationId: 'ABCD1234', interactor: 'Informer' }),
88
- 'Logging something...'])
147
+ logger.info('Logging something...')
148
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
149
+ timestamp: '2025-02-15T00:00:00.000Z',
150
+ level: 'info',
151
+ correlationId: 'ABCD1234',
152
+ interactor: 'Informer',
153
+ message: 'Logging something...'
154
+ })
155
+ })
89
156
 
90
- logger.error('Logging something...')
91
- expect(mockGlobal.errorArgs).toEqual([
92
- '2025-02-15T00:00:00.000Z [ERROR]',
93
- JSON.stringify({ correlationId: 'ABCD1234', interactor: 'Informer' }),
94
- 'Logging something...'])
157
+ it('can flatten context labels into top-level JSON fields with flat option', () => {
158
+ const { mockGlobal } = setup()
159
+ mockGlobal.LOGLEVEL = 'info'
160
+ const context = { correlationId: 'ABCD1234', interactor: 'Informer' }
161
+ const logger = new Logger({ global: mockGlobal, context, flat: true })
95
162
 
96
- logger.warn('Logging something...')
97
- expect(mockGlobal.warnArgs).toEqual([
98
- '2025-02-15T00:00:00.000Z [WARN]',
99
- JSON.stringify({ correlationId: 'ABCD1234', interactor: 'Informer' }),
100
- 'Logging something...'])
163
+ logger.info('Logging something...')
164
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
165
+ timestamp: '2025-02-15T00:00:00.000Z',
166
+ level: 'info',
167
+ correlationId: 'ABCD1234',
168
+ interactor: 'Informer',
169
+ message: 'Logging something...'
170
+ })
171
+ })
172
+
173
+ it('can keep nested context when flat option is disabled', () => {
174
+ const { mockGlobal } = setup()
175
+ mockGlobal.LOGLEVEL = 'info'
176
+ const context = { correlationId: 'ABCD1234', interactor: 'Informer' }
177
+ const logger = new Logger({ global: mockGlobal, context, flat: false })
101
178
 
102
179
  logger.info('Logging something...')
103
- expect(mockGlobal.infoArgs).toEqual([
104
- '2025-02-15T00:00:00.000Z [INFO]',
105
- JSON.stringify({ correlationId: 'ABCD1234', interactor: 'Informer' }),
106
- 'Logging something...'])
180
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
181
+ timestamp: '2025-02-15T00:00:00.000Z',
182
+ level: 'info',
183
+ context,
184
+ message: 'Logging something...'
185
+ })
186
+ })
107
187
 
108
- logger.debug('Logging something...')
109
- expect(mockGlobal.debugArgs).toEqual([
110
- '2025-02-15T00:00:00.000Z [DEBUG]',
111
- JSON.stringify({ correlationId: 'ABCD1234', interactor: 'Informer' }),
112
- 'Logging something...'])
188
+ it('keeps log metadata and payload fields over flat context collisions', () => {
189
+ const { mockGlobal } = setup()
190
+ mockGlobal.CORE_LOGLEVEL = 'info'
191
+ const context = {
192
+ timestamp: 'old',
193
+ level: 'fatal',
194
+ namespace: 'context-namespace',
195
+ message: 'context message',
196
+ args: ['context args'],
197
+ correlationId: 'ABCD1234'
198
+ }
199
+ const logger = new Logger({
200
+ global: mockGlobal,
201
+ namespace: 'core',
202
+ context,
203
+ flat: true
204
+ })
205
+
206
+ logger.info('real message', 'value')
207
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
208
+ timestamp: '2025-02-15T00:00:00.000Z',
209
+ level: 'info',
210
+ namespace: 'core',
211
+ message: 'real message',
212
+ args: ['context args'],
213
+ correlationId: 'ABCD1234',
214
+ arg1: 'value'
215
+ })
113
216
  })
114
217
 
115
218
  it('supports including static context labels', () => {
219
+ const originalContext = Logger.context
116
220
  Logger.context = () => {
117
221
  return { correlationId: 'ABCD1234' }
118
222
  }
119
223
 
224
+ try {
225
+ const { mockGlobal } = setup()
226
+ mockGlobal.LOGLEVEL = 'debug'
227
+ const context = { model: 'Product' }
228
+ const logger = new Logger({ global: mockGlobal, context })
229
+
230
+ logger.info('Logging something...')
231
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
232
+ timestamp: '2025-02-15T00:00:00.000Z',
233
+ level: 'info',
234
+ correlationId: 'ABCD1234',
235
+ model: 'Product',
236
+ message: 'Logging something...'
237
+ })
238
+ } finally {
239
+ Logger.context = originalContext
240
+ }
241
+ })
242
+
243
+ it('includes namespace in JSON output and uses the namespace loglevel variable', () => {
120
244
  const { mockGlobal } = setup()
121
- mockGlobal.LOGLEVEL = 'debug'
122
- const context = { model: 'Product' }
123
- const logger = new Logger({ global: mockGlobal, context })
245
+ mockGlobal.CORE_LOGLEVEL = 'info'
246
+ const logger = new Logger({ global: mockGlobal, namespace: 'core' })
124
247
 
125
248
  logger.info('Logging something...')
126
- expect(mockGlobal.infoArgs).toEqual([
249
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
250
+ timestamp: '2025-02-15T00:00:00.000Z',
251
+ level: 'info',
252
+ namespace: 'core',
253
+ message: 'Logging something...'
254
+ })
255
+ })
256
+
257
+ it('merges non-string object payloads into the JSON record', () => {
258
+ const { mockGlobal } = setup()
259
+ mockGlobal.LOGLEVEL = 'info'
260
+ const logger = new Logger({ global: mockGlobal })
261
+
262
+ logger.info({ code: 'E_UNEXPECTED' })
263
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
264
+ timestamp: '2025-02-15T00:00:00.000Z',
265
+ level: 'info',
266
+ code: 'E_UNEXPECTED'
267
+ })
268
+ })
269
+
270
+ it('stores null payloads as positional fields in JSON format', () => {
271
+ const { mockGlobal } = setup()
272
+ mockGlobal.LOGLEVEL = 'info'
273
+ const logger = new Logger({ global: mockGlobal })
274
+
275
+ logger.info(null)
276
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
277
+ timestamp: '2025-02-15T00:00:00.000Z',
278
+ level: 'info',
279
+ arg0: null
280
+ })
281
+ })
282
+
283
+ it('serializes Error payloads in JSON format', () => {
284
+ const { mockGlobal } = setup()
285
+ mockGlobal.LOGLEVEL = 'error'
286
+ const logger = new Logger({ global: mockGlobal })
287
+ const error = new Error('Unexpected failure')
288
+ error.code = 'E_UNEXPECTED'
289
+
290
+ logger.error(error)
291
+ const record = parseRecord(mockGlobal.errorArgs)
292
+
293
+ assert.strictEqual(record.level, 'error')
294
+ assert.strictEqual(record.name, 'Error')
295
+ assert.strictEqual(record.message, 'Unexpected failure')
296
+ assert.strictEqual(record.code, 'E_UNEXPECTED')
297
+ assert.match(record.stack, /Unexpected failure/)
298
+ })
299
+
300
+ it('serializes nested Error values inside object payloads', () => {
301
+ const { mockGlobal } = setup()
302
+ mockGlobal.LOGLEVEL = 'info'
303
+ const logger = new Logger({ global: mockGlobal })
304
+ const nested = new Error('Nested failure')
305
+ nested.code = 'E_NESTED'
306
+
307
+ logger.info({ nested })
308
+ const record = parseRecord(mockGlobal.infoArgs)
309
+
310
+ assert.strictEqual(record.nested.name, 'Error')
311
+ assert.strictEqual(record.nested.message, 'Nested failure')
312
+ assert.strictEqual(record.nested.code, 'E_NESTED')
313
+ assert.match(record.nested.stack, /Nested failure/)
314
+ })
315
+
316
+ it('lets additional payload fields overwrite previously defined keys', () => {
317
+ const { mockGlobal } = setup()
318
+ mockGlobal.LOGLEVEL = 'info'
319
+ const logger = new Logger({ global: mockGlobal })
320
+ const error = new TypeError('Bad input')
321
+ error.field = 'name'
322
+
323
+ logger.info('Validation failed', error)
324
+ const record = parseRecord(mockGlobal.infoArgs)
325
+
326
+ assert.strictEqual(record.message, 'Bad input')
327
+ assert.strictEqual(record.name, 'TypeError')
328
+ assert.strictEqual(record.field, 'name')
329
+ assert.match(record.stack, /Bad input/)
330
+ })
331
+
332
+ it('serializes special values and circular references in merged JSON fields', () => {
333
+ const { mockGlobal } = setup()
334
+ mockGlobal.LOGLEVEL = 'info'
335
+ const logger = new Logger({ global: mockGlobal })
336
+ const payload = {
337
+ count: 10n,
338
+ token: Symbol('auth')
339
+ }
340
+ payload.self = payload
341
+
342
+ logger.info('Special payload', (function () {}), function namedFn () {}, payload)
343
+ const record = parseRecord(mockGlobal.infoArgs)
344
+
345
+ assert.strictEqual(record.message, 'Special payload')
346
+ assert.strictEqual(record.arg1, '[Function anonymous]')
347
+ assert.strictEqual(record.arg2, '[Function namedFn]')
348
+ assert.strictEqual(record.count, '10')
349
+ assert.strictEqual(record.token, 'Symbol(auth)')
350
+ assert.strictEqual(record.self.count, '10')
351
+ assert.strictEqual(record.self.token, 'Symbol(auth)')
352
+ assert.strictEqual(record.self.self, '[Circular]')
353
+ })
354
+
355
+ it('stores array payloads as positional fields in JSON format', () => {
356
+ const { mockGlobal } = setup()
357
+ mockGlobal.LOGLEVEL = 'info'
358
+ const logger = new Logger({ global: mockGlobal })
359
+
360
+ logger.info('Array payload', [1, 2, 3])
361
+ assert.deepStrictEqual(parseRecord(mockGlobal.infoArgs), {
362
+ timestamp: '2025-02-15T00:00:00.000Z',
363
+ level: 'info',
364
+ message: 'Array payload',
365
+ arg1: [1, 2, 3]
366
+ })
367
+ })
368
+
369
+ it('supports plain formatting for backward compatibility', () => {
370
+ const { mockGlobal } = setup()
371
+ mockGlobal.LOGLEVEL = 'info'
372
+ const context = { correlationId: 'ABCD1234' }
373
+ const logger = new Logger({ global: mockGlobal, context, format: 'plain' })
374
+
375
+ logger.info('Logging something...', 0, false)
376
+ assert.deepStrictEqual(mockGlobal.infoArgs, [
127
377
  '2025-02-15T00:00:00.000Z [INFO]',
128
- JSON.stringify({ correlationId: 'ABCD1234', model: 'Product' }),
129
- 'Logging something...'])
378
+ JSON.stringify(context),
379
+ 'Logging something...',
380
+ 0,
381
+ false
382
+ ])
383
+ })
130
384
 
131
- })
385
+ it('supports plain formatting without context labels', () => {
386
+ const { mockGlobal } = setup()
387
+ mockGlobal.LOGLEVEL = 'info'
388
+ const logger = new Logger({ global: mockGlobal, format: 'plain' })
389
+
390
+ logger.info()
391
+ assert.deepStrictEqual(mockGlobal.infoArgs, [
392
+ '2025-02-15T00:00:00.000Z [INFO]'
393
+ ])
394
+ })
@@ -1,9 +1,10 @@
1
- import { it, expect } from '@jest/globals'
1
+ import assert from 'node:assert/strict'
2
+ import { it } from 'node:test'
2
3
  import { Translator, t, lt } from './translator.js'
3
4
 
4
5
  it('can be instantiated', () => {
5
6
  const translator = new Translator()
6
- expect(translator).toBeTruthy()
7
+ assert.ok(translator)
7
8
  })
8
9
 
9
10
  it('defines a translate function with options', () => {
@@ -11,7 +12,7 @@ it('defines a translate function with options', () => {
11
12
 
12
13
  const result = translator.translate('any.key')
13
14
 
14
- expect(result).toBe('any.key')
15
+ assert.strictEqual(result, 'any.key')
15
16
  })
16
17
 
17
18
  it('defines a t shortcut for function translate', () => {
@@ -19,30 +20,36 @@ it('defines a t shortcut for function translate', () => {
19
20
 
20
21
  const result = t('any.key')
21
22
 
22
- expect(result).toBe('any.key')
23
+ assert.strictEqual(result, 'any.key')
23
24
  })
24
25
 
25
26
  it('defines a global t shortcut translate', () => {
26
27
  const originalTranslate = Translator.translate
27
- Translator.translate = (key, _options) => {
28
- return `translated:${key}`
29
- }
28
+ try {
29
+ Translator.translate = (key, _options) => {
30
+ return `translated:${key}`
31
+ }
30
32
 
31
- const result = t('any.key')
33
+ const result = t('any.key')
32
34
 
33
- expect(result).toBe('translated:any.key')
34
- Translator.translate = originalTranslate
35
+ assert.strictEqual(result, 'translated:any.key')
36
+ } finally {
37
+ Translator.translate = originalTranslate
38
+ }
35
39
  })
36
40
 
37
41
  it('defines a global lt lazy-translation function', () => {
38
42
  const originalTranslate = Translator.translate
39
- Translator.translate = (key, _options) => {
40
- return `translated:${key}`
41
- }
43
+ try {
44
+ Translator.translate = (key, _options) => {
45
+ return `translated:${key}`
46
+ }
42
47
 
43
- const result = lt('any.key')
48
+ const result = lt('any.key')
44
49
 
45
- expect(result).toBeInstanceOf(Function)
46
- expect(result()).toBe('translated:any.key')
47
- Translator.translate = originalTranslate
50
+ assert.ok(result instanceof Function)
51
+ assert.strictEqual(result(), 'translated:any.key')
52
+ } finally {
53
+ Translator.translate = originalTranslate
54
+ }
48
55
  })
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@knowark/loggarkjs",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
4
4
  "description": "Utilitarian Logging Library",
5
5
  "main": "./lib/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "options": "NODE_OPTIONS='--experimental-vm-modules --no-warnings'",
9
- "test": "npm run options -- npx jest --coverage"
8
+ "test": "node --test --experimental-test-coverage --test-coverage-exclude='**/*.test.js'"
10
9
  },
11
10
  "repository": {
12
11
  "type": "git",
@@ -21,8 +20,5 @@
21
20
  "bugs": {
22
21
  "url": "https://github.com/knowark/loggarkjs/issues"
23
22
  },
24
- "homepage": "https://github.com/knowark/loggarkjs#readme",
25
- "devDependencies": {
26
- "jest": "^29.7.0"
27
- }
23
+ "homepage": "https://github.com/knowark/loggarkjs#readme"
28
24
  }
@@ -1,7 +0,0 @@
1
- {
2
- "workbench.colorCustomizations": {
3
- "activityBar.background": "#2C2D34",
4
- "titleBar.activeBackground": "#3E3F49",
5
- "titleBar.activeForeground": "#FAFAFB"
6
- }
7
- }