| name | improve-logging |
| description | Audit and improve logging quality across a codebase. Use when the user asks to "improve logging", "fix log levels", "add logging", "review our logs", "make logging consistent", "we have bad logging", or anything about log messages being unclear, missing, or at the wrong severity. Produces a prioritized list of recommendations — does not edit files directly. |
| tools | Bash, PowerShell, Read, Glob, Grep, TaskCreate, TaskUpdate |
| model | haiku |
| metadata | {"version":1} |
Improve Logging
Audit a codebase's logging and produce a prioritized list of recommendations covering three areas: missing log statements, incorrect severity levels, and poor message quality. Also enforces (or proposes) a consistent logging pattern across the whole application.
This skill is read-only. It produces a recommendation report. It does not edit any file.
Task Tracking
Before doing any work, call TaskCreate for each phase below. Call TaskUpdate (status in_progress) when you begin a phase and TaskUpdate (status completed) when you finish it.
- Detect languages and logging frameworks
- Establish logging pattern
- Scan for issues
- Write report
Phase 1: Detect Languages and Logging Frameworks
find . -maxdepth 4 -not -path "*/.git/*" -not -path "*/node_modules/*" \
-not -path "*/vendor/*" -not -path "*/.venv/*" \
\( -name "*.py" -o -name "*.java" -o -name "*.kt" -o -name "*.ts" \
-o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.go" \
-o -name "*.rs" -o -name "*.cs" -o -name "*.rb" \) \
| sed 's/.*\.//' | sort | uniq -c | sort -rn | head -10
Detect the logging framework in use per language:
| Language | Frameworks to detect |
|---|
| Python | import logging, from loguru, import structlog |
| Java/Kotlin | import org.slf4j, import org.apache.logging, import io.github.oshai |
| TypeScript/JS | import winston, import pino, console.log/warn/error |
| Go | import "log", "go.uber.org/zap", "github.com/sirupsen/logrus" |
| C# | ILogger, Microsoft.Extensions.Logging, Serilog, NLog |
| Ruby | Rails.logger, Logger.new |
grep -rn "import logging\|from loguru\|import structlog" --include="*.py" . | head -5
grep -rn "import org.slf4j\|import org.apache.logging\|LoggerFactory\|KotlinLogging" \
--include="*.java" --include="*.kt" . | head -5
grep -rn "require.*winston\|require.*pino\|import.*winston\|import.*pino\|console\.\(log\|warn\|error\|info\|debug\)" \
--include="*.ts" --include="*.js" --include="*.tsx" --include="*.jsx" . | head -5
grep -rn '"log"\|"go.uber.org/zap"\|"github.com/sirupsen/logrus"\|"golang.org/x/exp/slog"' \
--include="*.go" . | head -5
Phase 2: Establish the Logging Pattern
Sample up to 20 existing log call sites to understand the current pattern (or absence of one):
grep -rn "logger\.\|logging\." --include="*.py" . | head -20
grep -rn "log\.\(info\|debug\|warn\|error\|trace\)" --include="*.java" --include="*.kt" . | head -20
grep -rn "log\.\|logger\.\|zap\.\|logrus\." --include="*.go" . | head -20
Read 3–5 representative files where logging is present to understand:
- Message style — verb-first ("Processing request"), noun-first ("Request received"), or mixed?
- Context fields — are structured fields used (e.g.
log.Info("done", "user_id", id))? Which fields appear consistently?
- Logger instantiation — module-level singleton, passed via context, or created per function?
- Level discipline — is DEBUG used for fine-grained tracing? Is INFO for business events?
Required fields
Every log call must include at minimum:
| Field | Purpose | When required |
|---|
| Event name | What happened (the message) | Always |
request_id / trace_id | Correlate logs across a request | Any code that handles an incoming request (HTTP, gRPC, queue consumer) |
user_id | Who triggered the action | Any user-facing operation where a user ID is in scope |
error / err | The error value or message | Any log at WARN or ERROR level |
If the codebase already uses these fields consistently, document them as required. If not, include them in the proposed pattern. Flag any log call that is in request-scoped code but omits request_id / trace_id as an inconsistency (Phase 3d).
Pattern decision
- If a clear pattern exists (consistent message style, consistent fields, consistent logger instantiation): document it and treat any deviation as an inconsistency finding.
- If no clear pattern exists (< 5 log calls in the whole codebase, or wildly inconsistent): propose a pattern appropriate for the language and framework. State the proposed pattern at the top of the report so the user can accept or adjust it before acting on the recommendations.
Recommended patterns by framework:
Python (structlog or logging):
log = structlog.get_logger(__name__)
log.info("order.placed", order_id=order_id, user_id=user_id)
Go (zap or slog):
logger.Info("order placed", zap.String("order_id", orderID), zap.String("user_id", userID))
Java/Kotlin (SLF4J):
log.info("Order placed: orderId={}, userId={}", orderId, userId);
TypeScript/JS (pino or winston):
logger.info({ orderId, userId }, 'Order placed')
Phase 3: Scan for Issues
3a. Missing log statements
Look for code paths that almost certainly should be logged but aren't. Focus on high-value locations:
Error handlers with no log:
grep -rn "except " --include="*.py" -A 3 . | grep -v "log\|logger\|print" | head -20
grep -rn "catch\s*(" --include="*.java" -A 3 . | grep -v "log\.\|LOG\.\|logger\." | head -20
grep -rn "if err != nil" --include="*.go" -A 2 . | grep -v "log\.\|logger\." | head -20
grep -rn "catch\s*(" --include="*.ts" --include="*.js" -A 3 . | grep -v "console\.\|logger\.\|log\." | head -20
Important operations with no surrounding log — use these grep patterns to find high-value functions, then read them to check whether they log:
grep -rn "def \(create\|update\|delete\|remove\|send\|publish\|process\|handle\|execute\|run\|dispatch\)_" \
--include="*.py" .
grep -rn "func \(create\|update\|delete\|remove\|send\|publish\|process\|handle\|execute\|run\|dispatch\)[A-Z]" \
--include="*.go" .
grep -rn "^\s*\(public\|private\|protected\).*\(create\|update\|delete\|send\|process\|handle\)[A-Z]" \
--include="*.java" --include="*.kt" .
grep -rn "^\(export \)\?\(async \)\?function \(create\|update\|delete\|send\|process\|handle\)" \
--include="*.ts" --include="*.js" .
Also check these locations regardless of name:
- HTTP handlers / route controllers (any file in
handlers/, controllers/, routes/, views/)
- Background jobs / scheduled tasks (
jobs/, workers/, tasks/, cron/)
- Startup and shutdown sequences (main entry point,
init(), signal handlers)
Flag an operation as missing a log if:
- It handles an error path with no log
- It completes a significant state change (create, update, delete) with no log
- It calls an external service with no log on failure
- It is a re-raise (exception caught and immediately re-thrown) with no log before the re-raise — the caller may never log it either, so this is the last chance
3b. Incorrect severity levels
For each log call found, evaluate whether the level matches the situation. Apply these rules:
| Level | When to use | Common mistakes |
|---|
DEBUG | Implementation details useful during development; disabled in production | Using DEBUG for events that operators need to see in prod |
INFO | Normal, expected application flow; key business events | Logging every row in a loop as INFO; using INFO for recoverable errors |
WARN | Something unexpected happened but the application recovered; a condition that may cause problems later | Using WARN for actual errors; using WARN for routine things |
ERROR | An operation failed and requires attention; may affect a user or data | Swallowing errors silently; logging errors as WARN |
FATAL / CRITICAL | Application cannot continue; imminent shutdown | Overusing FATAL for recoverable errors |
Exception handling rules — apply these precisely when reading error-handling paths:
| Situation | Correct level | Rationale |
|---|
| Exception caught and re-raised | ERROR | The operation failed; the caller sees an exception. Log here so there is at least one record even if callers don't. |
| Exception caught and handled (request still succeeds) | WARN | Something unexpected happened but was recovered from. |
| Exception caught and swallowed (no re-raise, no response change) | Flag as missing log | Silent swallowing hides failures entirely. Always log before swallowing. |
| Expected, routine condition (e.g. cache miss, 404 lookup) | DEBUG or no log | Not an error; high-volume events at WARN/ERROR pollute alerting. |
HTTP status code → log level mapping — when code sets or returns an HTTP status, the log level should follow:
| Status range | Log level | Rationale |
|---|
| 2xx | INFO (or no log if framework logs requests) | Success — normal flow |
| 3xx | DEBUG | Redirect — expected, low value |
| 4xx (client error) | WARN | Client made a bad request; server is fine |
| 5xx (server error) | ERROR | Server failed; needs attention |
grep -rn "status_code\s*=\s*[45][0-9][0-9]\|return.*[45][0-9][0-9]" --include="*.py" -A 3 . | \
grep "logger\.\(debug\|info\|warning\)"
grep -rn "res\.status(5[0-9][0-9])" --include="*.ts" --include="*.js" -A 5 . | \
grep "console\.warn\|logger\.warn"
grep -rn "res\.status(4[0-9][0-9])" --include="*.ts" --include="*.js" -A 5 . | \
grep "console\.error\|logger\.error"
Also scan for:
grep -rn "logger\.info\|logging\.info\|logger\.warning" --include="*.py" . | grep -i "error\|fail\|exception\|crash"
grep -rn "\.debug(" --include="*.java" --include="*.kt" . | grep -i "exception\|error\|fail"
grep -rn "console\.log" --include="*.ts" --include="*.js" . | grep -i "error\|fail\|exception"
Read through error-handling paths in representative files — grep catches obvious mismatches, but reading reveals the subtler ones (e.g. a re-raise with no preceding log).
3c. Logs to remove
Scan for log calls that should be deleted entirely rather than improved. Flag all matches — these are the highest-priority recommendations because sensitive data in logs is a security incident waiting to happen.
Sensitive data:
grep -rn "password\|passwd\|secret\|api_key\|apikey\|token\|credential\|ssn\|credit_card\|cvv\|private_key" \
--include="*.py" --include="*.java" --include="*.ts" --include="*.js" --include="*.go" --include="*.rb" . \
| grep -i "log\|logger\|print\|console" | head -30
Flag any log call that passes a sensitive field as a parameter or includes it in the message string. Include a clear explanation of the security risk (e.g. "API keys in logs are stored in plain text and accessible to anyone with log access, including log aggregation services").
Debug artifacts: print() in Python, console.log() / console.debug() in JS/TS, fmt.Println() in Go, System.out.println() in Java — these are almost never intentional in production code. Flag all of them.
Log spam: log calls inside tight loops (for/while) that fire on every iteration. A single INFO log per item in a large batch creates millions of log entries. Suggest replacing with a summary log after the loop (e.g. "Processed N items in Xms").
Redundant logs: multiple log calls at the same level for the same event within a few lines, where one clearly subsumes the other.
3d. Poor message quality
A good log message answers: what happened, in what context, and why does it matter?
Flag messages that:
- Are too vague to act on:
"Error", "Something went wrong", "Failed", "null", "done", "ok"
- Include no context when context is available:
"User not found" when the user ID is in scope
- Are redundant with the exception type alone:
log.error("NullPointerException", e) adds nothing
- Use inconsistent casing or tense compared to the established pattern
- Expose sensitive data: passwords, tokens, PII in log messages
grep -rn "logger\.\w\+(['\"].\{1,20\}['\"])" --include="*.py" . | \
grep -i '"error"\|"fail"\|"exception"\|"done"\|"ok"\|"success"\|"null"'
grep -rn 'log\.\w\+("\w\{1,10\}")' --include="*.java" . | head -20
For files with message quality issues, read the surrounding code to suggest a better message that includes the relevant context variables.
3e. Inconsistency with the established pattern
Compare each log call against the pattern established in Phase 2:
- Logger instantiated differently than the norm
- Message casing or verb tense differs
- Structured fields used in some places but not others
- Required fields (from Phase 2) are missing — e.g. a log inside a request handler that omits
request_id, or a WARN/ERROR log that omits error/err
Phase 4: Output the Report
Group all findings into a single report. Order: Remove → Missing logs → Wrong level → Poor messages → Inconsistencies.
Put Remove first because sensitive data in logs is an immediate security risk — it must be addressed before any other improvement.
Within each section, order by file. For each finding include the file path, line number, the current code (if any), and a concrete suggested improvement.
Use this format:
## Logging Improvement Report
### Established Pattern
[State the detected or proposed pattern here. If proposed, prefix with "⚡ No consistent pattern found — proposing:"]
Example:
logger.info("order.placed", order_id=order_id, amount=amount)
# noun.verb message, structured keyword args, module-level logger
---
### 1. Remove (N findings)
**src/auth/login.py:14** — sensitive field in log
Current: `logger.info("login success", extra={"user_id": user.id, "password": password})`
Remove: the `password` field from the extra dict
Why: passwords in logs are stored in plain text and accessible to everyone with log access, including log aggregation services
**src/debug/util.py:8** — debug artifact
Current: `print(f"Login attempt for {username}")`
Action: Replace with `logger.info("auth.login_attempt", username=username)` or remove
Why: print() bypasses the logging framework and cannot be filtered or silenced in production
---
### 2. Missing Log Statements (N findings)
**src/payments/processor.py:88** — error path with no log
Context: `except StripeError as e:`
Add: `logger.error("payment.charge_failed", order_id=order_id, error=str(e))`
Why: stripe failures are silent — operators have no visibility when charges fail
**src/jobs/email_sender.py:34** — external call with no failure log
Context: `response = smtp.send(msg)`
Add (on failure): `logger.error("email.send_failed", recipient=msg.to, error=str(e))`
---
### 3. Incorrect Log Levels (N findings)
**src/auth/login.py:52** — ERROR logged as WARNING
Current: `logger.warning("Login failed for user %s", user_id)`
Suggest: `logger.error("auth.login_failed", user_id=user_id)`
Why: a failed login attempt is an error worth alerting on, not just a warning
**src/cache/client.py:19** — DEBUG in a tight loop producing log spam
Current: `logger.debug("Cache miss for key %s", key)` (called on every request)
Suggest: Remove or gate behind a feature flag; emit a summary metric instead
---
### 4. Poor Message Quality (N findings)
**src/api/users.py:101**
Current: `logger.error("Error")`
Suggest: `logger.error("user.fetch_failed", user_id=user_id, error=str(e))`
Why: "Error" is unactionable — which user, which operation, what went wrong?
**src/db/connection.py:14**
Current: `logger.info("Connected")`
Suggest: `logger.info("db.connected", host=host, port=port, database=db_name)`
Why: context needed for debugging connection issues across environments
---
### 5. Inconsistencies (N findings)
**src/orders/service.py:67** — unstructured message where structured is the norm
Current: `logger.info(f"Order {order_id} placed by {user_id}")`
Suggest: `logger.info("order.placed", order_id=order_id, user_id=user_id)`
---
### Summary
| Category | Count |
|-----------------------|-------|
| Remove | 2 |
| Missing log statements| 4 |
| Incorrect log levels | 3 |
| Poor message quality | 6 |
| Inconsistencies | 5 |
| **Total** |**20** |
### Recommended next steps
- Address "Remove" immediately — sensitive data in logs is a security incident waiting to happen.
- Address "Missing log statements" next — silent error paths are the highest operational risk.
- Apply the established pattern to all new log calls going forward.
- Consider adding a linting rule to enforce log level discipline in CI.
If no issues are found in a section, omit that section entirely rather than writing "No issues found."
If the codebase has fewer than 5 log calls total, lead with the pattern proposal and focus the report on where to add logging rather than what to fix.