Automate Linux Logs with Cron
Build a Log Watchdog with Linux text tools, archives, PATH, and cron.
Introduction
30 Second Summary
A stream of website requests quickly becomes more lines than anyone wants to inspect by hand. Important traffic patterns can stay hidden until a clear summary pulls them into view.
In this project, you will build a local Log Watchdog that turns a simulated website log into timestamped traffic and error summaries. It runs by name from any directory before a five-minute test schedule proves the automation works.
What You'll Build
Your finished Log Watchdog grows a single report with request totals, ranked IP addresses, error counts, and timestamped proof of both manual and automatic runs.
By the end of this project, you'll have:
- A traffic analysis workflow that ranks IP addresses with awk and anonymizes a copy with sed while preserving the original 50-line log.
- A verified backup workflow that creates a gzip-compressed archive, restores it separately, and confirms the restored files match their source.
- A personal Log Watchdog command that runs from any directory and appends summaries automatically through cron.
- Secret Mission: Perform a complete backup-and-restore drill on the earlier devops-journey folder.
Are there any prerequisites?
You should have completed Linux Levels 1-5 and be comfortable writing Bash scripts. You also need a Linux Mint Xfce computer with its Terminal app and GNU nano.
Before We Start
Before the hands-on work begins, lock in what you are building and why it matters. You are creating a local Log Watchdog that automatically summarizes simulated web traffic.
Build a Practice Log
Your Log Watchdog needs realistic traffic before it can produce useful summaries. A controlled practice log gives you that input without touching personal files or real system logs.
You will verify the tools on your Linux Mint computer before generating 50 repeatable records with Bash. You will then test what simple totals can reveal about the traffic.
In this step, get ready to:
- Verify the local text tools plus the cron scheduler.
- Create an executable make-log.sh generator inside ~/linux-level-6.
- Generate an access.log file with 50 simulated requests.
Verify the local tools
Each later command depends on a small local toolchain. Checking command paths first reveals anything missing before it interrupts the workflow.
- Check every required command from the Linux Mint Terminal you used earlier by running:
command -v bash nano awk sed sort uniq head wc grep tar gzip date realpath diff crontab
What does this command do?
The command -v check prints the path Bash uses for every available command. One resolved path per tool means the complete toolchain is ready.
✔️ Every command prints a path
Your local tools are ready. Continue to the cron service check below.
ⓧ One or more commands print no path
A missing path means Linux Mint cannot currently resolve that command. Its configured repositories provide the packages used in this project.
- Refresh the package information plus install the standard text and archive tools by running:
sudo apt update
sudo apt install bash nano mawk sed coreutils grep tar gzip diffutils
What do these commands do?
The first command refreshes the available package information. The second installs the required text-processing and archive packages.
- Install the package that provides crontab by running:
sudo apt install cron
What does this command do?
This installs the scheduler package. It provides the crontab command plus the packaged cron service.
- Repeat the complete command check after installation by running:
command -v bash nano awk sed sort uniq head wc grep tar gzip date realpath diff crontab
What should I see?
You should now see one resolved command path for every listed tool. A blank result still identifies the command whose package needs attention.
Still missing a command?
Check that the installation command completed without a package or network error. Repeat the command check after resolving the reported issue.
Ask for help with the exact missing command and the installation output: help me diagnose a missing Linux command
The crontab command can exist while its scheduler service is stopped. The service must be active before the watchdog can run automatically later.
- Check the packaged cron service by running:
systemctl is-active cron
What does this command do?
This asks the service manager for cron's current state. An active result confirms the scheduler is running.
✔️ Cron is active
The scheduler is running. Your machine is ready for the later automation step.
ⓧ Cron is inactive
Cron is installed but stopped. Starting it now also configures it to start when the computer boots.
- Enable plus start cron by running:
sudo systemctl enable --now cron
What does this command do?
The enable operation configures cron to start at boot. The --now option starts it during the same operation.
- Confirm the scheduler is running by checking it again:
systemctl is-active cron
What should I see?
You should now see active. This confirms cron is running after the change.
The practice files need an isolated home that every later command can find. The linux-level-6 directory keeps the generated traffic separate from your other files.
- Create the Level 6 workspace plus enter it by running:
mkdir -p "$HOME/linux-level-6"
cd "$HOME/linux-level-6"
What do these commands do?
The first command creates linux-level-6 inside your home directory. The second command moves the current Terminal session into that workspace.
- Confirm the Terminal is inside the new workspace by running:
pwd
What should I see?
The displayed path should end in /linux-level-6. This confirms every project file you create next lands in the intended workspace.
Generate the fake access log
A generator makes the same practice data whenever you need a clean copy. Each run recreates access.log with predictable traffic for later analysis.
- Create make-log.sh in the current workspace by opening it with:
nano make-log.sh
What does this command do?
The command opens make-log.sh in the terminal editor. Saving the new buffer creates the script inside ~/linux-level-6.
- Enter the complete generator code below into make-log.sh:
#!/usr/bin/env bash
OUTPUT="$HOME/linux-level-6/access.log"
: > "$OUTPUT"
for i in {1..50}; do
ip="192.168.1.$((10 + (i % 6)))"
path="/page$((i % 5)).html"
status="200"
if (( i % 10 == 0 )); then
status="500"
elif (( i % 4 == 0 )); then
status="404"
fi
printf '%s [31/Aug/2026:10:%02d:00] %s %s\n' \
"$ip" "$((i % 60))" "$path" "$status" >> "$OUTPUT"
done
printf 'Created %s with %s lines.\n' "$OUTPUT" "$(wc -l < "$OUTPUT")"
What does this code do?
- The OUTPUT variable points to the practice log. The next line clears any previous content before generation begins.
- The i loop produces exactly 50 records. Each pass calculates the ip plus the path fields.
- The status value begins at 200. The conditions assign predictable 404 or 500 values to selected records.
- The first printf appends each completed record. The final one reports the output path plus the generated line count.
- Save make-log.sh by pressing Ctrl+O.
- Confirm the displayed filename by pressing Enter.
- Exit the editor by pressing Ctrl+X.
✔️ Awesome, I've got everything!
Your generator is saved. Continue once make-log.sh is back at the Terminal prompt.
ⓧ I'd like to double check the full code
Compare your saved make-log.sh with this complete reference:
#!/usr/bin/env bash
OUTPUT="$HOME/linux-level-6/access.log"
: > "$OUTPUT"
for i in {1..50}; do
ip="192.168.1.$((10 + (i % 6)))"
path="/page$((i % 5)).html"
status="200"
if (( i % 10 == 0 )); then
status="500"
elif (( i % 4 == 0 )); then
status="404"
fi
printf '%s [31/Aug/2026:10:%02d:00] %s %s\n' \
"$ip" "$((i % 60))" "$path" "$status" >> "$OUTPUT"
done
printf 'Created %s with %s lines.\n' "$OUTPUT" "$(wc -l < "$OUTPUT")"
How should I use this reference?
Check the variable names plus the condition order carefully. The 500 condition comes first so matching records receive one final status.
- Give the generator executable permission by running:
chmod +x make-log.sh
What does this command do?
The +x mode adds executable permission to make-log.sh. This allows Bash to launch the file directly from the current directory.
- Generate the 50-record practice log by running:
./make-log.sh
What should I see?
You should see a confirmation naming access.log plus 50 lines. This proves the executable generator completed its loop.
- Preview the first five generated records by running:
head -n 5 access.log
What should I see?
You should see five records with an IP address plus a timestamp. Each record also contains a /pageN.html path plus a numeric status.
Generator did not run?
If the Terminal reports Permission denied, repeat the executable-permission command. If it cannot find the file, confirm your path ends in /linux-level-6.
If the script reports a syntax problem, compare make-log.sh with the full-code tab. Check the closing fi plus done lines first.
Ask for help using the exact Terminal output: help me debug my log generator
Verify the traffic totals
Simple counts provide a quick integrity check for the generated data. They can prove the file size plus the error mix before you rely on the log in later steps.
Before you run the checks, think about what each total can prove about the traffic.
- Count all records in access.log by running:
wc -l access.log
What should I see?
You should see 50 access.log. This verifies the generator wrote all 50 records.
- Count records containing status 404 by running:
grep -c "404" access.log
What should I see?
You should see 10. This confirms ten records contain status 404.
- Count records containing status 500 by running:
grep -c "500" access.log
What should I see?
You should see 5. The remaining 35 records carry status 200.
- Compare the two error totals to identify the more common error.
- Use only the three totals above to identify which IP address sent the most requests.
The 404 error is more common. The totals cannot identify the busiest IP because they do not isolate or rank the IP field.
Your controlled practice log is ready with 50 verified records. Next, you will use awk to extract the IP field plus uncover the busiest senders.
Analyze Log Fields with awk
Your generated access.log now contains 50 requests with known error totals. Those results prove your practice data is ready for deeper analysis.
The totals from grep cannot reveal which IP addresses sent the most requests. In this step, awk isolates fields so you can rank activity with a pipeline.
In this step, get ready to:
- Extract IP addresses, paths, and HTTP status codes from access.log.
- Build ranked counts with sort, uniq, and head.
- Test a shorter disk-usage extraction for the earlier sysreport.sh script.
Extract one field at a time
Each line in access.log is a record. Whitespace splits each record into fields that awk references by position.
How does awk see the log?
- The $1 field contains the IP address.
- The $3 field contains the requested page path.
- The $4 field contains the status code.
- Extract every IP address by running this command:
awk '{print $1}' access.log
What does this extraction show?
The print action outputs the first field from every record. You should see 50 IP addresses with one address on each line.
- Limit the IP output to its first ten lines by running:
awk '{print $1}' access.log | head
Why use head?
The head command shows the first ten lines by default. This keeps a quick field check readable.
- Extract the first ten page paths by running:
awk '{print $3}' access.log | head
What does the third field contain?
The $3 reference selects each requested path. You should see ten values beginning with /page.
- Extract the first ten status codes by running:
awk '{print $4}' access.log | head
What does the fourth field contain?
The $4 reference selects the result of each request. You should see values such as 200 and 404.
Seeing blank field output?
Confirm that the field reference sits inside single quotes. A misplaced quote can make the shell interpret the dollar sign before awk reads it.
Check that your Terminal is still inside ~/linux-level-6. The command needs to read the existing access.log file.
help me diagnose blank awk field output
Build the top-talkers pipeline
A pipeline passes one command's output into the next command. You will add one stage at a time so each transformation stays visible.
- Group identical IP addresses together by running:
awk '{print $1}' access.log | sort
What does sorting change?
The first command extracts IP addresses. The sort command places matching addresses beside each other.
- Count each group of adjacent IP addresses by running:
awk '{print $1}' access.log | sort | uniq -c
Why does sort come first?
The uniq command only combines adjacent matches. Its -c option prefixes each unique address with its count.
- Rank the busiest IP addresses first by running:
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head
How does the ranking work?
- The final sort compares the counts numerically through -n.
- The -r option reverses the order so the largest counts appear first.
- The output shows two IP addresses with 9 requests. The remaining four IP addresses each have 8 requests.
Before you run the next pipeline, which status code do you expect to have the largest count?
- Count each status code in descending order by running:
awk '{print $4}' access.log | sort | uniq -c | sort -nr
What should you see?
- The first result is 35 200.
- The second result is 10 404.
- The third result is 5 500.
That closes the blind spot from your earlier counts. You can now isolate a field before calculating which values appear most often.
Do the counts look wrong?
Make sure sort appears before uniq. Matching values must be adjacent before they can be counted together.
Check that the final sort includes both -n and -r. Those options place the largest numerical count first.
help me fix my awk ranking pipeline
Patch the earlier disk check
The same field technique can shorten the disk extraction from your Level 5 report. First, test the new expression before changing any file.
- Extract the root filesystem's usage percentage by running:
df -h / | awk 'NR==2 {print $5}'
How does this disk extraction work?
- The NR==2 condition selects the second output record.
- The $5 reference prints the usage percentage field.
- You should see one percentage such as 42%. Your computer's value will differ.
You can apply this tested pattern to the earlier report with GNU nano. Use the fallback script if sysreport.sh is no longer available.
I Have sysreport.sh
- Navigate in Terminal to the directory where you saved sysreport.sh during Level 5.
- Open sysreport.sh with GNU nano.
- Take a screenshot of its current disk extraction that uses df with grep.
- Replace only that extraction with this tested pattern:
disk_usage="$(df -h / | awk 'NR==2 {print $5}')"
What does this replacement preserve?
The command substitution captures the percentage printed by awk. The disk_usage variable keeps the result available to the rest of the report.
- Save sysreport.sh in GNU nano.
- Exit GNU nano.
- Run the report with the command you used in Level 5.
- Take an after screenshot showing the disk percentage from the shorter extraction.
You should see the same disk percentage that the longer extraction produced.
I Need the Fallback
A small fallback script isolates the pattern from the unavailable Level 5 file. It captures one percentage before printing it.
- Return to ~/linux-level-6 in Terminal.
- Create disk-check.sh inside ~/linux-level-6 with GNU nano.
- Enter this fallback code:
disk_usage="$(df -h / | awk 'NR==2 {print $5}')"
printf '%s\n' "$disk_usage"
What does the fallback do?
- The first line stores the fifth field from the filesystem record in disk_usage.
- The second line prints the stored percentage on its own line.
- Save disk-check.sh in GNU nano.
- Exit GNU nano.
- Run the fallback script through Bash by running:
bash disk-check.sh
What should the fallback print?
Bash reads the commands from disk-check.sh before printing one disk percentage. The value should match the direct extraction you tested.
Is the disk percentage blank?
Run the direct disk command again to confirm the filesystem information has a second line. The extraction depends on that record containing the usage field.
Check the braces around the print action. Also check the single quotes around the complete awk expression.
help me debug the blank disk percentage
- Return to ~/linux-level-6 in Linux Mint Terminal.
Before you run the final check, do you think any of these extraction commands changed the original access.log file?
- Confirm the preserved status totals by running:
awk '{print $4}' access.log | sort | uniq -c | sort -nr
What does the final check prove?
You should still see 35 200, 10 404, and 5 500 in descending order. The unchanged totals prove that your analysis only read the original log.
Strong result. You can now turn raw log fields into ranked evidence while preserving the source file. Next, you will transform matching text safely.
Sanitize Logs with sed
Your awk pipelines now rank the IP addresses in access.log. They also count each status code.
Those pipelines leave the original text untouched. This step uses GNU sed to rewrite matching text in a stream or a safe copy.
In this step, get ready to:
- Compare one replacement with global replacement.
- Edit a disposable copy in place while preserving the original log.
- Create an anonymized log that hides each final IP octet.
Compare one replacement with global replacement
A sed substitution searches for a pattern before replacing the matching text. The optional g flag applies that replacement to every match on each line.
- Test a single replacement by running this command:
printf 'old old old\n' | sed 's/old/new/'
What does this command do?
The substitution expression replaces the first old match on the line. The remaining matches pass through unchanged.
You'll see new old old in the Terminal.
- Apply the replacement globally by running this command:
printf 'old old old\n' | sed 's/old/new/g'
What changes with the global flag?
The g flag repeats the substitution across the whole line. Every matching word is replaced.
You'll now see new new new in the Terminal.
- Transform the status text as a stream by running this pipeline:
sed 's/404/NOT-FOUND/g' access.log | grep 'NOT-FOUND' | head -n 3
How does this pipeline work?
- The sed expression changes every 404 match to NOT-FOUND in the output stream.
- The grep stage keeps the transformed records.
- The head stage limits the preview to three records.
You'll see three transformed records containing NOT-FOUND. The command writes no changes to access.log.
No transformed records?
Check that the substitution contains 404 as its search pattern. Confirm that NOT-FOUND uses the same spelling in the sed expression and the grep stage.
Make sure your Terminal is still inside ~/linux-level-6.
help me debug the streaming sed pipeline
Edit a disposable copy safely
In-place editing changes a file directly. A disposable copy protects your 50-record source log while you practice this riskier mode.
- Create the disposable copy by running this command:
cp access.log access-edit.log
Why make a copy?
The cp command creates access-edit.log from the unchanged source. Your in-place experiment now has a separate target.
No Terminal output means the copy completed successfully.
Protect the original log
The -i option writes the replacement into its target file. Your command targets only access-edit.log.
This keeps access.log available as the trusted source for every later report.
- Replace every copied 404 value in place by running this command:
sed -i 's/404/NOT-FOUND/g' access-edit.log
What does in-place editing change?
The -i option saves the transformed text back into access-edit.log. The global flag replaces every matching value in that copy.
No Terminal output means the in-place edit completed.
- Count the transformed values in the edited copy by running this command:
grep -c 'NOT-FOUND' access-edit.log
What does this count prove?
The count confirms how many records now contain NOT-FOUND. It verifies that all ten copied error values were replaced.
You'll see 10 in the Terminal.
- Prove the original still contains its status values by running this command:
grep -c '404' access.log
What does the source count prove?
The original still contains all ten 404 records. Your in-place edit affected only the disposable copy.
You'll see 10 again.
Seeing the wrong count?
A zero count for NOT-FOUND usually means the in-place command targeted the wrong file or omitted the g flag. Repeat the copy command before trying the edit again.
If the original count changed, restore access.log by running the existing executable generator from this workspace.
help me restore the practice logs safely
Anonymize the final IP octet
An extended regular expression can recognize the four-part structure of each IP address. A capture group preserves the first three octets before \1 restores them in the replacement.
- Create the sanitized log by running this command:
sed -E 's/([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)[0-9]{1,3}/\1XXX/g' access.log > access-clean.log
How does the anonymizing pattern work?
- The -E option enables the extended regular expression.
- The capture group holds the first three IP octets plus their dots.
- The \1XXX replacement restores that captured prefix before adding XXX.
- The redirection writes the complete transformed stream to access-clean.log.
No Terminal output means the sanitized copy was created.
- Preview the first three sanitized records by running this command:
head -n 3 access-clean.log
What should the preview show?
Each displayed address keeps 192.168.1. before ending with XXX. The timestamp, path, and status remain in their original positions.
You'll see records shaped like 192.168.1.XXX [31/Aug/2026:10:01:00] /page1.html 200.
IP address still visible?
Check that the command includes -E. Confirm that every literal dot in the matching pattern has a backslash.
Make sure the replacement contains \1XXX. The backreference restores the captured prefix.
help me debug the IP anonymization pattern
Before you run the final check, what do you expect the original count and sanitized first record to show?
- Verify both final files by running these commands:
grep -c "404" access.log
head -n 1 access-clean.log
What does the final check prove?
- The first command proves that access.log still contains its ten original 404 records.
- The second command proves that access-clean.log hides the final IP octet with XXX.
You'll see 10 first. The sanitized record below it starts with 192.168.1.XXX.
You have the safe editing pattern locked in. Your source log remains trustworthy while both transformed copies show their intended changes.
Your original log and sanitized copy are ready for the next stage. Next, you will package the important files into a compressed archive before testing a separate restore.
Archive and Restore the Logs
Your original log is still intact. You also have an anonymized copy that is safe to package for practice.
Separate log files are awkward to preserve or move. A gzip-compressed tar archive bundles them into one portable file that you can restore and verify.
In this step, get ready to:
- Stage copies of the original log, anonymized log, and generator script.
- Package the staged files in an archive whose contents you can inspect.
- Restore a separate copy that matches the source directory.
Stage and compress the backup
The backup-source directory gives your archive one clear top-level folder. Keeping logs-backup.tar.gz beside that directory prevents the archive from being packed inside itself.
- Prepare the backup source directory by running these commands:
mkdir -p backup-source
cp access.log access-clean.log make-log.sh backup-source/
What do these commands do?
- The mkdir -p command creates backup-source when the directory is absent.
- The -p option also accepts an existing directory.
- The cp command places copies of access.log, access-clean.log, and make-log.sh inside that directory.
A silent prompt means the staging command completed. Your backup files are now grouped under backup-source.
- Create the gzip-compressed archive from the staged directory by running:
tar -czf logs-backup.tar.gz backup-source/
How does tar build the archive?
- The c flag selects create mode.
- The z flag filters the archive through gzip compression.
- The f flag identifies the next argument as the archive filename.
- The trailing backup-source/ operand places that directory at the top of the archive.
- Confirm the archive exists by running:
ls -lh logs-backup.tar.gz
What does the listing show?
- The -l option shows a detailed file listing.
- The -h option displays the archive size in a readable unit.
You'll see one listing ending in logs-backup.tar.gz with a non-zero size.
Archive missing or empty?
- Check that your Terminal is still inside ~/linux-level-6.
- Confirm that access.log, access-clean.log, and make-log.sh remain in that directory.
- Repeat the staging commands if backup-source is empty.
help me diagnose why my logs-backup.tar.gz archive is missing or empty.
Inspect the archive without extracting it
An archive can reveal its stored paths before you extract anything. The t mode lists those paths without changing your files.
- Inspect the archive contents by running:
tar -tzf logs-backup.tar.gz
How does archive inspection work?
- The t flag selects list mode.
- The z flag reads the gzip-compressed archive.
- The f flag identifies logs-backup.tar.gz as the archive to inspect.
You'll see backup-source/, backup-source/access.log, backup-source/access-clean.log, and backup-source/make-log.sh in the listing.
Archive listing failed?
- Check that the archive name is exactly logs-backup.tar.gz.
- Confirm that the listing command uses t instead of the create flag c.
help me inspect my gzip-compressed tar archive.
Restore and compare the files
A backup becomes trustworthy after a separate extraction reproduces the source. The -C option directs the extracted files into an existing destination.
- Create the separate restore destination by running:
mkdir -p restored
Why use a separate destination?
The restored directory keeps the extracted copy away from backup-source. This protects the source during the restore test.
- Extract the archive into that destination by running:
tar -xzf logs-backup.tar.gz -C restored
How does extraction work?
- The x flag selects extraction mode.
- The z flag processes the gzip compression.
- The -C restored option sends the extracted directory into restored.
- Inspect the restored directory by running:
ls restored/backup-source
What does this check prove?
This listing checks that the archive recreated its top-level directory. It also confirms that the expected files are available for comparison.
You'll see access.log, access-clean.log, and make-log.sh in the restored directory.
Before you compare the directories, do you expect diff to print any differences?
- Compare the source with the restored copy by running:
diff -qr backup-source restored/backup-source
How does diff verify the restore?
- The -q option reports only differences.
- The -r option compares subdirectories recursively.
- No output with exit status 0 means the restored directory matches the source.
That's the restore test passed. Your archive reproduced the staged logs and generator without any reported differences.
Seeing differences after the restore?
- Read each reported line to identify the missing or changed file.
- Confirm that the comparison uses backup-source as the source path.
- Confirm that the restored path is exactly restored/backup-source.
help me understand my diff output after restoring the archive.
Your logs now have a tested backup that you can inspect and restore. Next, you'll turn the analysis into a Log Watchdog that runs by name and on a schedule.
Install and Schedule the Log Watchdog
Your restored archive proves you can protect the files behind your simulated website. The next goal is to turn those files into an automated traffic report.
The analysis still depends on someone remembering to run it. You will package the commands into a Bash script that works through PATH. You will use cron to run it on a schedule.
In this step, get ready to:
- Build a Log Watchdog that appends request summaries to a report.
- Install the watchdog as a personal command available from any directory.
- Test a five-minute schedule before removing it safely.
Build and run the watchdog manually
The Log Watchdog combines the text-processing commands you have already tested. Each run appends a timestamped snapshot to watchdog-report.txt.
- Open logwatch.sh in GNU nano by running this command:
nano logwatch.sh
What does this command do?
GNU nano opens logwatch.sh inside your existing ~/linux-level-6 workspace. Nano creates the file when it does not already exist.
- Enter this script into logwatch.sh:
#!/usr/bin/env bash
PROJECT_DIR="$HOME/linux-level-6"
LOG_FILE="$PROJECT_DIR/access.log"
REPORT_FILE="$PROJECT_DIR/watchdog-report.txt"
if [[ ! -f "$LOG_FILE" ]]; then
printf 'Error: %s was not found.\n' "$LOG_FILE" >&2
exit 1
fi
{
printf '\n=== Log Watchdog: %s ===\n' "$(date '+%Y-%m-%d %H:%M:%S')"
printf 'Total requests: %s\n' "$(wc -l < "$LOG_FILE")"
printf '404 errors: %s\n' "$(grep -c ' 404$' "$LOG_FILE")"
printf '500 errors: %s\n' "$(grep -c ' 500$' "$LOG_FILE")"
printf 'Top 3 IPs:\n'
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n 3
} >> "$REPORT_FILE"
printf 'Updated %s\n' "$REPORT_FILE"
What does this code do?
- The three path variables identify the project directory, preserved input log, and append-only report.
- The file check stops the script if access.log is missing.
- The report block uses date, wc, grep, and awk to build one traffic snapshot.
- The final pipeline sorts request IPs by frequency and keeps the top three.
- Press Ctrl+O to save the script.
- Press Enter to confirm the file name.
- Press Ctrl+X to exit nano.
✔️ Awesome, I've got everything!
Your Log Watchdog is saved. Check that nano has returned you to the terminal prompt.
ⓧ I'd like to double check the full code
Compare your complete logwatch.sh file with this reference:
#!/usr/bin/env bash
PROJECT_DIR="$HOME/linux-level-6"
LOG_FILE="$PROJECT_DIR/access.log"
REPORT_FILE="$PROJECT_DIR/watchdog-report.txt"
if [[ ! -f "$LOG_FILE" ]]; then
printf 'Error: %s was not found.\n' "$LOG_FILE" >&2
exit 1
fi
{
printf '\n=== Log Watchdog: %s ===\n' "$(date '+%Y-%m-%d %H:%M:%S')"
printf 'Total requests: %s\n' "$(wc -l < "$LOG_FILE")"
printf '404 errors: %s\n' "$(grep -c ' 404$' "$LOG_FILE")"
printf '500 errors: %s\n' "$(grep -c ' 500$' "$LOG_FILE")"
printf 'Top 3 IPs:\n'
awk '{print $1}' "$LOG_FILE" | sort | uniq -c | sort -nr | head -n 3
} >> "$REPORT_FILE"
printf 'Updated %s\n' "$REPORT_FILE"
How should I use this reference?
Check each path, quoted variable, status pattern, and redirection operator. The closing redirection makes every run append a new section to the same report.
- Make logwatch.sh executable by running this command:
chmod +x logwatch.sh
What does this command do?
The executable permission lets Bash launch logwatch.sh as a command. The shebang selects Bash as its interpreter.
- Run the watchdog manually from ~/linux-level-6 with this command:
./logwatch.sh
What happens during this run?
The script reads the preserved access.log file. It appends the first timestamped summary to watchdog-report.txt.
You should see Updated /home/your-username/linux-level-6/watchdog-report.txt with your own username in the path.
- Display the first completed report by running this command:
cat watchdog-report.txt
What should the report contain?
The report shows one timestamped heading. It also shows Total requests: 50, 404 errors: 10, and 500 errors: 5.
The final three lines rank the busiest IP addresses. This turns the earlier analysis pipeline into a repeatable operator report.
Report missing or incomplete?
Confirm the terminal is still inside ~/linux-level-6. Check that access.log still contains the preserved records.
If execution is denied, repeat the executable-permission command. If the script reports a syntax problem, compare it with the full-code reference.
help me debug my Log Watchdog script
Install the command on PATH
An environment variable gives programs a named value inherited from the current shell. HOME identifies your home directory. PATH lists the directories Bash searches for commands.
- Display your home directory by running this command:
echo "$HOME"
What does this show?
The output is your home directory. Your project, personal command directory, and Bash configuration all live beneath this location.
- Display the current command-search directories by running this command:
echo "$PATH"
How does PATH work?
The output is a colon-separated list of directories. Bash searches them in order when a command name has no slash.
- Preview the current exported environment by running this command:
printenv | head
What does this preview show?
Each line uses NAME=value form. The short preview keeps the full environment from filling your terminal.
- Create the temporary Level 6 status variable by running these commands:
export LEVEL6_STATUS="watchdog-ready"
printenv LEVEL6_STATUS
What does export change?
The first line marks LEVEL6_STATUS for inheritance by later commands. The second line confirms its value is watchdog-ready.
- Create your personal command directory and install a copy of the watchdog by running these commands:
mkdir -p "$HOME/bin"
cp "$HOME/linux-level-6/logwatch.sh" "$HOME/bin/logwatch.sh"
Why install a copy?
The ~/bin directory gives your personal commands a predictable home. The project copy remains available for future edits.
Changing ~/.bashrc affects future interactive shells. The backup gives you a direct recovery copy if the shell behaves unexpectedly.
- Back up ~/.bashrc and open the original in nano by running these commands:
cp "$HOME/.bashrc" "$HOME/.bashrc.level6-backup"
nano "$HOME/.bashrc"
What do these commands protect?
The first command preserves the configuration that existed before this project. The second command opens the active Bash configuration for editing.
- Add this line at the end of ~/.bashrc:
export PATH="$HOME/bin:$PATH"
Why prepend your bin directory?
This line places $HOME/bin at the start of the existing search list. Bash can then find your personal watchdog by name.
- Press Ctrl+O to save ~/.bashrc.
- Press Enter to confirm the file name.
- Press Ctrl+X to exit nano.
- Apply the updated configuration to the current shell by running this command:
source "$HOME/.bashrc"
What does source do?
The source command executes the updated configuration in the current shell. This activates the new search path without waiting for another login.
- Open a new Linux Mint Terminal window from the applications menu.
- Verify how Bash resolves the installed watchdog by running this command:
command -v logwatch.sh
What does command lookup prove?
The resolved path proves Bash found the installed copy through the updated search list. You should see a path ending in /bin/logwatch.sh.
You should see /home/your-username/bin/logwatch.sh with your own username.
- Run the installed watchdog away from the project directory with these commands:
cd /tmp
logwatch.sh
Why does this work from tmp?
Bash finds logwatch.sh inside your personal command directory. The script uses absolute paths derived from HOME, so it still reads the Level 6 log.
The updated-report message proves the installed copy works outside ~/linux-level-6.
Command not found?
Confirm your ~/.bashrc line matches the reference exactly. Make sure ~/bin/logwatch.sh exists.
Apply the configuration again with the source command above. You can also open another Terminal window to load the updated file.
help me fix my PATH setup
Schedule, prove, remove, and archive
Cron runs commands with a smaller default environment than your interactive terminal. An absolute script path keeps the schedule independent of your personal command lookup settings.
- Confirm the cron service is active by running this command:
systemctl is-active cron
What should the service report?
You should see active. This confirms the scheduler is ready to check user crontabs.
- Resolve the installed script's absolute path by running this command:
realpath "$HOME/bin/logwatch.sh"
Why resolve the path?
The result contains your actual home directory with no shorthand. Cron can use this literal path without depending on an interactive shell.
- Record the resolved path here: /home/your-username/bin/logwatch.sh.
- Open the current user's crontab in nano by running this command:
EDITOR=nano crontab -e
What opens here?
The command opens your current user's schedule in nano. Saving the file installs the updated schedule automatically.
A user cron line starts with five time fields. They represent minute, hour, day of month, month, and weekday in that order.
- Add this five-minute template to the crontab:
*/5 * * * * /home/mintuser/bin/logwatch.sh
How does this schedule work?
The */5 minute field runs the command on every clock minute divisible by five. The remaining asterisks allow every hour and calendar date.
- Replace /home/mintuser/bin/logwatch.sh with /home/your-username/bin/logwatch.sh.
- Press Ctrl+O to save the crontab.
- Press Enter to confirm the save.
- Press Ctrl+X to exit nano.
- Confirm the installed schedule by running this command:
crontab -l
What should the crontab show?
The output should include one watchdog entry with five time fields. Its command path should match the result you recorded from realpath.
This wait can take up to five minutes because the schedule runs only on matching clock minutes. Keep the computer awake while cron reaches the next minute divisible by five.
- Wait until the next matching clock minute has passed.
Before you count the sections, do you expect the report to contain only manual runs or a new scheduled run too?
- Count the report headings by running this command:
grep -c '^=== Log Watchdog:' "$HOME/linux-level-6/watchdog-report.txt"
What proves the schedule worked?
A result of 2 or greater proves the report has multiple timestamped sections. At least one section comes from a manual run. At least one section comes from cron.
That is the automation working: your Log Watchdog now produces traffic summaries without a manual launch.
- Display the report timestamps by running this command:
cat "$HOME/linux-level-6/watchdog-report.txt"
Which entry was automatic?
Compare the timestamped headings with the time you waited for the matching cron minute. The scheduled section contains the same totals because every run analyzes the preserved 50-record log.
Report count did not grow?
Confirm the cron service still reports active. Check that the installed crontab contains your complete absolute path.
A five-minute schedule only runs on clock minutes divisible by five. Keep the computer awake until the next matching minute.
help me debug my cron schedule
Removing the test schedule stops future automatic runs. Your existing report remains intact.
- Reopen the current user's crontab in nano by running this command:
EDITOR=nano crontab -e
Why edit instead of remove?
Editing lets you delete only the temporary watchdog line. Other schedules belonging to your user remain untouched.
- Delete only the line containing logwatch.sh.
- Press Ctrl+O to save the cleaned crontab.
- Press Enter to confirm the save.
- Press Ctrl+X to exit nano.
- Check that no watchdog entry remains by running this command:
crontab -l 2>/dev/null | grep -F 'logwatch.sh'
What confirms safe cleanup?
No output means the temporary watchdog entry is gone. An empty user crontab is acceptable when this was its only schedule.
The automation test is complete. The final archive now preserves the project scripts, reports, practice archive, and restored files.
- Create the final archive outside ~/linux-level-6 by running this command:
tar -czf "$HOME/linux-level-6-final.tar.gz" -C "$HOME" linux-level-6
Why create the archive outside the project?
The archive is written directly beneath your home directory. The change-directory option makes every archived path begin with the project folder name.
Before you inspect the archive, what path prefix do you expect every listed item to share?
- Inspect the start of the final archive listing by running this command:
tar -tzf "$HOME/linux-level-6-final.tar.gz" | head
What proves the archive is ready?
You should see paths beginning with linux-level-6/. This confirms the archive contains the project beneath one top-level directory.
You have turned the practice log into a personal command with tested scheduled execution. The test schedule is gone, while the report and final archive remain ready to demonstrate.
Secret mission
Recover the Level 1 Journey
A backup earns your trust when a restore test reproduces the original files. Can you archive your Level 1 journey, restore it somewhere separate, and prove both copies match?
Clean Up Your Resources
Clean Up Your Resources
Choose a cleanup option for your local resources. Everything runs locally at no ongoing cost.
Resources you used:
- The ~/linux-level-6/ workspace containing your scripts, logs, reports, practice archive, backup source, and restored files.
- The installed ~/bin/logwatch.sh command.
- The Level 6 PATH line in your ~/.bashrc configuration.
- The ~/.bashrc.level6-backup safety copy.
- The final ~/linux-level-6-final.tar.gz archive.
- The Secret Mission recovery archive at ~/devops-journey-backup.tar.gz.
- The Secret Mission restored copy under ~/devops-journey-restored.
Keep everything running
No action needed. The removed test schedule means the watchdog runs only when you launch it.
- Keep ~/linux-level-6/ to regenerate logs or run the watchdog again.
- Keep ~/bin/logwatch.sh to launch the watchdog by name from any directory.
- Keep the Level 6 PATH line in ~/.bashrc.
- Keep ~/.bashrc.level6-backup as a copy of your earlier Bash configuration.
- Keep the final archive as a portable copy of the finished project.
- Keep the Secret Mission archive and restored copy as evidence of your recovery test.
What stays active?
No watchdog schedule remains in your current-user crontab. Keeping these files does not start a recurring process.
Pause - I'll come back to this later
Use this option when you want the project files without the personal command.
The test schedule is already removed. No background watchdog needs to be stopped.
- Keep ~/linux-level-6/ as your editable project copy.
- Keep ~/linux-level-6-final.tar.gz as your finished backup.
- Remove the installed command copy and check that it no longer resolves by running:
rm "$HOME/bin/logwatch.sh"
command -v logwatch.sh
What Does This Pause?
The first command removes only the copy stored in ~/bin. Your source script remains in ~/linux-level-6/.
No output from command -v confirms that the named command is unavailable. Your Log Watchdog is now paused without losing its source or reports.
Delete - I don't want to use this again
Remove the Level 6 workspace, personal command, configuration change, and project archives. Follow the cleanup in order so your shell configuration is restored before its backup disappears.
Deletion Is Permanent
Deleting these files removes your scripts, generated logs, reports, and final archive. Keep a separate copy first if you want to revisit the project.
The cleanup targets only the Secret Mission archive and restored copy. It does not target your original devops-journey folder.
- Confirm that the removed watchdog schedule has not returned by running:
crontab -l 2>/dev/null | grep -F 'logwatch.sh'
What Should You See?
You should see no output. This confirms that deleting the script does not leave a scheduled command pointing to a missing file.
- Return ~/.bashrc to its earlier PATH behavior by opening it with:
nano "$HOME/.bashrc"
Why Edit This File First?
GNU nano opens your current shell configuration for a precise edit. Removing only the Level 6 line preserves every setting that existed before this project.
- Delete this exact line from the end of the file: export PATH="$HOME/bin:$PATH".
- Save ~/.bashrc with Ctrl+O.
- Press Enter to confirm the filename.
- Exit GNU nano with Ctrl+X.
- Apply the restored configuration to your current shell by running:
source "$HOME/.bashrc"
What Does Source Do?
The source command reads the updated file in your current shell. New Terminal windows also stop receiving the Level 6 PATH line.
- Review every path below before deleting anything.
- Confirm each recursive removal when rm asks.
- Delete the listed Level 6 resources by running these commands:
rm "$HOME/bin/logwatch.sh"
rm "$HOME/.bashrc.level6-backup"
rm -r -I "$HOME/linux-level-6"
rm "$HOME/linux-level-6-final.tar.gz"
rm "$HOME/devops-journey-backup.tar.gz"
rm -r -I "$HOME/devops-journey-restored"
What Did These Commands Remove?
The file commands remove the installed watchdog, shell backup, final archive, and Secret Mission archive.
The recursive commands remove the Level 6 workspace and Secret Mission restore directory after confirmation.
Your original Level 1 folder remains outside every listed deletion path. A prompt that returns without an error confirms that the selected resource was removed.
- Confirm that Bash no longer finds the personal watchdog command by running:
command -v logwatch.sh
Cleanup Complete
You should see no output. That confirms the installed command copy is gone and the Log Watchdog no longer resolves by name.
Nice Work!
Nice Work!
You did it! Your local Log Watchdog now turns a preserved practice log into timestamped traffic summaries through manual runs or a tested schedule.
You've learned how to:
- Build a safe text-processing workflow around a repeatable 50-record access.log. You extracted fields with awk. You ranked traffic with sort plus uniq. You anonymized IP addresses with sed. You preserved the original log.
- Create gzip-compressed archives with tar. You inspected each archive before extraction. You restored files into separate directories. You confirmed each restored tree with diff.
- Build an executable Log Watchdog that appends timestamped summaries to watchdog-report.txt. You made the command available from any directory through PATH. You used cron to add an automatic report. You proved the schedule worked before removing its test entry.
- Secret Mission: Create a recovery archive for the earlier devops-journey folder or the linux-level-6 fallback. You inspected the archive before restoring it. You confirmed the restored tree matched the source.
Ready to quiz yourself?