One of the most common issues I see in Claude Code-generated code is console.log in production handlers. Here's the complete setup to prevent it and get proper structured logging instead.
CLAUDE.md: The Foundation
## Logging Rules
### Logger
- Use logger.ts (src/lib/logger.ts) — never console.log/console.error
- Logger is structured JSON (pino or winston)
- Include request context: userId, requestId, traceId where available
### Log Levels
- error: application errors that need investigation (5xx, DB errors)
- warn: unexpected but recoverable situations (retries, fallbacks)
- info: important business events (user registered, order placed)
- debug: development-only details (never in production)
### What to LOG
- Request entry/exit (timing, status)
- Business events (user.created, payment.completed)
- External service calls (start, success, failure, duration)
- Errors with context (userId, requestId, error code)
### What NOT to LOG
- Passwords, tokens, API keys
- PII: email, name, phone, SSN, credit card numbers
- Full request bodies (may contain sensitive data)
- Raw database queries with user data
### Format
Every log must include: { timestamp, level, message, service, ...context }
Logger Setup with Pino
Generate a structured logger module using pino.
Requirements:
- JSON output in production, pretty-print in development
- Default context: { service: 'my-app', version: process.env.npm_package_version }
- redact: ['password', 'token', 'authorization', 'cookie', '*.password', '*.token']
- Child logger support: logger.child({ userId, requestId })
- Never expose as global — inject as dependency
Location: src/lib/logger.ts
Request Logging Middleware
Generate Express request logging middleware.
Requirements:
- Log at request start: method, url, requestId (generate if missing)
- Log at request end: status code, duration (ms)
- Attach logger child with requestId to req.log
- Do NOT log request body (security risk)
- Exclude health check endpoints: /health, /ping
Location: src/middleware/requestLogger.ts
Pre-commit Hook: Block console.log
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [{
"type": "command",
"command": "python .claude/hooks/no_console_log.py"
}]
}
]
}
}
# .claude/hooks/no_console_log.py
import json, sys, re
data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")
# Only check production source files
if not fp or "test" in fp or "spec" in fp or "scripts" in fp:
sys.exit(0)
if not fp.endswith((".ts", ".js", ".py")):
sys.exit(0)
# Check for console.log (allow console.error as it might be intentional in scripts)
if re.search(r'console\.log\(', content):
# Allow if there's an explicit override comment
if '// allow-console' not in content:
print("[LOG] console.log detected in source file. Use logger.ts instead.", file=sys.stderr)
sys.exit(2) # Block
sys.exit(0)
Adding Request Context
Generate a context propagation utility that makes request context
(requestId, userId) available throughout the call chain without
passing it as a function argument.
Use AsyncLocalStorage (Node.js built-in).
Context should be automatically available in logger.ts.
Location: src/lib/context.ts
PII Scrubbing
Generate a log sanitizer for user data objects.
Requirements:
- Remove: password, passwordHash, token, refreshToken, apiKey
- Mask partially: email → user@***.com, phone → ***-***-1234
- Never mutate the original object
- Apply automatically in logger.ts before writing any log
Location: src/lib/logSanitizer.ts
Testing That Logging Works
Generate tests that verify:
1. Sensitive fields are redacted (password, token)
2. PII is masked in user objects
3. request logger includes requestId
4. console.log is NOT called in production code (verify logger.ts is used)
Alerting on Errors
Generate a Pino transport that sends error-level logs to Slack.
Requirements:
- Only error level and above
- Include: message, error.code, error.stack (first 3 lines), requestId
- Debounce: max 1 message/minute per error type
- Never fail the main request if Slack send fails
Location: src/lib/slackTransport.ts
Code Review Pack (¥980) includes /code-review which flags console.log and missing log sanitization.
Myouga (@myougatheaxo) — Security-focused Claude Code engineer.
Top comments (0)