Network Log Analyzer with AI and Power BI

Build a Python pipeline that detects network anomalies and explains them with Gemini AI.

Introduction

30 Second Summary

Every large network generates thousands of log entries every day. When something goes wrong, the clues are buried in rows of timestamps and IP addresses that no one has time to read manually.

In this project, you will build a Python script that automatically detects suspicious patterns in network log data. You will connect it to Google's Gemini AI for plain-English explanations and build a Power BI dashboard to present the results.

What You'll Build

Picture running one script and watching your terminal flag every suspicious pattern hiding in your logs, each paired with an AI-generated explanation ready to present on a Power BI dashboard.

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

  • A Python script that scans hundreds of network log entries and flags brute-force attacks, traffic spikes, and port scans in seconds.
  • AI-generated explanations from Gemini that describe each anomaly in plain English with a severity rating and a recommended action.
  • A professional Power BI dashboard with charts showing anomaly distribution, severity breakdown, and a table of AI-generated insights you can show to any manager.
  • Secret Mission: An automated executive summary that Gemini writes for you, summarizing all findings into a single paragraph ready to paste into a report.

Are there any prerequisites?

This project assumes basic Python knowledge like variables, loops, and functions. You will also need Power BI Desktop installed and a Google account to get a free Gemini API key.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Python Environment and Gemini API Key

Your network log analyzer needs two things before you can write any analysis code: a clean Python environment to work in, and a connection to Google's Gemini AI.

In this step, you'll create a project folder with its own virtual environment, install the Gemini SDK, and get an API key to connect Python to the AI.

In this step, get ready to:
  • Create a project folder with a Python virtual environment.
  • Install the Google GenAI Python package.
  • Get a Gemini API key and test the connection.
Create your project folder and virtual environment

A virtual environment is an isolated Python workspace. It keeps the packages you install for this project separate from any other Python projects on your machine.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open a terminal inside VS Code by pressing Ctrl+` (backtick key, above Tab) or by clicking Terminal in the top menu bar, then New Terminal.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir network-log-analyzer
cd network-log-analyzer
  • Create a Python virtual environment inside the folder by running:
python -m venv venv
  • Confirm the virtual environment was created by running:
ls

You should see a venv folder listed in the output.

  • Activate the virtual environment so that Python uses this project's isolated workspace:

macOS

source venv/bin/activate

Windows

venv\Scripts\activate

Using PowerShell instead of CMD?

If you are using PowerShell, run venv\Scripts\Activate.ps1 instead. If PowerShell blocks the script, run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser first, then try again.

After activation, your terminal prompt changes to start with (venv). This confirms every package you install from now on stays inside this project.

Don't see (venv) in your prompt?

  • Make sure you are inside the network-log-analyzer folder. Run cd ~/Desktop/network-log-analyzer to get there.
  • On Windows, confirm you typed the backslash path correctly. The activation script lives inside venv\Scripts\.

Still stuck? Help me activate my Python virtual environment in VS Code's terminal.

Install the Gemini Python SDK

The google-genai package gives Python the ability to call Google's Gemini AI. Without it, your script has no way to send prompts or receive AI-generated explanations.

  • Install the package by running:
pip install google-genai
  • Verify the SDK installed correctly by running:
python -c "from google import genai; print('Gemini SDK ready')"

You should see Gemini SDK ready printed with no errors.

Seeing a ModuleNotFoundError?

  • Check that your virtual environment is still active. You should see (venv) at the start of your terminal prompt. If not, re-activate it using the activation command from the previous substep.
  • If pip install google-genai showed errors, try running pip install --upgrade pip first, then pip install google-genai again.

Still stuck? Help me install the google-genai Python package in my virtual environment.

Get your Gemini API key and test the connection

An API key is a unique string that identifies your project to Google's servers. Gemini uses it to authenticate every request your Python script sends.

  • Open https://aistudio.google.com in your browser.
  • Sign in with your Google account.
  • In the left sidebar, click Get API key.
  • Click Create API key.
  • Copy the key that appears. You will only see the full key once, so keep it handy.

What is an API key?

Think of an API key like a library card. It does not prove who you are, but it gives you access to a specific service. Google uses it to track usage against your free-tier allowance.

The Gemini free tier gives you free input and output tokens with no billing setup required. Your synthetic log data is fine to send through the free tier.

Now you need to make the key available to Python. You will store it as an environment variable so your code can read it without you pasting the key directly into a script.

  • Set the environment variable in your terminal by running:

macOS

export GEMINI_API_KEY=your-api-key-here
  • Replace your-api-key-here with the key you just copied from Google AI Studio.

Windows

set GEMINI_API_KEY=your-api-key-here
  • Replace your-api-key-here with the key you just copied from Google AI Studio.
  • If you are using PowerShell, run $env:GEMINI_API_KEY="your-api-key-here" instead.

Why use an environment variable?

Storing the key in an environment variable keeps it out of your code files. If you ever share your code or push it to GitHub, the key stays private because it only lives in your terminal session.

Time to test the full connection. You will create a small test script, send a message to Gemini, and see if it responds.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Select the network-log-analyzer folder on your Desktop, then click Open (macOS) or Select Folder (Windows).

VS Code may ask if you trust the authors

If a trust dialog appears, click Yes, I trust the authors. This is your own project folder, so it is safe.

  • If your terminal closed when the folder opened, open a new one by pressing Ctrl+`.
  • Re-activate your virtual environment if the prompt no longer shows (venv). Use the same activation command from above.
  • Create a new file called test_connection.py by clicking the new file icon at the top of the Explorer sidebar.
  • Paste the following code into test_connection.py:
import os
from google import genai

# Read the API key from the environment variable
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))

# Send a test message to Gemini
response = client.models.generate_content(
    model="gemini-3.6-flash",
    contents="Say hello"
)
print(response.text)

What does this code do?

  • os.environ.get("GEMINI_API_KEY") reads the API key from the environment variable you just set.
  • genai.Client() creates a connection to Google's Gemini service using that key.
  • client.models.generate_content() sends a prompt to the gemini-3.6-flash model and returns the AI's response.
  • Save test_connection.py.

Before you run this, what do you think Gemini will respond with when you ask it to say hello?

  • Run the test script by executing:
python test_connection.py

You should see a friendly greeting from Gemini printed in your terminal. The exact wording varies each time, but it confirms your API key is working and Python can talk to the AI.

Seeing an error instead of a greeting?

  • If you see a message about GEMINI_API_KEY being None, the environment variable was not set in this terminal session. Run the set (Windows) or export (macOS) command again in the same terminal where you run the script.
  • If you see an authentication or invalid key error, double-check that you pasted the full API key with no extra spaces.
  • If you see a connection error, check that your internet is working. Gemini is a cloud service and needs an active connection.

Still stuck? Help me connect to the Gemini API from my Python script.

  • Now that the test is done, you can delete test_connection.py. You will not need it again.

Your environment is ready. Python can talk to Gemini, and everything is isolated inside your virtual environment. Next up, you will generate realistic network log data to give your analyzer something to work with.

Generate Realistic Network Log Data

Your Python environment is set up and Gemini is ready to go. Now you need something for the AI to actually analyze.

Real telecom network logs are confidential, so you'll write a Python script that generates realistic synthetic data instead. The script produces login attempts, traffic measurements, and port scans, mixing in suspicious patterns that mimic real-world attacks.

In this step, get ready to:
  • Create a generate_logs.py script that produces 500 realistic network log entries.
  • Export the data to network_logs.csv and verify the output.
  • Inspect the raw data to see what anomaly detection will need to work with.
Build the log generator function

The script needs to produce data that looks like real network traffic. That means timestamps, IP addresses, event types, and transfer sizes. You'll also plant three suspicious IPs that generate attack-like patterns so there's something meaningful to detect later.

  • In VS Code, create a new file called generate_logs.py inside your network-log-analyzer folder.
  • Start the file with the imports and the opening of the generate_logs function by pasting this code:
import csv
import random
from datetime import datetime, timedelta


def generate_logs(num_entries=500):
    # Start with an empty list to hold all log entries
    logs = []
    base_time = datetime(2026, 8, 19, 0, 0, 0)

    # Generate 20 random "normal" IP addresses for background traffic
    normal_ips = [f"10.0.{random.randint(1,10)}.{random.randint(1,254)}" for _ in range(20)]
    # These three IPs will generate suspicious patterns
    brute_force_ip = "192.168.1.45"
    high_traffic_ip = "10.0.0.99"
    scanner_ip = "172.16.5.12"

    dest_ips = ["10.100.0.1", "10.100.0.2", "10.100.0.3", "10.100.0.5"]

What does this code do?

  • The imports bring in csv for file writing, random for generating varied data, and datetime for realistic timestamps.
  • normal_ips creates 20 random IP addresses that represent harmless background traffic.
  • The three named IPs (brute_force_ip, high_traffic_ip, scanner_ip) are your planted attackers. They'll generate the suspicious patterns your anomaly detector needs to find later.
  • Add the main loop inside the generate_logs function, directly below the dest_ips line, by pasting this code:
    for i in range(num_entries):
        timestamp = base_time + timedelta(minutes=random.randint(0, 1440))

        roll = random.random()

        # ~9% chance: brute-force attacker hammering SSH login
        if roll < 0.09:
            source_ip = brute_force_ip
            event_type = "LOGIN_ATTEMPT"
            status = "FAILED" if random.random() < 0.95 else "SUCCESS"
            bytes_transferred = random.randint(100, 500)
            port = 22
        # ~5% chance: suspiciously high traffic from one source
        elif roll < 0.14:
            source_ip = high_traffic_ip
            event_type = "TRAFFIC_MEASUREMENT"
            status = "SUCCESS"
            bytes_transferred = random.randint(500000, 2000000)
            port = random.choice([80, 443, 8080])
        # ~3% chance: port scanning activity
        elif roll < 0.17:
            source_ip = scanner_ip
            event_type = "PORT_SCAN"
            status = "BLOCKED"
            bytes_transferred = random.randint(40, 200)
            port = random.randint(1, 65535)
        # ~83% chance: normal everyday traffic
        else:
            source_ip = random.choice(normal_ips)
            event_type = random.choice(["LOGIN_ATTEMPT", "TRAFFIC_MEASUREMENT", "CONNECTION"])
            status = "SUCCESS" if random.random() < 0.9 else "FAILED"
            bytes_transferred = random.randint(500, 50000)
            port = random.choice([22, 80, 443, 3306, 8080])

What does the loop do?

Each iteration generates one log entry. A random number (roll) decides which type of entry it becomes.

  • About 9% of entries come from the brute-force IP (192.168.1.45) with 95% of its login attempts failing. That creates a realistic attack signature.
  • About 5% come from the high-traffic IP (10.0.0.99) transferring 500 KB to 2 MB per event, far above normal.
  • About 3% are port scans from 172.16.5.12, all blocked by the firewall.
  • The remaining 83% is normal traffic from the 20 random IPs. This background noise is what makes the anomalies hard to spot manually.
  • Complete the generate_logs function by pasting this code directly below the else block you just added:
        # Package each entry as a dictionary and add to the list
        logs.append({
            "timestamp": timestamp.strftime("%Y-%m-%d %H:%M:%S"),
            "source_ip": source_ip,
            "destination_ip": random.choice(dest_ips),
            "event_type": event_type,
            "status": status,
            "bytes_transferred": bytes_transferred,
            "port": port
        })

    # Sort all entries by timestamp for realistic chronological order
    logs.sort(key=lambda x: x["timestamp"])
    return logs

What does this code do?

  • logs.append() packages all the variables from the if/elif/else block into a dictionary and adds it to the list. This runs inside the for loop, so every entry gets appended.
  • logs.sort() runs after the loop finishes, ordering all 500 entries by timestamp so they look like a real chronological log file.
  • Save generate_logs.py.
  • Test the function by running this command in your terminal:
python -c "from generate_logs import generate_logs; logs = generate_logs(); print(f'Generated {len(logs)} entries. First entry: {logs[0]}')"

You should see Generated 500 entries followed by a dictionary showing the first log entry with fields like timestamp, source_ip, and event_type.

Seeing an IndentationError?

  • The logs.append() block must be indented with 8 spaces (two levels). It sits inside the for loop, at the same level as the if/elif/else statements.
  • The logs.sort() and return logs lines must be indented with 4 spaces (one level). They sit inside the function but outside the for loop.
  • Make sure your editor is using spaces, not tabs. VS Code shows this in the bottom status bar.

Help me fix my indentation in generate_logs.py

Save the logs to CSV and print a summary

The function generates data in memory, but you need it written to a file so other scripts (and Power BI) can read it. You'll also print a quick summary so you can see the data breakdown at a glance.

  • Add the save_logs function below the generate_logs function by pasting this code at the bottom of generate_logs.py:

def save_logs(logs, filename="network_logs.csv"):
    # Define CSV columns in order
    fieldnames = ["timestamp", "source_ip", "destination_ip", "event_type", "status", "bytes_transferred", "port"]
    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(logs)

What does this function do?

save_logs uses Python's built-in csv module to write all 500 log dictionaries to a CSV file. DictWriter maps each dictionary key to a column automatically, so the output file has clean headers and rows.

  • Add the print_summary function and the main execution block below save_logs by pasting this code:

def print_summary(logs):
    # Count how many events of each type and status
    event_counts = {}
    status_counts = {}
    for log in logs:
        event_type = log["event_type"]
        status = log["status"]
        event_counts[event_type] = event_counts.get(event_type, 0) + 1
        status_counts[status] = status_counts.get(status, 0) + 1

    print(f"\nGenerated {len(logs)} log entries. Saved to network_logs.csv\n")
    print("Events by type:")
    for event_type, count in sorted(event_counts.items()):
        print(f"  {event_type}: {count}")
    print("\nEvents by status:")
    for status, count in sorted(status_counts.items()):
        print(f"  {status}: {count}")


if __name__ == "__main__":
    # Generate, save, and summarize
    logs = generate_logs()
    save_logs(logs)
    print_summary(logs)

What does this code do?

  • print_summary loops through the logs and counts events by type and status. It gives you a quick breakdown without opening the CSV.
  • The if __name__ == "__main__": block is the entry point. When you run the script directly, it generates logs, saves them, and prints the summary in sequence.

Before you run the script, what do you think the output will show? How many event types do you expect to see?

  • Save generate_logs.py.
  • Run the script by entering this command in your terminal:
python generate_logs.py

You should see output that starts with Generated 500 log entries. Saved to network_logs.csv followed by a breakdown of four event types (CONNECTION, LOGIN_ATTEMPT, PORT_SCAN, TRAFFIC_MEASUREMENT) and three statuses (BLOCKED, FAILED, SUCCESS).

Seeing a NameError or SyntaxError?

  • Check that save_logs and print_summary are defined outside the generate_logs function (no indentation on the def lines).
  • Make sure you saved the file after adding all the code. Unsaved changes won't run.
  • Confirm your terminal is inside the network-log-analyzer folder and the virtual environment is still active (you should see (venv) in your prompt).

Help me debug my generate_logs.py script

✔️ Awesome, I've got everything!

Great. Double check you've saved generate_logs.py and that network_logs.csv appeared in your network-log-analyzer folder.

ⓧ I'd like to double check the full code

Your generate_logs.py is built from five code blocks you added in order. Scroll up and confirm each is present:

  • Block 1: Imports (csv, random, datetime) and the generate_logs function header with IP address setup.
  • Block 2: The for loop with four condition branches (brute force, high traffic, port scan, normal).
  • Block 3: The logs.append() dictionary, logs.sort(), and return logs.
  • Block 4: The save_logs function that writes to CSV.
  • Block 5: The print_summary function and the if __name__ == "__main__": block.
Inspect the raw data

You have 500 log entries sitting in a CSV file. Before writing any detection code, take a look at what the raw data actually looks like.

  • In VS Code's Explorer sidebar, click on network_logs.csv to open it.
  • Scroll through the rows. You should see columns for timestamp, source_ip, destination_ip, event_type, status, bytes_transferred, and port.

The data is a mix of normal and suspicious entries. You know the attacker IPs because you wrote the script, but try to spot them just by scrolling. It is surprisingly hard to tell which rows are problematic when hundreds of normal entries are mixed in.

Why is this hard to read?

This is the exact problem network analysts face every day. Logs pile up fast, and the dangerous entries hide in a sea of normal traffic. You need code that can automatically scan the data and flag the needles in the haystack.

You've got realistic data to work with. Next up, you'll write Python code that automatically finds those hidden anomalies for you.

Detect Anomalies with Python

You now have 500 log entries sitting in network_logs.csv. Scrolling through those rows, you might spot a few FAILED statuses or unusually large bytes_transferred values. But finding every suspicious pattern by eye across 500 rows is impractical.

You need code that automatically finds the needles in the haystack. In this step, you will build three detection functions that flag brute-force login attempts, abnormal traffic volumes, and port scans.

In this step, get ready to:
  • Create an analysis script that loads your CSV log data.
  • Build detection functions for brute-force attempts, traffic spikes, and port scans.
  • Run the full detection and see what it finds in your data.
Create the log loader

Before you can detect anything, your script needs to read the CSV file and convert each row into a Python dictionary. The csv module handles the parsing, but numeric columns like bytes_transferred arrive as strings. You will need to cast them to integers so your math works later.

  • In VS Code, create a new file called analyze_logs.py inside your network-log-analyzer folder.
  • Add the following imports and load_logs function by pasting this code:
import csv
import os
import time
from google import genai


# Read the CSV log file and convert numeric fields
def load_logs(filename="network_logs.csv"):
    logs = []
    with open(filename, "r") as f:
        reader = csv.DictReader(f)
        for row in reader:
            row["bytes_transferred"] = int(row["bytes_transferred"])
            row["port"] = int(row["port"])
            logs.append(row)
    return logs

What does this code do?

  • The top four imports bring in the modules you need across this step and the next. csv reads your log file. os, time, and genai are for the AI integration you will add in Step 4.
  • csv.DictReader turns each CSV row into a dictionary keyed by column name, so you can reference fields like row["event_type"] instead of tracking column positions.
  • int(row["bytes_transferred"]) converts the string value from the CSV into an integer so you can do arithmetic on it later.
  • Save analyze_logs.py.
  • Verify the loader works by running this in your terminal:
python -c "from analyze_logs import load_logs; logs = load_logs(); print(f'Loaded {len(logs)} entries')"

You should see Loaded 500 entries printed in the terminal.

Seeing a FileNotFoundError?

  • Make sure you are running the command from inside your network-log-analyzer folder where network_logs.csv lives.
  • If you haven't run generate_logs.py yet, run it first to create the CSV file.
  • Help me debug my load_logs function
Build the brute-force detector

A brute-force attack hammers a login page with hundreds of password guesses from the same IP. Your detector will count how many failed logins each IP produced and flag any that cross a threshold.

  • In analyze_logs.py, add the detect_brute_force function below load_logs by pasting this code:
def detect_brute_force(logs, threshold=10):
    # Count failed login attempts per source IP
    failed_logins = {}
    for log in logs:
        if log["event_type"] == "LOGIN_ATTEMPT" and log["status"] == "FAILED":
            ip = log["source_ip"]
            failed_logins[ip] = failed_logins.get(ip, 0) + 1

    # Flag any IP exceeding the threshold
    anomalies = []
    for ip, count in failed_logins.items():
        if count > threshold:
            anomalies.append({
                "anomaly_type": "BRUTE_FORCE",
                "source_ip": ip,
                "detail": f"{count} failed login attempts detected",
                "raw_count": count
            })
    return anomalies

What does this code do?

  • The function loops through every log entry, filtering for rows where event_type is LOGIN_ATTEMPT and status is FAILED.
  • It tallies failures per IP in a dictionary, then builds an anomaly record for any IP that exceeds the threshold of 10.
  • Each anomaly dictionary contains four fields: anomaly_type, source_ip, detail, and raw_count. Every detection function you write will return this same shape.
  • Save analyze_logs.py.
  • Test the brute-force detector by running this command:
python -c "from analyze_logs import load_logs, detect_brute_force; logs = load_logs(); results = detect_brute_force(logs); print(results)"

You should see a list containing one anomaly dictionary for IP 192.168.1.45 with a high count of failed login attempts.

Seeing an empty list?

  • Check that your detect_brute_force function is indented correctly and sits at the top level of the file, not nested inside load_logs.
  • Make sure the string comparisons match exactly: "LOGIN_ATTEMPT" and "FAILED" are case-sensitive.
  • Help me debug my brute-force detection
Add traffic spike and port scan detectors

Brute-force is only one type of threat. A source pumping abnormally high traffic could be exfiltrating data. An IP scanning ports is probing for open services to exploit.

  • In analyze_logs.py, add the detect_traffic_spikes function below detect_brute_force by pasting this code:
def detect_traffic_spikes(logs, multiplier=3):
    # Group traffic measurements by source IP
    traffic_by_ip = {}
    for log in logs:
        if log["event_type"] == "TRAFFIC_MEASUREMENT":
            ip = log["source_ip"]
            if ip not in traffic_by_ip:
                traffic_by_ip[ip] = []
            traffic_by_ip[ip].append(log["bytes_transferred"])
    if not traffic_by_ip:
        return []
    # Calculate per-IP and overall average traffic
    all_averages = []
    ip_averages = {}
    for ip, values in traffic_by_ip.items():
        avg = sum(values) / len(values)
        ip_averages[ip] = avg
        all_averages.append(avg)
    overall_avg = sum(all_averages) / len(all_averages)
    # Flag IPs exceeding the multiplier threshold
    anomalies = []
    for ip, avg in ip_averages.items():
        if avg > overall_avg * multiplier:
            total_mb = sum(traffic_by_ip[ip]) / (1024 * 1024)
            anomalies.append({
                "anomaly_type": "TRAFFIC_SPIKE",
                "source_ip": ip,
                "detail": f"Average {avg/1024:.1f} KB per event (overall average: {overall_avg/1024:.1f} KB). Total: {total_mb:.1f} MB",
                "raw_count": len(traffic_by_ip[ip])
            })
    return anomalies

What does this code do?

  • The function collects every TRAFFIC_MEASUREMENT event and groups the bytes_transferred values by source IP.
  • It calculates the average traffic per IP, then calculates the overall average across all IPs. Any IP whose average is more than 3x the overall average gets flagged as a spike.
  • The detail string shows both the IP's average and the baseline, so you can see how far above normal the traffic was.
  • Now add the detect_port_scans function and a run_detection function that combines all three detectors. Paste this code below detect_traffic_spikes:
def detect_port_scans(logs):
    # Count port scan events per source IP
    scan_counts = {}
    for log in logs:
        if log["event_type"] == "PORT_SCAN":
            ip = log["source_ip"]
            scan_counts[ip] = scan_counts.get(ip, 0) + 1
    # Flag every IP that triggered port scans
    anomalies = []
    for ip, count in scan_counts.items():
        anomalies.append({
            "anomaly_type": "PORT_SCAN",
            "source_ip": ip,
            "detail": f"{count} port scan events detected",
            "raw_count": count
        })
    return anomalies


# Combine all detection functions into one call
def run_detection(logs):
    anomalies = []
    anomalies.extend(detect_brute_force(logs))
    anomalies.extend(detect_traffic_spikes(logs))
    anomalies.extend(detect_port_scans(logs))
    return anomalies

What does this code do?

  • detect_port_scans flags any IP that triggered PORT_SCAN events. Unlike brute-force, any count is suspicious because legitimate users do not scan ports.
  • run_detection is a convenience wrapper that calls all three detectors and merges their results into a single list of anomaly dictionaries.
  • Save analyze_logs.py.
  • Test the full detection pipeline by running this command:
python -c "from analyze_logs import load_logs, run_detection; logs = load_logs(); anomalies = run_detection(logs); print(f'Found {len(anomalies)} anomalies'); [print(a) for a in anomalies]"

You should see 3 anomaly dictionaries printed: one BRUTE_FORCE, one TRAFFIC_SPIKE, and one PORT_SCAN.

Only seeing 1 or 2 anomalies?

  • Your log data is randomly generated, so the exact counts vary each time. If a detector finds zero anomalies, re-run python generate_logs.py to regenerate the data, then test again.
  • Make sure run_detection calls all three functions: detect_brute_force, detect_traffic_spikes, and detect_port_scans.
  • Help me debug my anomaly detection
Run the full analysis and see the gap

The detectors work, but right now they only print raw dictionaries. A proper script should load the data, run the detection, and print the results in a readable format.

  • At the bottom of analyze_logs.py, add this main block that ties everything together:
if __name__ == "__main__":
    print("Loading network logs...")
    logs = load_logs()
    print(f"Loaded {len(logs)} log entries.\n")
    print("Running anomaly detection...")
    anomalies = run_detection(logs)
    print(f"Found {len(anomalies)} anomalies:\n")
    # Print each anomaly with its type, source, and details
    for anomaly in anomalies:
        print(f"  [{anomaly['anomaly_type']}] {anomaly['source_ip']}: {anomaly['detail']}")

What does this code do?

The if __name__ == "__main__" guard means this code only runs when you execute the file directly. It loads the logs, runs all three detectors through run_detection, and prints each anomaly in a clean one-line format.

  • Save analyze_logs.py.

Before you run this, take a moment to predict: what will the output look like? Will it be enough for a non-technical manager to understand what is happening on the network?

  • Run the full analysis by running this command:
python analyze_logs.py

You should see output like this:

Loading network logs...
Loaded 500 log entries.

Running anomaly detection...
Found 3 anomalies:

  [BRUTE_FORCE] 192.168.1.45: 43 failed login attempts detected
  [TRAFFIC_SPIKE] 10.0.0.99: Average 1220.3 KB per event (overall average: 95.7 KB). Total: 15.2 MB
  [PORT_SCAN] 172.16.5.12: 14 port scan events detected

Why does this output feel incomplete?

The detector found the problems correctly. You can see which IPs are suspicious and what they did. But look at it through a manager's eyes. What does 43 failed login attempts actually mean? Is that dangerous? What should someone do about it?

The output is raw technical data with no context. There is no explanation of the threat, no severity rating, and no recommended action. This is the gap that AI will fill in the next step.

Your exact numbers will differ

Because generate_logs.py uses random generation, your counts (43 failed logins, 15.2 MB) will not match these examples exactly. The important thing is that the same three IPs appear: 192.168.1.45, 10.0.0.99, and 172.16.5.12.

Script runs but no anomalies found?

  • Make sure your network_logs.csv was generated by the latest version of generate_logs.py from Step 2. Re-run python generate_logs.py to regenerate it.
  • Check that each detection function is defined at the top level of the file, not accidentally indented inside another function.
  • Help me figure out why my anomaly detector finds nothing

✔️ Awesome, I've got everything!

Great. Double check that you have saved analyze_logs.py.

ⓧ I'd like to double check the full code

Your analyze_logs.py should contain these sections in order. Scroll up and confirm each one is present:

  • Four imports at the top: csv, os, time, and from google import genai.
  • load_logs function that reads network_logs.csv and converts numeric fields.
  • detect_brute_force function with a threshold=10 parameter.
  • detect_traffic_spikes function with a multiplier=3 parameter.
  • detect_port_scans function.
  • run_detection function that calls all three detectors.
  • An if __name__ == "__main__" block that loads logs, runs detection, and prints results.

Your detection engine works. It found the suspicious IPs and counted exactly what they did. But the output is just IP addresses and numbers. In the next step, you will send these anomalies to Gemini AI and get plain-English explanations, severity ratings, and recommended actions for each one.

Add AI-Powered Explanations with Gemini

Your anomaly detector works. It flagged brute-force attacks, traffic spikes, and port scans from 500 log entries in seconds.

But look at the output you got. It is raw IP addresses and numbers. A network planning manager reading "43 failed login attempts from 192.168.1.45" would ask: is this dangerous? What should I do about it? Google's Gemini AI fills that gap by turning each anomaly into a plain-English explanation with a severity rating and recommended action.

In this step, get ready to:
  • Write a function that sends anomaly data to Gemini and receives plain-English explanations.
  • Update the main script flow to enrich every detected anomaly with AI-generated context, severity ratings, and recommended actions.
Add the AI explanation function

Each anomaly your detector found is a dictionary with fields like anomaly_type, source_ip, and detail. You need a function that takes one of these dictionaries, builds a prompt from its fields, and sends it to Gemini for analysis.

  • In analyze_logs.py, find the run_detection function. Add the following new function directly below it:
def get_ai_explanation(client, anomaly):
    # Build a structured prompt from the anomaly data
    prompt = (
        f"You are a network security analyst. Analyze this network anomaly and provide:\n"
        f"1. A plain-English explanation of what this means (2-3 sentences)\n"
        f"2. A severity rating: LOW, MEDIUM, HIGH, or CRITICAL\n"
        f"3. A recommended action (1-2 sentences)\n\n"
        f"Anomaly type: {anomaly['anomaly_type']}\n"
        f"Source IP: {anomaly['source_ip']}\n"
        f"Details: {anomaly['detail']}\n\n"
        f"Format your response exactly as:\n"
        f"Explanation: [your explanation]\n"
        f"Severity: [LOW/MEDIUM/HIGH/CRITICAL]\n"
        f"Action: [your recommendation]"
    )

    # Send the prompt to Gemini and return the text response
    response = client.models.generate_content(
        model="gemini-3.6-flash",
        contents=prompt
    )
    return response.text

What does this code do?

This function takes a genai.Client instance and a single anomaly dictionary. It builds a prompt that includes the anomaly type, source IP, and technical details.

The prompt asks Gemini to respond in a specific format: an explanation, a severity rating, and a recommended action. This structured format makes the output predictable and easier to parse.

The client.models.generate_content() call sends the prompt to the gemini-3.6-flash model and returns the AI's response as text.

This function does not do anything on its own yet. The next chunk wires it into the main flow so every anomaly gets an explanation.

Parse the AI response and update the main flow

Gemini returns a block of text that includes the severity rating somewhere inside it. You need a helper function that scans the response and extracts just the severity level as a clean string.

  • In analyze_logs.py, add this helper function directly below the get_ai_explanation function you just wrote:
def parse_severity(ai_text):
    # Check for severity keywords from most to least severe
    for level in ["CRITICAL", "HIGH", "MEDIUM", "LOW"]:
        if level in ai_text.upper():
            return level
    return "UNKNOWN"

What does this code do?

This function searches the AI's response text for severity keywords. It checks from most severe to least severe so that if the text mentions both "HIGH" and "MEDIUM", it returns the higher one. If no severity keyword is found, it returns "UNKNOWN" as a fallback.

Now update the main block at the bottom of your file to connect to Gemini and loop through each anomaly.

  • Scroll down to the if __name__ == "__main__": block at the bottom of analyze_logs.py.
  • Find the last line of the existing for loop:
    for anomaly in anomalies:
        print(f"  [{anomaly['anomaly_type']}] {anomaly['source_ip']}: {anomaly['detail']}")
  • Add the following code directly after that for loop, at the same indentation level (one level inside the if __name__ block):
    # Connect to Gemini and enrich each anomaly with AI analysis
    print("\nConnecting to Gemini AI for explanations...")
    api_key = os.environ.get("GEMINI_API_KEY")
    if not api_key:
        print("ERROR: GEMINI_API_KEY environment variable not set.")
        print("Set it with: set GEMINI_API_KEY=your-key-here")
    else:
        client = genai.Client(api_key=api_key)
        for i, anomaly in enumerate(anomalies):
            print(f"\n  Analyzing anomaly {i+1}/{len(anomalies)}...")
            ai_text = get_ai_explanation(client, anomaly)
            anomaly["ai_explanation"] = ai_text
            anomaly["severity"] = parse_severity(ai_text)
            print(f"  [{anomaly['anomaly_type']}] {anomaly['source_ip']}")
            print(f"  {ai_text}")
            # Stay within free tier rate limits
            if i < len(anomalies) - 1:
                time.sleep(2)

What does this code do?

First, the code reads your GEMINI_API_KEY environment variable. If the key is missing, it prints a clear error message instead of crashing.

When the key exists, it creates a genai.Client and loops through every anomaly. For each one, it calls get_ai_explanation, stores the AI response under ai_explanation, and extracts a clean severity level with parse_severity.

The time.sleep(2) call pauses for 2 seconds between API calls. This keeps you within the Gemini free tier rate limits so you do not get throttled.

  • Save analyze_logs.py.

✔️ Awesome, I've got everything!

Great. Double check that you have saved analyze_logs.py before moving on.

ⓧ I'd like to double check the full code

Your analyze_logs.py file is over 30 lines, so scroll through it and confirm these sections are present in this order:

  • Imports at the top: csv, os, time, and from google import genai.
  • The load_logs function (unchanged from Step 3).
  • The three detection functions: detect_brute_force, detect_traffic_spikes, and detect_port_scans (unchanged from Step 3).
  • The run_detection function (unchanged from Step 3).
  • The new get_ai_explanation function (added in this step).
  • The new parse_severity function (added in this step).
  • The if __name__ == "__main__": block that loads logs, runs detection, prints anomalies, then connects to Gemini and loops through each anomaly with a 2-second delay.
Run the enriched analysis

In the last step, running this script gave you bare IP addresses and counts. Now every anomaly will be sent to Gemini for analysis.

Before you run the script, think about this: what do you expect the output to look like now compared to Step 3? Will the technical details still be there, or will they be replaced by the AI explanations?

  • Make sure your GEMINI_API_KEY environment variable is still set in your terminal. If you opened a new terminal since Step 1, set it again by running:
set GEMINI_API_KEY=your-api-key-here

PowerShell uses different syntax

If you are using PowerShell instead of Command Prompt, use this syntax instead:

$env:GEMINI_API_KEY="your-api-key-here"
  • Run the enriched analysis by running this command:
python analyze_logs.py

The script takes a few seconds longer than before because of the 2-second delays between API calls. You see the same technical anomaly list from Step 3 printed first. Then Gemini analyzes each anomaly one by one.

Each anomaly now prints with an AI-generated explanation beneath it. You see plain-English descriptions of what the anomaly means, a severity rating like HIGH or CRITICAL, and a recommended action.

Why is this better than Step 3's output?

In Step 3, the output said something like "43 failed login attempts detected". That is technically accurate but tells a manager nothing about risk.

Now the same anomaly gets an explanation like "This IP attempted 43 failed logins, consistent with an automated brute-force attack. Severity: HIGH. Recommended action: Block the IP and review authentication logs." The same data now tells a story that anyone can act on.

Seeing an error about the API key?

  • If you see "ERROR: GEMINI_API_KEY environment variable not set", your environment variable was not set in this terminal session. Run the set command above again with your actual API key.
  • If you see a 400 or 403 error from the API, double check that your API key is correct. You can verify it at https://aistudio.google.com by viewing your existing keys.
  • If you see a rate limit error, wait 60 seconds and try again. The free tier has request-per-minute limits.

Help me debug my Gemini API connection in analyze_logs.py

Your script now turns raw log data into analyst-ready explanations automatically. Next up, you will export this enriched data to CSV and build a professional Power BI dashboard that makes the whole analysis visual and shareable.

Export to CSV and Build the Power BI Dashboard

In the last step, you connected your anomaly detector to Gemini and turned raw IP addresses and counts into plain-English explanations with severity ratings.

But all that output still only lives in your terminal. To share these insights with a manager, you need to export the data to files and build a visual dashboard. In this step, you will add CSV export functions to your script, then import those files into Power BI to create a professional monitoring dashboard.

In this step, get ready to:
  • Export anomaly data and enriched logs to CSV files.
  • Import the CSV files into Power BI Desktop.
  • Build a dashboard with charts, tables, and AI-generated insights.
Add CSV export functions to your script

Your script currently prints enriched anomaly data to the terminal. You need two export functions: one that writes the anomaly summary to a focused report file, and one that tags every log entry with an is_anomalous flag so Power BI can filter and highlight them.

  • In analyze_logs.py, scroll down to below the parse_severity function.
  • Add the export_anomaly_report function by copying the following code below parse_severity:
def export_anomaly_report(anomalies, filename="anomaly_report.csv"):
    # Define columns for the anomaly summary report
    fieldnames = ["anomaly_type", "source_ip", "detail", "raw_count", "severity", "ai_explanation"]
    with open(filename, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        # Write each anomaly with its AI-generated fields
        for anomaly in anomalies:
            writer.writerow({
                "anomaly_type": anomaly["anomaly_type"],
                "source_ip": anomaly["source_ip"],
                "detail": anomaly["detail"],
                "raw_count": anomaly["raw_count"],
                "severity": anomaly.get("severity", ""),
                "ai_explanation": anomaly.get("ai_explanation", "")
            })
    print(f"\nAnomaly report saved to {filename}")

What does this code do?

This function takes the list of anomaly dictionaries and writes each one to a CSV file called anomaly_report.csv. Each row contains the anomaly type, source IP, details, count, severity rating, and the full AI explanation.

The encoding="utf-8" parameter ensures special characters in AI-generated text are preserved correctly.

  • Directly below export_anomaly_report, add the export_enriched_logs function by copying this code:
def export_enriched_logs(logs, anomalies, filename="enriched_logs.csv"):
    # Build a set of IPs that were flagged as anomalous
    anomalous_ips = set()
    for anomaly in anomalies:
        anomalous_ips.add(anomaly["source_ip"])

    # Write every log entry with an is_anomalous tag
    fieldnames = ["timestamp", "source_ip", "destination_ip", "event_type", "status", "bytes_transferred", "port", "is_anomalous"]
    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        for log in logs:
            row = dict(log)
            row["is_anomalous"] = "YES" if log["source_ip"] in anomalous_ips else "NO"
            writer.writerow(row)
    print(f"Enriched logs saved to {filename}")

What does this code do?

This function writes all 500 log entries to enriched_logs.csv with an extra column called is_anomalous. Any log entry from an IP address that was flagged gets YES. Everything else gets NO.

This column lets Power BI split visuals by anomalous vs. normal traffic, so you can see at a glance how much of your network activity is suspicious.

Wire up the exports and run the full pipeline

The two export functions exist, but nothing calls them yet. You need to restructure the bottom of your script so it calls these functions after the AI analysis completes.

  • In analyze_logs.py, scroll to the bottom and find the if __name__ == "__main__": line.
  • Change that line to def main(): so the script logic becomes a proper function.
  • Find this section inside the block you just renamed:
    if not api_key:
        print("ERROR: GEMINI_API_KEY environment variable not set.")
        print("Set it with: set GEMINI_API_KEY=your-key-here")
    else:
        client = genai.Client(api_key=api_key)
        for i, anomaly in enumerate(anomalies):
  • Replace the else: pattern with a return statement so the code exits early if no key is found.
  • De-indent everything that was inside the else block by one level, so it looks like this:
    if not api_key:
        print("ERROR: GEMINI_API_KEY environment variable not set.")
        print("Set it with: set GEMINI_API_KEY=your-key-here")
        return

    client = genai.Client(api_key=api_key)

    for i, anomaly in enumerate(anomalies):
  • After the time.sleep(2) line at the end of the for loop, add the export calls and a final print message at the same indentation level as the for loop:
    export_anomaly_report(anomalies)
    export_enriched_logs(logs, anomalies)

    print("\nDone! Open anomaly_report.csv and enriched_logs.csv in Power BI to build your dashboard.")
  • At the very bottom of the file, after the main() function definition ends, add the new entry point:
if __name__ == "__main__":
    main()

Why refactor into a main function?

Wrapping the script logic in a main() function keeps things organized. The return statement exits cleanly if the API key is missing, instead of nesting the entire script inside an else block.

  • Save analyze_logs.py.

Before you run the script, what do you expect to see differently compared to the last time you ran it?

  • Run the full pipeline by executing this command in your terminal:
python analyze_logs.py

You see the same anomaly detection and AI explanation output as before. This time, two new lines appear at the end:

Anomaly report saved to anomaly_report.csv
Enriched logs saved to enriched_logs.csv

Done! Open anomaly_report.csv and enriched_logs.csv in Power BI to build your dashboard.
  • Confirm both files were created by running:
ls *.csv

You should see anomaly_report.csv, enriched_logs.csv, and network_logs.csv in the output.

Not seeing the new CSV files?

  • Make sure you saved analyze_logs.py before running it. Unsaved changes won't execute.
  • Check that both export_anomaly_report and export_enriched_logs are called inside main(), after the for loop that calls get_ai_explanation.
  • If you see an error about GEMINI_API_KEY, make sure your environment variable is still set in this terminal session.

Help me debug my export functions

✔️ Awesome, I've got everything!

Great. Make sure you have saved analyze_logs.py with all the changes before moving on.

ⓧ I'd like to double check the full code

Your analyze_logs.py is now quite long. Scroll through your file and confirm each of these sections is present, in this order:

  • Imports (csv, os, time, genai) at the top.
  • load_logs function.
  • detect_brute_force function.
  • detect_traffic_spikes function.
  • detect_port_scans function.
  • run_detection function.
  • get_ai_explanation function.
  • parse_severity function.
  • export_anomaly_report function (new in this step).
  • export_enriched_logs function (new in this step).
  • main() function containing load, detect, AI loop, export calls, and final print (refactored in this step).
  • if __name__ == "__main__": main() at the very bottom.
Import data into Power BI and create visuals

With both CSV files on disk, you can now build a dashboard that visualizes your analysis. Power BI Desktop turns your CSV data into interactive charts and tables.

  • Press the Windows key to open your search bar.
  • Type Power BI Desktop and press Enter to open it.
  • On the Home tab in the ribbon, click Get Data.
  • Select Text/CSV from the dropdown.
  • Navigate to your network-log-analyzer folder on your Desktop.
  • Select anomaly_report.csv and click Open.
  • In the preview dialog, verify the columns look correct, then click Load.

The anomaly_report table now appears in the Data pane on the right side of Power BI Desktop.

Now create your first visual: a clustered bar chart showing how many anomalies of each type were detected.

  • Click on an empty area of the canvas to deselect any existing visual.
  • In the Visualizations pane, click the Clustered bar chart icon.
  • In the Data pane, expand anomaly_report and drag anomaly_type to the Y-axis field well.
  • Drag anomaly_type again to the X-axis field well. Power BI automatically aggregates it as a count.

You should see a bar chart with bars for BRUTE_FORCE, TRAFFIC_SPIKE, and PORT_SCAN.

Next, create a table visual so the AI explanations are readable.

  • Click an empty area of the canvas to deselect the bar chart.
  • In the Visualizations pane, click the Table icon.
  • From the Data pane under anomaly_report, drag source_ip to the Columns section in the Visualizations pane.
  • Drag anomaly_type to Columns as well.
  • Drag severity to Columns.
  • Drag ai_explanation to Columns.

The table now shows each flagged IP alongside the AI's explanation and severity rating. This is the visual where a manager can read exactly what happened.

Now add a donut chart to show the severity breakdown.

  • Click an empty area of the canvas.
  • In the Visualizations pane, click the Donut chart icon.
  • Drag severity to the Legend field well.
  • Drag severity again to the Values field well. Power BI counts the entries per severity level.

You should see a donut chart with slices for severity levels like HIGH, MEDIUM, or CRITICAL, depending on what Gemini assigned.

Finally, import the enriched logs and add a line chart showing events over time.

  • On the Home tab, click Get Data again.
  • Select Text/CSV from the dropdown.
  • Select enriched_logs.csv from your network-log-analyzer folder, click Open, then click Load.
  • Click an empty area of the canvas.
  • In the Visualizations pane, click the Line chart icon.
  • In the Data pane, expand enriched_logs and drag timestamp to the X-axis field well.
  • Drag event_type to the Y-axis field well. Power BI aggregates it as a count of events.
  • Drag is_anomalous to the Legend field well.

The line chart splits into two lines: one for YES and one for NO. Anomalous events are visually separated from normal traffic.

What does the line chart Legend do?

Placing is_anomalous in the Legend field well tells Power BI to draw a separate line for each unique value. You get one line for YES (anomalous events) and one for NO (normal traffic), making spikes in suspicious activity easy to spot.

Visuals look empty or show unexpected values?

  • Make sure you are dragging fields from the correct table in the Data pane. The bar chart, table, and donut chart use anomaly_report. The line chart uses enriched_logs.
  • If a chart shows a single bar or no data, check that you dragged the field to the correct field well (Y-axis vs X-axis vs Legend).
  • If the line chart shows a date hierarchy instead of timestamps, click the small down arrow on the X-axis field and select the raw timestamp field instead of a hierarchy level.

Help me fix my Power BI visuals

Your four visuals are on the canvas. Now give the dashboard a title and arrange the visuals so the page tells a clear story.

  • Click the Insert tab in the ribbon.
  • Click Text box in the Insert section.
  • Type Network Security Monitor into the text box. Increase the font size so it serves as a page title.
  • Drag and resize the visuals so they all fit on a single page. Place the title at the top, the bar chart and donut chart side by side below it, the table below them, and the line chart at the bottom.
  • Save your report by pressing Ctrl+S. Name it Network Security Monitor.

Take a look at your finished dashboard. You should see four visuals on a single page:

  • A clustered bar chart showing the count of each anomaly type.
  • A table where anyone can read the AI-generated explanation for each flagged IP.
  • A donut chart breaking down severity distribution across your anomalies.
  • A line chart showing events over time with anomalous traffic highlighted as a separate line.

Why does this dashboard matter?

This is the deliverable you would present to a network planning manager. The bar chart shows what types of threats were found. The severity donut shows how urgent they are. The table provides the AI's plain-English explanation so anyone can understand what happened without reading raw logs.

The line chart adds a time dimension, letting viewers spot when suspicious activity peaked during the day.

Secret mission

Generate an Automated Executive Summary

Your dashboard shows individual anomalies, but a manager wants the bottom line in one paragraph. Can you get Gemini to synthesize ALL your findings into a single executive briefing?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, and the Gemini API free tier has no billing attached. None of the resources you created incur ongoing costs.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • network-log-analyzer project folder on your Desktop (Python scripts, CSV files, virtual environment, and executive_summary.txt).
  • Power BI report file (.pbix), saved wherever you chose in Power BI Desktop.
  • Gemini API key (free tier, created in Google AI Studio).

Keep everything running

No action needed. Choose this if you are still actively building or want to keep the project for your portfolio.

  • Your network-log-analyzer folder stays on your Desktop with all scripts, CSV files, and the executive summary.
  • Your Power BI report stays saved wherever you stored it.
  • Your Gemini API key remains active on the free tier at no cost. It stays available until you delete it from Google AI Studio.
  • The GEMINI_API_KEY environment variable you set with set is session-scoped. It disappears when you close that terminal window.

Pause - I'll come back to this later

Shut down running processes to free up memory. Your files and API key stay intact for when you return.

  • Close Power BI Desktop if it is still open.
  • In your VS Code terminal, deactivate the virtual environment by running:
deactivate
  • Close VS Code. Your project folder, CSV files, and API key all remain in place for next time.
  • When you come back, reactivate the virtual environment with venv\Scripts\activate and re-set the GEMINI_API_KEY environment variable in your new terminal session.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each item below to fully clean up.

  • Close Power BI Desktop and delete your saved .pbix report file from wherever you saved it.
  • Close VS Code. Then delete the entire project folder by running this in a new terminal window.

macOS

rm -rf ~/Desktop/network-log-analyzer

Windows

rmdir /s /q %USERPROFILE%\Desktop\network-log-analyzer

This removes all Python scripts, CSV files, the virtual environment, and executive_summary.txt in one step.

  • Delete your Gemini API key. Go to https://aistudio.google.com, open the API keys section, find the key you created for this project, and delete it.

Nice Work!

Nice Work!

You just built a complete AI-powered network log analysis pipeline from scratch. Raw log data goes in, and a professional dashboard with AI-generated explanations comes out.

Here's what you learned:

  • Built a Python script that generates realistic network log data and an anomaly detection engine that automatically flags brute-force attacks, traffic spikes, and port scans.
  • Integrated Google's Gemini AI to generate plain-English explanations, severity ratings, and recommended actions for each detected anomaly.
  • Exported enriched data to CSV and built a professional Power BI dashboard combining visualizations with AI-generated context.
  • Secret Mission: Generated an automated executive summary that synthesizes all findings into a manager-ready briefing.

Ready to quiz yourself?