skills/walkeros-using-logger/SKILL.md
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.
npx skillsauth add elbwalker/walkeros walkeros-using-loggerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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:
testing
Use when wiring `@walkeros/transformer-ga4` into a server flow, overriding default GA4 event mappings, dropping events, adding custom event keys, or troubleshooting GA4 Measurement Protocol decoding. Covers the `before`-chain wiring contract, configuration recipes, and per-field patching with extend/remove.
development
Use when adding read-through caching to a walkerOS store, memoizing a slow API/Sheets backing, composing multi-tier cache chains, or deduplicating concurrent store reads. Covers recipes, TTL choice, error policy, and observability counters.
development
Use when writing or updating walkerOS documentation - README, website docs, or skills. Covers quality standards, example validation, and DRY patterns.
testing
Use when writing, simulating, validating, or testing with walkerOS step examples. Covers the complete lifecycle from authoring examples to CI integration.