Build a log aggregation pipeline that collects, processes, and makes searchable the millions of log lines your distributed system produces. Covers structured logging standards, collection architectures, storage strategies, query patterns, retention policies, and the pipeline that turns noise into signal.
When systems are distributed across multiple services, containers, or nodes, logs become scattered and fragmented. The ability to correlate events from different sources into a unified timeline is essential for debugging, monitoring, and operational visibility. This process requires structured ingestion pipelines that can handle high-volume, real-time log streams while preserving context and enabling efficient querying.
To collect logs from running processes, use journalctl with the --output=json flag to produce structured output:
journalctl --output=json --since="10 minutes ago" > /tmp/logs.json
For containerized workloads, capture logs using docker logs with timestamp formatting:
docker logs --since=1h --until=now <container_id> | sed 's/^/timestamp="'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'"/' > /tmp/container.log
When collecting from Kubernetes pods, stream logs using kubectl logs with a JSON-formatted output:
kubectl logs --since=10m <pod_name> -c <container_name> | jq -R '{"message": .}' > /tmp/pod.json
Logs must be structured to enable filtering and aggregation. When writing application logs, include a consistent timestamp field:
{
"timestamp": "2023-10-15T14:30:45.123Z",
"level": "INFO",
"service": "payment-service",
"message": "User 12345 initiated payment processing"
}
Use the --format option with logging libraries to enforce structured output. When using Python's logging module, set up a JSON formatter:
import json
import logging
class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"service": "my-service",
"message": record.getMessage()
}
return json.dumps(log_entry)
Use jq to filter logs by field values:
cat /tmp/logs.json | jq '. | select(.level == "ERROR")'
For timestamp-based filtering, use the --date flag with grep:
grep -E '2023-10-15T[0-9]{2}:[0-9]{2}:[0-9]{2}' /tmp/logs.json
To search for error messages across multiple log files, combine find with grep:
find /var/log -name "*.log" -exec grep -l "ERROR\|FATAL" {} \;
Create a central log aggregation pipeline using rsyslog or similar tools. Configure rsyslog to forward logs over UDP:
# /etc/rsyslog.conf
*.* @<aggregator-host>:514
In the receiving instance, configure rsyslog to parse and store logs in structured format:
# /etc/rsyslog.d/01-forward.conf
module(load="imudp")
input(type="imudp" port="514")
$ActionFileDefaultTemplate RSYSLOG_FileFormat
When logs are part of a distributed trace, include a correlation ID in each log entry:
{
"timestamp": "2023-10-15T14:30:45.123Z",
"trace_id": "abc123def456",
"span_id": "xyz789uvw012",
"level": "INFO",
"message": "Payment validated"
}
Use grep to find all logs from a specific trace:
grep "abc123def456" /var/log/*.log
Configure log rotation using logrotate with retention policies:
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 7
compress
delaycompress
copytruncate
}
For high-volume logs, enable compression at the source:
journalctl --output=json --since="1 hour ago" | gzip > /tmp/logs.json.gz
Set up alerts using awk to detect recurring errors:
tail -f /var/log/app.log | awk '/ERROR/ { print strftime(), $0 }' | grep -E 'ERROR.*timeout'
To detect log lines that exceed a size threshold:
grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}' /var/log/app.log | awk 'length > 1000'
Logs may be dropped silently if the receiving system is overwhelmed. Monitor rsyslog queue sizes with:
grep -i queue /var/log/rsyslog.log
When logs contain malformed JSON, filter out invalid entries:
cat /tmp/logs.json | jq -c 'select(.timestamp and .level and .message)' > /tmp/cleaned.json
Timestamp drift between systems can cause misalignment. Use timedatectl to verify clock synchronization:
timedatectl status
Use grep with regular expressions to search across multiple log files:
find /var/log -name "*.log" -exec grep -l "failed\|error" {} \; | xargs grep -n "failed"
For structured log analysis, use jq with filters:
cat /tmp/aggregated.json | jq '. | select(.level == "ERROR") | .timestamp, .message'
Configure automatic cleanup of old logs:
find /var/log -name "*.log" -mtime +30 -delete
Set up log archival to long-term storage:
tar -czf /backup/logs-$(date +%Y-%m-%d).tar.gz /var/log/app/
Monitor for dropped log entries by comparing expected and received counts:
echo "Expected: $(wc -l < /tmp/input.log)" > /tmp/counts.txt
echo "Actual: $(jq -r 'length' /tmp/output.json)" >> /tmp/counts.txt
When logs are missing from aggregation, check for network issues with:
ping -c 3 <aggregator-host>
Log files may contain non-ASCII characters. Use iconv to convert encodings:
iconv -f utf-8 -t utf-8 //TRANSLIT /tmp/utf8.log > /tmp/conversion.log
Check for log pipeline failures using system monitoring tools:
systemctl status rsyslog
journalctl -u rsyslog --since="10 minutes ago"
Use stat to verify log file integrity:
stat /var/log/app.log
When logs from different services have varying formats, standardize using sed or awk:
cat /tmp/inconsistent.log | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\} [0-9]\{2\}:[0-9]\{2\}:[0-9]\{2\}\) \(.*\)/{"timestamp":"\1","message":"\2"}/'
Monitor disk usage with:
df -h /var/log
When logs grow too large, implement partitioning by date or service:
mkdir -p /var/log/app/$(date +%Y-%m)
mv /var/log/app/*.log /var/log/app/$(date +%Y-%m)/
Set up a log forwarding agent to send logs to an external system:
# Example: forward logs to HTTP endpoint
curl -X POST -H "Content-Type: application/json" --data-binary @/tmp/logs.json http://log-collector.example.com/logs
When log files rotate, ensure that the ingestion system can handle file transitions:
tail -F /var/log/app.log | while read line; do echo "$line"; done
This approach ensures that when a log file is rotated, tail continues reading from the new file without interruption.
This page was rewritten on 15 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.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy