Observability

How to make a running system explainable from the outside: what metrics, logs, traces and wide events each answer, why cardinality is the cost driver, how sampling and context propagation actually work, and the failure modes that turn an observability stack into expensive noise.

Observability is the practice of making system behavior visible through structured data collection, analysis, and alerting. It matters when you need to understand what your systems are doing in production without direct access to their internals, particularly during incidents or performance degradation.

Understanding System Behavior Through Logs

Collecting Logs from Applications

Applications emit logs as structured JSON or plain text output to standard error or standard out. For containers running on Kubernetes, logs are collected by the container runtime and stored in the node's filesystem.

Example of a log line:

{"timestamp":"2023-10-15T14:30:45.123Z","level":"ERROR","message":"Database connection failed","error":"connection timeout after 30s"}

To collect logs from a pod in Kubernetes:

kubectl logs <pod-name> -n <namespace>

Searching Logs with Timestamps

Logs are usually timestamped and stored in chronological order. When investigating an incident, you may need to search within a time window.

Example command to get logs from the last hour:

kubectl logs --since=1h <pod-name> -n <namespace>

If your system uses a log aggregation platform (like Elasticsearch or Splunk), you can query logs using their respective query languages.

Filtering Log Messages

Most log systems support filtering by level, message content, or metadata fields. For example, in a system where logs are structured as JSON, you might filter by error type:

kubectl logs <pod-name> -n <namespace> | grep '"error":"connection timeout"'

Monitoring System Metrics

Collecting Metrics from Processes

Metrics are numeric values that represent system state over time. They are typically collected by agents running on each host or in containers.

Example metric line:

http_requests_total{method="GET",endpoint="/api/users",status="200"} 1234

Using Prometheus for Metrics Collection

Prometheus scrapes metrics endpoints exposed by applications. The scrape configuration is defined in a YAML file:

scrape_configs:
  - job_name: 'my-app'
    static_configs:
      - targets: ['localhost:8080']

To add a metric to your application, you can use the Prometheus client library:

import "github.com/prometheus/client_golang/prometheus"

var httpRequestCount = prometheus.NewCounterVec(
    prometheus.CounterOpts{
        Name: "http_requests_total",
        Help: "Total number of HTTP requests",
    },
    []string{"method", "endpoint", "status"},
)

func main() {
    prometheus.MustRegister(httpRequestCount)
    // ...
}

Querying Metrics

Once collected, metrics are queried using PromQL (Prometheus Query Language). To find total requests in the last 5 minutes:

rate(http_requests_total[5m])

Alerting on System Behavior

Creating Alerts Based on Metrics

Alerts are defined by threshold conditions that trigger when metric values cross certain boundaries. For Prometheus, alert rules are written in YAML and stored in a file:

groups:
  - name: app-alerts
    rules:
      - alert: HighErrorRate
        expr: rate(http_requests_total{status!="200"}[5m]) > 0.1
        for: 2m
        labels:
          severity: page
        annotations:
          summary: "High error rate detected"

Alerting on Log Patterns

Some systems support alerting based on log patterns. For example, if a particular error message appears more than once per minute:

kubectl logs <pod-name> -n <namespace> --since=1m | grep -c "Database connection failed"

If the count is greater than 1, this could trigger an alert.

Correlating Logs and Metrics

Matching Log Events with Metrics

When you see a log message indicating an error, you should correlate it with metrics from that time. If your application logs a request failure, you want to check if there's a corresponding spike in error rates or latency.

Example workflow:

  1. Identify failing requests in logs
  2. Query metrics for the same time period
  3. Check for correlations between error counts and resource usage

Using Trace IDs to Follow Requests

If your system supports distributed tracing (like OpenTelemetry), you can correlate logs with trace IDs:

kubectl logs <pod-name> -n <namespace> | grep 'trace_id=12345'

Understanding Alert Behavior

Alert Suppression

Alerts may be suppressed during known maintenance windows or when other alerts are already active. This is typically handled by the alerting system configuration.

Example of a suppression rule:

- name: maintenance_window
  conditions:
    - type: time_window
      start: "02:00"
      end: "06:00"

Alert Firing and Resolving

An alert fires when its condition is met for the specified duration (for clause in PromQL). It resolves when the condition is no longer true.

Detecting Silent Failures

Missing Metrics or Logs

A common silent failure occurs when logs or metrics are not being collected. This can happen due to:

To detect missing data, you should monitor the ingestion rate of your observability tools.

Metric Cardinality Issues

High cardinality in metrics (many unique label combinations) can cause performance problems or make queries slow. For example, a metric with labels like:

http_requests_total{method="GET",endpoint="/api/users/12345",status="200"}

is less efficient than:

http_requests_total{method="GET",endpoint="/api/users",status="200"}

Handling Common Failures

Data Loss in Log Collection

If logs are not being collected, the system might silently drop them or continue running without visibility. You can detect this by monitoring log volume or using a health check endpoint.

Example command to check if logs are flowing:

kubectl logs <pod-name> -n <namespace> | tail -n 10

If no output appears, or if you see errors like "EOF" or "connection reset", it indicates the log collection is failing.

Alerting on Unusual Patterns

Alerts should not just react to absolute thresholds but also detect unusual patterns. For instance, a sudden change in request latency distribution might indicate an issue even if the average hasn't crossed a threshold.

Example pattern detection:

kubectl logs <pod-name> -n <namespace> | grep 'latency' | awk '{print $NF}' | sort -n | head -10

This will show the 10 lowest latency values, which might reveal an unexpected performance degradation.

Troubleshooting Observability Tools

If you cannot see logs or metrics from a specific application:

  1. Check if the application is configured to emit logs to stdout/stderr
  2. Verify that logging agents are running and healthy
  3. Confirm that metrics endpoints are accessible
  4. Examine network connectivity between pods and observability systems

Example check for agent status:

kubectl get pods -n monitoring

Monitoring Alerting System Health

The alerting system itself can fail. You should monitor:

Example command to verify Prometheus is scraping:

curl http://prometheus:9090/api/v1/targets

If targets are down, you'll see status "down" in the response.

Best Practices for Observability

Avoid Overlogging

Too many log messages can overwhelm systems and make relevant information harder to find. Log at appropriate levels:

Use Consistent Log Formats

All applications should use consistent log formats. This allows easy parsing and searching across systems.

Example of a consistent format:

{"timestamp":"2023-10-15T14:30:45.123Z","level":"ERROR","service":"auth-service","message":"Database connection failed","error":"connection timeout after 30s"}

Monitor Observability System Itself

The observability system is critical for understanding your systems. You should monitor:

This creates a feedback loop where you can detect failures in your observability infrastructure.

Keep Alerting Simple

Start with basic alerts and add complexity only when needed. Complex alerting logic is harder to debug and maintain.

Example of a simple alert:

rate(http_requests_total{status!="200"}[5m]) > 0.1

Example of a complex alert:

rate(http_requests_total{status!="200"}[5m]) > 0.1 and 
abs(rate(http_requests_total[5m]) - avg(rate(http_requests_total[5m])) by (job)) > 0.5

The complex version adds a comparison against average rates, which can be useful but also harder to understand when it triggers.

This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.