How to Monitor Log Files in Bash

How to Monitor Log Files in Bash

How to Monitor Log Files in Bash

Long before I had a proper monitoring stack in place on any project, I was watching log files directly from the terminal — tailing them live during a deploy, grepping through them after an incident, writing small scripts to alert me the moment an error pattern showed up. Bash turns out to be genuinely excellent at this, largely thanks to tail -f, grep, and awk working together. This article covers everything I use for log monitoring in Bash, from watching a file live to building a full alerting script.

The Foundation: tail -f

The single most useful command for log monitoring is:

tail -f /var/log/syslog

-f (follow) keeps the command running, printing new lines to the terminal as they’re appended to the file — exactly what you want when watching a live deployment or debugging an issue in real time. Press Ctrl+C to stop.

For log files that get rotated (renamed and replaced with a fresh empty file, which is standard practice with tools like logrotate), use -F instead:

tail -F /var/log/syslog

-F is smarter than -f — it detects when the file has been rotated and automatically reopens the new file by the same name, rather than continuing to follow the old, now-renamed file that’s no longer being written to.

Filtering a Live Log Stream

Combining tail -f with grep is the pattern I use constantly:

tail -f /var/log/nginx/access.log | grep "ERROR"

For case-insensitive matching:

tail -f /var/log/app.log | grep -i "error"

To highlight matches in color while still seeing everything (using grep --color with a pattern that matches everything, .*, combined with the specific pattern):

tail -f /var/log/app.log | grep --color=always -E "ERROR|WARN|$"

This colors ERROR and WARN lines while still passing every line through, since |$ (end of line) matches every line and lets it through unfiltered.

Step-by-Step: A Real-Time Error Watcher

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/var/log/app.log"
PATTERN="ERROR|CRITICAL|FATAL"

echo "Watching $LOG_FILE for: $PATTERN"

tail -F "$LOG_FILE" | grep --line-buffered -E "$PATTERN" | while IFS= read -r line; do
    echo "[ALERT] $(date '+%Y-%m-%d %H:%M:%S') - $line"
done

Explaining the Script Internally

Real-World Use Case: Alerting on Error Spikes

Simply catching every error can be noisy. A pattern I actually use in production is counting errors over a time window and only alerting if the rate crosses a threshold:

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/var/log/app.log"
WINDOW_SECONDS=60
THRESHOLD=10

error_count=0
window_start=$(date +%s)

tail -F "$LOG_FILE" | grep --line-buffered "ERROR" | while IFS= read -r line; do
    now=$(date +%s)
    elapsed=$((now - window_start))

    if [ "$elapsed" -ge "$WINDOW_SECONDS" ]; then
        error_count=0
        window_start=$now
    fi

    error_count=$((error_count + 1))

    if [ "$error_count" -ge "$THRESHOLD" ]; then
        echo "ALERT: $error_count errors in the last ${WINDOW_SECONDS}s!"
        # trigger notification here
        error_count=0
        window_start=$now
    fi
done

Note: because the final while loop runs in a subshell (due to the pipe), variables like error_count reset each time the script itself restarts, but persist correctly within this run since the whole counting logic lives inside that same subshell.

Real-World Use Case: Searching Historical Logs for a Pattern

Live monitoring is only half the picture — I also frequently need to search through existing, potentially huge log files after the fact:

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/var/log/app.log"
SEARCH_TERM="$1"
START_DATE="$2"

grep "$SEARCH_TERM" "$LOG_FILE" | awk -v start="$START_DATE" '$1 >= start'

Assuming log lines start with an ISO-format date like 2026-07-28 ..., this filters lines matching the search term and then further filters by date using awk‘s string comparison, which works correctly for ISO 8601 dates since they sort lexicographically in chronological order.

For very large log files, grep is far faster than reading the file line by line in a Bash loop, since grep is a highly optimized compiled tool, while Bash’s own while read loop has meaningfully more per-line overhead.

Automation Example: Daily Log Summary Report

#!/usr/bin/env bash
set -euo pipefail

LOG_FILE="/var/log/app.log"
TODAY=$(date +%Y-%m-%d)
REPORT_FILE="/tmp/log_summary_${TODAY}.txt"

{
    echo "Log Summary for $TODAY"
    echo "========================"
    echo ""
    echo "Total lines: $(grep -c "^${TODAY}" "$LOG_FILE")"
    echo "Errors: $(grep -c "^${TODAY}.*ERROR" "$LOG_FILE")"
    echo "Warnings: $(grep -c "^${TODAY}.*WARN" "$LOG_FILE")"
    echo ""
    echo "Top 5 error messages:"
    grep "^${TODAY}.*ERROR" "$LOG_FILE" | awk -F'ERROR' '{print $2}' | sort | uniq -c | sort -rn | head -5
} > "$REPORT_FILE"

cat "$REPORT_FILE"

I run this nightly via cron and feed the resulting report into the email script covered elsewhere in this series, giving me a daily digest without having to manually dig through logs each morning.

Scheduled via:

0 23 * * * /usr/local/bin/daily_log_summary.sh

Multi-File Monitoring

To watch several log files at once and know which file each line came from:

tail -F /var/log/app1.log /var/log/app2.log /var/log/app3.log

When following multiple files, tail automatically prefixes each block of output with a header showing which file it came from whenever the source switches, making it easy to distinguish interleaved output.

Security Considerations

Optimization Tips

Troubleshooting

Common Mistakes to Avoid

FAQs

How do I monitor logs across multiple servers at once? Combine ssh with tail -f in a loop, or better, forward logs to a centralized system like rsyslog, journald with a central collector, or a dedicated log aggregation tool once you outgrow single-server Bash monitoring.

Can I monitor journald logs (systemd) the same way? Not with tail, since journald stores logs in a binary format; use journalctl -f instead, which provides the same “follow” behavior for systemd’s journal.

How do I handle multi-line log entries like stack traces in my pattern matching? Use awk with a record separator trick, or a tool like sed with pattern-range matching, to treat a stack trace as one logical block rather than matching line by line.

Is Bash log monitoring a replacement for a real monitoring/alerting system? For small setups or quick diagnostics, yes it’s genuinely sufficient. For larger production systems, dedicated tools (Prometheus, ELK/Elastic stack, Datadog, etc.) offer far more robust aggregation, retention, and alerting — but the Bash patterns here remain useful even alongside those tools for quick manual investigation.

Summary

Log monitoring in Bash comes down to a small number of tools working together well: tail -f/-F for live streaming, grep --line-buffered for real-time filtering, and awk for structured parsing and aggregation. Whether I’m watching a deploy in real time, building a threshold-based alerting script, or generating a daily summary report, these same few building blocks cover the vast majority of what I need — no dedicated log monitoring platform required for smaller-scale needs.

References

Exit mobile version