10 Linux Logging Best Practices Every Sysadmin Must Know


grep, awk, tail -f - and still no clue what broke your system.
If that sounds familiar, your logs are not the problem. Your logging strategy is.

Every Linux system generates logs. The problem isn't that there isn't enough data, it's that there isn't enough useful data. If you don't handle your linux log management well, your observability layer could become a problem. On the other side, a good logging technique decreases the time it takes to fix problems from hours to minutes. This post talks about the 10 linux logging best practices that will help you tell the difference between a system you can troubleshoot with confidence and one that makes too much noise at untime.

These criteria apply to all Linux distributions, whether you're using a RHEL server, a CentOS stack, a distributed Ubuntu environment, or any other Linux distribution. You may run a real terminal example for each rule right away. No theory without evidence.

Note

Logging controls what gets recorded. File access and ownership are separate concerns.
If you need to restrict who can read your log files, that is a job for
chmod and
chown.

Rules

Before You Start — What You Need
  • A Linux system: RHEL, CentOS, or Ubuntu (all examples tested on these)
  • sudo or root access — some log paths and journalctl filters require elevated privilege
  • systemd running — Rules 08 and 09 use journalctl and logrotate
  • Basic familiarity with the terminal — if you're new, start with
    Basic Linux Commands first



Key Commands — Logging Toolchain
journalctl [OPTIONS] # query the systemd journal
logger [OPTIONS] MESSAGE # write a message to syslog
tail -f FILE # follow a log file in real time
grep PATTERN FILE # search log output for a pattern
awk '{print $0}' FILE # parse and process log fields

♥ Quick Reference — journalctl Options
Flag Long Form Description
-u --unit Show logs for a specific systemd service unit
-p --priority Filter by syslog priority level (0=emerg to 7=debug)
-f --follow Stream new log entries in real time (like tail -f)
-S --since Show entries from a specific date/time onward
-U --until Show entries up to a specific date/time
-o --output Set output format — use json-pretty for structured export
-n --lines Limit output to the last N lines (default: 10)
-r --reverse Show newest entries first

♥ Linux Log Levels — When to Use Each
Level When to Use Environment Who Acts On It
ERROR System cannot continue — something broke that won't self-recover All environments On-call engineer, immediately
WARN Not broken yet, but today's WARN is tomorrow's ERROR if ignored All environments Team review within 24h
INFO Key business milestone reached — tracks system workflow All environments Anyone — business and technical
DEBUG Developer-level detail — disable in production Dev / Staging only Developer during active debugging
TRACE Execution path tracing — extremely granular, isolate one issue Dev only Developer — temporary use only

♥ Syslog Severity Reference — RFC 5424 Standard
Value Severity journalctl -p Meaning
0 Emergency emerg System is completely unusable
1 Alert alert Action must be taken immediately
2 Critical crit Critical condition — hardware or software failure
3 Error err Error condition that needs attention
4 Warning warning Warning — may become an error if not handled
5 Notice notice Normal but significant event
6 Informational info Informational message — routine operations
7 Debug debug Detailed debug-level messages

#01

Linux Logging Best Practices Start With Writing for the Reader, Not Yourself

When you write a log entry, picture a colleague — exhausted, on-call, mid-incident — who has
nothing but your log output and a deadline. Every log entry you write is a message to that person.
Make it count. The single biggest shift in your linux logging best practices is writing for the
reader, not the author.

✗ Bad — no context, tells you nothing
log output
INFO Processing started
INFO Processing finished
✓ Good — full context, immediately actionable
log output
INFO Payment initiated userId=u_9921 orderId=ORD-4477 amount=149.00
INFO Payment confirmed userId=u_9921 orderId=ORD-4477 result=SUCCESS latencyMs=84

The second version tells you who did it, what happened, and how long it took — without opening a single database query.

#02

More Output Is Not More Visibility — Log with Purpose Only

"Just add more logs" is advice that sounds helpful and destroys your system. Every log line costs
you disk I/O, storage space, and — most critically — the time it takes a human to read through it.
Flooding your output with loop-level noise buries the entries that actually matter.

✗ Bad — logs every item in a loop (thousands of lines)
bash
# logs one line per item — 10,000 items = 10,000 log lines
for item in "${items[@]}"; do
echo "INFO Processing item: $item"
process "$item"
done
✓ Good — one summary before, one result after
bash
echo "INFO Starting batch: count=${#items[@]}"
for item in "${items[@]}"; do
process "$item" && (( success++ )) || (( failed++ ))
done
echo "INFO Batch complete: success=$success failed=$failed"
Sample Output
INFO Starting batch: count=10000
INFO Batch complete: success=9987 failed=13
Warning — Performance Impact

Heavy synchronous logging can reduce application throughput by 30–50%.
Always summarise loop output and use asynchronous log writers in production environments.
See Linux System Monitoring for performance tooling.

#03

Understand What Each Linux Log Level Means — and Never Mix Them Up

Log levels are a contract, not a suggestion. The moment you log a wrong-password attempt as
ERROR, your on-call alert fires for something that happens a hundred times a day.
That's how alert fatigue starts. Here is the catch-block mistake that most teams make:

✗ Wrong — every exception becomes an ERROR
bash / pseudocode
catch Exception e:
log.error("Login failed", e) # wrong — this fires your pager every time
✓ Correct — intent determines the level
bash / pseudocode
catch InvalidCredentials e:
log.info("Login attempt: bad credentials", userId) # expected path
catch Exception e:
log.error("Auth system fault", userId, e) # true system failure
Rule of Thumb

Ask yourself: does this require a human to wake up and act right now?
If yes → ERROR. If it might become a problem later → WARN.
If it's just tracking what happened → INFO.

#04

Never Write Sensitive Data Into a Log File — Mask It Always

This is the rule with the most severe consequences when broken. A log file with a plaintext
password, API token, or card number sitting in /var/log/ is a data breach waiting
to happen. It is also the easiest rule to follow — mask before it hits disk, every time.

✗ Dangerous — credentials visible in plain log output
log output
INFO User login: username=admin password=MySecret123
✓ Safe — credential masked before reaching the log
log output
INFO User login: username=admin password=****
Danger — Never Log These

The following must never appear in any log file under any circumstance:

  • Passwords and passphrases
  • API tokens, session keys, OAuth secrets
  • Credit / debit card numbers (PAN)
  • National ID numbers, passport numbers, SSN
  • User home addresses and geolocation data

#05

Every Useful Log Entry Needs These Six Fields — No Exceptions

A log entry without context is a riddle. Here are the six fields that make every entry
self-contained — readable and actionable with no additional lookups.

log output — complete well-formed entry
2025-10-02T14:22:15Z INFO [traceId=c7d2eb9f] userId=u_9921 orderId=ORD-4477 action=payment result=SUCCESS latencyMs=84
♥ The Six Required Log Fields
Field Example Why It Matters
Timestamp 2025-10-02T14:22:15Z ISO 8601 UTC — essential for cross-region correlation
Trace ID c7d2eb9f Links every log entry in the same request chain
Subject userId=u_9921 Who triggered the action — user, service, or job
Parameters orderId=ORD-4477 The key inputs — enough to reproduce the scenario
Outcome result=SUCCESS What happened — success, error code, or partial
Duration latencyMs=84 The most underlogged field — catches regressions fast

#06

Structured JSON Logs Outperform Plain Text in Production

Plain text logs are written for humans to skim. Structured JSON logs are written for machines
to index, search, and alert on — and still perfectly readable by humans. In any linux log
management setup that uses ELK, EFK, Loki, or Datadog, unstructured text is a dead end.

✗ Unstructured — grep only, no field-level search
plain text log
Oct 02 14:22:15 INFO Payment confirmed for user u_9921 order ORD-4477 amount 149.00
✓ Structured JSON — indexable, filterable, alertable
structured log output
{
"timestamp": "2025-10-02T14:22:15Z",
"level": "INFO",
"service": "order-center",
"traceId": "c7d2eb9f",
"message": "Payment confirmed",
"userId": "u_9921",
"orderId": "ORD-4477",
"amount": 149.00,
"latencyMs": 84
}
Tip — Use JSON in Production

JSON structured logs are machine-readable and work directly with
ELK,
EFK, Loki, and Grafana without any custom parsing rules. Make JSON the default format for every production environment
from day one. See the Linux System Monitoring Cheat Sheet for toolchain options.

#07

Keep Loops Out of Your Log Calls — and Guard Expensive Debug Statements

Two habits that silently degrade your logs: logging inside loops (covered in Rule 02), and
building expensive strings for debug messages that get evaluated even when debug is disabled.
In other words — the string gets built whether or not the log line ever gets written.

✗ Wrong — string built even if DEBUG is off
bash / pseudocode
# String concatenation happens regardless of log level
log.debug("User: " + user.name + " ID: " + user.id)
✓ Correct — evaluation skipped when level is off
bash / pseudocode
# Use placeholders — params evaluated only if level is active
log.debug("User: {} ID: {}", user.name, user.id)

# For expensive calls, add a level guard first
if log.is_debug_enabled():
log.debug("Detail: {}", build_expensive_context(obj))

A Mistake I See Often

Teams disable DEBUG in production but forget that string concatenation in log calls still
executes. On a high-traffic system processing 50,000 req/s, that wasted CPU adds up.
Switch every debug log to placeholder syntax and guard expensive calls — it costs one line
and saves measurable overhead.

#08

Use journalctl to Search and Filter System Logs Like a Pro

tail -f /var/log/syslog is a blunt instrument. journalctl is a scalpel.
It queries the systemd journal with filtering by unit, priority, time, and output format.
Here are the five commands you will use in every real incident.

Note — /var/log vs journald

Traditional flat files in /var/log/ are written by rsyslog or syslog-ng.
journald (queried with journalctl) is the systemd binary journal —
faster to filter and supports structured output. On modern RHEL 8 and Ubuntu 22.04, both run
side by side. Use journalctl for systemd services; use /var/log/
for application logs that write their own files.

a. View logs for a specific systemd service unit

bash
$ journalctl -u nginx.service
Sample Output
Apr 03 09:11:22 server1 nginx[1024]: Starting A high performance web server
Apr 03 09:11:22 server1 nginx[1024]: Started nginx.service

b. Filter by priority — errors and above only

bash
$ journalctl -p err
Sample Output
Apr 03 08:44:01 server1 kernel: EXT4-fs error (device sda1): ext4_journal_check_start:61
Apr 03 08:55:12 server1 sshd[4421]: error: Could not load host key /etc/ssh/ssh_host_ed25519_key

c. Show logs within a specific time window

bash
$ journalctl -S "2025-10-02 14:00:00" -U "2025-10-02 15:00:00"

d. Follow live log output in real time

bash
$ journalctl -u sshd.service -f

e. Export as JSON for machine parsing

bash
$ journalctl -u nginx.service -o json-pretty | head -40
Sample Output
{
"__REALTIME_TIMESTAMP" : "1696253535000000",
"_SYSTEMD_UNIT" : "nginx.service",
"PRIORITY" : "6",
"MESSAGE" : "Started nginx.service"
}
Pro Tip — Combine Filters

Stack -u, -p, and -S in a single command for laser-precise
incident investigation:
journalctl -u nginx -p err -S "2025-10-02 14:00"
See Linux System Monitoring Cheat Sheet for more filtering patterns.

#09

Set Up Log Rotation — Disk Space Is Not Infinite

A logging strategy without rotation is a ticking disk-full incident. Left unchecked, a single
busy service can fill /var/log/ in days. logrotate handles this
automatically — here is a production-ready configuration.

bash — create per-app config
$ sudo nano /etc/logrotate.d/myapp
logrotate config
/var/log/myapp/*.log {
daily # rotate once per day
rotate 30 # keep 30 days of history
size 100M # rotate if file exceeds 100MB
maxsize 100M # hard cap per file
compress # gzip rotated files
delaycompress # compress on second rotation cycle
missingok # no error if log file is missing
notifempty # skip rotation if file is empty
dateext # append date to rotated filename
dateformat -%Y-%m-%d # e.g. app-2025-10-02.log
copytruncate # rotate without restarting the process
}
Test your config without applying
$ sudo logrotate --debug /etc/logrotate.d/myapp
Note — /etc/logrotate.d/

Drop one config file per application into /etc/logrotate.d/. The main
logrotate daemon picks them all up automatically via cron. No need to edit
the global /etc/logrotate.conf for per-app rules.
Use cron
if you need custom rotation schedules beyond daily/weekly/monthly.

#10

Build Alerts That Fire on Signal — Not on Noise

An alert that fires too often trains your team to ignore it. An alert that never fires means
something is silently broken. The goal is alerts that fire only when a human genuinely needs
to act. Here are the four patterns that cover most production scenarios.

♥ Four Alerting Patterns — Signal vs Noise
Pattern Trigger Condition Example
Error Rate Spike ERROR volume exceeds a multiplier of baseline — not a fixed count ERRORs jump from 2/min to 40/min in under 60 seconds
Keyword Watch A specific exception type or error code appears in the log OutOfMemoryError, DISK_FULL, CONNECTION_RESET
Pattern Shift Log shape changes unexpectedly — new fields, missing fields, format change JSON logs suddenly switch to plain text after a bad deploy
Absence Detection An expected log never appears within its time window Nightly backup job ran, but left no completion entry in the log

Count errors per minute directly from a log file using grep and awk:

bash
$ grep "ERROR" /var/log/myapp/app.log \
| awk '{print substr($1,1,16)}' \
| sort | uniq -c | sort -rn | head -10
Sample Output — errors per minute
41 2025-10-02T14:22
8 2025-10-02T14:21
2 2025-10-02T14:20

In other words — 41 errors in one minute compared to 2 the minute before. That is your spike. That is your alert trigger.

Pro Tip — Centralised Log Platform

For teams managing more than a handful of servers, a centralised platform (ELK stack:
Elasticsearch + Logstash + Kibana, or EFK with Fluentd) gives you all four alerting patterns
out of the box with no shell scripting. See
Best Linux Monitoring Tools
for a curated comparison.

A Mistake I See Often — and One That Cost a Team 4 Hours

A payment service went down during a peak traffic window. The logs showed only this:

log output — the actual logs during the incident
ERROR Process failed
ERROR Process failed
ERROR Process failed

No userId. No orderId. No error code. No trace ID. Four engineers spent four hours manually
cross-referencing database records to find that a third-party payment API was timing out.
That's not a logging volume problem — that's a logging content problem. Rules 01
and 05 in this guide would have cut that incident to under 10 minutes.

The Fix

Every ERROR entry must carry enough context to reproduce the scenario without any additional
data source. If your ERROR log requires a database query to understand — it is not a log entry,
it is an incomplete sentence. Go back to Rule 05 and add the six fields.

Now that you've got this down — your logs should tell a story, not ask
questions. Apply Rules 01 and 05 first (they have the highest immediate impact), set up
log rotation from Rule 09 before you forget, and graduate to structured JSON from Rule 06
when you're ready to hook into a monitoring platform. The next logical step from here is
mastering Linux system monitoring
— because good logs and good metrics are two sides of the same observability coin.

Danger — Log File Permissions

Log files in /var/log/ are only as safe as their permissions.
If a sensitive application log is world-readable, any user on the system can read it.

Lock down log file ownership and permissions with
chmod and
chown after setting up
any new application logging. A typical safe baseline: chmod 640 and
chown root:adm.

FAQ

People Also Ask

What is the difference between /var/log and journald in Linux?

/var/log/ contains flat text files written by traditional syslog daemons
(rsyslog, syslog-ng). journald is the systemd binary journal, queried with
journalctl. On modern systems both run simultaneously. Use journalctl
for systemd service logs; use /var/log/ for application-managed log files.
The binary journal is faster to filter and supports structured JSON export natively.

Which log level should I use in production — INFO or WARN?

Use INFO for key business events you want to track under normal operation.
Use WARN for conditions that are not yet broken but need monitoring.
In production, disable DEBUG and TRACE entirely — they
generate excessive output and carry a measurable performance cost under load.

How do I view only error logs in Linux?

Use journalctl -p err to filter the systemd journal to priority 3 (error) and
above. For flat log files, use grep "ERROR" /var/log/myapp/app.log.
To also catch critical and emergency entries, use journalctl -p crit.

What is structured logging and why does it matter?

Structured logging writes log entries as machine-parseable data (typically JSON) instead of
free-form text strings. Each field — timestamp, level, traceId, userId — is a named key,
not part of a sentence. This makes logs directly searchable by field, compatible with ELK
and EFK stacks, and filterable without regex. For any system generating more than a few
hundred log lines per minute, structured logging is the only practical approach.

How do I stop log files from filling up disk space in Linux?

Set up logrotate with a configuration file in /etc/logrotate.d/.
Define a maximum file size (e.g. size 100M), a retention period
(e.g. rotate 30), and enable compression (compress).
Test your configuration without applying it using logrotate --debug.
See Rule 09 in this guide for a complete working example.

Can logging slow down my Linux application?

Yes — synchronous high-volume logging can reduce application throughput by 30–50% under
load. The main culprits are disk I/O, string construction inside log calls, and thread
contention in multi-threaded environments. To mitigate: use asynchronous log writers,
avoid logging inside loops, use placeholder syntax instead of string concatenation, and
keep DEBUG and TRACE disabled in production.

LinuxTeck — A Complete Linux Learning Blog
From your first terminal command to advanced sysadmin skills — every guide here is written in plain English with real examples you can run right now.



About Aneeshya S

Aneeshya S is a Senior Linux Trainer and System Administrator with over 10 years of experience. She actively follows emerging technologies and industry trends. Outside the terminal, she enjoys music and travel.

View all posts by Aneeshya S →

Leave a Reply

Your email address will not be published.

L