Part-3: Crash-Proof Server Log Reader

Build a Python log reader that handles missing files and writes event reports.

Introduction

30 Second Summary

A long text file can hide one important failure among dozens of routine updates. Reading every line by hand makes important details easy to miss.

In this project, you will build a command-line server log reader with Python. It monitors a log, handles a missing input file, and saves a formatted summary report.

What You'll Build

You'll run the finished reader, watch it classify server events, and open a saved report that surfaces problems at a glance.

By the end of this project, you'll have:

  • Automated server checks: Run one instruction across several named servers and repeat retry-style checks without copying code.
  • Crash-proof file access: Show a clear message when server_events.txt is missing instead of stopping with a traceback.
  • Formatted monitoring report: Turn log lines into a saved summary_report.txt with readable event totals.
  • Secret Mission: Classify INFO, WARN, and ERROR events with a dictionary and add warning totals to the report.

Are there any prerequisites?

You need basic familiarity with variables, lists, if checks, and print() from Projects 1 and 2.

You also need Linux Mint XFCE with Xed, Xfce Terminal, and Python 3. Step 1 verifies each tool without changing your system Python installation.

Before We Start

Before We Start...

Before the hands-on work begins, lock in what your log reader will do and why its report matters.

Check the Existing Tools

Your log reader needs a reliable place to run before you add loops or file handling. This step confirms that Linux Mint can run a saved Python file.

You will use Xed to edit the project and Xfce Terminal to run it. These tools are already part of your confirmed environment, so this step does not change the system Python installation.

In this step, get ready to:
  • Open Xed and Xfce Terminal.
  • Confirm that the python3 command works.
  • Create and run project3_loops.py.
Open the project tools
  • Open the Linux Mint application menu.
  • Search for Xed.
  • Select Xed from the results.
  • Open the application menu again.
  • Search for Terminal.
  • Select Xfce Terminal from the results.

Xed is the text editor where you save Python instructions. Xfce Terminal runs those instructions through the Python interpreter.

Verify Python 3
  • Print a readiness message by running this command in Xfce Terminal:
python3 -c "print('Python 3 is ready')"

You should see exactly Python 3 is ready.

✔️ I see the readiness message

Python 3 is ready to run the project. Continue to the workspace file.

ⓧ The command is unavailable

The command is unavailable in your current terminal session. Use the official Linux Mint python3 package page to review the package, then follow your environment's approved installation process.

Command not found?

Check that you typed python3 in lowercase. Reopen Xfce Terminal after Python is installed.

Help me check my Python setup.

Create the workspace file
  • Open project3_loops.py in Xed by running this command:
xed project3_loops.py
  • Enter this starting code in project3_loops.py:
# Confirm Python can run this saved script
print("Project 3 workspace ready")

What does this code do?

The comment records why this line exists. The print() call produces a visible result when Python runs the file.

  • Save project3_loops.py in Xed.
  • Return to Xfce Terminal.

Before you run the script, what message do you expect to see?

  • Run the saved workspace file with this command:
python3 project3_loops.py

You should see exactly Project 3 workspace ready.

Your workspace works

That first check is complete. Python can now find and run the file you saved in Xed.

Don't see the workspace message?

Confirm that Xfce Terminal is showing the same folder where you opened project3_loops.py. Check that the filename ends in .py.

Help me fix the workspace check.

✔️ Awesome, I've got everything!

Your workspace file is saved and runnable.

ⓧ I'd like to double check the full code

Compare your saved file with this complete version.

# Confirm Python can run this saved script
print("Project 3 workspace ready")

Your tools are ready. Next, you will replace the starter message with loops that repeat server checks.

Repeat Server Checks

Your Python workspace now runs from Xfce Terminal. The next job is to repeat checks without copying the same instruction for every machine.

A server is a computer that provides a service, such as a website, database, or cache. Monitoring means checking those systems regularly and recording signs of failure.

In this step, get ready to:
  • Run one check across three server names.
  • Compare a while counter with a fixed range().
  • Verify six output lines from one script run.
Repeat checks with a for loop

A for loop visits each item in a sequence. Here, the sequence contains the website, cache, and database server names.

  • Replace the current contents of project3_loops.py with this code:
# Store the servers that need a health check
servers = ["web-1", "cache-1", "db-1"]

# Run the same check once for each server
for server in servers:
    print("Checking", server)

What does this code do?

  • servers keeps the three names in one list.
  • for server in servers: gives each name to the loop in order.
  • The indented print() call runs once for each name.
  • Save project3_loops.py.
  • Run the server loop with this command:
python3 project3_loops.py

You should see Checking web-1, Checking cache-1, and Checking db-1 on separate lines.

Missing a server line?

Check that the loop header ends with a colon. Indent print("Checking", server) with four spaces.

Help me fix the server loop.

Control repetition with a condition

A while loop repeats while its condition remains true. The counter must change inside the loop so the condition eventually becomes false.

  • Add this practice loop below the server loop in project3_loops.py:
# Start with the first retry attempt
attempt = 1

# Repeat until all three attempts have run
while attempt <= 3:
    print("Server check", attempt)
    attempt = attempt + 1

Why does the loop stop?

  • attempt = 1 starts the counter at the first attempt.
  • attempt <= 3 allows three runs.
  • attempt = attempt + 1 moves the loop toward its stopping point.
  • Save project3_loops.py.
  • Run the updated script with this command:
python3 project3_loops.py

You should see the three server names followed by Server check 1 through Server check 3.

Simplify a fixed retry count

A fixed number of attempts has a known start and end. range(1, 4) expresses those three values directly, so it needs no manual counter update.

  • Find the retry block that starts with attempt = 1.
  • Replace that entire block with this shorter loop:
# Run three retry-style checks without a manual counter
for attempt in range(1, 4):
    print("Server check", attempt)

Why use range here?

range(1, 4) produces 1, 2, and 3. The stopping value 4 is excluded.

  • Save project3_loops.py.

Before you run the final version, which group of output lines do you expect to appear first?

  • Verify both loop patterns with this command:
python3 project3_loops.py

You should see three Checking lines followed by Server check 1, Server check 2, and Server check 3.

Both loops are working

Your repeated checks now use a loop for each type of repetition. The fixed retry count is shorter because range() controls the numbers for you.

Seeing a loop problem?

  • Check that both loop headers end with a colon.
  • Indent each print() call with four spaces.
  • Press Ctrl+C if an earlier while loop is still running.

Help me fix both loops.

✔️ Awesome, I've got everything!

Your two loop patterns are saved and produce six lines.

ⓧ I'd like to double check the full code

Compare your saved file with this complete version.

# Store the servers that need a health check
servers = ["web-1", "cache-1", "db-1"]

# Run the same check once for each server
for server in servers:
    print("Checking", server)

# Run three retry-style checks without a manual counter
for attempt in range(1, 4):
    print("Server check", attempt)

Your repeated checks are working. Next, you will place server-checking work inside reusable functions.

Build a Reusable Server Check

Your loop script can repeat work across several servers. The next improvement gives that work a reusable name.

A function keeps one responsibility in one place. Parameters make the function flexible, while a return value gives the calling code a result it can reuse.

In this step, get ready to:
  • Define a reusable server-check function.
  • Use a parameter, argument, and return value.
  • Run a file reader against a missing log file.
Create and call a reusable function

The def keyword starts a function definition. A parameter receives a value inside the function, while an argument supplies that value when you call it.

  • Open project3_functions.py in Xed by running this command:
xed project3_functions.py
  • Build the reusable check by entering this code in project3_functions.py:
def check_server(server):
    # Show which machine is being inspected
    print("Checking", server)

    # Return a result that other code can reuse
    return "OK"

# Call the function with a specific server name
result = check_server("web-1")
print("Result:", result)

What does this code do?

  • check_server(server) gives the check a reusable name.
  • server holds the name supplied by the caller.
  • return "OK" sends a value back to the calling code.
  • result stores that returned value.

Why use a function here?

A descriptive function name reduces the need to inspect its body. Small functions are also easier to test, debug, and reuse than one long block of main logic.

  • Save project3_functions.py.

Before you run the script, which output line do you expect to appear first?

  • Test the function with this command:
python3 project3_functions.py

You should see Checking web-1 followed by Result: OK.

Your function works

Your function now accepts a server name and returns a result. That reusable boundary is the same pattern the final log reader uses for reading, counting, and reporting.

Function not running?

Indent every line inside check_server() with four spaces. Keep the function call below the definition.

Help me debug the reusable function.

✔️ Awesome, I've got everything!

Your reusable function is saved and returns the expected result.

ⓧ I'd like to double check the full code

Compare your saved file with this complete version.

def check_server(server):
    # Show which machine is being inspected
    print("Checking", server)

    # Return a result that other code can reuse
    return "OK"

# Call the function with a specific server name
result = check_server("web-1")
print("Result:", result)
Test a missing log file

The built-in open() function asks Python to open a file. A with block closes that file after the block finishes.

  • Open project3_safe_file.py in Xed by running this command:
xed project3_safe_file.py
  • Enter this first file-reading version in project3_safe_file.py:
def read_log():
    # Open the log as UTF-8 text
    with open("server_events.txt", encoding="utf-8") as log_file:
        print(log_file.read())

# Run the reader before the log file exists
read_log()

What does this code test?

read_log() holds the file operation under one name. The file does not exist yet, so this version exposes the failure instead of handling it.

  • Save project3_safe_file.py.

Before you run the file, do you think the read will succeed or fail?

  • Run the first reader with this command:
python3 project3_safe_file.py

You should see a traceback ending with FileNotFoundError and a reference to server_events.txt.

The failure reveals the gap

That failure is expected. You have proved that an ordinary file read can stop the program when its input is missing.

Didn't see FileNotFoundError?

If the file opens successfully, a previous run may have created server_events.txt in this folder. Rename or remove that practice file before rerunning this designed failure.

Help me reproduce the missing-file result.

Your reusable code works, and you have seen the missing-file weakness firsthand. Next, you will give that failure a planned response.

Handle Missing Log Files

The previous step exposed a weakness in project3_safe_file.py. A missing server_events.txt file stopped the script with a traceback.

Exception handling gives an expected runtime problem a planned response. This step catches only FileNotFoundError so unrelated bugs remain visible.

In this step, get ready to:
  • Understand how try and except respond to an expected failure.
  • Add a targeted missing-file handler.
  • Confirm that the script prints a clear message without a traceback.
Understand targeted exception handling

An exception is a problem Python finds while a program is running. A targeted handler responds only to the exception type it names.

What is error handling?

  • The try block contains the file operation that might fail.
  • The except FileNotFoundError block defines the response for a missing file.
  • Execution continues after the handler instead of ending with that expected traceback.
  • A bare except: can hide unrelated bugs, so this project avoids it.

A friendly error message should name the missing resource and tell the learner what went wrong. server_events.txt was not found. is more useful than an unhandled stack trace for this expected case.

Catch the missing-file exception
  • Return to project3_safe_file.py in Xed.
  • Replace its current contents with this crash-proof version:
def read_log():
    try:
        # Open the log as UTF-8 text
        with open("server_events.txt", encoding="utf-8") as log_file:
            print(log_file.read())
    except FileNotFoundError:
        # Replace the traceback with a clear response
        print("server_events.txt was not found.")

# Run the protected file reader
read_log()

What does this code do?

  • try protects the operation that may fail.
  • with open(...) closes the file after a successful read.
  • except FileNotFoundError catches only the expected missing-file problem.
  • print() replaces that traceback with a useful response.
  • Save project3_safe_file.py.

Before you run the script, do you expect the missing file to produce another traceback?

  • Run the protected reader with this command:
python3 project3_safe_file.py

You should see exactly server_events.txt was not found. No traceback should follow.

The reader survives the failure

You handled the failure cleanly. The script now tells you why it cannot read the log without hiding other exception types.

Still seeing a traceback?

Align try: and except FileNotFoundError: at the same indentation level. Keep the with open(...) line inside the try block.

Help me fix the missing-file handler.

✔️ Awesome, I've got everything!

Your saved reader now handles a missing file with a clear response.

ⓧ I'd like to double check the full code

Compare your saved file with this complete version.

def read_log():
    try:
        # Open the log as UTF-8 text
        with open("server_events.txt", encoding="utf-8") as log_file:
            print(log_file.read())
    except FileNotFoundError:
        # Replace the traceback with a clear response
        print("server_events.txt was not found.")

# Run the protected file reader
read_log()

The missing-file path is safe now. Next, you will create real server events and turn them into a formatted report.

Read Logs and Write a Report

Your missing-file reader now handles an absent log without crashing. The finished tool still needs real server events to process and a report that remains after the terminal closes.

File handling lets the program load events from one file and save useful totals in another. Small functions keep reading, counting, and reporting separate so each responsibility is easier to test.

In this step, get ready to:
  • Create a server log with three event levels.
  • Build focused functions for reading, counting, and reporting.
  • Generate and inspect a formatted server report.
Create and read the event file

How do server logs work?

A log is a record of events produced by a system. Administrators use logs to reconstruct what happened and spot problems.

INFO records normal activity, such as a service starting. WARN signals a condition that may become a problem.

ERROR records a failure that needs attention. The server names web-1, db-1, and cache-1 identify the systems that produced each event.

  • Open project3_files.py in Xed by running this command:
xed project3_files.py
  • Enter this file-handling example in project3_files.py:
# Replace the event file with a known test data set
with open("server_events.txt", "w", encoding="utf-8") as log_file:
    log_file.write("INFO web-1 started\n")
    log_file.write("ERROR db-1 disk full\n")
    log_file.write("WARN cache-1 response slow\n")

# Read the saved events back for verification
with open("server_events.txt", encoding="utf-8") as log_file:
    print(log_file.read(), end="")

What does this code do?

  • "w" mode creates the file or replaces its old contents.
  • write() saves one event at a time.
  • The second with open(...) block reads the same file.
  • encoding="utf-8" makes the text encoding explicit.
  • Save project3_files.py.

Before you run it, which three event levels do you expect to see?

  • Create and read server_events.txt by running this command:
python3 project3_files.py

You should see INFO web-1 started, ERROR db-1 disk full, and WARN cache-1 response slow on separate lines.

Event lines look wrong?

Keep each \n inside its quoted string. Confirm that both file operations use the exact filename server_events.txt.

Help me fix the event file.

Build focused reader functions

The final script gives each job its own function. Descriptive names make the main workflow readable without forcing you to inspect every function body.

  • Open project3_log_reader.py in Xed by running this command:
xed project3_log_reader.py
  • Add the event check to project3_log_reader.py:
def contains_error_event(log_line):
    # Return True only when this line records an error
    return "ERROR" in log_line

Why use a descriptive name?

contains_error_event(log_line) states exactly what the function checks. The parameter name also shows that the input is one line from a log.

  • Save project3_log_reader.py.
  • Test the function with this command:
python3 -c "import project3_log_reader as reader; print(reader.contains_error_event('ERROR db-1 disk full'))"

You should see True.

Didn't see True?

Check that the function name matches the command exactly. Keep return indented with four spaces.

Help me debug the event check.

  • Add the log-reading function below contains_error_event():
def read_log(file_name):
    # Try a fixed number of times without a manual counter
    for attempt in range(1, 4):
        print("Read attempt", attempt)

        try:
            # Return all lines after the first successful read
            with open(file_name, encoding="utf-8") as log_file:
                return list(log_file)
        except FileNotFoundError:
            print(file_name, "was not found.")

    # Signal that every read attempt failed
    return None

How does the retry function work?

  • range(1, 4) produces three attempts without a separate counter update.
  • return list(log_file) sends the loaded lines back after the first successful read.
  • FileNotFoundError produces a clear message for each failed attempt.
  • return None tells the main workflow that every attempt failed.
  • Save project3_log_reader.py.
  • Confirm that the function loads all three lines by running:
python3 -c "import project3_log_reader as reader; print(len(reader.read_log('server_events.txt')))"

You should see Read attempt 1 followed by 3.

Didn't load three lines?

Confirm that server_events.txt is in the same Xfce Terminal folder as the script. Check that return list(log_file) remains inside the with block.

Help me debug the log reader.

  • Add the counting function below read_log():
def count_events(log_lines):
    # Start both totals before inspecting the log
    total_events = 0
    error_events = 0

    for log_line in log_lines:
        # Count every line as one server event
        total_events = total_events + 1

        # Count error events separately
        if contains_error_event(log_line):
            error_events = error_events + 1

        print("Checked:", log_line.strip())

    return total_events, error_events

How are the events counted?

  • total_events increases for every log line.
  • error_events increases only when the descriptive helper returns True.
  • strip() removes the saved newline before the event is printed.
  • The final return sends both totals back together.
  • Save project3_log_reader.py.
  • Test the counters with this command:
python3 -c "import project3_log_reader as reader; lines = reader.read_log('server_events.txt'); print(reader.count_events(lines))"

You should see the three checked events followed by (3, 1).

Totals don't match?

Keep both counters before the loop. Align the if statement with the total-event increment, then indent the error increment beneath it.

Help me debug the event counters.

  • Add the report-writing function below count_events():
def write_report(total_events, error_events):
    # Format the totals as a readable operations report
    report = (
        "===== Server Report =====\n"
        f"Total Events : {total_events}\n"
        f"Error Events : {error_events}\n"
        "=========================\n"
    )

    # Replace the old report with the latest totals
    with open("summary_report.txt", "w", encoding="utf-8") as report_file:
        report_file.write(report)

    print(report, end="")

How is the report formatted?

  • report holds the complete formatted text.
  • The f-strings place each counter beside its label.
  • "w" mode replaces an older report with the latest totals.
  • print(report, end="") shows the same report in the terminal.
  • Save project3_log_reader.py.
  • Generate a test report by running:
python3 -c "import project3_log_reader as reader; reader.write_report(3, 1)"

You should see a bordered server report containing Total Events : 3 and Error Events : 1.

Report missing or misaligned?

Check that every report string ends with \n. Confirm that the output filename is exactly summary_report.txt.

Help me debug the report writer.

Run the complete log reader
  • Add the main workflow below write_report():
def main():
    # Stop cleanly if every read attempt fails
    log_lines = read_log("server_events.txt")
    if log_lines is None:
        return

    # Pass the loaded data through counting and reporting
    total_events, error_events = count_events(log_lines)
    write_report(total_events, error_events)

# Start the complete log reader
main()

Why split the main logic?

  • main() shows the high-level workflow without repeating each implementation detail.
  • read_log() owns file access and retry handling.
  • count_events() owns classification and totals.
  • write_report() owns formatting and output.
  • Save project3_log_reader.py.

Before you run the finished tool, what event and error totals do you expect?

  • Run the complete reader with this command:
python3 project3_log_reader.py

You should see one successful read attempt, three checked events, and the formatted report with totals of 3 and 1.

  • Open the saved report in Xed by running this command:
xed summary_report.txt

You should see the same bordered report in summary_report.txt.

The monitoring report is ready

Your log reader is complete. It reads input safely, counts events through focused functions, and preserves the results in a report you can reopen.

Complete reader not working?

Run project3_files.py again if server_events.txt is missing. Compare each function name and its indentation with the full reference below.

Help me debug the complete reader.

✔️ Awesome, I've got everything!

Your modular reader and formatted report are complete.

ⓧ I'd like to double check the full code

Compare your saved file with this complete version.

def contains_error_event(log_line):
    # Return True only when this line records an error
    return "ERROR" in log_line

def read_log(file_name):
    # Try a fixed number of times without a manual counter
    for attempt in range(1, 4):
        print("Read attempt", attempt)

        try:
            # Return all lines after the first successful read
            with open(file_name, encoding="utf-8") as log_file:
                return list(log_file)
        except FileNotFoundError:
            print(file_name, "was not found.")

    # Signal that every read attempt failed
    return None

def count_events(log_lines):
    # Start both totals before inspecting the log
    total_events = 0
    error_events = 0

    for log_line in log_lines:
        # Count every line as one server event
        total_events = total_events + 1

        # Count error events separately
        if contains_error_event(log_line):
            error_events = error_events + 1

        print("Checked:", log_line.strip())

    return total_events, error_events

def write_report(total_events, error_events):
    # Format the totals as a readable operations report
    report = (
        "===== Server Report =====\n"
        f"Total Events : {total_events}\n"
        f"Error Events : {error_events}\n"
        "=========================\n"
    )

    # Replace the old report with the latest totals
    with open("summary_report.txt", "w", encoding="utf-8") as report_file:
        report_file.write(report)

    print(report, end="")

def main():
    # Stop cleanly if every read attempt fails
    log_lines = read_log("server_events.txt")
    if log_lines is None:
        return

    # Pass the loaded data through counting and reporting
    total_events, error_events = count_events(log_lines)
    write_report(total_events, error_events)

# Start the complete log reader
main()

Secret mission

Classify Every Event

Classify INFO, WARN, and ERROR lines with one function and a dictionary. Your finished report will show normal activity, early warnings, and active failures separately.

Clean Up Your Resources

Clean Up Your Resources

Your Project 3 files cost $0 to keep on this computer. Choose whether to keep them, close the apps for now, or delete them.

Resources you used:

  • Five Python scripts: project3_loops.py, project3_functions.py, project3_safe_file.py, project3_files.py, and project3_log_reader.py.
  • Local input file server_events.txt containing three server events.
  • Local output file summary_report.txt containing the latest formatted totals.

Keep everything running

No action is needed. Choose this if you are still experimenting with the reader or plan to continue the Python series.

  • Keep all seven project files in their current folder.
  • Open summary_report.txt whenever you want to review the latest totals.

Pause - I'll come back to this later

Close the running applications to free up screen space and memory. Every saved file remains available at no cost.

  • Save any open file in Xed.
  • Close Xed.
  • Close Xfce Terminal.

Delete - I don't want to use this again

Deleting these files is permanent. The command below targets only the seven project filenames listed above.

  • Open Xfce Terminal in the folder containing the project files.
  • Review the filenames above before deleting them.
  • Remove the project files by running this command:
rm project3_loops.py project3_functions.py project3_safe_file.py project3_files.py server_events.txt project3_log_reader.py summary_report.txt

What does this command remove?

The rm command permanently removes only the seven named local files. It does not remove Python, Xed, or Xfce Terminal.

  • Confirm that the project files are gone by running:
ls

You should no longer see any of the seven project filenames. Unrelated files remain untouched.

Still see a project file?

Confirm that Xfce Terminal is showing the folder where you created the project files. Run ls before repeating the delete command so you can inspect what remains.

Help me check the cleanup safely.

Nice Work!

Nice Work!

You did it! Your Python log reader now retries missing input safely, classifies server events, and saves a formatted monitoring report.

You've learned how to:

  • Automated repeated work with loops. Used range(1, 4) to express a fixed retry count without a manual counter.
  • Refactored a long workflow into focused functions for reading logs, counting events, writing reports, and coordinating the program.
  • Applied exception handling to catch FileNotFoundError without hiding unrelated bugs. Saved readable totals to summary_report.txt.
  • Secret Mission: Classified INFO, WARN, and ERROR events with a dictionary and added each total to the formatted report.

Ready to quiz yourself?