Posted on
Artificial Intelligence

Artificial Intelligence Log Analysis Projects

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence Log Analysis Projects (for Bash-Friendly Linux Users)

Ever felt like your logs are trying to tell you something, but you don’t speak “10GB of text per day”? You’re not alone. Logs are full of operational gold—security signals, performance regressions, and early warnings. The problem is volume and variability. This article shows you how to use practical AI techniques with Bash and Python to turn raw logs into reliable insights—without buying a SIEM or running a massive cluster.

You’ll learn 3 hands-on projects:

  • Anomaly detection for Nginx/Apache access logs

  • SSH brute-force detection using robust statistics

  • Template mining to de-duplicate noisy logs and find “new” errors

Each example comes with minimal, production-minded commands and installation instructions for apt, dnf, and zypper.

Why AI for Log Analysis?

  • Scale and speed: AI models (even simple ones) find patterns and outliers faster than ad-hoc greps or dashboards.

  • Fewer false positives: Instead of 100 regex alerts, you learn the “shape” of normal behavior and get alerted only when something breaks that shape.

  • Adaptable to new signals: Logs evolve; models retrain. You don’t have to rewrite rules for every service update.

Prerequisites: One-Time Setup

We’ll use Python with a virtual environment for ML, plus jq and ripgrep to slice-and-dice logs in Bash.

Choose the command that matches your distro:

APT (Debian/Ubuntu):

sudo apt update
sudo apt install -y python3 python3-venv python3-pip build-essential jq ripgrep

DNF (Fedora/RHEL/CentOS Stream):

sudo dnf install -y python3 python3-pip python3-virtualenv gcc gcc-c++ make python3-devel jq ripgrep

Zypper (openSUSE/SLE):

sudo zypper refresh
sudo zypper install -y python3 python3-pip python3-virtualenv gcc gcc-c++ make python3-devel jq ripgrep

Create a Python virtual environment and install libraries:

python3 -m venv ~/.venvs/logai
source ~/.venvs/logai/bin/activate
pip install --upgrade pip
pip install numpy pandas scikit-learn matplotlib drain3 ujson

Recommended workspace:

mkdir -p ~/log-ai && cd ~/log-ai

Note: You may need read access to system logs. Use sudo where appropriate or adjust group membership (e.g., adm on Debian/Ubuntu).


Project 1: Access-Log Anomaly Detection (Isolation Forest)

Goal: Flag unusual web requests without writing dozens of brittle rules.

What it does:

  • Parses Nginx/Apache “combined” logs

  • Builds simple numeric features (method, status, path length/depth, hour, bytes)

  • Trains an IsolationForest to score each line; surfaces the most anomalous

Save as nginx_anomaly.py:

#!/usr/bin/env python3
import re
import sys
import argparse
import pandas as pd
import numpy as np
from datetime import datetime
from sklearn.ensemble import IsolationForest

# Regex for "combined" format: 
# $remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent ...
LOG_RE = re.compile(
    r'^(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] "(?P<method>[A-Z]+)\s(?P<path>[^" ]*)(?: [^"]*)?" (?P<status>\d{3}) (?P<bytes>\d+|-)\b'
)

def parse_time(s):
    # Example: 12/Mar/2026:19:55:32 +0000
    try:
        return datetime.strptime(s, "%d/%b/%Y:%H:%M:%S %z")
    except Exception:
        return None

def features_from_line(line):
    m = LOG_RE.match(line)
    if not m:
        return None
    d = m.groupdict()
    dt = parse_time(d["time"])
    hour = dt.hour if dt else -1
    method = d["method"]
    method_map = {"GET":0, "POST":1, "PUT":2, "DELETE":3, "HEAD":4, "PATCH":5, "OPTIONS":6}
    mcode = method_map.get(method, 7)
    path = d["path"] or "/"
    path_len = len(path)
    # depth: number of segments ("/a/b" -> 2). Normalize "/" to 0.
    p = path.strip("/")
    depth = 0 if p == "" else p.count("/") + 1
    status = int(d["status"])
    bytes_sent = 0 if d["bytes"] == "-" else int(d["bytes"])
    return [hour, mcode, path_len, depth, status, bytes_sent], d, line

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--log", required=True, help="Path to access log")
    ap.add_argument("--top", type=int, default=50, help="Show N most anomalous lines")
    ap.add_argument("--seed", type=int, default=42)
    args = ap.parse_args()

    feats = []
    meta = []
    raw = []
    with open(args.log, "r", errors="ignore") as f:
        for line in f:
            out = features_from_line(line.rstrip("\n"))
            if out:
                x, d, l = out
                feats.append(x)
                meta.append(d)
                raw.append(l)

    if not feats:
        print("No parseable lines found. Check your log format.", file=sys.stderr)
        sys.exit(1)

    X = np.array(feats, dtype=float)

    # IsolationForest: robust unsupervised anomaly detector
    iso = IsolationForest(n_estimators=200, contamination="auto", random_state=args.seed)
    iso.fit(X)
    scores = iso.score_samples(X)  # higher is more normal; lower is more anomalous

    df = pd.DataFrame(meta)
    df["score"] = scores
    df["raw"] = raw
    # Show lowest scores first (most anomalous)
    df = df.sort_values("score").head(args.top)

    for i, row in df.iterrows():
        print(f"[score={row['score']:.4f}] {row['raw']}")

if __name__ == "__main__":
    main()

Run it:

# Example paths; adjust for Apache vs Nginx
# Nginx: /var/log/nginx/access.log
# Apache: /var/log/apache2/access.log (Debian/Ubuntu) or /var/log/httpd/access_log (RHEL/Fedora)
sudo python3 nginx_anomaly.py --log /var/log/nginx/access.log --top 40

Tips:

  • If your log format deviates, tweak the regex near LOG_RE.

  • Consider running this daily and storing results in a file:

sudo python3 nginx_anomaly.py --log /var/log/nginx/access.log --top 200 | tee -a anomalies-access.log

Project 2: SSH Brute-Force/Outlier IP Detection (Robust Stats)

Goal: Identify suspicious source IPs hitting SSH without complicated correlation rules.

We’ll:

  • Pull the last 24h of SSH logs

  • Count failed attempts by IP

  • Use a robust z-score (Median Absolute Deviation) to flag outliers

Export logs (journalctl or file-based):

# Systemd journals
sudo journalctl -u ssh -u sshd --since "24 hours ago" --output short-iso > ssh-24h.log
# Or Debian/Ubuntu file-based:
# sudo cp /var/log/auth.log ssh-24h.log

Save as ssh_bruteforce.py:

#!/usr/bin/env python3
import re
import sys
from statistics import median

FAIL_RE = re.compile(r'(Failed password|Invalid user).*(?:from|rhost=)\s(?P<ip>\d+\.\d+\.\d+\.\d+)')
# Optional: Success lines to compute success/fail ratio
OK_RE = re.compile(r'Accepted (?:password|publickey).*(?:from|rhost=)\s(?P<ip>\d+\.\d+\.\d+\.\d+)')

def mad_zscores(counts):
    vals = list(counts.values())
    if not vals:
        return {}
    med = median(vals)
    abs_dev = [abs(x - med) for x in vals]
    mad = median(abs_dev) or 1e-9
    z = {}
    for k, x in counts.items():
        z[k] = 0.6745 * (x - med) / mad
    return z

def main(path):
    fails = {}
    oks = {}
    with open(path, "r", errors="ignore") as f:
        for line in f:
            m = FAIL_RE.search(line)
            if m:
                ip = m.group("ip")
                fails[ip] = fails.get(ip, 0) + 1
                continue
            m2 = OK_RE.search(line)
            if m2:
                ip = m2.group("ip")
                oks[ip] = oks.get(ip, 0) + 1

    z = mad_zscores(fails)
    # Flags: high z-score or high absolute count
    alerts = [(ip, fails.get(ip,0), z.get(ip,0.0), oks.get(ip,0)) for ip in fails]
    alerts.sort(key=lambda t: (t[2], t[1]), reverse=True)

    print("ip,fail_count,robust_z,ok_count")
    for ip, fc, rz, ok in alerts:
        if fc >= 10 or rz >= 3:  # tune thresholds
            print(f"{ip},{fc},{rz:.2f},{ok}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: ssh_bruteforce.py <logfile>", file=sys.stderr)
        sys.exit(1)
    main(sys.argv[1])

Run it:

python3 ssh_bruteforce.py ssh-24h.log | column -t -s,

Pure-Bash quick check (handy for triage):

rg -oN '(?<=from )\d+\.\d+\.\d+\.\d+' ssh-24h.log | sort | uniq -c | sort -nr | head

Project 3: Log Template Mining (Drain3) to Reduce Noise and Surface “New” Errors

Goal: Automatically group similar log lines into templates, cut noise, and alert on never-seen patterns.

Why it matters: Production logs often have dynamic values (IDs, IPs). Template mining reduces millions of lines to a manageable set of patterns. “New template” often equals “new failure mode.”

Save as drain_stream.py:

#!/usr/bin/env python3
import sys
import json
from drain3 import TemplateMiner
from drain3.template_miner_config import TemplateMinerConfig
from drain3.file_persistence import FilePersistence

# Config with default settings; adjust as desired
config = TemplateMinerConfig()
persistence = FilePersistence("drain3_state.json")
miner = TemplateMiner(persistence, config)

for line in sys.stdin:
    line = line.strip()
    if not line:
        continue
    r = miner.add_log_message(line)
    # r['change_type']: "None", "cluster_created", "cluster_template_changed"
    if r["change_type"] != "None":
        out = {
            "change_type": r["change_type"],
            "template": r["cluster"]["template_mined"],
            "size": r["cluster"]["size"],
        }
        print(json.dumps(out, ensure_ascii=False))
        sys.stdout.flush()

Stream your logs into it:

# Follow system logs with minimal formatting and pipe into Drain3
sudo journalctl -f -o cat | python3 drain_stream.py | tee drain3-events.jsonl

Interpretation:

  • You’ll see JSON messages when a new template is discovered or a template changes significantly.

  • These events are excellent alert candidates. For example, send lines with "change_type": "cluster_created" to your alerting channel.

Optional: You can pre-seed the miner with a day’s worth of logs before going live:

sudo journalctl --since "1 day ago" -o cat | python3 drain_stream.py > /dev/null

Operationalizing: Scheduling and Hygiene

  • Cron (daily anomaly scan for access logs):
# Edit root’s crontab
sudo crontab -e
# Run at 01:25 daily and append to a dated file
25 1 * * * /usr/bin/python3 /home/youruser/log-ai/nginx_anomaly.py --log /var/log/nginx/access.log --top 200 >> /var/log/nginx/anomaly-$(date +\%F).log 2>&1
  • Systemd service/timer (for Drain3 streaming):
# /etc/systemd/system/drain3.service
[Unit]
Description=Drain3 Log Template Miner

[Service]
ExecStart=/bin/bash -lc 'source ~/.venvs/logai/bin/activate && journalctl -f -o cat | python3 /home/youruser/log-ai/drain_stream.py >> /var/log/drain3-events.jsonl'
Restart=always

# /etc/systemd/system/drain3.timer
[Unit]
Description=Start Drain3 at boot

[Timer]
OnBootSec=15sec
Unit=drain3.service

[Install]
WantedBy=multi-user.target

Enable:

sudo systemctl daemon-reload
sudo systemctl enable --now drain3.service
  • Keep data light:
    • Rotate output logs with logrotate.
    • Store only features or templates, not raw PII where not required.
    • Document thresholds and retraining cadence.

Real-World Tips

  • Start simple: IsolationForest and MAD-based outliers catch a surprising amount of real issues.

  • Keep parsers resilient: Logs change. Write regex to fail gracefully and log parse rates.

  • Thresholds are context: Tune per environment; watch false positives for a week before paging.

  • Use Bash to pre-filter: It’s faster to pipe a small, relevant slice into Python than to parse gigabytes unfiltered.

Handy Bash filters:

# Fetch only 5xx lines, last day
sudo journalctl --since "1 day ago" -u nginx -o cat | rg 'HTTP/1\.[01]" 5\d{2}'

# Extract suspicious user agents or long URLs
rg -n 'curl|bot|wget|Mozilla.*\?[^ ]{200,}' /var/log/nginx/access.log

Conclusion and Call to Action

You don’t need a full-blown data platform to get meaningful AI-powered insights from logs. With a few Bash-friendly tools and small Python scripts, you can:

  • Find unusual web requests without handcrafting rules

  • Catch SSH brute-force attempts early

  • Collapse noisy logs into stable templates and detect “new” errors in real-time

Your next step: 1) Install the prerequisites (apt/dnf/zypper commands above). 2) Run Project 1 on your access logs and review the top anomalies. 3) Enable Project 2 daily and tune thresholds for your SSH profile. 4) Pilot Project 3 on one service and alert on new templates for a week.

If you want a packaged repo with these scripts, reply and I’ll assemble a ready-to-clone project with Makefile and systemd units.