Skip to content

Commit 861f6d2

Browse files
committed
feat: concatenate arrays in log.set merge
1 parent 2df9562 commit 861f6d2

4 files changed

Lines changed: 49 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'evlog': minor
3+
---
4+
5+
`log.set()` concatenates arrays when merging context for the same key. For example, `set({ items: [1, 2] })` followed by `set({ items: [3] })` yields `{ items: [1, 2, 3] }` instead of replacing with `[3]`. Plain objects are still deep-merged recursively; if either the existing or incoming value is not an array, the new value replaces the old one.
6+
7+
**Breaking change:** Call sites that relied on the last `set` overwriting an array now accumulate elements. To replace a value at emit time, use `emit({ ... })` overrides or a different field name.

packages/evlog/src/logger.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ function mergeInto(target: Record<string, unknown>, source: Record<string, unkno
1919
const targetVal = target[key]
2020
if (isPlainObject(sourceVal) && isPlainObject(targetVal)) {
2121
mergeInto(targetVal, sourceVal)
22+
} else if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
23+
target[key] = [...targetVal, ...sourceVal]
2224
} else {
2325
target[key] = sourceVal
2426
}

packages/evlog/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,10 @@ export type FieldContext<T extends object = Record<string, unknown>> =
466466
*/
467467
export interface RequestLogger<T extends object = Record<string, unknown>> {
468468
/**
469-
* Add context to the wide event (deep merge via defu)
469+
* Add context to the wide event. Plain objects are merged recursively.
470+
* When both the existing and incoming values for a key are arrays, elements are
471+
* concatenated (existing order preserved, new elements appended). Otherwise the
472+
* new value replaces the old one (including when only one side is an array).
470473
*/
471474
set: (context: FieldContext<T>) => void
472475

packages/evlog/test/logger.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,42 @@ describe('createRequestLogger', () => {
252252
expect(context.cart).toEqual({ items: ['item1'], total: 50 })
253253
})
254254

255+
it('concatenates arrays on the same key with set()', () => {
256+
const logger = createRequestLogger({})
257+
258+
logger.set({ array: [1, 2] })
259+
logger.set({ array: [3] })
260+
261+
expect(logger.getContext().array).toEqual([1, 2, 3])
262+
})
263+
264+
it('concatenates nested arrays on the same key with set()', () => {
265+
const logger = createRequestLogger({})
266+
267+
logger.set({ job: { steps: ['a'] } })
268+
logger.set({ job: { steps: ['b', 'c'] } })
269+
270+
expect(logger.getContext().job).toEqual({ steps: ['a', 'b', 'c'] })
271+
})
272+
273+
it('replaces array with non-array on the same key with set()', () => {
274+
const logger = createRequestLogger({})
275+
276+
logger.set({ tags: ['a', 'b'] })
277+
logger.set({ tags: 'done' })
278+
279+
expect(logger.getContext().tags).toBe('done')
280+
})
281+
282+
it('does not drop prior array elements when appending an empty array', () => {
283+
const logger = createRequestLogger({})
284+
285+
logger.set({ ids: [1, 2] })
286+
logger.set({ ids: [] })
287+
288+
expect(logger.getContext().ids).toEqual([1, 2])
289+
})
290+
255291
it('records error with error()', () => {
256292
const logger = createRequestLogger({})
257293
const error = new Error('Payment failed')

0 commit comments

Comments
 (0)