Posted on
Artificial Intelligence

Artificial Intelligence Log Correlation

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

Artificial Intelligence Log Correlation on Linux (with Bash-first workflows)

Ever feel like you only find out about incidents after the damage is done—and your logs didn’t “say it out loud” soon enough? The truth is: your logs did say it. Across SSH, web, and app logs, the signal was there—just scattered. AI-powered log correlation helps you stitch together weak signals from different sources into a single, timely warning you can act on.

In this post, you’ll learn a practical, Unix-friendly way to do AI log correlation using Bash and a small Python helper. You’ll normalize logs, extract features, score anomalies, and get actionable output you can automate via cron. No heavy stacks required.

Why AI log correlation is worth your time

  • Detect multi-vector attacks: SSH brute-force aligned with web scanning and DB errors in the same time window? That’s coordinated.

  • Reduce MTTR: Surface cross-system relationships automatically so you can jump straight to the likely root cause.

  • Scale beyond rules: Static rules miss novel behaviors; anomaly detection adapts as your environment changes.

  • Stay Unix-friendly: Keep your pipelines simple—pipe, parse, learn, alert—without committing to a heavyweight platform up front.

What we’ll build

A minimal, scriptable AI correlation pipeline you can run anywhere:

  • Input: systemd/journald (for sshd) + Nginx access logs

  • Transform: per-minute features per source IP (e.g., SSH failures, HTTP 5xx/404 counts)

  • Model: Isolation Forest (unsupervised anomaly detection)

  • Output: ranked anomalies with a short “why” (feature spikes) you can email, log, or page

Installation (Debian/Ubuntu, Fedora/RHEL, openSUSE)

We’ll use Python 3 with pip to install ML dependencies.

  • APT (Debian/Ubuntu and derivatives):
sudo apt update
sudo apt install -y python3 python3-pip
  • DNF (Fedora/RHEL/CentOS Stream):
sudo dnf install -y python3 python3-pip
  • Zypper (openSUSE/SLE):
sudo zypper refresh
sudo zypper install -y python3 python3-pip

Then install Python packages for the script:

python3 -m pip install --user pandas scikit-learn

Tip: If you prefer a virtual environment:

python3 -m venv ~/ai-log-corr-venv
. ~/ai-log-corr-venv/bin/activate
pip install pandas scikit-learn

Step-by-step: From raw logs to correlated anomalies

1) Save the correlation script

Create ai_log_corr.py with the following content:

#!/usr/bin/env python3
import argparse, json, re, subprocess, sys
from collections import defaultdict
from datetime import datetime
import pandas as pd
from sklearn.ensemble import IsolationForest

SSH_FAIL_RE = re.compile(r"Failed password .* from (?P<ip>\d+\.\d+\.\d+\.\d+)")
NGINX_CLF_RE = re.compile(
    r'^(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] "(?P<req>[^"]*)" (?P<status>\d{3}) (?P<size>\S+) "(?P<ref>[^"]*)" "(?P<ua>[^"]*)"'
)

def epoch_minute(ts_seconds: float) -> int:
    return int(ts_seconds // 60)

def load_ssh_events(since: str):
    # journalctl outputs JSON per line with __REALTIME_TIMESTAMP in microseconds
    cmd = ["journalctl", "-u", "ssh.service", "--since", since, "-o", "json"]
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
    by_key = defaultdict(lambda: {"ssh_fail": 0})
    for line in p.stdout:
        line = line.strip()
        if not line:
            continue
        try:
            obj = json.loads(line)
        except json.JSONDecodeError:
            continue
        msg = obj.get("MESSAGE", "")
        m = SSH_FAIL_RE.search(msg)
        if not m:
            continue
        ip = m.group("ip")
        ts_micro = int(obj.get("__REALTIME_TIMESTAMP", "0"))
        minute = epoch_minute(ts_micro / 1_000_000.0)
        by_key[(minute, ip)]["ssh_fail"] += 1
    p.wait()
    return by_key

def parse_nginx_time(t: str) -> int:
    # Example: 10/Jul/2026:13:45:01 +0000
    dt = datetime.strptime(t, "%d/%b/%Y:%H:%M:%S %z")
    return epoch_minute(dt.timestamp())

def load_nginx_events(path: str):
    by_key = defaultdict(lambda: {"nginx_5xx": 0, "nginx_404": 0})
    try:
        with open(path, "r", encoding="utf-8", errors="ignore") as f:
            for line in f:
                m = NGINX_CLF_RE.match(line)
                if not m:
                    continue
                ip = m.group("ip")
                t = m.group("time")
                status = int(m.group("status"))
                minute = parse_nginx_time(t)
                if 500 <= status <= 599:
                    by_key[(minute, ip)]["nginx_5xx"] += 1
                elif status == 404:
                    by_key[(minute, ip)]["nginx_404"] += 1
    except FileNotFoundError:
        pass
    return by_key

def merged_features(ssh_by_key, ngx_by_key):
    keys = set(ssh_by_key.keys()) | set(ngx_by_key.keys())
    rows = []
    for (minute, ip) in keys:
        ssh = ssh_by_key.get((minute, ip), {})
        ngx = ngx_by_key.get((minute, ip), {})
        rows.append({
            "minute": minute,
            "ip": ip,
            "ssh_fail": int(ssh.get("ssh_fail", 0)),
            "nginx_5xx": int(ngx.get("nginx_5xx", 0)),
            "nginx_404": int(ngx.get("nginx_404", 0)),
        })
    df = pd.DataFrame(rows)
    if df.empty:
        return df
    return df.fillna(0)

def explain_row(row):
    parts = []
    for k in ["ssh_fail", "nginx_5xx", "nginx_404"]:
        v = int(row[k])
        if v > 0:
            parts.append(f"{k}={v}")
    return "; ".join(parts) if parts else "no features > 0"

def main():
    ap = argparse.ArgumentParser(description="AI log correlation: SSH + Nginx anomaly detector")
    ap.add_argument("--nginx", default="/var/log/nginx/access.log", help="Path to Nginx access.log")
    ap.add_argument("--ssh-since", default="2 hours ago", help="journalctl --since window (e.g., '90 min ago', 'today')")
    ap.add_argument("--top", type=int, default=10, help="Show top-N anomalies")
    ap.add_argument("--contamination", type=float, default=0.01, help="Expected anomaly proportion (0.0-0.5)")
    args = ap.parse_args()

    ssh_by_key = load_ssh_events(args.ssh_since)
    ngx_by_key = load_nginx_events(args.nginx)
    df = merged_features(ssh_by_key, ngx_by_key)

    if df.empty:
        print("No data in the selected window/logs.", file=sys.stderr)
        sys.exit(0)

    X = df[["ssh_fail", "nginx_5xx", "nginx_404"]].values
    model = IsolationForest(
        n_estimators=200,
        contamination=args.contamination,
        random_state=0,
        n_jobs=-1,
    )
    model.fit(X)
    scores = -model.score_samples(X)  # higher => more anomalous
    df = df.assign(anomaly_score=scores)

    # Rank and display
    out = df.sort_values("anomaly_score", ascending=False).head(args.top)
    print("minute,ip,ssh_fail,nginx_5xx,nginx_404,anomaly_score,why")
    for _, r in out.iterrows():
        print(f"{int(r['minute'])},{r['ip']},{int(r['ssh_fail'])},{int(r['nginx_5xx'])},{int(r['nginx_404'])},{r['anomaly_score']:.4f},{explain_row(r)}")

if __name__ == "__main__":
    main()

Make it executable:

chmod +x ai_log_corr.py

Notes:

  • You may need sudo to read /var/log/nginx/access.log.

  • The script uses journalctl for sshd; if your unit is sshd.service instead of ssh.service, adjust the command accordingly.

2) Run it against live logs

Examples:

# Past 2 hours, default top-10
sudo ./ai_log_corr.py

# Past 30 minutes, show top-5 anomalies
sudo ./ai_log_corr.py --ssh-since "30 minutes ago" --top 5

# Custom Nginx path
sudo ./ai_log_corr.py --nginx /var/log/nginx/your_vhost.access.log

Sample output:

minute,ip,ssh_fail,nginx_5xx,nginx_404,anomaly_score,why
29123456,203.0.113.45,18,0,0,0.8921,ssh_fail=18
29123456,198.51.100.27,0,5,12,0.8443,nginx_5xx=5; nginx_404=12
29123457,203.0.113.45,6,2,7,0.8209,ssh_fail=6; nginx_5xx=2; nginx_404=7
...

Interpretation:

  • Each row is a minute bucket and source IP with feature counts.

  • anomaly_score is higher for “strange” combinations or spikes across features.

  • why summarizes which features fired, so you can act quickly.

3) Automate with cron

Schedule it to run every 5 minutes and log results:

sudo install -d -m 750 /var/log/ai-log-corr
sudo sh -c 'echo "*/5 * * * * root /usr/bin/python3 /opt/ai-log-corr/ai_log_corr.py --ssh-since \"30 minutes ago\" --top 5 >> /var/log/ai-log-corr/anomalies.log 2>&1" >/etc/cron.d/ai-log-corr'

Or with a user crontab (ensure the user can read the logs):

crontab -e
# Add:
*/5 * * * * /usr/bin/python3 /home/you/ai_log_corr.py --ssh-since "30 minutes ago" --top 5 >> /home/you/ai-log-corr.log 2>&1

4) Tune and extend features

  • Add more sources: parse app logs with a small regex and add columns (e.g., app_errors, db_timeouts).

  • Adjust contamination to control sensitivity (e.g., 0.02 for noisier environments).

  • Use per-service or per-path features in Nginx (e.g., 40x on /wp-login.php).

  • Whitelist known scanners or internal CIDRs before modeling.

  • Align time windows to known events (deploys, migrations) to catch causality.

5) Real-world correlation patterns to watch

  • Coordinated probe: SSH brute-force plus a spike of 404/403 on WordPress or admin paths from the same IP block within minutes.

  • Outage precursor: Rising 5xx on a service that maps to a rollout window, followed by elevated SSH activity from an automation IP.

  • Data exfil attempt: An unusual burst of large 200 responses after repeated 401/403s, accompanied by SSH auth noise.

Why this works (and where to go next)

Isolation Forest is good at flagging unusual combinations of features. By bucketing per minute and per IP, you get a simple “correlation surface” where spikes across sources stand out, even if each signal would be ignorable on its own. This is the essence of AI log correlation: fuse weak signals to find strong incidents.

Next steps if you want more:

  • Enrich with GeoIP, ASN, or user-agent classes to improve discriminative power.

  • Persist features to a time-series DB and feed a streaming model (e.g., online anomaly detection).

  • Consider an open-source stack with dashboards and built-in anomaly detection (e.g., OpenSearch + Anomaly Detection plugin) once you outgrow scripts.

Call to Action

  • Install the prerequisites, drop in the script, and run it against the last 2 hours of logs.

  • Put it on cron for a week and see what it surfaces; iterate your features.

  • Share a sanitized top anomaly with your team and decide what alert threshold makes sense for production.

Your logs already know what’s wrong. Correlate them with a dash of AI—and let your shell do the rest.