| name | log-analyzer |
| description | Parse, search, and analyze application logs across formats. Use when debugging from log files, setting up structured logging, analyzing error patterns, correlating events across services, parsing stack traces, or monitoring log output in real time. |
| metadata | {"clawdbot":{"emoji":"📋","requires":{"anyBins":["grep","awk","jq","python3"]},"os":["linux","darwin","win32"]}} |
Log Analyzer
Parse, search, and debug from application logs. Covers plain text logs, structured JSON logs, stack traces, multi-service correlation, and real-time monitoring.
When to Use
- Debugging application errors from log files
- Searching logs for specific patterns, errors, or request IDs
- Parsing and analyzing stack traces
- Setting up structured logging (JSON) in applications
- Correlating events across multiple services or log files
- Monitoring logs in real time during development
- Generating error frequency reports or summaries
Quick Search Patterns
Find errors and exceptions
grep -i 'error\|exception\|fatal\|panic\|fail' app.log
grep -i -C 3 'error\|exception' app.log
HOUR_AGO=$(date -u -d '1 hour ago' '+%Y-%m-%dT%H:%M' 2>/dev/null || date -u -v-1H '+%Y-%m-%dT%H:%M')
awk -v t="$HOUR_AGO" '$0 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}T/ && $1 >= t' app.log | grep -i 'error'
grep -oP '(?:Error|Exception): \K[^\n]+' app.log | sort | uniq -c | sort -rn | head -20
awk '$9 >= 500' access.log
Search by request or correlation ID
grep 'req-abc123' app.log
grep -r 'req-abc123' /var/log/myapp/
grep -rH 'correlation-id-xyz' /var/log/service-a/ /var/log/service-b/ /var/log/service-c/
Time-range filtering
awk '$0 >= "2026-02-03T10:00" && $0 <= "2026-02-03T11:00"' app.log
tail -1000 app.log | grep -i error
awk -v start="$(date -d '30 minutes ago' '+%Y-%m-%dT%H:%M')" '$1 >= start' app.log
JSON / Structured Logs
Parse with jq
cat app.log | jq '.'
cat app.log | jq 'select(.level == "error")'
cat app.log | jq 'select(.timestamp >= "2026-02-03T10:00:00Z")'
cat app.log | jq -r '[.timestamp, .level, .message] | @tsv'
cat app.log | jq -r '.level' | sort | uniq -c | sort -rn
cat app.log | jq 'select(.context.userId == "user-123")'
cat app.log | jq -r 'select(.level == "error") | .message' | sort | uniq -c | sort -rn
cat app.log | jq -r 'select(.duration != null) | .duration' | awk '{sum+=$1; count++; if($1>max)max=$1} END {print "count="count, "avg="sum/count, "max="max}'
Parse mixed-format logs (JSON lines mixed with plain text)
while IFS= read -r line; do
echo "$line" | jq '.' 2>/dev/null && continue
done < app.log
grep '^\s*{' app.log | jq '.'
Stack Trace Analysis
Extract and deduplicate stack traces
awk '/Exception|Error/{trace=$0; while(getline && /^\t/) trace=trace"\n"$0; print trace"\n---"}' app.log
awk '/^Traceback/{p=1} p{print} /^[A-Za-z].*Error/{if(p) print "---"; p=0}' app.log
awk '/Error:/{trace=$0; while(getline && /^ at /) trace=trace"\n"$0; print trace"\n---"}' app.log
awk '/Exception|Error:/{cause=$0} /^\tat|^ at /{next} cause{print cause; cause=""}' app.log | sort | uniq -c | sort -rn
Python traceback parser
"""Parse Python tracebacks from log files and group by root cause."""
import sys
import re
from collections import Counter
def extract_tracebacks(filepath):
tracebacks = []
current = []
in_trace = False
with open(filepath) as f:
for line in f:
if line.startswith('Traceback (most recent call last):'):
in_trace = True
current = [line.rstrip()]
elif in_trace:
current.append(line.rstrip())
if re.match(r'^[A-Za-z]\w*(Error|Exception|Warning)', line):
tracebacks.append('\n'.join(current))
in_trace = False
current = []
return tracebacks
if __name__ == '__main__':
filepath = sys.argv[1] if len(sys.argv) > 1 else '/dev/stdin'
traces = extract_tracebacks(filepath)
causes = Counter()
for trace in traces:
lines = trace.split('\n')
cause = lines[-1] if lines else 'Unknown'
causes[cause] += 1
print(f"Found {len(traces)} tracebacks, {len(causes)} unique causes:\n")
for cause, count in causes.most_common(20):
print(f" {count:4d}x {cause}")
Real-Time Monitoring
Tail and filter
tail -f app.log | grep --color=always -i 'error\|warn\|$'
tail -f app.log | grep --line-buffered -i 'error\|exception'
tail -f app.log | while IFS= read -r line; do
level=$(echo "$line" | jq -r '.level // empty' 2>/dev/null)
if [ "$level" = "error" ] || [ "$level" = "fatal" ]; then
echo "$line" | jq '.'
fi
done
tail -f /var/log/service-a/app.log /var/log/service-b/app.log
tail -f app.log | while IFS= read -r line; do
echo "$(date '+%H:%M:%S') $line"
done
Watch for specific patterns and alert
tail -f app.log | grep --line-buffered -i 'error' | while read line; do
echo -e "\a$line"
done
tail -f app.log | grep --line-buffered -i 'error' | while read line; do
echo "$(date '+%Y-%m-%d %H:%M') ERROR"
done | uniq -c
Log Format Parsing
Common access log (Apache/Nginx)
awk '{print $1, $9, $7}' access.log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20
awk '{if ($NF > 1000000) print $0}' access.log
awk '{split($4,a,":"); print a[1]":"a[2]":"a[3]}' access.log | uniq -c
awk '{print $9}' access.log | sort | uniq -c | sort -rn
awk '$9 >= 400 {print $9, $7}' access.log | sort | uniq -c | sort -rn | head -20
Custom delimited logs
awk -F'|' '{print $2, $3, $4}' app.log
awk -F'\t' '$2 == "ERROR" {print $1, $4}' app.log
python3 -c "
import csv, sys
with open(sys.argv[1]) as f:
for row in csv.DictReader(f):
if row.get('level') == 'error':
print(f\"{row['timestamp']} {row['message']}\")
" app.csv
Setting Up Structured Logging
Node.js (pino — fast JSON logger)
const pino = require('pino');
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
base: { service: 'my-api', version: '1.2.0' },
});
logger.info({ userId: 'u123', action: 'login' }, 'User logged in');
logger.error({ err, requestId: req.id }, 'Request failed');
const reqLogger = logger.child({ requestId: req.id, userId: req.user?.id });
reqLogger.info('Processing order');
reqLogger.error({ err }, 'Order failed');
Python (structlog)
import structlog
structlog.configure(
processors=[
structlog.processors.TimeStamper(fmt="iso"),
structlog.processors.add_log_level,
structlog.processors.JSONRenderer(),
],
)
logger = structlog.get_logger(service="my-api")
logger.info("user_login", user_id="u123", ip="1.2.3.4")
logger.error("request_failed", request_id="req-abc", error=str(e))
Go (zerolog)
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
func init() {
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = zerolog.New(os.Stdout).With().
Timestamp().
Str("service", "my-api").
Logger()
}
log.Info().Str("userId", "u123").Msg("User logged in")
log.Error().Err(err).Str("requestId", reqID).Msg("Request failed")
Error Pattern Reports
Generate error frequency report
#!/bin/bash
LOG="${1:?Usage: error-report.sh <logfile>}"
echo "=== Error Report: $(basename "$LOG") ==="
echo "Generated: $(date -u '+%Y-%m-%dT%H:%M:%SZ')"
echo ""
total=$(wc -l < "$LOG")
errors=$(grep -ci 'error\|exception\|fatal' "$LOG")
warns=$(grep -ci 'warn' "$LOG")
echo "Total lines: $total"
echo "Errors: $errors"
echo "Warnings: $warns"
echo ""
echo "--- Top 15 Error Messages ---"
grep -i 'error\|exception' "$LOG" | \
sed 's/^[0-9TZ:.+\-]* //' | \
sed 's/\b[0-9a-f]\{8,\}\b/ID/g' | \
sed 's/[0-9]\{1,\}/N/g' | \
sort | uniq -c | sort -rn | head -15
echo ""
echo "--- Errors Per Hour ---"
grep -i 'error\|exception' "$LOG" | \
grep -oP '\d{4}-\d{2}-\d{2}T\d{2}' | \
sort | uniq -c
echo ""
echo "--- First Occurrence of Each Error Type ---"
grep -i 'error\|exception' "$LOG" | \
sed 's/^[0-9TZ:.+\-]* //' | \
sort -u | head -10
JSON log error report with Python
"""Generate error summary from JSON log files."""
import json
import sys
from collections import Counter, defaultdict
from datetime import datetime
def analyze_logs(filepath):
errors = []
levels = Counter()
errors_by_hour = defaultdict(int)
with open(filepath) as f:
for line in f:
try:
entry = json.loads(line.strip())
except (json.JSONDecodeError, ValueError):
continue
level = entry.get('level', entry.get('severity', '')).lower()
levels[level] += 1
if level in ('error', 'fatal', 'critical'):
msg = entry.get('message', entry.get('msg', entry.get('event', 'unknown')))
ts = entry.get('timestamp', entry.get('time', ''))
errors.append({'message': msg, 'timestamp': ts, 'entry': entry})
try:
hour = ts[:13]
errors_by_hour[hour] += 1
except (TypeError, IndexError):
pass
error_counts = Counter(e['message'] for e in errors)
print(f"=== Log Analysis: {filepath} ===\n")
print("Level distribution:")
for level, count in levels.most_common():
print(f" {level:10s} {count}")
print(f"\nTotal errors: {len(errors)}")
print(f"Unique error messages: {len(error_counts)}\n")
print("Top 15 errors:")
for msg, count in error_counts.most_common(15):
print(f" {count:4d}x {msg[:100]}")
if errors_by_hour:
print("\nErrors by hour:")
for hour in sorted(errors_by_hour):
bar = '#' * min(errors_by_hour[hour], 50)
print(f" {hour} {errors_by_hour[hour]:4d} {bar}")
if __name__ == '__main__':
analyze_logs(sys.argv[1])
Multi-Service Log Correlation
Merge and sort logs from multiple services
sort -m -t'T' -k1,1 service-a.log service-b.log service-c.log > merged.log
sort -t'T' -k1,1 service-*.log > merged.log
for f in service-*.log; do
service=$(basename "$f" .log)
jq --arg svc "$service" '. + {source: $svc}' "$f"
done | jq -s 'sort_by(.timestamp)[]'
Trace a request across services
REQUEST_ID="req-abc-123"
grep -rH "$REQUEST_ID" /var/log/services/ | sort -t: -k2
for f in /var/log/services/*.log; do
jq --arg rid "$REQUEST_ID" 'select(.requestId == $rid or .correlationId == $rid)' "$f" 2>/dev/null
done | jq -s 'sort_by(.timestamp)[]'
Log Rotation and Large Files
Working with rotated/compressed logs
zgrep -i 'error' /var/log/app.log*
zgrep -i 'error' /var/log/app.log /var/log/app.log.1
zcat app.log.3.gz | grep 'ERROR' | gzip > errors-day3.gz
Sampling large files
shuf -n 1000 huge.log > sample.log
awk 'NR % 100 == 0' huge.log > sample.log
{ head -500 huge.log; echo "--- TRUNCATED ---"; tail -500 huge.log; } > excerpt.log
Tips
- Always search for a request ID or correlation ID first — it narrows the haystack faster than timestamps or error messages.
- Use
--line-buffered with grep when piping from tail -f so output isn't delayed by buffering.
- Normalize IDs and numbers before grouping errors (
sed 's/[0-9a-f]\{8,\}/ID/g') to collapse duplicates that differ only by ID.
- For JSON logs,
jq is indispensable. Install it if it's not available: apt install jq / brew install jq.
- Structured logging (JSON) is always worth the setup cost. It makes every analysis task easier: filtering, grouping, correlation, and alerting all become
jq one-liners.
- When debugging a production issue: get the time window and affected user/request ID first, then filter logs to that scope before reading anything.
awk is faster than grep | sort | uniq -c pipelines for large files. Use it for counting and aggregation.