.agents/skills/analyze-logs/SKILL.md
Read and analyze structured wide-event logs from the local .evlog/logs/ directory to debug errors, investigate performance issues, and understand application behavior.
Logs are written by evlog's file system drain as .jsonl files, organized by date.
Format detection: The drain supports two modes:
pretty: false): One compact JSON object per line. Parse line-by-line.pretty: true): Multi-line indented JSON per event. Parse by reading the entire file and splitting on top-level objects (e.g. JSON.parse('[' + content.replace(/\}\n\{/g, '},{') + ']')) or use a streaming JSON parser.Always check the first few bytes of the file to detect the format: if the second character is a newline or ", it's NDJSON; if it's a space or newline followed by spaces, it's pretty-printed.
Search order — check these locations relative to the project root:
.evlog/logs/ (default).evlog/logs/ inside app directories (monorepos: apps/*/.evlog/logs/)Use glob to find log files:
.evlog/logs/*.jsonl
*/.evlog/logs/*.jsonl
apps/*/.evlog/logs/*.jsonl
Files are named by date: 2026-03-14.jsonl. Start with the most recent file.
The file system drain may not be enabled. Guide the user to set it up:
import { createFsDrain } from 'evlog/fs'
// Nuxt / Nitro: server/plugins/evlog-drain.ts
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('evlog:drain', createFsDrain())
})
// Hono / Express / Elysia: pass in middleware options
app.use(evlog({ drain: createFsDrain() }))
// Fastify: pass in plugin options
await app.register(evlog, { drain: createFsDrain() })
// NestJS: pass in module options
EvlogModule.forRoot({ drain: createFsDrain() })
// Standalone: pass to initLogger
initLogger({ drain: createFsDrain() })
After setup, the user needs to trigger some requests to generate logs, then re-analyze.
Each line is a self-contained JSON object (wide event). Key fields:
| Field | Type | Description |
|---|---|---|
timestamp | string | ISO 8601 timestamp |
level | string | info, warn, error, debug |
service | string | Service name |
environment | string | development, production, etc. |
method | string | HTTP method (GET, POST, etc.) |
path | string | Request path (/api/checkout) |
status | number | HTTP response status code |
duration | string | Request duration ("234ms") |
requestId | string | Unique request identifier |
error | object | Error details: name, message, stack, statusCode, data |
error.data.why | string | Human-readable explanation of what went wrong |
error.data.fix | string | Suggested fix for the error |
source | string | client for browser logs, absent for server logs |
userAgent | object | Parsed browser/OS/device info |
All other fields are application-specific context added via log.set() (e.g. user, cart, payment).
Read the latest .jsonl file. Each line is one JSON event. Parse each line independently.
Filter based on the user's question:
"level":"error" or status >= 400pathduration (e.g. "706ms") and filter high values"source":"client"timestamp valuesFor each relevant event:
path, method, status, levelerror.message, error.data.why, and the stack traceerror.data.fix for suggested remediationFilter: level === "error"
Group by: error.message or path
Look for: recurring patterns, common failure modes
Filter: parse duration string, compare > threshold (e.g. 1000ms)
Sort by: duration descending
Look for: specific endpoints, time-of-day patterns
Filter: requestId === "the-request-id"
Result: single wide event with all context for that request
Group events by: path
Count: total events vs error events per path
Look for: endpoints with high error ratios
Split by: source === "client" vs no source field
Compare: error patterns between client and server
Look for: client errors that don't have corresponding server errors (network issues)
error.data.why and error.data.fix fields are evlog-specific structured error fields. When present, they provide the most actionable information."706ms"). Parse the numeric part for comparisons."source":"client" originated from browser-side logging and were sent to the server via the transport endpoint..gitignore'd automatically — they exist only on the local machine or server where the app runs.