| name | walkeros-using-logger |
| description | Use when working with walkerOS sources/destinations to understand standard logging patterns, replace console.log, or add logging to external API calls. Covers DRY principles, when to log, and migration patterns. |
Using the walkerOS Logger
Overview
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.
Logger Access
In Sources
export const sourceFetch = async (
config: PartialConfig,
env: Types['env'],
): Promise<FetchSource> => {
env.logger.info('Server listening on port 3000');
};
In Destinations
export const destinationDataManager: DestinationInterface = {
async init({ config, env, logger }) {
logger.debug('Auth client created');
},
async push(event, { config, data, env, logger }) {
logger.debug('API response', { status: 200 });
},
};
Note: You don't need to create or configure the logger—it's provided
automatically with proper scoping.
Logger Methods
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;
}
Log Levels
- ERROR (0): Always visible—use for fatal/critical errors only
- WARN (1): Degraded state, config issues, transient failures
- INFO (2): High-level operations (server startup, event processed)
- DEBUG (3): Low-level details (API calls, transformations)
Default: ERROR only (must configure to see WARN/INFO/DEBUG)
Context Parameter
All methods accept optional structured context:
logger.debug('Sending to API', {
endpoint: '/events',
method: 'POST',
eventCount: 5,
});
When to Log (and When NOT to)
❌ DON'T Log These (Collector Handles)
- Init status: "Initializing...", "Init started", "Init complete"
- Push status: "Processing event...", "Event received"
- Generic status: "Settings validated", "Config loaded"
- Duplicate scoping: Don't add source/dest name (already in scope)
Why: Collector can log these automatically since it calls init/push and has
scoped logger.
✅ DO Log These (Meaningful Operations)
- External API calls: Before/after with request/response details
- Auth operations: Token refresh, client creation/failures
- Transformations: Complex mappings or data processing
- Validation errors: Always use
logger.throw for fatal errors
Usage Patterns
Pattern 1: Validation Errors (Always Use logger.throw)
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:
- Logs the error at ERROR level (always visible)
- Throws Error automatically (no separate throw needed)
- Collector catches and handles gracefully
- Never returns (TypeScript type:
never)
Pattern 2: External API Calls
async push(event, { config, logger }) {
const endpoint = 'https://api.vendor.com/events';
logger.debug('Calling API', {
endpoint,
method: 'POST',
eventId: event.id,
});
const response = await fetch(endpoint, {
method: 'POST',
body: JSON.stringify(event),
});
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}`);
}
}
Pattern 3: Auth Operations
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'}`,
);
}
}
Pattern 4: Server Startup (Sources)
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`,
);
});
}
Anti-Patterns (What NOT to Do)
❌ BAD: Verbose Init Logging
async init({ logger }) {
logger.debug('Data Manager init started');
logger.info('Data Manager initializing...');
logger.debug('Settings validated');
const authClient = await createAuthClient();
logger.debug('Auth client created');
logger.info('Data Manager ready');
}
Problem: Collector knows when init is called. Only log meaningful operations
(auth client creation).
❌ BAD: Redundant Push Logging
async push(event, { logger }) {
logger.debug('Processing event', {
name: event.name,
id: event.id,
});
logger.info('Event processed');
}
Problem: Collector knows when push is called and can log automatically.
❌ BAD: Using console.log
console.log('Processing event:', event.name);
logger.debug('API call', { endpoint });
Migration Checklist
When updating a source/destination to use the logger:
Testing with Logger
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');
});
Log Level Configuration
Default log level is ERROR. To see INFO/DEBUG logs:
import { startFlow } from '@walkeros/collector';
const { elb } = await startFlow({
logger: {
level: 'DEBUG',
},
destinations: {
},
});
Levels:
'ERROR': Only errors (default)
'WARN': Errors + warnings
'INFO': Errors + warnings + info
'DEBUG': Everything
Related Skills
Key Files:
Best Practice Examples:
Needs Improvement: