From walkeros
Guides walkerOS logger usage in sources/destinations: access methods, levels, structured context, best practices for external API calls, avoiding redundant collector logs.
npx claudepluginhub elbwalker/walkerosThis skill uses the workspace's default tool permissions.
The logger is walkerOS's standard logging system, available in all sources and
Creates isolated Git worktrees for feature branches with prioritized directory selection, gitignore safety checks, auto project setup for Node/Python/Rust/Go, and baseline verification.
Executes implementation plans in current session by dispatching fresh subagents per independent task, with two-stage reviews: spec compliance then code quality.
Dispatches parallel agents to independently tackle 2+ tasks like separate test failures or subsystems without shared state or dependencies.
The logger is walkerOS's standard logging system, available in all sources and
destinations via env.logger or logger parameter. It provides scoped,
level-aware logging that replaces console.log.
Core principle: Don't log what the collector already logs. Only log meaningful operations like external API calls, transformations, and validation errors.
export const sourceFetch = async (
config: PartialConfig,
env: Types['env'], // env.logger is available here
): Promise<FetchSource> => {
// Logger is scoped automatically by collector: [type:sourceId]
env.logger.info('Server listening on port 3000');
};
export const destinationDataManager: DestinationInterface = {
async init({ config, env, logger }) {
// logger parameter is scoped automatically: [datamanager]
logger.debug('Auth client created');
},
async push(event, { config, data, env, logger }) {
// logger parameter is scoped: [datamanager]
logger.debug('API response', { status: 200 });
},
};
Note: You don't need to create or configure the logger—it's provided automatically with proper scoping.
interface Logger.Instance {
error(message: string | Error, context?: unknown | Error): void;
warn(message: string | Error, context?: unknown | Error): void;
info(message: string | Error, context?: unknown | Error): void;
debug(message: string | Error, context?: unknown | Error): void;
throw(message: string | Error, context?: unknown): never;
json(data: unknown): void;
scope(name: string): Logger.Instance;
}
Default: ERROR only (must configure to see WARN/INFO/DEBUG)
All methods accept optional structured context:
logger.debug('Sending to API', {
endpoint: '/events',
method: 'POST',
eventCount: 5,
});
// Output: DEBUG [datamanager] Sending to API { endpoint: '/events', method: 'POST', eventCount: 5 }
Why: Collector can log these automatically since it calls init/push and has scoped logger.
logger.throw for fatal errors// ✅ GOOD - Fatal configuration error
async init({ config, logger }) {
const { apiKey, projectId } = config.settings || {};
if (!apiKey) {
logger.throw('Config settings apiKey missing');
}
if (!projectId) {
logger.throw('Config settings projectId missing');
}
}
Why logger.throw:
never)// ✅ GOOD - Log external calls with context
async push(event, { config, logger }) {
const endpoint = 'https://api.vendor.com/events';
// Log before call
logger.debug('Calling API', {
endpoint,
method: 'POST',
eventId: event.id,
});
const response = await fetch(endpoint, {
method: 'POST',
body: JSON.stringify(event),
});
// Log after call
logger.debug('API response', {
status: response.status,
ok: response.ok,
});
if (!response.ok) {
const errorText = await response.text();
logger.throw(`API error (${response.status}): ${errorText}`);
}
}
// ✅ GOOD - Log auth client creation
async init({ config, logger }) {
try {
const authClient = await createAuthClient(config.settings);
logger.debug('Auth client created');
return {
env: { authClient },
};
} catch (error) {
logger.throw(
`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
}
}
// ✅ GOOD - Log server listening (high-level info)
if (settings.port !== undefined) {
server = app.listen(settings.port, () => {
env.logger.info(
`Express source listening on port ${settings.port}\n` +
` POST ${settings.path} - Event collection (JSON body)\n` +
` GET ${settings.path} - Pixel tracking (query params)\n` +
` OPTIONS ${settings.path} - CORS preflight`,
);
});
}
async init({ logger }) {
logger.debug('Data Manager init started'); // Redundant
logger.info('Data Manager initializing...'); // Redundant
logger.debug('Settings validated'); // Redundant
const authClient = await createAuthClient();
logger.debug('Auth client created'); // OK
logger.info('Data Manager ready'); // Redundant
}
Problem: Collector knows when init is called. Only log meaningful operations (auth client creation).
async push(event, { logger }) {
logger.debug('Processing event', {
// Redundant
name: event.name,
id: event.id,
});
// Do work...
logger.info('Event processed'); // Redundant
}
Problem: Collector knows when push is called and can log automatically.
// ❌ NEVER use console.log in sources/destinations
console.log('Processing event:', event.name);
// ✅ Use logger instead
logger.debug('API call', { endpoint });
When updating a source/destination to use the logger:
console.log, console.warn, console.error statementslogger.throw for all validation errors (apiKey missing, etc.)logger.debug before external API calls (with endpoint, method)logger.debug after external API calls (with response status)logger.debug for auth operations (client creation, token refresh)Use createMockLogger from @walkeros/core in tests:
import { createMockLogger } from '@walkeros/core';
test('throws on missing apiKey', () => {
const logger = createMockLogger();
expect(() => {
destination.init({ config: {}, logger });
}).toThrow('Config settings apiKey missing');
expect(logger.throw).toHaveBeenCalledWith('Config settings apiKey missing');
});
Default log level is ERROR. To see INFO/DEBUG logs:
import { startFlow } from '@walkeros/collector';
const { elb } = await startFlow({
logger: {
level: 'DEBUG', // Show all logs
},
destinations: {
/* ... */
},
});
Levels:
'ERROR': Only errors (default)'WARN': Errors + warnings'INFO': Errors + warnings + info'DEBUG': EverythingKey Files:
Best Practice Examples:
Needs Improvement: