Debug logs: what happened. Security logs: who, why, whether malicious.
Security-useful logs
- Who - user, API key, IP
- What - action (login, download, permission change)
- Where - service, endpoint
- When - UTC, milliseconds
- Outcome - success/failure
- Context - request ID, tenant
{
"timestamp": "2025-03-15T14:23:01.042Z",
"event": "auth.login.success",
"actor": { "userId": "u_8f3k2", "ip": "203.0.113.45" },
"service": "auth-api",
"traceId": "abc-123-def"
}
Event naming
Consistent, hierarchical: auth.login.failure, authz.permission.changed, resource.file.uploaded.
What to log
Always: Auth events, authz events, data access, config changes, rate limit triggers.
Never: Passwords, tokens, API keys, full card numbers, session token values.
Detection rules
name: Brute force
query: auth.login.failure | count by actor.ip | where count > 10
timeframe: 5m
Patterns:
- Threshold: Failed logins > 5 in 2m
- Anomaly: Login from unusual country
- Sequence: Failed login → success → permission change
- Absence: No backup in 24h
Map to MITRE ATT&CK: T1110 (Brute Force), T1078 (Valid Accounts), T1098 (Account Manipulation).
Implementation
// Pino
const logger = pino({
redact: ['req.headers.authorization']
});
logger.info({ event: 'auth.login.success', actor: { userId, ip } });
Pipeline
App → Shipper → Aggregator → Detection → Alerts
Use Sigma rules for vendor-agnostic detection.
The takeaway
Structured logs. Consistent event names. Actor context. Never log creds. Map detection rules to ATT&CK. Test rules like code.
