# Observability

> Patterns for metrics, tracing, logging, alerting, dashboards, and SLO definition

- Skill: `navraj007in/observability` (Agent Skill)
- Install (CLI): `npx skillmds@latest add navraj007in/observability`
- Raw SKILL.md: https://api.skillmd.com/api/skills/navraj007in/observability/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: navraj007in (https://skillmd.com/u/navraj007in)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/navraj007in/observability

---


# Observability & Monitoring Skill

Comprehensive observability strategy: metrics collection, distributed tracing, structured logging, alerting rules, dashboard design, and SLO/SLA definitions.

## Overview

Observability answers "what is happening in production?" across three pillars:

1. **Metrics** — numeric signals over time (CPU %, error rate, latency p99)
2. **Logs** — structured text events from code (request IDs, decisions, state changes)
3. **Traces** — request flows across services (entry → service A → service B → exit)

This skill provides framework-agnostic patterns and thresholds for building each pillar.

## Pillar 1: Metrics

### RED Method (Request-driven Services)

For services handling requests, collect three metrics per endpoint:

| Metric | Prometheus | Purpose | Query |
|--------|-----------|---------|-------|
| **Rate** | `http_requests_total` | requests per second | `rate(http_requests_total[1m])` |
| **Errors** | `http_requests_total{status=~"5.."}` | failed % | `rate(http_requests_total{status=~"5.."}[1m]) / rate(http_requests_total[1m])` |
| **Duration** | `http_request_duration_seconds` | latency histogram | `histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[1m]))` |

**Implementation pattern (Node.js + Prometheus client):**

```typescript
import promClient from 'prom-client';

const httpRequestDuration = new promClient.Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request latency in seconds',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.001, 0.01, 0.05, 0.1, 0.5, 1, 2, 5]
});

const httpRequestTotal = new promClient.Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'route', 'status_code']
});

// Middleware (Express)
app.use((req, res, next) => {
  const startTime = Date.now();
  res.on('finish', () => {
    const duration = (Date.now() - startTime) / 1000;
    httpRequestDuration
      .labels(req.method, req.route?.path || req.url, res.statusCode)
      .observe(duration);
    httpRequestTotal
      .labels(req.method, req.route?.path || req.url, res.statusCode)
      .inc();
  });
  next();
});
```

### USE Method (Resource-driven Services)

For background workers or batch services, collect three metrics per resource:

| Metric | Prometheus | Purpose | Alert threshold |
|--------|-----------|---------|------------------|
| **Utilization** | `process_cpu_seconds_total`, `process_resident_memory_bytes` | % of capacity used | CPU > 70%, Memory > 80% |
| **Saturation** | `job_queue_length`, `db_connection_pool_active` | items waiting | queue > 100 items |
| **Errors** | `task_failures_total`, `db_connection_errors_total` | failed operations | > 1% of operations |

**Example (background worker):**

```typescript
const jobQueueLength = new promClient.Gauge({
  name: 'job_queue_length',
  help: 'Number of jobs waiting in queue'
});

const taskFailures = new promClient.Counter({
  name: 'task_failures_total',
  help: 'Total failed tasks',
  labelNames: ['task_type', 'error_code']
});

// Worker loop
async function processJob(job: Job) {
  try {
    jobQueueLength.set(await queue.length());
    await job.execute();
  } catch (error) {
    taskFailures.labels(job.type, error.code).inc();
    throw error;
  }
}
```

### Golden Signals (All Services)

Monitor these four signals universally:

| Signal | Metric | MVP | Growth | Enterprise |
|--------|--------|-----|--------|------------|
| **Latency** | p50, p95, p99 | p99 < 500ms | p99 < 200ms | p99 < 100ms |
| **Traffic** | requests/sec | > 0 | track trends | auto-scale rule |
| **Errors** | error rate % | < 1% | < 0.5% | < 0.1% |
| **Saturation** | queue depth, CPU % | manual review | alert @ 70% CPU | auto-scale @ 60% |

**Prometheus dashboard queries:**

```promql
# Latency (p99)
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# Request rate
sum(rate(http_requests_total[1m]))

# Error rate
sum(rate(http_requests_total{status=~"5.."}[1m])) / sum(rate(http_requests_total[1m]))

# CPU utilization
rate(process_cpu_seconds_total[1m]) * 100
```

## Pillar 2: Distributed Tracing

### OpenTelemetry Semantic Conventions

Standard attribute names ensure interoperability across tools (Datadog, Jaeger, New Relic, etc.).

**HTTP attributes:**

```
http.request.method          "GET" | "POST" | ...
http.url                     "https://example.com/users?id=123"
http.target                  "/users?id=123" (path + query)
http.host                    "example.com"
http.status_code             200, 404, 500, ...
http.request.body.size       bytes
http.response.body.size      bytes
```

**Database attributes:**

```
db.system                    "postgresql" | "mysql" | "mongodb" | ...
db.name                      "users_db"
db.statement                 "SELECT * FROM users WHERE id = ?"
db.connection_string         "postgresql://..." (scrubbed of credentials)
db.operation                 "SELECT" | "INSERT" | "UPDATE" | "DELETE"
db.rows_affected             N
```

**Service/span attributes:**

```
service.name                 "api-server"
service.version              "0.1.0"
trace.id                     unique identifier
span.id                      unique per span
span.parent_id               parent span ID (or root span has no parent)
span.kind                    "SERVER" | "CLIENT" | "INTERNAL" | "PRODUCER" | "CONSUMER"
span.status                  "OK" | "ERROR" | "UNSET"
```

### Trace Context Propagation

Pass trace IDs across service boundaries for end-to-end visibility.

**HTTP headers (W3C Trace Context standard):**

```http
traceparent: version-traceId-spanId-traceFlags
Example: traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
```

**Implementation (Node.js + OpenTelemetry):**

```typescript
import { getTracer } from '@opentelemetry/api';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';

const tracer = getTracer('api-server');

// Outbound HTTP client call
const span = tracer.startSpan('external-api-call', {
  attributes: {
    'http.method': 'GET',
    'http.url': 'https://dependency-api.example.com/data',
    'span.kind': 'CLIENT'
  }
});

// Automatic propagation via fetch or axios with OpenTelemetry instrumentation
const response = await fetch('https://dependency-api.example.com/data');
span.end();
```

### Trace Sampling Rules

Control what fraction of requests are traced (100% tracing is expensive; smart sampling targets slow/errored requests).

**Sampling strategies by stage:**

| Stage | Sampling rule | Example |
|-------|---------------|---------|
| MVP | 100% (trace all) | `ratio=1.0` — smaller user base, capture everything |
| Growth | Error + tail latency | Trace all errors + slowest 5% (`ratio=0.05` for success paths) |
| Enterprise | Intelligent sampling | Datadog intelligent sampling, New Relic tail-based sampling |

**Implementation (Node.js):**

```typescript
import { NodeTracerProvider, BatchSpanProcessor } from '@opentelemetry/node';
import { ProbabilitySampler } from '@opentelemetry/core';

const provider = new NodeTracerProvider({
  sampler: new ProbabilitySampler(0.1) // 10% sampling for growth stage
});
```

**Tail-based sampling rules (Enterprise):**

```yaml
# Example: sample all errors + slowest 1%
tail_sampling:
  policies:
    - name: error-policy
      type: status_code
      status_code:
        status_codes: [ERROR]
    
    - name: latency-policy
      type: latency
      latency:
        threshold_ms: 1000
        upper_threshold_ms: 5000
```

## Pillar 3: Structured Logging

### Log Levels and Guidelines

Use standardized levels consistently:

| Level | When to use | Example |
|-------|------------|---------|
| **DEBUG** | Development only; very detailed state | `db_query_params: {...}`, `cache_hit: true` |
| **INFO** | Notable events in request flow | `"user created"`, `"payment processed"`, `"email sent"` |
| **WARN** | Recoverable issue needing attention | `"retry after 3 failures"`, `"fallback to default value"` |
| **ERROR** | Request/operation failed, needs investigation | `"database connection timeout"`, `"payment gateway returned 500"` |
| **FATAL** | Service cannot continue, immediate human action needed | `"out of disk space"`, `"database unreachable"` |

**Guidelines:**
- Never log at DEBUG in production (disable via config)
- Use ERROR only for actual errors (not every validation failure)
- WARN for things that worked but are sub-optimal
- Include request context (trace ID, user ID) in every log

### Structured Log Fields

Use consistent field names across all services for dashboard aggregation:

**Request context (always include in every log):**

```json
{
  "trace_id": "0af7651916cd43dd",
  "span_id": "b7ad6b716920",
  "request_id": "req-xyz",
  "user_id": "user-123",
  "tenant_id": "org-456",
  "session_id": "sess-789"
}
```

**Business context:**

```json
{
  "entity_type": "order",
  "entity_id": "order-001",
  "action": "create",
  "status": "pending",
  "amount": 123.45,
  "currency": "USD"
}
```

**Execution context:**

```json
{
  "service": "api-server",
  "version": "0.1.0",
  "environment": "production",
  "function": "processPayment",
  "duration_ms": 234
}
```

**Error context (only when level is ERROR or FATAL):**

```json
{
  "error_code": "PAYMENT_TIMEOUT",
  "error_message": "Payment gateway did not respond within 30s",
  "error_stack": "...stack trace...",
  "retry_count": 2,
  "retriable": true
}
```

### Structured Logging Implementation

**Node.js (Winston):**

```typescript
import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.json(),
  defaultMeta: {
    service: 'api-server',
    version: process.env.APP_VERSION,
    environment: process.env.NODE_ENV
  },
  transports: [
    new winston.transports.Console({
      format: winston.format.simple()
    }),
    new winston.transports.File({
      filename: 'logs/error.log',
      level: 'error',
      format: winston.format.json()
    })
  ]
});

// Log with context
logger.info('order created', {
  trace_id: req.traceId,
  user_id: req.userId,
  entity_type: 'order',
  entity_id: orderId,
  amount: 123.45,
  duration_ms: Date.now() - startTime
});

logger.error('payment failed', {
  trace_id: req.traceId,
  error_code: error.code,
  error_message: error.message,
  retry_count: retries,
  retriable: error.retriable
});
```

**Python (structlog):**

```python
import structlog

logger = structlog.get_logger()

logger.info(
    "order_created",
    trace_id=trace_id,
    user_id=user_id,
    entity_type="order",
    entity_id=order_id,
    amount=123.45,
    duration_ms=elapsed_time
)

logger.error(
    "payment_failed",
    trace_id=trace_id,
    error_code=error.code,
    error_message=str(error),
    retry_count=retries
)
```

**Log aggregation (Loki/ELK/Datadog):**

All logs are JSON-serialized. Log aggregation systems parse and index these fields automatically.

Example query (Grafana Loki):

```
{service="api-server", level="error"} | json | error_code != ""
```

## Alerting Rules

### Alert Thresholds by Stage

Use these thresholds as baselines; adjust per service's SLA.

**MVP stage:**

| Alert | Threshold | Duration | Action |
|-------|-----------|----------|--------|
| **Service Down** | Status code 5xx > 10% | 1 min | Page on-call |
| **High Latency** | p95 > 500ms | 5 min | Monitor; page if sustained |
| **Error Spike** | 5x baseline error rate | 2 min | Page on-call |

**Growth stage:**

| Alert | Threshold | Duration | Action |
|-------|-----------|----------|--------|
| **Service Down** | Status code 5xx > 5% | 2 min | Page on-call |
| **High Latency** | p95 > 200ms | 10 min | Page on-call |
| **High Latency** | p99 > 500ms | 5 min | Page on-call |
| **Error Spike** | Error rate > 1% | 5 min | Create incident |
| **CPU Saturation** | > 70% for 10 min | 10 min | Auto-scale or page |
| **Memory Leak** | Memory usage rising > 50%/hour | sustained | Page on-call |

**Enterprise stage:**

| Alert | Threshold | Duration | Action |
|-------|-----------|----------|--------|
| **Service Degradation** | Status code 5xx > 0.5% | 1 min | Create incident |
| **Latency SLO Miss** | p99 > SLO target | 5 min | Create incident |
| **Error Budget Burn** | Consumed > 10%/day | realtime | Page on-call |
| **CPU Saturation** | > 60% for 5 min | sustained | Auto-scale |
| **Database Connection Pool** | Active > 80% | 5 min | Page DBA |
| **Disk Space** | Free < 10% | realtime | Critical alert |

### Prometheus Alert Rules (YAML)

**File location:** `monitoring/alerts/rules.yaml` or integrated into Prometheus config

```yaml
groups:
  - name: api-server
    interval: 30s
    rules:
      # MVP threshold
      - alert: HighErrorRate
        expr: |
          (sum(rate(http_requests_total{status=~"5.."}[1m])) 
           / sum(rate(http_requests_total[1m]))) > 0.1
        for: 1m
        annotations:
          summary: "High error rate (>10%) in api-server"
          description: "Error rate: {{ $value | humanizePercentage }}"

      # Growth threshold
      - alert: HighLatencyP95
        expr: |
          histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.2
        for: 10m
        annotations:
          summary: "High latency (p95 > 200ms)"

      # CPU saturation (growth stage)
      - alert: HighCPUUsage
        expr: |
          rate(process_cpu_seconds_total[1m]) > 0.7
        for: 10m
        annotations:
          summary: "CPU utilization > 70%"

      # Memory leak detection
      - alert: MemoryLeak
        expr: |
          rate(process_resident_memory_bytes[1h]) > 52428800  # 50 MiB/hour
        for: 30m
        annotations:
          summary: "Possible memory leak (memory growing > 50 MiB/hour)"
```

## SLO and SLA Definition

### SLO vs SLA

| Term | Means | Owner | Consequence |
|------|-------|-------|------------|
| **SLO** (Service Level Objective) | Internal goal (e.g., "99% uptime") | Engineering team | Guides investment + on-call |
| **SLA** (Service Level Agreement) | Promise to customers; has penalties | Product/Legal | Financial | Compensation if breached |

SLOs are typically **more aggressive** than SLAs (SLO: 99.9%, SLA: 99% — gives 0.9% buffer).

### SLO Template (per service)

```markdown
## [Service Name] SLO

### Availability
- **Target:** 99.5% uptime
- **Budget:** 21.6 minutes downtime/month
- **Measurement:** HTTP status 2xx or 3xx / total requests

### Latency
- **Target:** 95th percentile < 200ms
- **Measurement:** p95(request duration) measured over 1-minute windows

### Error Rate
- **Target:** Error rate < 0.5%
- **Measurement:** 5xx responses / total requests

### Duration & Review
- **Quarter:** Q2 2026 (Apr-Jun)
- **Review:** Monthly; escalate if budget consumed > 33%/month
```

### Error Budget Tracking

Once SLO is defined, calculate "error budget" — how much failure is acceptable:

```
Availability SLO: 99.5%
Allowed downtime per month: (1 - 0.995) × 30 days × 24 hours = ~21.6 minutes

If 5 minutes of unexpected downtime occurs on April 3:
  Remaining budget: 21.6 - 5 = 16.6 minutes for the rest of April
  % consumed: (5 / 21.6) × 100 = 23% of monthly budget
```

**Decision rule:**
- If < 33% budget consumed in first week → proceed with risky changes/deployments
- If > 66% budget consumed → freeze risky changes, focus on stability

## Dashboard Design

### Essential Dashboards (by service type)

**REST API service:**
- RED metrics (rate, errors, duration p50/p95/p99)
- Instance health (CPU, memory, disk)
- Database connection pool (active, idle, wait queue)
- Cache hit rate (if applicable)

**Background worker:**
- USE metrics (utilization, saturation, errors)
- Job queue length and processing time
- Failed job count and retry rate
- Resource trends (CPU/memory over 24h)

**Web frontend:**
- Core Web Vitals (LCP, FID, CLS)
- JavaScript errors (tracked via Sentry/Rollbar)
- User session count and session duration
- Page load performance by route

**Database:**
- Query latency (p95, p99)
- Connection pool (active, idle, max)
- Replication lag (if applicable)
- Disk usage trend
- Slow query count

### Prometheus + Grafana Setup

**Prometheus configuration** (`prometheus.yml`):

```yaml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'api-server'
    static_configs:
      - targets: ['localhost:3000']
    metrics_path: '/metrics'
    scrape_interval: 10s

  - job_name: 'database'
    static_configs:
      - targets: ['localhost:9187']  # postgres_exporter
```

**Grafana dashboard (JSON):**

```json
{
  "dashboard": {
    "title": "API Server — RED Metrics",
    "panels": [
      {
        "title": "Request Rate",
        "targets": [{
          "expr": "sum(rate(http_requests_total[1m]))"
        }]
      },
      {
        "title": "Error Rate",
        "targets": [{
          "expr": "sum(rate(http_requests_total{status=~\"5..\"}[1m])) / sum(rate(http_requests_total[1m]))"
        }]
      },
      {
        "title": "Latency P99",
        "targets": [{
          "expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))"
        }]
      }
    ]
  }
}
```

## Multi-Cloud Observability

### Provider-Specific SDKs

Integrate with cloud-native observability platforms for rich integrations.

**Datadog:**

```typescript
import { datadogRum } from '@datadog/browser-rum';
import tracer from 'dd-trace';

// Backend
tracer.init(); // Auto-instruments HTTP, DB, cache
tracer.trace('custom-operation', () => {
  // custom logic
});

// Frontend
datadogRum.init({
  applicationId: 'app-id',
  clientToken: 'token',
  site: 'datadoghq.com',
  service: 'web-app',
  env: 'production',
  sessionSampleRate: 100,
  sessionReplaySampleRate: 20,
  trackUserInteractions: true
});
```

**New Relic:**

```typescript
const newrelic = require('newrelic');

newrelic.instrumentLoadedModule('pg', new newrelic.API.QuerySpec({
  operation: 'query'
}));

newrelic.startSegment('custom-segment', false, () => {
  // custom logic
});
```

**AWS CloudWatch:**

```typescript
import { CloudWatchClient, PutMetricDataCommand } from "@aws-sdk/client-cloudwatch";

const client = new CloudWatchClient({ region: 'us-east-1' });
await client.send(new PutMetricDataCommand({
  Namespace: 'api-server',
  MetricData: [{
    MetricName: 'RequestCount',
    Value: 1,
    Unit: 'Count'
  }]
}));
```

## Observability Checklist

- [ ] **Metrics**: RED (rate, errors, duration) for all APIs; USE (utilization, saturation, errors) for workers
- [ ] **Tracing**: OpenTelemetry initialized; trace IDs propagated across service boundaries
- [ ] **Logging**: Structured JSON logs with trace_id, user_id, entity_id, service, environment
- [ ] **Alerting**: Alert rules defined per stage (MVP/Growth/Enterprise); on-call escalation path documented
- [ ] **SLO**: Availability, latency, and error rate targets documented; error budget calculated
- [ ] **Dashboards**: RED dashboard, resource dashboard, SLO dashboard created in Grafana/Datadog
- [ ] **Log retention**: Retention policy documented (e.g., 30 days for INFO, 90 days for ERROR)
- [ ] **Privacy**: No PII, API keys, or credentials in logs; sensitive fields masked or hashed

