Skip to content

Commit f44e87d

Browse files
authored
feat: use defu to compose wide-events (#28)
1 parent 8e4d810 commit f44e87d

5 files changed

Lines changed: 72 additions & 6 deletions

File tree

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/evlog/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"typecheck": "echo 'Typecheck handled by build'"
6868
},
6969
"dependencies": {
70-
"@nuxt/kit": "^4.3.0"
70+
"@nuxt/kit": "^4.3.0",
71+
"defu": "^6.1.4"
7172
},
7273
"devDependencies": {
7374
"@nuxt/devtools": "^3.1.1",

packages/evlog/src/logger.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { defu } from 'defu'
12
import type { EnvironmentContext, Log, LogLevel, LoggerConfig, RequestLogger, RequestLoggerOptions, SamplingConfig, TailSamplingContext, WideEvent } from './types'
23
import { colors, detectEnvironment, formatDuration, getConsoleMethod, getLevelColor, isDev, matchesPattern } from './utils'
34

@@ -216,22 +217,22 @@ export function createRequestLogger(options: RequestLoggerOptions = {}): Request
216217

217218
return {
218219
set<T extends Record<string, unknown>>(data: T): void {
219-
context = { ...context, ...data }
220+
context = defu(data, context) as Record<string, unknown>
220221
},
221222

222223
error(error: Error | string, errorContext?: Record<string, unknown>): void {
223224
hasError = true
224225
const err = typeof error === 'string' ? new Error(error) : error
225226

226-
context = {
227-
...context,
227+
const errorData = {
228228
...errorContext,
229229
error: {
230230
name: err.name,
231231
message: err.message,
232232
stack: err.stack,
233233
},
234234
}
235+
context = defu(errorData, context) as Record<string, unknown>
235236
},
236237

237238
emit(overrides?: Record<string, unknown> & { _forceKeep?: boolean }): WideEvent | null {

packages/evlog/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ export type WideEvent = BaseWideEvent & Record<string, unknown>
204204
*/
205205
export interface RequestLogger {
206206
/**
207-
* Add context to the wide event (shallow merge)
207+
* Add context to the wide event (deep merge via defu)
208208
*/
209209
set: <T extends Record<string, unknown>>(context: T) => void
210210

packages/evlog/test/logger.test.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ describe('createRequestLogger', () => {
140140
expect(context.cart).toEqual({ items: 3 })
141141
})
142142

143-
it('overwrites existing keys with set()', () => {
143+
it('overwrites existing primitive keys with set()', () => {
144144
const logger = createRequestLogger({})
145145

146146
logger.set({ status: 'pending' })
@@ -150,6 +150,50 @@ describe('createRequestLogger', () => {
150150
expect(context.status).toBe('complete')
151151
})
152152

153+
it('deep merges nested objects with set()', () => {
154+
const logger = createRequestLogger({})
155+
156+
logger.set({ user: { name: 'Alice' } })
157+
logger.set({ user: { id: '123' } })
158+
159+
const context = logger.getContext()
160+
expect(context.user).toEqual({ name: 'Alice', id: '123' })
161+
})
162+
163+
it('deep merges multiple levels of nesting', () => {
164+
const logger = createRequestLogger({})
165+
166+
logger.set({ order: { customer: { name: 'Alice' } } })
167+
logger.set({ order: { customer: { email: 'alice@example.com' } } })
168+
logger.set({ order: { total: 99.99 } })
169+
170+
const context = logger.getContext()
171+
expect(context.order).toEqual({
172+
customer: { name: 'Alice', email: 'alice@example.com' },
173+
total: 99.99,
174+
})
175+
})
176+
177+
it('new values override existing values in nested objects', () => {
178+
const logger = createRequestLogger({})
179+
180+
logger.set({ user: { status: 'pending' } })
181+
logger.set({ user: { status: 'active' } })
182+
183+
const context = logger.getContext()
184+
expect(context.user).toEqual({ status: 'active' })
185+
})
186+
187+
it('handles arrays in nested objects', () => {
188+
const logger = createRequestLogger({})
189+
190+
logger.set({ cart: { items: ['item1'] } })
191+
logger.set({ cart: { total: 50 } })
192+
193+
const context = logger.getContext()
194+
expect(context.cart).toEqual({ items: ['item1'], total: 50 })
195+
})
196+
153197
it('records error with error()', () => {
154198
const logger = createRequestLogger({})
155199
const error = new Error('Payment failed')
@@ -177,6 +221,25 @@ describe('createRequestLogger', () => {
177221
})
178222
})
179223

224+
it('deep merges errorContext with nested objects after set()', () => {
225+
const logger = createRequestLogger({})
226+
227+
logger.set({ order: { id: '123', status: 'pending' } })
228+
logger.error(new Error('Payment failed'), { order: { payment: { method: 'card' } } })
229+
230+
const context = logger.getContext()
231+
expect(context.order).toEqual({
232+
id: '123',
233+
status: 'pending',
234+
payment: { method: 'card' },
235+
})
236+
expect(context.error).toEqual({
237+
name: 'Error',
238+
message: 'Payment failed',
239+
stack: expect.any(String),
240+
})
241+
})
242+
180243
it('emits wide event on emit()', () => {
181244
const logger = createRequestLogger({
182245
method: 'GET',

0 commit comments

Comments
 (0)