Logs & Output Streams

Understanding how Kubernetes captures and handles logs from containers is fundamental to effective debugging and monitoring. This guide explains stdout/stderr redirection, log collection, rotation, and best practices for containerized applications.

Understanding Container Logs

In Kubernetes, container logs are captured from standard output (stdout) and standard error (stderr) streams. Unlike traditional applications that write to log files, containerized applications should write all output to these streams.

graph TB A[Application Process] --> B[stdout] A --> C[stderr] B --> D[Container Runtime] C --> D D --> E[Kubelet] E --> F[Node Storage] F --> G[/var/log/pods/] F --> H[/var/log/containers/] I[Log Rotation] --> F J[Log Collection] --> F style A fill:#e1f5ff style D fill:#e8f5e9 style E fill:#fff4e1 style F fill:#f3e5f5

stdout and stderr

stdout (Standard Output)

Normal program output goes to stdout:

  • Application logs (INFO, DEBUG levels)
  • Application output
  • Successful operations

stderr (Standard Error)

Error output goes to stderr:

  • Error messages
  • Warnings
  • Stack traces
  • Fatal errors

Why Use stdout/stderr?

Kubernetes automatically captures and aggregates logs from these streams:

  • Automatic collection - No need to configure log file locations
  • Pod-level aggregation - All containers in a pod can be accessed together
  • API access - Access logs via kubectl logs
  • Standard practice - Follows 12-factor app principles

How Kubernetes Captures Logs

Container Runtime Capture

The container runtime (containerd, CRI-O, Docker) captures stdout/stderr:

sequenceDiagram participant App as Application participant Runtime as Container Runtime participant Kubelet as Kubelet participant Node as Node Filesystem App->>Runtime: Write to stdout/stderr Runtime->>Runtime: Capture stream Runtime->>Kubelet: Expose via CRI Kubelet->>Node: Store logs Node->>Node: Rotate logs

Log Storage Locations

Logs are stored on the node’s filesystem:

Pod logs:

/var/log/pods/<namespace>_<pod-name>_<pod-uid>/
  <container-name>/
    <instance-number>.log

Container logs (symlinks):

/var/log/containers/<pod-name>_<namespace>_<container-name>-<container-id>.log

These paths are symlinks managed by kubelet for easier access.

Viewing Logs

kubectl logs

The primary way to view logs:

# View logs from pod
kubectl logs <pod-name>

# Follow logs in real-time
kubectl logs -f <pod-name>

# View logs from previous container instance
kubectl logs <pod-name> --previous

# View last N lines
kubectl logs <pod-name> --tail=100

# View logs with timestamps
kubectl logs <pod-name> --timestamps

# View logs since specific time
kubectl logs <pod-name> --since=1h

# View logs from specific time range
kubectl logs <pod-name> --since-time='2024-01-01T00:00:00Z'

Multi-Container Pods

# List containers in pod
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'

# View logs from specific container
kubectl logs <pod-name> -c <container-name>

# View logs from all containers
kubectl logs <pod-name> --all-containers=true

Aggregating Logs from Multiple Pods

# View logs from all pods with label
kubectl logs -l app=my-app

# View logs from all pods in namespace
kubectl logs -l app=my-app -n production

# Aggregate and follow
kubectl logs -f -l app=my-app --prefix=true

Streaming Logs

# Follow logs (like tail -f)
kubectl logs -f <pod-name>

# Follow logs from multiple pods
kubectl logs -f -l app=my-app

# Follow with prefix showing pod name
kubectl logs -f -l app=my-app --prefix=true

Log Format

Default Log Format

By default, Kubernetes logs include:

  • Timestamp - When the log entry was written
  • Stream - stdout or stderr
  • Content - The actual log message

Example:

2024-01-15T10:30:45.123456789Z stdout F This is a log message
2024-01-15T10:30:45.234567890Z stderr F This is an error message

Structured Logging

For better parsing and analysis, use structured formats (JSON):

{"timestamp":"2024-01-15T10:30:45Z","level":"INFO","message":"Request processed","method":"GET","path":"/api/users","status":200,"duration_ms":45}

Benefits:

  • Easy parsing by log aggregation tools
  • Better filtering and searching
  • Support for nested structures
  • Consistent format across services

Log Rotation

Kubernetes automatically rotates logs to prevent disk space issues:

Rotation Policy

  • Size limit: 10MB per log file (default)
  • Number of files: Maximum 5 log files per container (default)
  • Rotation: When size limit is reached
  • Cleanup: Oldest files are deleted automatically

Configuration

Log rotation is controlled by kubelet configuration:

# kubelet config (partial)
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
containerLogMaxSize: 10Mi
containerLogMaxFiles: 5

Log Retention

  • Logs are kept on the node until rotated out
  • No built-in long-term retention
  • For retention, use centralized logging solutions

Log Aggregation Patterns

Sidecar Pattern

A sidecar container collects logs from the application container:

apiVersion: v1
kind: Pod
metadata:
  name: app-with-log-sidecar
spec:
  containers:
  - name: app
    image: my-app:latest
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
  - name: log-collector
    image: fluent-bit:latest
    volumeMounts:
    - name: logs
      mountPath: /var/log/app
      readOnly: true
  volumes:
  - name: logs
    emptyDir: {}

DaemonSet Pattern

A DaemonSet runs on every node to collect logs:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluent-bit
spec:
  selector:
    matchLabels:
      name: fluent-bit
  template:
    metadata:
      labels:
        name: fluent-bit
    spec:
      containers:
      - name: fluent-bit
        image: fluent/fluent-bit:latest
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers

Common Log Output Issues

Missing Logs

Problem: Logs not appearing in kubectl logs

Causes:

  • Application not writing to stdout/stderr
  • Logs written to files instead of streams
  • Container crashed before logs were flushed

Solution:

# Check if container is running
kubectl get pod <pod-name>

# Check previous instance logs
kubectl logs <pod-name> --previous

# Verify application writes to stdout
kubectl exec <pod-name> -- env | grep LOG

Truncated Logs

Problem: Logs are cut off or incomplete

Causes:

  • Log rotation during high-volume logging
  • Buffer overflow
  • Application not flushing buffers

Solution:

# View logs from previous instances
kubectl logs <pod-name> --previous

# Check all rotated log files on node
# (requires node access)
ls -la /var/log/pods/<namespace>_<pod-name>_*/

High Log Volume

Problem: Too many logs causing performance issues

Causes:

  • Verbose logging (DEBUG level)
  • Logging in tight loops
  • Including unnecessary data

Solution:

  • Use appropriate log levels
  • Implement log sampling
  • Use structured logging efficiently
  • Configure log rotation appropriately

No Timestamps

Problem: Logs missing timestamp information

Causes:

  • Application not including timestamps
  • Using –timestamps flag not set

Solution:

# View with timestamps
kubectl logs <pod-name> --timestamps

# Or configure application to include timestamps

Best Practices

1. Write to stdout/stderr

Always write logs to stdout/stderr, not files:

# Good
import sys
print("Log message", file=sys.stdout)

# Bad
with open("/var/log/app.log", "a") as f:
    f.write("Log message\n")

2. Use Structured Logging

Use JSON or other structured formats:

import json
import sys

log_entry = {
    "timestamp": "2024-01-15T10:30:45Z",
    "level": "INFO",
    "message": "Request processed",
    "request_id": "abc123"
}
print(json.dumps(log_entry), file=sys.stdout)

3. Set Appropriate Log Levels

Use log levels appropriately:

  • DEBUG: Detailed information for debugging
  • INFO: General informational messages
  • WARN: Warning messages
  • ERROR: Error messages
  • FATAL: Critical errors

4. Include Context

Add contextual information to logs:

{
  "timestamp": "2024-01-15T10:30:45Z",
  "level": "INFO",
  "message": "Request processed",
  "user_id": "123",
  "request_id": "abc123",
  "duration_ms": 45,
  "status_code": 200
}

5. Avoid Sensitive Data

Never log:

  • Passwords
  • API keys
  • Tokens
  • Personal information (PII)
  • Credit card numbers

6. Flush Logs Regularly

Ensure logs are flushed, especially for crash scenarios:

import sys
sys.stdout.flush()
sys.stderr.flush()

7. Use Log Solutions

For production, use log solutions:

  • ELK Stack
  • Loki
  • Datadog
  • CloudWatch (AWS)
  • Azure Monitor (Azure)

Accessing Logs Directly on Node

If you have node access, you can view logs directly:

# View pod logs
cat /var/log/pods/<namespace>_<pod-name>_<pod-uid>/<container-name>/<instance>.log

# Follow logs
tail -f /var/log/pods/<namespace>_<pod-name>_<pod-uid>/<container-name>/<instance>.log

# View container logs (symlink)
tail -f /var/log/containers/<pod-name>_<namespace>_<container-name>-<container-id>.log

Log Levels and Filtering

Filtering by Log Level

If using structured logging with levels:

# View only ERROR logs (requires jq)
kubectl logs <pod-name> | jq 'select(.level == "ERROR")'

# View logs from last hour
kubectl logs <pod-name> --since=1h

Searching Logs

# Search for specific text
kubectl logs <pod-name> | grep "ERROR"

# Case-insensitive search
kubectl logs <pod-name> | grep -i "error"

# Count occurrences
kubectl logs <pod-name> | grep -c "ERROR"

See Also