
Incident Response
- 51 installs
- 8 repo stars
- Updated February 6, 2026
- hieutrtr/ai1-skills
Production incident response procedures for Python/React apps: severity classification, diagnostics, rollback, and blameless post-mortems.
About
Covers SEV1-SEV4 severity classification, incident commander role, diagnostic commands for FastAPI/PostgreSQL/Redis, rollback, and post-mortems. A developer uses it when responding to outages, error spikes, or performance degradation.
- SEV1-SEV4 classification with incident commander role and comms templates
- Diagnostic commands for FastAPI, PostgreSQL, and Redis plus blameless post-mortem
Incident Response by the numbers
- 51 all-time installs (skills.sh)
- Ranked #721 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/hieutrtr/ai1-skills --skill incident-responseAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 51 |
|---|---|
| repo stars | ★ 8 |
| Last updated | February 6, 2026 |
| Repository | hieutrtr/ai1-skills ↗ |
What it does
Production incident response procedures for Python/React apps: severity classification, diagnostics, rollback, and blameless post-mortems.
Files
Incident Response
When to Use
Activate this skill when:
- Production service is down or returning errors to users
- Error rate has spiked beyond normal thresholds
- Performance has degraded significantly (latency increase, timeouts)
- An alert has fired from the monitoring system
- Users are reporting issues that indicate a systemic problem
- A failed deployment needs investigation and remediation
- Conducting a post-mortem or root cause analysis after an incident
Output: Write runbooks to docs/runbooks/<service>-runbook.md and post-mortems to postmortem-YYYY-MM-DD.md.
Do NOT use this skill for:
- Setting up monitoring or alerting rules (use
monitoring-setup) - Performing routine deployments (use
deployment-pipeline) - Docker image or infrastructure issues (use
docker-best-practices) - Feature development or code changes (use
python-backend-expertorreact-frontend-expert)
Instructions
Severity Classification
Classify every incident immediately. Severity determines response urgency, communication cadence, and escalation path.
| Severity | Impact | Examples | Response Time | Update Cadence |
|---|---|---|---|---|
| SEV1 (P1) | Complete outage, all users affected | Service down, data loss, security breach | Immediate (< 5 min) | Every 15 min |
| SEV2 (P2) | Major degradation, most users affected | Core feature broken, severe latency | < 15 min | Every 30 min |
| SEV3 (P3) | Partial degradation, some users affected | Non-critical feature broken, intermittent errors | < 1 hour | Every 2 hours |
| SEV4 (P4) | Minor issue, few users affected | Cosmetic bug, edge case error | < 4 hours | Daily |
Escalation rules:
- SEV1: Page on-call engineer + engineering manager immediately
- SEV2: Page on-call engineer, notify engineering manager
- SEV3: Notify on-call engineer via Slack
- SEV4: Create ticket, address during normal working hours
See references/escalation-contacts.md for the contact matrix.
5-Minute Triage Workflow
When an incident is detected, follow this triage workflow within the first 5 minutes.
┌─────────────────────────────────────────────────────────┐
│ MINUTE 0-1: Acknowledge and Classify │
│ • Acknowledge the alert or report │
│ • Assign severity (SEV1-SEV4) │
│ • Designate incident commander │
├─────────────────────────────────────────────────────────┤
│ MINUTE 1-2: Assess Scope │
│ • Check health endpoints for all services │
│ • Check error rate and latency dashboards │
│ • Determine: which services are affected? │
├─────────────────────────────────────────────────────────┤
│ MINUTE 2-3: Identify Recent Changes │
│ • Check: was there a recent deployment? │
│ • Check: any infrastructure changes? │
│ • Check: any external dependency issues? │
├─────────────────────────────────────────────────────────┤
│ MINUTE 3-4: Initial Communication │
│ • Post in #incidents channel │
│ • Update status page if SEV1/SEV2 │
│ • Page additional responders if needed │
├─────────────────────────────────────────────────────────┤
│ MINUTE 4-5: Begin Investigation or Mitigate │
│ • If recent deploy: consider immediate rollback │
│ • If not deploy-related: begin diagnostic commands │
│ • Start incident timeline log │
└─────────────────────────────────────────────────────────┘Quick health check command:
./skills/incident-response/scripts/health-check-all-services.sh \
--output-dir ./incident-triage/Incident Commander Role
The incident commander (IC) coordinates the response. They do NOT investigate directly.
IC responsibilities: 1. Coordinate -- Assign tasks to responders, prevent duplicate work 2. Communicate -- Post regular updates to stakeholders 3. Decide -- Make go/no-go decisions on rollback, escalation, communication 4. Track -- Maintain the incident timeline 5. Close -- Declare the incident resolved and schedule the post-mortem
IC communication template (initial):
INCIDENT DECLARED: [Title]
Severity: [SEV1/SEV2/SEV3/SEV4]
Commander: [Name]
Start time: [UTC timestamp]
Impact: [What users are experiencing]
Status: Investigating
Next update: [Time]IC communication template (update):
INCIDENT UPDATE: [Title]
Severity: [SEV level]
Duration: [Time since start]
Status: [Investigating/Identified/Mitigating/Resolved]
Current findings: [What we know]
Actions in progress: [What we are doing]
Next update: [Time]Investigation Steps
Follow these diagnostic steps based on the type of issue.
Application Errors (FastAPI)
# 1. Check application logs for errors
./skills/incident-response/scripts/fetch-logs.sh \
--service backend \
--since "15 minutes ago" \
--output-dir ./incident-logs/
# 2. Check error rate from logs
docker logs app-backend --since 15m 2>&1 | grep -c "ERROR"
# 3. Check active connections and request patterns
curl -s http://localhost:8000/health/ready | jq .
# 4. Check if the issue is in a specific endpoint
docker logs app-backend --since 15m 2>&1 | \
grep "ERROR" | \
grep -oP '"path":"[^"]*"' | sort | uniq -c | sort -rn
# 5. Check Python process status
docker exec app-backend ps aux
docker exec app-backend python -c "import sys; print(sys.version)"Database Issues (PostgreSQL)
# 1. Check database connectivity
docker exec app-db pg_isready -U postgres
# 2. Check active connections (connection pool exhaustion?)
docker exec app-db psql -U postgres -d app_prod -c "
SELECT count(*), state FROM pg_stat_activity
GROUP BY state ORDER BY count DESC;
"
# 3. Check for long-running queries (locks, deadlocks?)
docker exec app-db psql -U postgres -d app_prod -c "
SELECT pid, now() - pg_stat_activity.query_start AS duration,
query, state
FROM pg_stat_activity
WHERE (now() - pg_stat_activity.query_start) > interval '30 seconds'
AND state != 'idle'
ORDER BY duration DESC;
"
# 4. Check for lock contention
docker exec app-db psql -U postgres -d app_prod -c "
SELECT blocked_locks.pid AS blocked_pid,
blocking_locks.pid AS blocking_pid,
blocked_activity.query AS blocked_query
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity
ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks
ON blocking_locks.locktype = blocked_locks.locktype
AND blocking_locks.relation = blocked_locks.relation
AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity
ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;
"
# 5. Check disk space
docker exec app-db df -h /var/lib/postgresql/dataRedis Issues
# 1. Check Redis connectivity
docker exec app-redis redis-cli ping
# 2. Check memory usage
docker exec app-redis redis-cli info memory | grep used_memory_human
# 3. Check connected clients
docker exec app-redis redis-cli info clients | grep connected_clients
# 4. Check slow log
docker exec app-redis redis-cli slowlog get 10
# 5. Check keyspace
docker exec app-redis redis-cli info keyspaceNetwork and Infrastructure
# 1. Check DNS resolution
nslookup api.example.com
# 2. Check SSL certificate expiry
echo | openssl s_client -servername api.example.com -connect api.example.com:443 2>/dev/null | \
openssl x509 -noout -dates
# 3. Check container resource usage
docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}"
# 4. Check disk space on host
df -h /
# 5. Check if dependent services are reachable
curl -sf https://external-api.example.com/health || echo "External API unreachable"Remediation Actions
Immediate Mitigations (apply within minutes)
| Issue | Mitigation | Command |
|---|---|---|
| Bad deployment | Rollback | ./scripts/deploy.sh --rollback --env production --version $PREV_SHA --output-dir ./results/ |
| Connection pool exhausted | Restart backend | docker restart app-backend |
| Long-running query | Kill query | SELECT pg_terminate_backend(<pid>); |
| Memory leak | Restart service | docker restart app-backend |
| Redis full | Flush non-critical keys | `redis-cli --scan --pattern "cache:*" \ |
| SSL expired | Apply new cert | Update cert in load balancer |
| Disk full | Clean logs/temp files | docker system prune -f |
Longer-Term Fixes (apply after stabilization)
1. Fix the root cause in code -- Create a branch, fix, test, deploy through normal pipeline 2. Add monitoring -- If the issue was not caught by existing alerts, add new alert rules 3. Add tests -- Write regression tests for the failure scenario 4. Update runbooks -- Document the new failure mode and remediation steps
Communication Protocol
Internal Communication
Channels:
#incidents-- Active incident coordination (SEV1/SEV2)#incidents-low-- SEV3/SEV4 tracking#engineering-- Post-incident summaries
Rules: 1. All communication happens in the designated incident channel 2. Use threads for investigation details, keep main channel for status updates 3. IC posts updates at the defined cadence (see severity table) 4. Tag relevant people explicitly, do not assume they are watching 5. Timestamp all significant findings and actions
External Communication (SEV1/SEV2)
Status page update template:
[Investigating] We are investigating reports of [issue description].
Users may experience [user-visible impact].
We will provide an update within [time].[Identified] The issue has been identified as [brief description].
We are working on a fix. Estimated resolution: [time estimate].[Resolved] The issue affecting [service] has been resolved.
The root cause was [brief description].
We apologize for the disruption and will publish a detailed post-mortem.Post-Mortem / RCA Framework
Conduct a blameless post-mortem within 48 hours of every SEV1/SEV2 incident. SEV3 incidents receive a lightweight review.
See references/post-mortem-template.md for the full template.
Post-mortem principles: 1. Blameless -- Focus on systems and processes, not individuals 2. Thorough -- Identify all contributing factors, not just the trigger 3. Actionable -- Every finding must produce a concrete action item with an owner 4. Timely -- Conduct within 48 hours while details are fresh 5. Shared -- Publish to the entire engineering team
Post-mortem structure: 1. Summary -- What happened, when, and what was the impact 2. Timeline -- Minute-by-minute account of detection, investigation, mitigation 3. Root cause -- The fundamental reason the incident occurred 4. Contributing factors -- Other conditions that made the incident worse 5. What went well -- Effective parts of the response 6. What could be improved -- Gaps in detection, response, or tooling 7. Action items -- Specific tasks with owners and due dates
Five Whys technique for root cause analysis:
Why did users see 500 errors?
-> Because the backend service returned errors to the load balancer.
Why did the backend service return errors?
-> Because database connections timed out.
Why did database connections time out?
-> Because the connection pool was exhausted.
Why was the connection pool exhausted?
-> Because a new endpoint opened connections without releasing them.
Why were connections not released?
-> Because the endpoint was missing the async context manager for sessions.
Root cause: Missing async context manager for database sessions in new endpoint.Generate a structured incident report:
python skills/incident-response/scripts/generate-incident-report.py \
--title "Database connection pool exhaustion" \
--severity SEV2 \
--start-time "2024-01-15T14:30:00Z" \
--end-time "2024-01-15T15:15:00Z" \
--output-dir ./post-mortems/Incident Response Scripts
| Script | Purpose | Usage |
|---|---|---|
scripts/fetch-logs.sh | Fetch recent logs from services | ./scripts/fetch-logs.sh --service backend --since "30m" --output-dir ./logs/ |
scripts/health-check-all-services.sh | Check health of all services | ./scripts/health-check-all-services.sh --output-dir ./health/ |
scripts/generate-incident-report.py | Generate structured incident report | python scripts/generate-incident-report.py --title "..." --severity SEV1 --output-dir ./reports/ |
Quick Reference: Common Incident Patterns
| Pattern | Symptom | Likely Cause | First Action |
|---|---|---|---|
| 502/503 errors | Users see error page | Backend crashed or overloaded | Check docker ps, restart if needed |
| Slow responses | High latency, timeouts | DB queries, external API | Check slow query log, DB connections |
| Partial failures | Some endpoints fail | Single dependency down | Check individual service health |
| Memory growth | OOM kills, restarts | Memory leak | Check docker stats, restart |
| Error spike after deploy | Errors start exactly at deploy time | Bug in new code | Rollback immediately |
| Gradual degradation | Slowly worsening metrics | Resource exhaustion, connection leak | Check resource usage trends |
Output Files
Runbooks: Write to docs/runbooks/<service>-runbook.md:
# Runbook: [Service Name]
## Service Overview
- Purpose, dependencies, critical paths
## Common Issues
### Issue 1: [Description]
- **Symptoms:** [What you see]
- **Diagnosis:** [Commands to run]
- **Resolution:** [Steps to fix]
## Escalation
- On-call: #ops-oncall
- Service owner: @team-namePost-mortems: Write to postmortem-YYYY-MM-DD.md:
# Post-Mortem: [Incident Title]
## Summary
- **Date:** YYYY-MM-DD
- **Severity:** SEV1-4
- **Duration:** X hours
- **Impact:** [Users/revenue affected]
## Timeline
- HH:MM - [Event]
## Root Cause
[Technical explanation]
## Action Items
- [ ] [Preventive measure] - Owner: @name - Due: YYYY-MM-DDEscalation Contacts
Contact Matrix by Severity
| Severity | Primary Contact | Secondary Contact | Management | Communication |
|---|---|---|---|---|
| SEV1 | On-call engineer (PagerDuty) | Backend lead + Frontend lead | Engineering Manager + VP Eng | Status page + #incidents |
| SEV2 | On-call engineer (PagerDuty) | Relevant team lead | Engineering Manager | #incidents |
| SEV3 | On-call engineer (Slack) | Relevant team lead | _Not required_ | #incidents-low |
| SEV4 | Team responsible for service | _Not required_ | _Not required_ | Ticket only |
On-Call Rotation
| Week | Primary | Secondary |
|---|---|---|
| Odd weeks | Engineer A | Engineer B |
| Even weeks | Engineer C | Engineer D |
On-call schedule: Managed in PagerDuty Rotation cadence: Weekly, handoff on Monday 09:00 UTC Override requests: Post in #on-call-swap channel
Service Ownership
| Service / Component | Team | Primary Contact | Slack Channel |
|---|---|---|---|
| Backend API (FastAPI) | Backend Team | Backend Tech Lead | #team-backend |
| Frontend (React) | Frontend Team | Frontend Tech Lead | #team-frontend |
| Database (PostgreSQL) | Platform Team | DBA Lead | #team-platform |
| Redis / Caching | Platform Team | Platform Engineer | #team-platform |
| CI/CD Pipeline | Platform Team | DevOps Lead | #team-platform |
| Authentication | Backend Team | Auth Module Owner | #team-backend |
| Infrastructure / Cloud | Platform Team | Infrastructure Lead | #team-platform |
| Third-party integrations | Backend Team | Integration Lead | #team-backend |
Escalation Paths
Technical Escalation
On-call Engineer
|
v
Team Lead (for affected service)
|
v
Engineering Manager
|
v
VP of Engineering (SEV1 only, if not resolved within 30 minutes)Data / Security Incidents
On-call Engineer
|
v
Security Lead
|
v
CTO + Legal (if data breach confirmed)Third-Party / Vendor Issues
On-call Engineer
|
v
Integration Lead
|
v
Vendor support (using premium support channel)Communication Channels
| Channel | Purpose | Who Posts |
|---|---|---|
#incidents | Active SEV1/SEV2 incident coordination | Incident commander, responders |
#incidents-low | SEV3/SEV4 tracking | On-call engineer |
#engineering | Post-incident summaries | Incident commander |
#status-updates | External-facing status updates | Incident commander |
| PagerDuty | Automated alerting and paging | Monitoring system |
Contact Information
_Replace placeholders with actual team contacts._
| Role | Name | Slack | PagerDuty | Phone (emergency) |
|---|---|---|---|---|
| On-call (primary) | _See rotation_ | _Via PagerDuty_ | Auto-paged | _Via PagerDuty_ |
| On-call (secondary) | _See rotation_ | _Via PagerDuty_ | Auto-paged | _Via PagerDuty_ |
| Backend Tech Lead | TBD | @backend-lead | @backend-lead | TBD |
| Frontend Tech Lead | TBD | @frontend-lead | @frontend-lead | TBD |
| DBA Lead | TBD | @dba-lead | @dba-lead | TBD |
| DevOps Lead | TBD | @devops-lead | @devops-lead | TBD |
| Engineering Manager | TBD | @eng-manager | @eng-manager | TBD |
| VP of Engineering | TBD | @vp-eng | @vp-eng | TBD |
When to Page vs. When to Slack
| Signal | Action | Channel |
|---|---|---|
| Service completely down | Page immediately | PagerDuty |
| Error rate > 5% | Page immediately | PagerDuty |
| Error rate 1-5% | Slack notification | #incidents |
| Performance degradation > 2x | Page if sustained > 5 min | PagerDuty |
| Single user report | Investigate, no page | #incidents-low |
| Multiple user reports | Evaluate severity, likely page | PagerDuty or #incidents |
| Scheduled maintenance issue | Slack notification | #incidents-low |
Post-Mortem Template
Instructions
Copy this template for each post-mortem. Fill in all sections. Conduct the post-mortem meeting within 48 hours of the incident for SEV1/SEV2, within 1 week for SEV3.
---
Post-Mortem: [Incident Title]
Date: [YYYY-MM-DD] Severity: [SEV1/SEV2/SEV3/SEV4] Duration: [Total duration] Author: [Name] Incident Commander: [Name] Attendees: [List of post-mortem participants]
1. Summary
_One paragraph describing what happened, when, and the impact on users._
Example: On January 15, 2024, from 14:30 to 15:15 UTC (45 minutes), the backend API returned 503 errors for approximately 80% of requests. The root cause was database connection pool exhaustion triggered by a new endpoint that failed to release connections. An estimated 2,400 users were affected during the incident window.
2. Impact
| Metric | Value |
|---|---|
| Duration | _minutes/hours_ |
| Users affected | _count or percentage_ |
| Requests failed | _count or percentage_ |
| Revenue impact | _if applicable_ |
| SLA impact | _if applicable_ |
| Data loss | _yes/no, describe if yes_ |
3. Timeline (UTC)
| Time | Event |
|---|---|
| HH:MM | _First sign of impact (from metrics/logs)_ |
| HH:MM | _Alert fired / issue detected_ |
| HH:MM | _Incident declared, severity assigned_ |
| HH:MM | _Incident commander designated_ |
| HH:MM | _Investigation started_ |
| HH:MM | _Root cause identified_ |
| HH:MM | _Mitigation applied_ |
| HH:MM | _Service recovered_ |
| HH:MM | _Incident declared resolved_ |
4. Root Cause
_Describe the fundamental reason the incident occurred. Use the Five Whys technique._
Five Whys: 1. Why did [symptom]? Because [cause 1]. 2. Why did [cause 1]? Because [cause 2]. 3. Why did [cause 2]? Because [cause 3]. 4. Why did [cause 3]? Because [cause 4]. 5. Why did [cause 4]? Because [root cause].
Root cause: _One sentence describing the fundamental issue._
5. Contributing Factors
_List all conditions that contributed to the incident occurring or worsening._
- [ ] _Factor 1: Description_
- [ ] _Factor 2: Description_
- [ ] _Factor 3: Description_
6. Detection
| Question | Answer |
|---|---|
| How was the incident detected? | _Alert / user report / manual check_ |
| Time from impact to detection | _minutes_ |
| Was the right alert in place? | _yes / no_ |
| Did the alert fire promptly? | _yes / no / N/A_ |
7. Response
| Question | Answer |
|---|---|
| Time from detection to response | _minutes_ |
| Were the right people paged? | _yes / no_ |
| Was the runbook useful? | _yes / no / no runbook existed_ |
| Time from response to mitigation | _minutes_ |
| Was communication clear and timely? | _yes / no_ |
8. What Went Well
- _List things that worked effectively during the incident_
- _Example: Alert fired within 2 minutes of impact_
- _Example: Rollback procedure completed in under 5 minutes_
9. What Could Be Improved
- _List gaps or problems in the response_
- _Example: No alert for connection pool saturation_
- _Example: Runbook did not mention this failure mode_
- _Example: It took 15 minutes to find the right dashboard_
10. Action Items
| # | Action | Owner | Priority | Due Date | Tracking |
|---|---|---|---|---|---|
| 1 | _Description_ | _Name_ | P1/P2/P3 | _Date_ | _Ticket link_ |
| 2 | _Description_ | _Name_ | P1/P2/P3 | _Date_ | _Ticket link_ |
| 3 | _Description_ | _Name_ | P1/P2/P3 | _Date_ | _Ticket link_ |
Action item categories:
- Prevent: Changes to prevent this class of incident from recurring
- Detect: Improvements to detect similar issues faster
- Mitigate: Changes to reduce time-to-recovery
- Process: Improvements to incident response procedures
11. Lessons Learned
_Key insights from this incident that the broader team should understand._
1. _Lesson 1_ 2. _Lesson 2_ 3. _Lesson 3_
---
_Reviewed and approved by: [Engineering Manager Name], [Date]_
#!/usr/bin/env bash
#
# fetch-logs.sh -- Fetch recent logs from application services
#
# Retrieves logs from Docker containers for the specified service
# and time range. Writes raw logs and a filtered error summary to
# the output directory.
#
# Usage:
# ./fetch-logs.sh --service backend --since "15 minutes ago" --output-dir ./logs/
# ./fetch-logs.sh --service db --since "1 hour ago" --output-dir ./logs/ --filter ERROR
#
set -euo pipefail
# ─── Defaults ────────────────────────────────────────────────────────────────
SERVICE=""
SINCE="15m"
OUTPUT_DIR="./incident-logs"
FILTER=""
CONTAINER_PREFIX="app"
TAIL_LINES=1000
# ─── Parse Arguments ─────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--service) SERVICE="$2"; shift 2 ;;
--since) SINCE="$2"; shift 2 ;;
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
--filter) FILTER="$2"; shift 2 ;;
--container-prefix) CONTAINER_PREFIX="$2"; shift 2 ;;
--tail) TAIL_LINES="$2"; shift 2 ;;
-h|--help)
echo "Usage: $0 --service <service-name> --output-dir <dir>"
echo ""
echo "Options:"
echo " --service Service name (backend, frontend, db, redis)"
echo " --since Time range for logs (default: 15m)"
echo " --output-dir Directory for log output files"
echo " --filter Filter logs by pattern (e.g., ERROR, WARNING)"
echo " --container-prefix Container name prefix (default: app)"
echo " --tail Maximum number of lines (default: 1000)"
exit 0
;;
*) echo "ERROR: Unknown argument: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$SERVICE" ]]; then
echo "ERROR: --service is required" >&2
exit 1
fi
# ─── Setup Output ────────────────────────────────────────────────────────────
mkdir -p "$OUTPUT_DIR"
TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ")
RAW_LOG_FILE="${OUTPUT_DIR}/${SERVICE}-raw-${TIMESTAMP}.log"
ERROR_LOG_FILE="${OUTPUT_DIR}/${SERVICE}-errors-${TIMESTAMP}.log"
SUMMARY_FILE="${OUTPUT_DIR}/${SERVICE}-summary-${TIMESTAMP}.json"
log() {
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] $1"
}
# ─── Determine Container Name ────────────────────────────────────────────────
CONTAINER_NAME="${CONTAINER_PREFIX}-${SERVICE}"
# Verify container exists
if ! docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log "ERROR: Container '${CONTAINER_NAME}' not found"
log "Available containers:"
docker ps -a --format '{{.Names}}' | while read -r name; do
log " - ${name}"
done
cat > "$SUMMARY_FILE" <<EOJSON
{
"service": "${SERVICE}",
"container": "${CONTAINER_NAME}",
"status": "error",
"error": "Container not found",
"timestamp": "${TIMESTAMP}"
}
EOJSON
exit 1
fi
# ─── Fetch Logs ──────────────────────────────────────────────────────────────
log "Fetching logs from ${CONTAINER_NAME} (since ${SINCE})..."
# Fetch raw logs
docker logs "${CONTAINER_NAME}" --since "${SINCE}" --tail "${TAIL_LINES}" \
> "$RAW_LOG_FILE" 2>&1
TOTAL_LINES=$(wc -l < "$RAW_LOG_FILE")
log "Fetched ${TOTAL_LINES} lines of logs"
# ─── Extract Errors ─────────────────────────────────────────────────────────
log "Extracting errors and warnings..."
grep -iE "(ERROR|CRITICAL|FATAL|Exception|Traceback)" "$RAW_LOG_FILE" \
> "$ERROR_LOG_FILE" 2>/dev/null || true
ERROR_LINES=$(wc -l < "$ERROR_LOG_FILE")
log "Found ${ERROR_LINES} error/exception lines"
# ─── Apply Custom Filter ────────────────────────────────────────────────────
FILTER_LINES=0
FILTER_FILE=""
if [[ -n "$FILTER" ]]; then
FILTER_FILE="${OUTPUT_DIR}/${SERVICE}-filtered-${TIMESTAMP}.log"
grep -i "$FILTER" "$RAW_LOG_FILE" > "$FILTER_FILE" 2>/dev/null || true
FILTER_LINES=$(wc -l < "$FILTER_FILE")
log "Filter '${FILTER}' matched ${FILTER_LINES} lines"
fi
# ─── Error Pattern Analysis ─────────────────────────────────────────────────
log "Analyzing error patterns..."
# Count error types
ERROR_COUNTS=""
if [[ $ERROR_LINES -gt 0 ]]; then
ERROR_COUNTS=$(grep -oP '(ERROR|CRITICAL|FATAL|\w+Error|\w+Exception)' "$ERROR_LOG_FILE" \
| sort | uniq -c | sort -rn | head -10 \
| while read -r count pattern; do
echo " {\"pattern\": \"${pattern}\", \"count\": ${count}}"
done | paste -sd ',' -)
fi
# ─── Container Status ───────────────────────────────────────────────────────
CONTAINER_STATUS=$(docker inspect "${CONTAINER_NAME}" --format '{{.State.Status}}' 2>/dev/null || echo "unknown")
CONTAINER_RESTARTS=$(docker inspect "${CONTAINER_NAME}" --format '{{.RestartCount}}' 2>/dev/null || echo "0")
CONTAINER_STARTED=$(docker inspect "${CONTAINER_NAME}" --format '{{.State.StartedAt}}' 2>/dev/null || echo "unknown")
# ─── Write Summary ──────────────────────────────────────────────────────────
cat > "$SUMMARY_FILE" <<EOJSON
{
"service": "${SERVICE}",
"container": "${CONTAINER_NAME}",
"timestamp": "${TIMESTAMP}",
"since": "${SINCE}",
"status": "collected",
"container_status": "${CONTAINER_STATUS}",
"container_restarts": ${CONTAINER_RESTARTS},
"container_started_at": "${CONTAINER_STARTED}",
"total_log_lines": ${TOTAL_LINES},
"error_lines": ${ERROR_LINES},
"filter_pattern": "${FILTER}",
"filter_matches": ${FILTER_LINES},
"files": {
"raw_log": "${RAW_LOG_FILE}",
"error_log": "${ERROR_LOG_FILE}",
"filtered_log": "${FILTER_FILE:-null}",
"summary": "${SUMMARY_FILE}"
},
"top_error_patterns": [${ERROR_COUNTS}]
}
EOJSON
# ─── Output Summary ─────────────────────────────────────────────────────────
log ""
log "=== Log Collection Summary ==="
log "Service: ${SERVICE}"
log "Container: ${CONTAINER_NAME} (${CONTAINER_STATUS})"
log "Restarts: ${CONTAINER_RESTARTS}"
log "Total lines: ${TOTAL_LINES}"
log "Error lines: ${ERROR_LINES}"
if [[ -n "$FILTER" ]]; then
log "Filter matches: ${FILTER_LINES} (pattern: ${FILTER})"
fi
log "Raw log: ${RAW_LOG_FILE}"
log "Error log: ${ERROR_LOG_FILE}"
log "Summary: ${SUMMARY_FILE}"
if [[ $ERROR_LINES -gt 0 ]]; then
log ""
log "=== Recent Errors (last 5) ==="
tail -5 "$ERROR_LOG_FILE"
fi
exit 0
#!/usr/bin/env python3
"""
generate-incident-report.py -- Generate a structured incident report.
Creates a markdown incident report and a JSON summary from provided
incident details. Used during or after an incident to document findings.
Usage:
python generate-incident-report.py \
--title "Database connection pool exhaustion" \
--severity SEV2 \
--start-time "2024-01-15T14:30:00Z" \
--end-time "2024-01-15T15:15:00Z" \
--output-dir ./post-mortems/
python generate-incident-report.py \
--title "API gateway 502 errors" \
--severity SEV1 \
--start-time "2024-01-15T14:30:00Z" \
--impact "All API requests returning 502" \
--root-cause "Expired TLS certificate on load balancer" \
--output-dir ./post-mortems/
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Generate a structured incident report"
)
parser.add_argument(
"--title",
required=True,
help="Incident title (brief description)",
)
parser.add_argument(
"--severity",
required=True,
choices=["SEV1", "SEV2", "SEV3", "SEV4"],
help="Incident severity level",
)
parser.add_argument(
"--start-time",
required=True,
help="Incident start time in ISO 8601 format (e.g., 2024-01-15T14:30:00Z)",
)
parser.add_argument(
"--end-time",
default=None,
help="Incident end time in ISO 8601 format (omit if ongoing)",
)
parser.add_argument(
"--output-dir",
default="./incident-reports",
help="Directory for report output files (default: ./incident-reports)",
)
parser.add_argument(
"--impact",
default="",
help="Description of user-facing impact",
)
parser.add_argument(
"--root-cause",
default="",
help="Root cause (if known)",
)
parser.add_argument(
"--commander",
default="",
help="Incident commander name",
)
parser.add_argument(
"--services-affected",
nargs="+",
default=[],
help="List of affected services",
)
parser.add_argument(
"--timeline-events",
nargs="+",
default=[],
help="Timeline events in 'HH:MM description' format",
)
return parser.parse_args()
def parse_iso_time(time_str: str) -> datetime:
"""Parse an ISO 8601 time string."""
try:
if time_str.endswith("Z"):
time_str = time_str[:-1] + "+00:00"
return datetime.fromisoformat(time_str)
except ValueError:
print(f"ERROR: Invalid time format: {time_str}", file=sys.stderr)
print("Expected ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ", file=sys.stderr)
sys.exit(1)
def calculate_duration(start: datetime, end: datetime | None) -> str:
"""Calculate human-readable duration between two times."""
if end is None:
return "Ongoing"
delta = end - start
total_seconds = int(delta.total_seconds())
hours, remainder = divmod(total_seconds, 3600)
minutes, seconds = divmod(remainder, 60)
if hours > 0:
return f"{hours}h {minutes}m"
return f"{minutes}m {seconds}s"
def generate_markdown_report(args: argparse.Namespace, duration: str) -> str:
"""Generate a markdown incident report."""
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
status = "Resolved" if args.end_time else "Ongoing"
services = ", ".join(args.services_affected) if args.services_affected else "TBD"
report = f"""# Incident Report: {args.title}
## Summary
| Field | Value |
|-------|-------|
| **Severity** | {args.severity} |
| **Status** | {status} |
| **Start Time** | {args.start_time} |
| **End Time** | {args.end_time or "Ongoing"} |
| **Duration** | {duration} |
| **Commander** | {args.commander or "TBD"} |
| **Services Affected** | {services} |
| **Report Generated** | {now} |
## Impact
{args.impact or "_Describe the user-facing impact here._"}
## Timeline
| Time (UTC) | Event |
|------------|-------|
| {args.start_time} | Incident detected |
"""
if args.timeline_events:
for event in args.timeline_events:
parts = event.split(" ", 1)
if len(parts) == 2:
report += f"| {parts[0]} | {parts[1]} |\n"
else:
report += f"| -- | {event} |\n"
if args.end_time:
report += f"| {args.end_time} | Incident resolved |\n"
report += f"""
_Add more timeline entries as the investigation progresses._
## Root Cause
{args.root_cause or "_To be determined during post-mortem investigation._"}
## Contributing Factors
- _List conditions that made the incident more likely or more severe_
- _Example: No alert configured for connection pool usage_
- _Example: Recent deployment changed database query pattern_
## Detection
- How was the incident detected? (Alert, user report, monitoring dashboard)
- How long between incident start and detection?
- Could detection have been faster?
## Response
- What mitigation steps were taken?
- Were the right people involved quickly enough?
- Was the runbook helpful?
## What Went Well
- _Example: Alert fired within 2 minutes of impact_
- _Example: Rollback procedure worked correctly_
- _Example: Clear communication in incident channel_
## What Could Be Improved
- _Example: No alert for connection pool exhaustion_
- _Example: Took 10 minutes to identify the root cause_
- _Example: Runbook did not cover this failure mode_
## Action Items
| Action | Owner | Priority | Due Date | Status |
|--------|-------|----------|----------|--------|
| _Add alert for [metric]_ | TBD | High | TBD | Open |
| _Add regression test for [scenario]_ | TBD | Medium | TBD | Open |
| _Update runbook with [procedure]_ | TBD | Medium | TBD | Open |
| _Fix root cause in [component]_ | TBD | High | TBD | Open |
## Lessons Learned
_Key takeaways from this incident that the broader team should know about._
---
_This report was generated by `generate-incident-report.py`. Fill in the TBD
sections during the post-mortem meeting._
"""
return report
def main() -> int:
args = parse_args()
# Setup output directory
output_dir = Path(args.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# Parse times
start_time = parse_iso_time(args.start_time)
end_time = parse_iso_time(args.end_time) if args.end_time else None
duration = calculate_duration(start_time, end_time)
# Generate file names
date_prefix = start_time.strftime("%Y%m%d")
safe_title = args.title.lower().replace(" ", "-")[:50]
base_name = f"incident-{date_prefix}-{safe_title}"
markdown_file = output_dir / f"{base_name}.md"
json_file = output_dir / f"{base_name}.json"
# Generate markdown report
markdown_content = generate_markdown_report(args, duration)
with open(markdown_file, "w") as f:
f.write(markdown_content)
# Generate JSON summary
json_summary = {
"title": args.title,
"severity": args.severity,
"status": "resolved" if args.end_time else "ongoing",
"start_time": args.start_time,
"end_time": args.end_time,
"duration": duration,
"commander": args.commander or None,
"impact": args.impact or None,
"root_cause": args.root_cause or None,
"services_affected": args.services_affected,
"timeline_events": args.timeline_events,
"report_generated": datetime.now(timezone.utc).isoformat(),
"files": {
"markdown": str(markdown_file),
"json": str(json_file),
},
}
with open(json_file, "w") as f:
json.dump(json_summary, f, indent=2, default=str)
# Output summary
print(f"Incident report generated:")
print(f" Markdown: {markdown_file}")
print(f" JSON: {json_file}")
print(f" Severity: {args.severity}")
print(f" Duration: {duration}")
print(f" Status: {'Resolved' if args.end_time else 'Ongoing'}")
return 0
if __name__ == "__main__":
sys.exit(main())
#!/usr/bin/env bash
#
# health-check-all-services.sh -- Check health of all application services
#
# Checks liveness and connectivity of backend, frontend, database, and Redis
# services. Writes a structured report to the output directory.
#
# Usage:
# ./health-check-all-services.sh --output-dir ./health-results/
# ./health-check-all-services.sh --backend-url https://api.example.com --output-dir ./results/
#
set -euo pipefail
# ─── Defaults ────────────────────────────────────────────────────────────────
OUTPUT_DIR="./health-check-results"
BACKEND_URL="${BACKEND_URL:-http://localhost:8000}"
FRONTEND_URL="${FRONTEND_URL:-http://localhost:3000}"
DB_CONTAINER="${DB_CONTAINER:-app-db}"
REDIS_CONTAINER="${REDIS_CONTAINER:-app-redis}"
TIMEOUT=10
# ─── Parse Arguments ─────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
case "$1" in
--output-dir) OUTPUT_DIR="$2"; shift 2 ;;
--backend-url) BACKEND_URL="$2"; shift 2 ;;
--frontend-url) FRONTEND_URL="$2"; shift 2 ;;
--db-container) DB_CONTAINER="$2"; shift 2 ;;
--redis-container) REDIS_CONTAINER="$2"; shift 2 ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
-h|--help)
echo "Usage: $0 --output-dir <dir>"
echo ""
echo "Options:"
echo " --output-dir Directory for health check result files"
echo " --backend-url Backend service URL (default: http://localhost:8000)"
echo " --frontend-url Frontend service URL (default: http://localhost:3000)"
echo " --db-container Database container name (default: app-db)"
echo " --redis-container Redis container name (default: app-redis)"
echo " --timeout HTTP request timeout in seconds (default: 10)"
exit 0
;;
*) echo "ERROR: Unknown argument: $1" >&2; exit 1 ;;
esac
done
# ─── Setup Output ────────────────────────────────────────────────────────────
mkdir -p "$OUTPUT_DIR"
TIMESTAMP=$(date -u +"%Y%m%dT%H%M%SZ")
RESULT_FILE="${OUTPUT_DIR}/health-all-services-${TIMESTAMP}.json"
LOG_FILE="${OUTPUT_DIR}/health-all-services-${TIMESTAMP}.log"
log() {
local msg="[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] $1"
echo "$msg" | tee -a "$LOG_FILE"
}
TOTAL=0
HEALTHY=0
UNHEALTHY=0
RESULTS=""
# ─── Check Function ─────────────────────────────────────────────────────────
check_http() {
local name="$1"
local url="$2"
local expected_status="${3:-200}"
TOTAL=$((TOTAL + 1))
log "Checking ${name}: ${url}"
local start_time
start_time=$(date +%s%N)
local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
--max-time "$TIMEOUT" "$url" 2>/dev/null) || http_code="000"
local end_time
end_time=$(date +%s%N)
local elapsed_ms=$(( (end_time - start_time) / 1000000 ))
local status="unhealthy"
if [[ "$http_code" == "$expected_status" ]]; then
status="healthy"
HEALTHY=$((HEALTHY + 1))
log " OK: ${name} returned ${http_code} in ${elapsed_ms}ms"
else
UNHEALTHY=$((UNHEALTHY + 1))
log " FAIL: ${name} returned ${http_code} (expected ${expected_status}) in ${elapsed_ms}ms"
fi
local entry="{\"name\":\"${name}\",\"url\":\"${url}\",\"status\":\"${status}\",\"http_code\":${http_code},\"response_time_ms\":${elapsed_ms}}"
if [[ -n "$RESULTS" ]]; then
RESULTS="${RESULTS},${entry}"
else
RESULTS="${entry}"
fi
}
check_container() {
local name="$1"
local container="$2"
local check_cmd="$3"
TOTAL=$((TOTAL + 1))
log "Checking ${name}: container ${container}"
local status="unhealthy"
local detail=""
# Check if container is running
if ! docker ps --format '{{.Names}}' | grep -q "^${container}$"; then
detail="Container not running"
UNHEALTHY=$((UNHEALTHY + 1))
log " FAIL: ${name} - container '${container}' not running"
else
# Run the health check command
if output=$(docker exec "$container" sh -c "$check_cmd" 2>&1); then
status="healthy"
detail="$output"
HEALTHY=$((HEALTHY + 1))
log " OK: ${name} is responsive"
else
detail="$output"
UNHEALTHY=$((UNHEALTHY + 1))
log " FAIL: ${name} - ${output}"
fi
fi
local detail_escaped
detail_escaped=$(echo "$detail" | tr '"' "'" | tr '\n' ' ' | head -c 200)
local entry="{\"name\":\"${name}\",\"container\":\"${container}\",\"status\":\"${status}\",\"detail\":\"${detail_escaped}\"}"
if [[ -n "$RESULTS" ]]; then
RESULTS="${RESULTS},${entry}"
else
RESULTS="${entry}"
fi
}
# ─── Run Health Checks ──────────────────────────────────────────────────────
log "=== Health Check: All Services ==="
log "Timestamp: ${TIMESTAMP}"
log ""
# Backend health checks
log "--- Backend ---"
check_http "Backend Liveness" "${BACKEND_URL}/health" "200"
check_http "Backend Readiness" "${BACKEND_URL}/health/ready" "200"
check_http "Backend API" "${BACKEND_URL}/api/v1/" "200"
log ""
# Frontend health checks
log "--- Frontend ---"
check_http "Frontend" "${FRONTEND_URL}/" "200"
log ""
# Database health checks
log "--- Database ---"
check_container "PostgreSQL" "${DB_CONTAINER}" "pg_isready -U postgres"
log ""
# Redis health checks
log "--- Redis ---"
check_container "Redis" "${REDIS_CONTAINER}" "redis-cli ping"
log ""
# ─── Docker Container Status ────────────────────────────────────────────────
log "--- Container Status ---"
CONTAINER_STATUS=$(docker ps --format "{{.Names}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "Docker not available")
log "$CONTAINER_STATUS"
log ""
# ─── Write Results ──────────────────────────────────────────────────────────
OVERALL="healthy"
if [[ $UNHEALTHY -gt 0 ]]; then
OVERALL="unhealthy"
fi
cat > "$RESULT_FILE" <<EOJSON
{
"timestamp": "${TIMESTAMP}",
"overall_status": "${OVERALL}",
"total_checks": ${TOTAL},
"healthy": ${HEALTHY},
"unhealthy": ${UNHEALTHY},
"services": {
"backend_url": "${BACKEND_URL}",
"frontend_url": "${FRONTEND_URL}",
"db_container": "${DB_CONTAINER}",
"redis_container": "${REDIS_CONTAINER}"
},
"results": [${RESULTS}]
}
EOJSON
# ─── Summary ─────────────────────────────────────────────────────────────────
log "=== Health Check Summary ==="
log "Overall: ${OVERALL}"
log "Total: ${TOTAL} Healthy: ${HEALTHY} Unhealthy: ${UNHEALTHY}"
log "Results: ${RESULT_FILE}"
log "Log: ${LOG_FILE}"
if [[ $UNHEALTHY -gt 0 ]]; then
log ""
log "WARNING: ${UNHEALTHY} service(s) are unhealthy"
exit 1
fi
log "ALL SERVICES HEALTHY"
exit 0