Files
labs/Lab-2/Lab-2-Solution/03_Data_Structures_Log_Parsing_Solution.ipynb
T

5.5 KiB

Module 2 — Lab 3: Data Structures and Raw Log Analysis

Topics: list, tuple, dictionary, set, parsing

Solution version: This notebook contains the complete solution. Run the cells in order and compare the results with your Lab notebook.

Input data

Each log line contains four comma-separated fields: timestamp,source_ip,username,status.

In [ ]:
raw_logs = [
    "2026-09-01T08:00:00Z,203.0.113.55,alice,SUCCESS",
    "2026-09-01T08:01:00Z,198.51.100.14,admin,FAILED",
    "2026-09-01T08:01:20Z,198.51.100.14,root,FAILED",
    "2026-09-01T08:02:00Z,192.0.2.88,bob,SUCCESS",
    "2026-09-01T08:02:30Z,198.51.100.14,guest,FAILED",
    "2026-09-01T08:03:00Z,192.0.2.99,carol,FAILED",
]

critical_fields = ("timestamp", "source_ip", "username", "status")
blocked_ips = {"198.51.100.14", "203.0.113.200"}

print(f"Rows: {len(raw_logs)}")
print("Critical fields:", critical_fields)

1. Convert one log line into a dictionary

In [ ]:
def parse_log_line(line):
    timestamp, source_ip, username, status = line.split(",")
    return {
        "timestamp": timestamp,
        "source_ip": source_ip,
        "username": username.lower(),
        "status": status.upper(),
    }


sample_event = parse_log_line(raw_logs[0])
print(sample_event)

2. Create an event list and a set of unique IP addresses

In [ ]:
events = [parse_log_line(line) for line in raw_logs]
unique_ips = {event["source_ip"] for event in events}

print("Unique IPs:", sorted(unique_ips))
print("Number of unique IPs:", len(unique_ips))

3. Count failed logins with a dictionary

In [ ]:
failed_by_ip = {}

for event in events:
    if event["status"] != "FAILED":
        continue
    ip = event["source_ip"]
    failed_by_ip[ip] = failed_by_ip.get(ip, 0) + 1

print(failed_by_ip)

4. Combine dictionaries and sets to create alerts

In [ ]:
alerts = []

for ip, count in failed_by_ip.items():
    if count >= 3 or ip in blocked_ips:
        alerts.append({
            "source_ip": ip,
            "failed_attempts": count,
            "blocked": ip in blocked_ips,
        })

print(alerts)

Self-check

In [ ]:
assert sample_event["username"] == "alice"
assert len(events) == 6
assert unique_ips == {"203.0.113.55", "198.51.100.14", "192.0.2.88", "192.0.2.99"}
assert failed_by_ip["198.51.100.14"] == 3
assert alerts == [{"source_ip": "198.51.100.14", "failed_attempts": 3, "blocked": True}]
print("✓ Lab 3 completed")

Reflection questions

  • Why is a set suitable for storing unique IP addresses or a blocklist?
  • Why is a tuple suitable for field names that should not change?