{ "cells": [ { "cell_type": "markdown", "id": "06f7188c", "metadata": {}, "source": [ "# Module 2 — Lab 3: Data Structures and Raw Log Analysis\n", " \n", "**Topics:** list, tuple, dictionary, set, parsing\n", "\n", "\n", "> **Solution version:** This notebook contains the complete solution. Run the cells in order and compare the results with your Lab notebook.\n" ] }, { "cell_type": "markdown", "id": "67ad00e0", "metadata": {}, "source": [ "## Input data\n", "\n", "Each log line contains four comma-separated fields: `timestamp,source_ip,username,status`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4b49fc55", "metadata": {}, "outputs": [], "source": [ "raw_logs = [\n", " \"2026-09-01T08:00:00Z,203.0.113.55,alice,SUCCESS\",\n", " \"2026-09-01T08:01:00Z,198.51.100.14,admin,FAILED\",\n", " \"2026-09-01T08:01:20Z,198.51.100.14,root,FAILED\",\n", " \"2026-09-01T08:02:00Z,192.0.2.88,bob,SUCCESS\",\n", " \"2026-09-01T08:02:30Z,198.51.100.14,guest,FAILED\",\n", " \"2026-09-01T08:03:00Z,192.0.2.99,carol,FAILED\",\n", "]\n", "\n", "critical_fields = (\"timestamp\", \"source_ip\", \"username\", \"status\")\n", "blocked_ips = {\"198.51.100.14\", \"203.0.113.200\"}\n", "\n", "print(f\"Rows: {len(raw_logs)}\")\n", "print(\"Critical fields:\", critical_fields)\n" ] }, { "cell_type": "markdown", "id": "3f8fcb17", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 1. Convert one log line into a dictionary\n" ] }, { "cell_type": "code", "execution_count": null, "id": "47d1eb8b", "metadata": { "tags": [ "solution" ] }, "outputs": [], "source": [ "def parse_log_line(line):\n", " timestamp, source_ip, username, status = line.split(\",\")\n", " return {\n", " \"timestamp\": timestamp,\n", " \"source_ip\": source_ip,\n", " \"username\": username.lower(),\n", " \"status\": status.upper(),\n", " }\n", "\n", "\n", "sample_event = parse_log_line(raw_logs[0])\n", "print(sample_event)\n" ] }, { "cell_type": "markdown", "id": "56f0258d", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 2. Create an event list and a set of unique IP addresses\n" ] }, { "cell_type": "code", "execution_count": null, "id": "a8c8af5e", "metadata": { "tags": [ "solution" ] }, "outputs": [], "source": [ "events = [parse_log_line(line) for line in raw_logs]\n", "unique_ips = {event[\"source_ip\"] for event in events}\n", "\n", "print(\"Unique IPs:\", sorted(unique_ips))\n", "print(\"Number of unique IPs:\", len(unique_ips))\n" ] }, { "cell_type": "markdown", "id": "7651a31c", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 3. Count failed logins with a dictionary\n" ] }, { "cell_type": "code", "execution_count": null, "id": "0c0a3675", "metadata": { "tags": [ "solution" ] }, "outputs": [], "source": [ "failed_by_ip = {}\n", "\n", "for event in events:\n", " if event[\"status\"] != \"FAILED\":\n", " continue\n", " ip = event[\"source_ip\"]\n", " failed_by_ip[ip] = failed_by_ip.get(ip, 0) + 1\n", "\n", "print(failed_by_ip)\n" ] }, { "cell_type": "markdown", "id": "21436c12", "metadata": { "tags": [ "exercise" ] }, "source": [ "## 4. Combine dictionaries and sets to create alerts\n" ] }, { "cell_type": "code", "execution_count": null, "id": "11954df9", "metadata": { "tags": [ "solution" ] }, "outputs": [], "source": [ "alerts = []\n", "\n", "for ip, count in failed_by_ip.items():\n", " if count >= 3 or ip in blocked_ips:\n", " alerts.append({\n", " \"source_ip\": ip,\n", " \"failed_attempts\": count,\n", " \"blocked\": ip in blocked_ips,\n", " })\n", "\n", "print(alerts)\n" ] }, { "cell_type": "markdown", "id": "b18d5731", "metadata": {}, "source": [ "## Self-check\n" ] }, { "cell_type": "code", "execution_count": null, "id": "d30adfba", "metadata": {}, "outputs": [], "source": [ "assert sample_event[\"username\"] == \"alice\"\n", "assert len(events) == 6\n", "assert unique_ips == {\"203.0.113.55\", \"198.51.100.14\", \"192.0.2.88\", \"192.0.2.99\"}\n", "assert failed_by_ip[\"198.51.100.14\"] == 3\n", "assert alerts == [{\"source_ip\": \"198.51.100.14\", \"failed_attempts\": 3, \"blocked\": True}]\n", "print(\"✓ Lab 3 completed\")\n" ] }, { "cell_type": "markdown", "id": "55d97609", "metadata": {}, "source": [ "## Reflection questions\n", "\n", "- Why is a `set` suitable for storing unique IP addresses or a blocklist?\n", "- Why is a `tuple` suitable for field names that should not change?\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.11.6" } }, "nbformat": 4, "nbformat_minor": 5 }