open atlas
↑ Back to track
Command line CLI · 10 · 01

Log analysis pipeline

Compose find, grep, cut, sort, uniq -c, and awk into a multi-stage pipeline that extracts real insight from access logs — top client IPs, status-code distribution, and error spikes — in a single shell one-liner.

CLI Senior ◷ 30 min
Level
FoundationsJuniorMiddleSenior

You have a server generating gigabytes of Nginx access logs. Something spiked P99 latency at 03:47 this morning. You need the answer in two minutes, not two hours of writing Python. Every tool you need — find, grep, cut, sort, uniq -c, awk — is already on the machine, and they compose cleanly through pipes. This lesson builds that pipeline one stage at a time, so you understand exactly what each stage contributes and can adapt it to any log format you encounter in production.

Goal

After this lesson you can build an incremental log-analysis pipeline using find | xargs grep, cut, sort | uniq -c | sort -rn, and awk to extract top client IPs, status-code counts, and error rates from standard Combined Log Format access logs.

1

Start with find to locate the log files across rotated archives. A production server often has access.log, access.log.1, access.log.2.gz, and so on. find locates all of them; xargs passes them as arguments to the next command rather than piping content — that distinction matters because some commands (like grep -h) need file arguments, not stdin.

find /var/log/nginx -name 'access.log*' -not -name '*.gz'

Output: a newline-separated list of matching paths.

Add -mtime -1 to restrict to files modified in the last 24 hours — essential when logs rotate daily and you only care about recent data.

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1

Never pipe find output directly to cat for very large lists — use xargs instead to batch the paths into fewer grep invocations.

2

Add grep to filter by time window or error class. The Combined Log Format timestamp looks like [21/Jun/2026:03:. Grep for an hour prefix to scope the data to the spike window.

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep '21/Jun/2026:03:'

-h suppresses the filename prefix from grep output (cleaner for downstream parsing):

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep -h '21/Jun/2026:03:'

You now have every request logged in that hour on stdout — potentially millions of lines but all relevant.

3

Extract the status code field with cut. A Combined Log Format line looks like:

192.0.2.1 - alice [21/Jun/2026:03:47:12 +0000] "GET /api/users HTTP/1.1" 200 1234

Fields are space-delimited. The status code is field 9. cut -d' ' -f9 extracts it.

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep -h '21/Jun/2026:03:' \
  | cut -d' ' -f9

Output: a column of status codes — 200, 404, 503, … one per line. This is the input for the counting stage.

4

Count and rank with sort | uniq -c | sort -rn. This three-command sequence is the canonical histogram idiom:

  1. sort — groups identical lines adjacent (required by uniq)
  2. uniq -c — collapses runs and prefixes each with its count
  3. sort -rn — sorts by numeric count descending (highest first)
find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep -h '21/Jun/2026:03:' \
  | cut -d' ' -f9 \
  | sort | uniq -c | sort -rn

Sample output:

   4821 200
    312 503
     87 404
     14 401

You now know 503s spiked to 312 during that hour — that is the signal. The whole pipeline runs in seconds on millions of lines.

5

Extend with awk for multi-field analysis and calculated rates. awk is the Swiss Army knife of field-based text processing. Use it to compute the error rate in a single pass, or to pull the client IP (field 1) for top-talker analysis.

Top client IPs during the spike:

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep -h '21/Jun/2026:03:' \
  | awk '{print $1}' \
  | sort | uniq -c | sort -rn \
  | head -10

Error rate as a percentage with awk:

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep -h '21/Jun/2026:03:' \
  | awk '$9 >= 500 {err++} {total++} END {printf "5xx rate: %.1f%%\n", err/total*100}'

awk processes each line in one pass — no intermediate sort needed. Use awk when you need arithmetic, conditionals, or multiple output columns. Use sort | uniq -c when you need ranked counts.

Worked example

Full incident triage: find the top 5 IPs hammering 503 errors during a 10-minute window.

find /var/log/nginx -name 'access.log*' -not -name '*.gz' -mtime -1 \
  | xargs grep -h '21/Jun/2026:03:4[0-9]:' \
  | awk '$9 == 503 {print $1}' \
  | sort | uniq -c | sort -rn \
  | head -5

Breaking this down:

  • grep '21/Jun/2026:03:4[0-9]:' — the regex 4[0-9] matches minutes 40–49 (the 10-minute window).
  • awk '$9 == 503 {print $1}' — keeps only 503 lines and prints the client IP (field 1).
  • sort | uniq -c | sort -rn | head -5 — the ranking idiom, limited to 5 results.

Sample output:

    142 192.0.2.47
     89 192.0.2.23
     61 192.0.2.105
     44 192.0.2.8
     31 192.0.2.66

One IP accounts for 142 out of ~329 total 503s in that window — that is a stuck client or an upstream dependency retry loop hammering the service. You found the source in under 10 seconds.

Why this works

On macOS / BSD, sort does not support -rn combined as one flag in some older versions — use sort -r -n explicitly. awk on macOS is one-true-awk (BWK awk), not gawk; most field-processing idioms work identically. For gzip-compressed rotated logs (access.log.2.gz), replace the cat / xargs grep stage with zcat or zgrep: find /var/log/nginx -name '*.gz' | xargs zgrep -h 'pattern'.

Common mistake

uniq -c requires sorted input. If you pipe directly to uniq -c without sorting first, you get counts per consecutive run — identical values that appear in different positions are counted separately. Always sort | uniq -c, not uniq -c alone. A second common mistake: using sort -n (ascending) without -r when you want the highest count first — you end up reading from the bottom of the output.

Check yourself
Quiz

You run: cat access.log | cut -d' ' -f9 | uniq -c | sort -rn. The output looks wrong — counts like '1' for every line. What is the most likely cause?

Recap

The log-analysis pipeline composes six tools in sequence: find locates log files across rotations; xargs grep scopes to a time window or pattern; cut -d' ' -fN extracts a single field; sort | uniq -c | sort -rn is the ranking idiom for any counted histogram; awk handles multi-field arithmetic and computed rates in one pass. Build the pipeline incrementally — add one stage at a time and verify its output before adding the next. uniq -c requires sorted input. awk is the right tool when you need conditionals or arithmetic; sort | uniq -c is the right tool when you need ranked counts. Together they answer incident questions in seconds on gigabyte logs without installing anything.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources6
expand
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05
  6. 06

Trademarks belong to their respective owners. Editorial reference only.