Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion core/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ export {GOOGLE_SEARCH, GoogleSearchTool} from './tools/google_search_tool.js';
export {LongRunningFunctionTool} from './tools/long_running_tool.js';
export {ToolConfirmation} from './tools/tool_confirmation.js';
export {ToolContext} from './tools/tool_context.js';
export {LogLevel, setLogLevel} from './utils/logger.js';
export {LogLevel, getLogger, setLogLevel, setLogger} from './utils/logger.js';
export type {Logger} from './utils/logger.js';
export {isGemini2OrAbove} from './utils/model_name.js';
export {zodObjectToSchema} from './utils/simple_zod_to_json.js';
export {GoogleLLMVariant} from './utils/variant_utils.js';
Expand Down
52 changes: 51 additions & 1 deletion core/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ class SimpleLogger implements Logger {
}
}

/**
* A no-op logger that discards all log messages.
*/
class NoOpLogger implements Logger {
log(_level: LogLevel, ..._args: unknown[]): void {}
debug(..._args: unknown[]): void {}
info(..._args: unknown[]): void {}
warn(..._args: unknown[]): void {}
error(..._args: unknown[]): void {}
}

const LOG_LEVEL_STR: Record<LogLevel, string> = {
[LogLevel.DEBUG]: 'DEBUG',
[LogLevel.INFO]: 'INFO',
Expand All @@ -116,7 +127,46 @@ function getColoredPrefix(level: LogLevel): string {
return `${CONSOLE_COLOR_MAP[level]}[ADK ${LOG_LEVEL_STR[level]}]:${RESET_COLOR}`;
}

let currentLogger: Logger = new SimpleLogger();

/**
* Sets a custom logger for ADK, or null to disable logging.
*/
export function setLogger(customLogger: Logger | null): void {
currentLogger = customLogger ?? new NoOpLogger();
}

/**
* Gets the current logger instance.
*/
export function getLogger(): Logger {
return currentLogger;
}

/**
* Resets the logger to the default SimpleLogger.
*/
export function resetLogger(): void {
currentLogger = new SimpleLogger();
}

/**
* The logger instance for ADK.
*/
export const logger = new SimpleLogger();
export const logger: Logger = {
log(level: LogLevel, ...args: unknown[]): void {
currentLogger.log(level, ...args);
},
debug(...args: unknown[]): void {
currentLogger.debug(...args);
},
info(...args: unknown[]): void {
currentLogger.info(...args);
},
warn(...args: unknown[]): void {
currentLogger.warn(...args);
},
error(...args: unknown[]): void {
currentLogger.error(...args);
},
};
149 changes: 149 additions & 0 deletions core/test/utils/logger_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {getLogger, Logger, LogLevel, setLogger, setLogLevel} from '@google/adk';

import {resetLogger} from '../../src/utils/logger.js';

describe('setLogger', () => {
beforeEach(() => {
resetLogger();
setLogLevel(LogLevel.DEBUG);
});

afterEach(() => {
resetLogger();
});

describe('custom logger', () => {
it('routes log messages to custom logger', () => {
const messages: Array<{level: string; args: unknown[]}> = [];
const customLogger: Logger = {
log: (level, ...args) => messages.push({level: LogLevel[level], args}),
debug: (...args) => messages.push({level: 'DEBUG', args}),
info: (...args) => messages.push({level: 'INFO', args}),
warn: (...args) => messages.push({level: 'WARN', args}),
error: (...args) => messages.push({level: 'ERROR', args}),
};

setLogger(customLogger);
const logger = getLogger();

logger.info('test message', 123);

expect(messages).toHaveLength(1);
expect(messages[0].level).toBe('INFO');
expect(messages[0].args).toEqual(['test message', 123]);
});

it('calls correct method for each log level', () => {
const calls: string[] = [];
const customLogger: Logger = {
log: () => calls.push('log'),
debug: () => calls.push('debug'),
info: () => calls.push('info'),
warn: () => calls.push('warn'),
error: () => calls.push('error'),
};

setLogger(customLogger);
const logger = getLogger();

logger.debug('debug');
logger.info('info');
logger.warn('warn');
logger.error('error');

expect(calls).toEqual(['debug', 'info', 'warn', 'error']);
});
});

describe('null logger (disable logging)', () => {
it('disables all logging when null is passed', () => {
const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {});

setLogger(null);
const logger = getLogger();

logger.info('this should not appear');

expect(consoleSpy).not.toHaveBeenCalled();
consoleSpy.mockRestore();
});

it('handles all log levels silently', () => {
setLogger(null);
const logger = getLogger();

expect(() => {
logger.debug('debug');
logger.info('info');
logger.warn('warn');
logger.error('error');
logger.log(LogLevel.INFO, 'log');
}).not.toThrow();
});
});

describe('backward compatibility', () => {
it('deprecated logger export still works with custom logger', async () => {
const {logger} = await import('../../src/utils/logger.js');

const messages: string[] = [];
const customLogger: Logger = {
log: () => {},
debug: () => {},
info: (...args) => messages.push(String(args[0])),
warn: () => {},
error: () => {},
};

setLogger(customLogger);

logger.info('backward compatible');

expect(messages).toContain('backward compatible');
});
});

describe('getLogger', () => {
it('returns the current logger instance', () => {
const customLogger: Logger = {
log: () => {},
debug: () => {},
info: () => {},
warn: () => {},
error: () => {},
};

setLogger(customLogger);

const logger = getLogger();
expect(logger).toBeDefined();
});

it('returns default logger initially', () => {
const logger = getLogger();
expect(logger).toBeDefined();
expect(typeof logger.info).toBe('function');
});
});

describe('resetLogger', () => {
it('restores the default logger', () => {
const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {});

setLogger(null);
resetLogger();

const logger = getLogger();
logger.info('after reset');

expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
});
Loading