Skip to content

Commit 63a2d36

Browse files
committed
feat(core): add kero logger integration to AsyncEventBus
- Add optional logger parameter to AsyncEventBusOptions - Use kero logger for all debug/error logging when provided - Graceful fallback to console.* when no logger provided - Add comprehensive logger integration tests Benefits: - Consistent structured logging across core components - Proper log levels and error context - Better test isolation with logger mocking - Aligns with scanner/observability logging patterns All 601 core tests passing (added 3 new tests).
1 parent 90e66e1 commit 63a2d36

File tree

2 files changed

+131
-7
lines changed

2 files changed

+131
-7
lines changed

packages/core/src/events/__tests__/event-bus.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,92 @@ describe('AsyncEventBus', () => {
257257
consoleErrorSpy.mockRestore();
258258
});
259259
});
260+
261+
describe('logger integration', () => {
262+
it('should use kero logger when provided', async () => {
263+
const mockLogger = {
264+
trace: vi.fn(),
265+
debug: vi.fn(),
266+
info: vi.fn(),
267+
success: vi.fn(),
268+
warn: vi.fn(),
269+
error: vi.fn(),
270+
fatal: vi.fn(),
271+
child: vi.fn(),
272+
startTimer: vi.fn(() => vi.fn()),
273+
isLevelEnabled: vi.fn(() => true),
274+
level: 'debug' as const,
275+
};
276+
277+
const busWithLogger = new AsyncEventBus({ debug: true, logger: mockLogger });
278+
279+
// Test debug logging for subscription
280+
busWithLogger.on('test.event', vi.fn());
281+
expect(mockLogger.debug).toHaveBeenCalledWith('Subscribed to "test.event" (priority: 0)');
282+
283+
// Test debug logging for emit
284+
await busWithLogger.emit('test.event', { data: 'test' });
285+
expect(mockLogger.debug).toHaveBeenCalledWith(
286+
expect.objectContaining({ payload: { data: 'test' } }),
287+
'Emitting "test.event"'
288+
);
289+
290+
busWithLogger.removeAllListeners();
291+
});
292+
293+
it('should use kero logger for error handling', async () => {
294+
const mockLogger = {
295+
trace: vi.fn(),
296+
debug: vi.fn(),
297+
info: vi.fn(),
298+
success: vi.fn(),
299+
warn: vi.fn(),
300+
error: vi.fn(),
301+
fatal: vi.fn(),
302+
child: vi.fn(),
303+
startTimer: vi.fn(() => vi.fn()),
304+
isLevelEnabled: vi.fn(() => true),
305+
level: 'debug' as const,
306+
};
307+
308+
const busWithLogger = new AsyncEventBus({ logger: mockLogger });
309+
310+
const error = new Error('Test error');
311+
const errorHandler = vi.fn().mockRejectedValue(error);
312+
busWithLogger.on('error.event', errorHandler);
313+
314+
await busWithLogger.emit('error.event', {});
315+
await new Promise((resolve) => setTimeout(resolve, 10));
316+
317+
expect(mockLogger.error).toHaveBeenCalledWith(error, 'Handler error for "error.event"');
318+
319+
busWithLogger.removeAllListeners();
320+
});
321+
322+
it('should fallback to console when no logger provided', async () => {
323+
const consoleDebugSpy = vi.spyOn(console, 'debug').mockImplementation(() => {});
324+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
325+
326+
const busWithoutLogger = new AsyncEventBus({ debug: true });
327+
328+
busWithoutLogger.on('test.event', vi.fn());
329+
expect(consoleDebugSpy).toHaveBeenCalledWith(
330+
'[EventBus] Subscribed to "test.event" (priority: 0)'
331+
);
332+
333+
const errorHandler = vi.fn().mockRejectedValue(new Error('Test error'));
334+
busWithoutLogger.on('error.event', errorHandler);
335+
336+
await busWithoutLogger.emit('error.event', {});
337+
await new Promise((resolve) => setTimeout(resolve, 10));
338+
339+
expect(consoleErrorSpy).toHaveBeenCalled();
340+
341+
consoleDebugSpy.mockRestore();
342+
consoleErrorSpy.mockRestore();
343+
busWithoutLogger.removeAllListeners();
344+
});
345+
});
260346
});
261347

262348
describe('createTypedEventBus', () => {

packages/core/src/events/event-bus.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import { randomUUID } from 'node:crypto';
99
import { EventEmitter } from 'node:events';
10+
import type { Logger } from '@lytics/kero';
1011
import type {
1112
EmitOptions,
1213
EventBus,
@@ -37,6 +38,8 @@ export interface AsyncEventBusOptions {
3738
source?: string;
3839
/** Enable debug logging */
3940
debug?: boolean;
41+
/** Optional kero logger for structured logging */
42+
logger?: Logger;
4043
}
4144

4245
/**
@@ -52,7 +55,8 @@ export interface AsyncEventBusOptions {
5255
export class AsyncEventBus implements EventBus {
5356
private emitter: EventEmitter;
5457
private handlers: Map<string, HandlerEntry[]> = new Map();
55-
private options: Required<AsyncEventBusOptions>;
58+
private options: Required<Omit<AsyncEventBusOptions, 'logger'>>;
59+
private logger?: Logger;
5660

5761
constructor(options: AsyncEventBusOptions = {}) {
5862
this.options = {
@@ -61,6 +65,7 @@ export class AsyncEventBus implements EventBus {
6165
source: options.source ?? 'event-bus',
6266
debug: options.debug ?? false,
6367
};
68+
this.logger = options.logger;
6469

6570
this.emitter = new EventEmitter();
6671
this.emitter.setMaxListeners(this.options.maxListeners);
@@ -103,7 +108,11 @@ export class AsyncEventBus implements EventBus {
103108
(entry as HandlerEntry & { _wrapped: typeof wrappedHandler })._wrapped = wrappedHandler;
104109

105110
if (this.options.debug) {
106-
console.debug(`[EventBus] Subscribed to "${eventName}" (priority: ${entry.priority})`);
111+
if (this.logger) {
112+
this.logger.debug(`Subscribed to "${eventName}" (priority: ${entry.priority})`);
113+
} else {
114+
console.debug(`[EventBus] Subscribed to "${eventName}" (priority: ${entry.priority})`);
115+
}
107116
}
108117

109118
// Return unsubscribe function
@@ -135,7 +144,11 @@ export class AsyncEventBus implements EventBus {
135144
handlerList.splice(index, 1);
136145

137146
if (this.options.debug) {
138-
console.debug(`[EventBus] Unsubscribed from "${eventName}"`);
147+
if (this.logger) {
148+
this.logger.debug(`Unsubscribed from "${eventName}"`);
149+
} else {
150+
console.debug(`[EventBus] Unsubscribed from "${eventName}"`);
151+
}
139152
}
140153
}
141154
}
@@ -151,7 +164,11 @@ export class AsyncEventBus implements EventBus {
151164
};
152165

153166
if (this.options.debug) {
154-
console.debug(`[EventBus] Emitting "${eventName}"`, { payload, meta });
167+
if (this.logger) {
168+
this.logger.debug({ payload, meta }, `Emitting "${eventName}"`);
169+
} else {
170+
console.debug(`[EventBus] Emitting "${eventName}"`, { payload, meta });
171+
}
155172
}
156173

157174
if (options.waitForHandlers) {
@@ -211,7 +228,12 @@ export class AsyncEventBus implements EventBus {
211228
}
212229

213230
if (this.options.debug) {
214-
console.debug(`[EventBus] Removed all listeners${eventName ? ` for "${eventName}"` : ''}`);
231+
const message = `Removed all listeners${eventName ? ` for "${eventName}"` : ''}`;
232+
if (this.logger) {
233+
this.logger.debug(message);
234+
} else {
235+
console.debug(`[EventBus] ${message}`);
236+
}
215237
}
216238
}
217239

@@ -233,7 +255,15 @@ export class AsyncEventBus implements EventBus {
233255
// Handle promise rejection
234256
if (result instanceof Promise) {
235257
result.catch((error) => {
236-
console.error(`[EventBus] Handler error for "${eventName}":`, error);
258+
if (this.logger) {
259+
if (error instanceof Error) {
260+
this.logger.error(error, `Handler error for "${eventName}"`);
261+
} else {
262+
this.logger.error({ error }, `Handler error for "${eventName}"`);
263+
}
264+
} else {
265+
console.error(`[EventBus] Handler error for "${eventName}":`, error);
266+
}
237267
});
238268
}
239269
};
@@ -260,7 +290,15 @@ export class AsyncEventBus implements EventBus {
260290
try {
261291
await entry.handler(payload);
262292
} catch (error) {
263-
console.error(`[EventBus] Handler error for "${eventName}":`, error);
293+
if (this.logger) {
294+
if (error instanceof Error) {
295+
this.logger.error(error, `Handler error for "${eventName}"`);
296+
} else {
297+
this.logger.error({ error }, `Handler error for "${eventName}"`);
298+
}
299+
} else {
300+
console.error(`[EventBus] Handler error for "${eventName}":`, error);
301+
}
264302
}
265303
});
266304

0 commit comments

Comments
 (0)