OC-201c · Module 2
Structured Logging
4 min read
The difference between a log and useful information is structure. An unstructured log says "Error: request failed." A structured log says "ERROR | 2026-02-22T03:47:12Z | module:weather | action:api_call | provider:openweather | status:429 | retry:2/3 | latency_ms:1240." The second one tells you what failed, when, where, why, how many retries were attempted, and how long it took. When you are debugging an issue at 3 AM, the difference between these two log entries is the difference between diagnosing in five minutes and guessing for an hour.
Every log entry should contain five fields. Timestamp — when did this happen, in ISO 8601 format with timezone. Level — is this an error, warning, info, or debug message. Module — which OpenClaw module generated this entry. Action — what was the module trying to do. Result — what happened (success, failure, retry, timeout). These five fields make every log entry searchable, filterable, and correlatable. When something breaks, you filter by module, sort by timestamp, and read the story of what happened.
interface LogEntry {
timestamp: string;
level: 'error' | 'warn' | 'info' | 'debug';
module: string;
action: string;
result: string;
meta?: Record<string, unknown>;
}
function log(entry: LogEntry) {
const line = [
entry.level.toUpperCase().padEnd(5),
entry.timestamp,
`module:${entry.module}`,
`action:${entry.action}`,
`result:${entry.result}`,
entry.meta ? JSON.stringify(entry.meta) : '',
].join(' | ');
console.log(line);
appendToFile('openclaw.log', line + '\n');
}
// Usage
log({
timestamp: new Date().toISOString(),
level: 'warn',
module: 'weather',
action: 'api_call',
result: 'rate_limited',
meta: { provider: 'openweather', retry: '2/3', latency_ms: 1240 },
});