Portal | Level: L1: Foundations | Topics: Linux Logging, Linux Fundamentals, systemd, Logging | Domain: Linux
Linux Logging — Primer¶
Why This Matters¶
Logs are the black box recorder of your systems. When a service crashes, when a security incident occurs, when performance degrades, when a deployment fails — logs are usually the first (and sometimes only) place you can find out what happened. If you don't understand how Linux logging works, you're debugging with your eyes closed.
This primer covers the complete Linux logging stack: the kernel ring buffer, syslog infrastructure, systemd's journal, log rotation, audit logging, and practical patterns for operating log infrastructure at scale.
The Linux Logging Architecture¶
Modern Linux systems have multiple overlapping logging systems:
┌─────────────────────────────────────────────────────┐
│ Applications │
│ (write to stdout, syslog, or log files directly) │
└──────────────┬──────────────┬───────────────────────┘
│ │
┌───────▼───────┐ │
│ journald │ │ Direct file writes
│ (systemd) │ │ (/var/log/myapp/)
│ │ │
└───────┬───────┘ │
│ │
┌───────▼───────┐ │
│ rsyslog │◀────┘
│ (or syslog-ng)│
└───────┬───────┘
│
┌───────▼───────┐
│ /var/log/* │
│ (log files) │
└───────────────┘
│
┌───────▼───────┐
│ logrotate │
│ (rotation) │
└───────────────┘
Additionally, the kernel has its own ring buffer (accessible via dmesg), and the audit subsystem (auditd) provides security-focused logging.
Kernel Logging: dmesg and the Ring Buffer¶
The kernel maintains a circular buffer of messages. These include hardware detection, driver loading, filesystem errors, OOM kills, and other kernel-level events.
# View kernel ring buffer
$ dmesg
[ 0.000000] Linux version 5.15.0-91-generic ...
[ 0.000000] Command line: BOOT_IMAGE=/vmlinuz-5.15.0-91-generic ...
[ 3.456789] EXT4-fs (sda2): mounted filesystem with ordered data mode
[ 345.678901] e1000e 0000:00:19.0 eth0: Link is Up 1000 Mbps Full Duplex
[12345.678901] Out of memory: Killed process 1234 (java)
# Human-readable timestamps
$ dmesg -T
[Fri Mar 19 10:45:22 2026] e1000e 0000:00:19.0 eth0: Link is Up
# Filter by severity
$ dmesg --level=err
$ dmesg --level=warn,err,crit
# Follow (watch for new messages)
$ dmesg -w
# Clear the ring buffer (rarely needed)
$ sudo dmesg -c
The ring buffer is fixed-size (default 256 KB, adjustable via log_buf_len kernel parameter). Once full, old messages are overwritten. This is why critical kernel messages should also flow through journald/syslog to persistent storage.
Kernel Log Levels¶
| Level | Name | Example |
|---|---|---|
| 0 | EMERG | System is unusable |
| 1 | ALERT | Action must be taken immediately |
| 2 | CRIT | Critical conditions (hardware failures) |
| 3 | ERR | Error conditions |
| 4 | WARNING | Warning conditions |
| 5 | NOTICE | Normal but significant |
| 6 | INFO | Informational |
| 7 | DEBUG | Debug-level messages |
# View/set console log level
$ cat /proc/sys/kernel/printk
4 4 1 7
# Current | Default | Minimum | Boot-time default
# Only messages with severity < 4 (WARNING) go to console
The Syslog Protocol¶
Syslog is both a protocol (RFC 5424) and the traditional Unix logging infrastructure. Messages have two key properties:
Facility¶
The facility indicates what type of program generated the message:
| Code | Facility | Typical use |
|---|---|---|
| 0 | kern | Kernel messages |
| 1 | user | User-level messages |
| 2 | Mail system | |
| 3 | daemon | System daemons |
| 4 | auth | Security/authorization |
| 10 | authpriv | Security/authorization (private) |
| 16-23 | local0-local7 | Custom/local use |
Severity (Priority)¶
Same as kernel log levels (0=EMERG through 7=DEBUG). The combination of facility and severity is the priority: priority = facility × 8 + severity.
The logger Command¶
logger sends messages to syslog from the command line — invaluable for testing and scripting:
# Basic message
$ logger "Deploy completed successfully"
# With facility and severity
$ logger -p local0.info "Application started on port 8080"
# With a tag (identifies the program)
$ logger -t myapp "Connection pool initialized"
# In scripts
#!/bin/bash
logger -t deploy-script "Starting deployment of $APP_VERSION"
# ... deploy steps ...
if [ $? -eq 0 ]; then
logger -t deploy-script -p local0.info "Deployment successful"
else
logger -t deploy-script -p local0.err "Deployment FAILED"
fi
rsyslog¶
rsyslog is the standard syslog daemon on most Linux distributions. It receives syslog messages (from local processes, journald, and remote sources), processes them through rules, and writes them to files, databases, or remote servers.
Configuration¶
Main config: /etc/rsyslog.conf with drop-in files in /etc/rsyslog.d/.
# /etc/rsyslog.conf structure:
#### MODULES ####
module(load="imuxsock") # Local system socket (/dev/log)
module(load="imklog") # Kernel log messages
module(load="imjournal") # systemd journal input
module(load="imtcp") # TCP syslog receiver (for remote logs)
#### RULES ####
# Facility.Severity Action
auth,authpriv.* /var/log/auth.log
*.*;auth,authpriv.none /var/log/syslog
kern.* /var/log/kern.log
mail.* /var/log/mail.log
cron.* /var/log/cron.log
# Emergency messages to all logged-in users
*.emerg :omusrmsg:*
# Custom application logging
local0.* /var/log/myapp.log
# Forward to remote server
*.* @@logserver.example.com:514 # @@ = TCP, @ = UDP
rsyslog Templates¶
Templates control the format of log output:
# Traditional syslog format
template(name="TraditionalFormat" type="string"
string="%timegenerated% %HOSTNAME% %syslogtag%%msg%\n")
# JSON format for log aggregation
template(name="JSONFormat" type="string"
string="{\"timestamp\":\"%timegenerated:::date-rfc3339%\",\"host\":\"%HOSTNAME%\",\"program\":\"%programname%\",\"severity\":\"%syslogseverity-text%\",\"message\":\"%msg:::json%\"}\n")
# Use a template
local0.* action(type="omfile" file="/var/log/myapp.json" template="JSONFormat")
Rate Limiting¶
rsyslog has built-in rate limiting that can silently drop messages:
# /etc/rsyslog.conf
# Default: 200 messages per 5 seconds per source
$SystemLogRateLimitInterval 5
$SystemLogRateLimitBurst 200
# Disable rate limiting entirely (high-volume systems)
$SystemLogRateLimitInterval 0
# Or per-module
module(load="imjournal" ratelimit.interval="0")
syslog-ng¶
syslog-ng is an alternative to rsyslog, popular in some environments. Key differences:
- More powerful filtering syntax
- Better structured data handling
- Simpler config for complex routing
# /etc/syslog-ng/syslog-ng.conf
source s_local {
system();
internal();
};
destination d_auth { file("/var/log/auth.log"); };
destination d_syslog { file("/var/log/syslog"); };
destination d_remote { tcp("logserver.example.com" port(514)); };
filter f_auth { facility(auth, authpriv); };
filter f_not_auth { not facility(auth, authpriv); };
log { source(s_local); filter(f_auth); destination(d_auth); };
log { source(s_local); filter(f_not_auth); destination(d_syslog); };
log { source(s_local); destination(d_remote); };
systemd Journal (journald)¶
systemd's journal is a structured, binary logging system that captures: - Service stdout/stderr - Syslog messages - Kernel messages - Audit messages
journalctl Basics¶
# View all logs (newest last)
$ journalctl
# Follow in real time (like tail -f)
$ journalctl -f
# Show logs from current boot
$ journalctl -b
# Show logs from previous boot
$ journalctl -b -1
# Show only errors and above
$ journalctl -p err
# Show warnings and above
$ journalctl -p warning
Filtering by Service¶
# Logs for a specific service
$ journalctl -u nginx.service
# Logs for a service, follow mode
$ journalctl -u nginx.service -f
# Logs for multiple services
$ journalctl -u nginx.service -u php-fpm.service
# Since last service restart
$ journalctl -u nginx.service --since "$(systemctl show -p ActiveEnterTimestamp nginx.service | cut -d= -f2)"
Filtering by Time¶
# Logs since a specific time
$ journalctl --since "2026-03-19 10:00:00"
# Logs in a time range
$ journalctl --since "2026-03-19 10:00:00" --until "2026-03-19 11:00:00"
# Relative time
$ journalctl --since "1 hour ago"
$ journalctl --since "30 minutes ago"
$ journalctl --since yesterday
Output Formats¶
# Short format (default, like syslog)
$ journalctl -u nginx -o short
# Verbose (all metadata fields)
$ journalctl -u nginx -o verbose
# JSON (one object per line — great for piping to jq)
$ journalctl -u nginx -o json | jq .
# JSON pretty-printed
$ journalctl -u nginx -o json-pretty
# Cat (message only, no timestamps)
$ journalctl -u nginx -o cat
# Export (binary, for transport to another system)
$ journalctl -u nginx -o export > nginx-logs.journal
Structured Fields¶
The journal stores structured metadata with every message:
# Show all fields for a message
$ journalctl -u nginx -o verbose -n 1
Thu 2026-03-19 10:45:22.123456 UTC [s=abc123;i=1234;b=def456...]
_SYSTEMD_UNIT=nginx.service
_PID=12345
_UID=0
_GID=0
_COMM=nginx
_EXE=/usr/sbin/nginx
_HOSTNAME=webserver01
PRIORITY=6
SYSLOG_FACILITY=3
MESSAGE=10.0.0.1 - - [19/Mar/2026:10:45:22 +0000] "GET / HTTP/1.1" 200 612
_TRANSPORT=stdout
_STREAM_ID=xyz789
# Filter by arbitrary fields
$ journalctl _PID=12345
$ journalctl _COMM=sshd
$ journalctl _UID=1000
$ journalctl CONTAINER_NAME=myapp
Journal Storage Configuration¶
Journal configuration lives in /etc/systemd/journald.conf:
[Journal]
# Storage: volatile (memory only), persistent (/var/log/journal), auto (persistent if dir exists)
Storage=persistent
# Size limits
SystemMaxUse=2G # Max total disk usage
SystemKeepFree=4G # Keep this much free on the filesystem
SystemMaxFileSize=128M # Max size per journal file
RuntimeMaxUse=256M # Max memory usage (volatile mode)
# Rate limiting
RateLimitIntervalSec=30s
RateLimitBurst=10000
# Forwarding
ForwardToSyslog=yes # Forward to rsyslog
ForwardToKMsg=no
ForwardToConsole=no
# Compression
Compress=yes
# Sealing (for tamper evidence)
Seal=yes
After changing journal config:
Persistent Journal Storage¶
By default on many distros, journal data is stored in /run/log/journal/ (volatile — lost on reboot). To persist:
$ sudo mkdir -p /var/log/journal
$ sudo systemd-tmpfiles --create --prefix /var/log/journal
$ sudo systemctl restart systemd-journald
# Verify
$ journalctl --disk-usage
Archived and active journals take up 456.0M in /var/log/journal.
/var/log Directory Structure¶
The traditional log directory:
| File | Source | Content |
|---|---|---|
/var/log/syslog (Debian) or /var/log/messages (RHEL) |
rsyslog | General system messages |
/var/log/auth.log (Debian) or /var/log/secure (RHEL) |
rsyslog | Authentication, sudo, SSH |
/var/log/kern.log |
rsyslog | Kernel messages |
/var/log/cron.log |
rsyslog | Cron job execution |
/var/log/mail.log |
rsyslog | Mail system |
/var/log/boot.log |
systemd | Boot messages |
/var/log/dmesg |
init scripts | Kernel ring buffer at boot |
/var/log/dpkg.log or /var/log/yum.log |
Package manager | Package install/remove history |
/var/log/lastlog |
login | Last login per user (binary) |
/var/log/wtmp |
login | Login history (binary, use last to read) |
/var/log/btmp |
login | Failed login attempts (binary, use lastb) |
/var/log/faillog |
login | Failed login counter (binary, use faillog) |
# View login history
$ last -10
deploy pts/0 10.0.0.50 Thu Mar 19 10:30 still logged in
deploy pts/0 10.0.0.50 Wed Mar 18 14:15 - 17:30 (03:15)
# View failed logins
$ sudo lastb -10
baduser ssh:notty 10.99.0.1 Thu Mar 19 10:29 - 10:29 (00:00)
logrotate¶
logrotate prevents log files from filling the disk by rotating, compressing, and deleting old logs.
Configuration¶
Main config: /etc/logrotate.conf. Per-application configs in /etc/logrotate.d/.
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily # Rotate daily (also: weekly, monthly)
missingok # Don't error if log file is missing
rotate 14 # Keep 14 rotated files
compress # Gzip old logs
delaycompress # Compress previous rotation (not current)
notifempty # Don't rotate empty files
create 0640 www-data adm # Permissions for new log file
sharedscripts # Run postrotate once (not per file)
postrotate
# Signal nginx to reopen log files
if [ -f /var/run/nginx.pid ]; then
kill -USR1 $(cat /var/run/nginx.pid)
fi
endscript
}
Common logrotate Options¶
| Option | Meaning |
|---|---|
daily/weekly/monthly |
Rotation frequency |
rotate N |
Keep N rotated files |
compress |
Gzip rotated files |
delaycompress |
Wait one cycle before compressing |
copytruncate |
Copy log then truncate original (for apps that don't reopen) |
create MODE OWNER GROUP |
Permissions for new log file |
maxsize SIZE |
Rotate when file exceeds SIZE |
minsize SIZE |
Don't rotate if smaller than SIZE |
maxage N |
Delete rotated files older than N days |
postrotate/endscript |
Script to run after rotation |
Testing logrotate¶
# Dry run (show what would happen)
$ sudo logrotate -d /etc/logrotate.d/nginx
# Force rotation (useful for testing)
$ sudo logrotate -f /etc/logrotate.d/nginx
# Verbose
$ sudo logrotate -v /etc/logrotate.conf
# View logrotate state (when each file was last rotated)
$ cat /var/lib/logrotate/status
"/var/log/nginx/access.log" 2026-3-19-10:0:0
"/var/log/nginx/error.log" 2026-3-19-10:0:0
copytruncate vs create¶
create (default): rename the old log, create a new empty file. Requires the application to reopen the file (via signal like SIGHUP or USR1).
copytruncate: copy the log file, then truncate the original. The application never notices. Risk: messages written between copy and truncate can be lost. Use only when the application cannot be signaled to reopen.
Audit Logging (auditd)¶
The Linux Audit System provides security-focused logging at the kernel level. It can log: - File access (reads, writes, attribute changes) - System calls - User commands - Authentication events - Network connections
Basic auditd Setup¶
# Check if auditd is running
$ sudo systemctl status auditd
# View audit rules
$ sudo auditctl -l
# View audit log
$ sudo ausearch -ts recent # Recent events
$ sudo ausearch -m LOGIN # Login events
$ sudo aureport --summary # Summary report
Common Audit Rules¶
Rules in /etc/audit/rules.d/audit.rules:
# Monitor /etc/passwd for changes
-w /etc/passwd -p wa -k identity
# Monitor /etc/shadow for any access
-w /etc/shadow -p rwa -k shadow_access
# Monitor sudo configuration
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Monitor SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Log all commands run by root
-a always,exit -F arch=b64 -F euid=0 -S execve -k root_commands
# Monitor Docker socket access
-w /var/run/docker.sock -p rwxa -k docker_socket
Flags: -w = watch path, -p = permissions filter (r=read, w=write, x=execute, a=attribute change), -k = key tag for searching.
# Search by key
$ sudo ausearch -k shadow_access
# Load new rules
$ sudo auditctl -R /etc/audit/rules.d/audit.rules
# Or restart auditd
$ sudo systemctl restart auditd
Log Forwarding¶
In production, logs should flow to a central aggregation system. Common patterns:
rsyslog Forwarding¶
# /etc/rsyslog.d/50-remote.conf
# Forward all logs via TCP
*.* @@logserver.example.com:514
# Forward only auth logs
auth,authpriv.* @@logserver.example.com:514
# Forward with TLS
$DefaultNetstreamDriverCAFile /etc/ssl/certs/ca.pem
$ActionSendStreamDriver gtls
$ActionSendStreamDriverMode 1
$ActionSendStreamDriverAuthMode x509/name
*.* @@logserver.example.com:6514
Forwarding to Modern Stacks¶
For ELK, Loki, or Datadog: - Filebeat watches log files and ships to Elasticsearch - Promtail watches log files and ships to Loki - Fluentd/Fluent Bit watches files, journal, and syslog - Vector watches files and journal, ships to many destinations
# Example: Promtail config for Loki
# /etc/promtail/config.yml
scrape_configs:
- job_name: journal
journal:
json: false
max_age: 12h
labels:
job: systemd-journal
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: 'unit'
- job_name: varlog
static_configs:
- targets: [localhost]
labels:
job: varlogs
__path__: /var/log/*.log
Structured Logging¶
Modern applications should emit structured logs (JSON) rather than unstructured text:
{"timestamp":"2026-03-19T10:45:22.123Z","level":"error","service":"api","request_id":"abc123","user_id":"u-456","message":"database connection timeout","latency_ms":30000,"db_host":"db-primary.internal"}
Benefits: - Machine-parseable without fragile regex - Queryable in log aggregation systems (field-level filtering) - Consistent schema across services - Correlation via request IDs, trace IDs
# Query structured JSON logs with jq
$ cat /var/log/myapp/app.json | jq 'select(.level == "error" and .latency_ms > 5000)'
# From journalctl JSON output
$ journalctl -u myapp -o json | jq 'select(.PRIORITY == "3")'
Container Logging (stdout/stderr)¶
Kubernetes captures anything written to stdout/stderr from containers:
# View current logs
kubectl logs my-pod -n production
# Follow logs (tail -f equivalent)
kubectl logs -f my-pod -n production
# Previous container instance (after a crash/restart)
kubectl logs my-pod -n production --previous
# All containers in a pod
kubectl logs my-pod -n production --all-containers
# Last 100 lines from all pods with a label
kubectl logs -l app=api-server -n production --tail=100
Container runtime stores logs at /var/log/containers/ on the node:
# On the node itself
ls /var/log/containers/
# my-pod_production_app-abc123.log -> /var/log/pods/...
# These are JSON-wrapped log lines
cat /var/log/containers/my-pod_production_app-abc123.log | head -1
# {"log":"2024-01-15T03:22:41Z INFO starting\n","stream":"stdout","time":"2024-01-15T03:22:41.123Z"}
For container logs, configure the container runtime:
Centralized Logging — ELK and EFK Stacks¶
ELK: Elasticsearch + Logstash + Kibana EFK: Elasticsearch + Fluentd/Fluent Bit + Kibana
+---------+ +--------------+ +---------------+ +---------+
| App Logs |---->| Fluent Bit |---->| Elasticsearch |---->| Kibana |
| (stdout) | | (collector) | | (storage) | | (UI) |
+---------+ +--------------+ +---------------+ +---------+
Fluent Bit DaemonSet (runs on every node, ships container logs):
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: logging
spec:
selector:
matchLabels:
app: fluent-bit
template:
spec:
containers:
- name: fluent-bit
image: fluent/fluent-bit:2.2
volumeMounts:
- name: varlog
mountPath: /var/log
- name: containers
mountPath: /var/log/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
- name: containers
hostPath:
path: /var/log/containers
Log Aggregation Patterns¶
Sidecar pattern — a logging container alongside your app container:
containers:
- name: app
image: myapp:latest
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
- name: log-shipper
image: fluent/fluent-bit:2.2
volumeMounts:
- name: shared-logs
mountPath: /var/log/app
readOnly: true
DaemonSet pattern — one collector per node (most common for Kubernetes):
- Fluent Bit or Fluentd as DaemonSet
- Reads from /var/log/containers/
- Lower resource overhead than sidecars
Aggregator pattern — two-tier shipping: - DaemonSet collectors forward to a central aggregator (Fluentd, Logstash, Vector) - Aggregator handles filtering, enrichment, and routing - Reduces connections to Elasticsearch
Log Retention and Compliance¶
Retention Guidelines:
- Application logs: 30-90 days hot, 1 year cold storage
- Security/audit logs: 1-7 years (regulatory dependent)
- Debug logs: 7-14 days maximum
- Access logs: 90 days hot, 1 year archive
Cost control:
- Ship only WARN+ to expensive storage (Elasticsearch)
- Archive DEBUG/INFO to cheap storage (S3, GCS)
- Use index lifecycle management (ILM) in Elasticsearch
- Sample high-volume logs (keep 10% of DEBUG in production)
Summary¶
Linux logging is a layered system. The kernel ring buffer captures hardware and driver events. journald captures everything from systemd-managed services with rich metadata. rsyslog writes traditional log files and forwards to remote systems. logrotate keeps disk usage under control. auditd provides security-focused audit trails. Understanding all these layers — and how they interact — is essential for debugging production issues, investigating security incidents, and maintaining system health. The most important practical skills: journalctl -u <service> for quick service debugging, dmesg -T for kernel issues, and proper logrotate configuration to prevent disk-full emergencies.
Wiki Navigation¶
Prerequisites¶
- Linux Ops (Topic Pack, L0)
Related Content¶
- Deep Dive: Linux Boot Sequence (deep_dive, L2) — Linux Fundamentals, systemd
- Deep Dive: Systemd Architecture (deep_dive, L2) — Linux Fundamentals, systemd
- Deep Dive: Systemd Timers Journald Cgroups and Resource Control (deep_dive, L2) — Linux Fundamentals, systemd
- LPIC / LFCS Exam Preparation (Topic Pack, L2) — Linux Fundamentals, systemd
- Linux Boot Process (Topic Pack, L1) — Linux Fundamentals, systemd
- Linux Ops (Topic Pack, L0) — Linux Fundamentals, systemd
- Ops Archaeology: The Service That Won't Start (Case Study, L1) — Linux Fundamentals, systemd
- RHCE (EX294) Exam Preparation (Topic Pack, L2) — Linux Fundamentals, systemd
- Skillcheck: Linux Fundamentals (Assessment, L0) — Linux Fundamentals, systemd
- systemctl & journalctl Deep Dive (Topic Pack, L1) — Linux Fundamentals, systemd
Pages that link here¶
- Anti-Primer: Linux Logging
- Certification Prep: CKA — Certified Kubernetes Administrator
- Certification Prep: CKAD — Certified Kubernetes Application Developer
- Comparison: Logging Platforms
- Incident Replay: Disk Full on Root Partition — Services Down
- Incident Replay: Runaway Logs Fill Disk
- LPIC / LFCS Exam Preparation
- Linux Boot Process
- Linux Boot Sequence - From Power-On to Full Boot
- Linux Fundamentals - Skill Check
- Linux Logging
- Master Curriculum: 40 Weeks
- Ops Archaeology: The Service That Won't Start
- Production Readiness Review: Answer Key
- Production Readiness Review: Study Plans