Using LLMs for Log Analysis: Parsing, Clustering, and Queries

Published July 13, 2026

By Charles Givre

generative AILLM securitylog analysisSOCdata science

An LLM will not read your logs for you. It cannot, at least not the way vendors imply: a single busy host emits millions of lines a day, and no model context window or API budget survives that volume. The teams getting value from large language models in log analysis are the ones who reduce the data with deterministic tooling first, then point the model at the language-heavy remainder.

Here is the split that works, the tools to use, and where it breaks.

Reduce Before You Reason

The first mistake is treating the model as the parser. It is the last step, not the first. Before an LLM sees anything, collapse the raw stream into a small set of representatives.

For high-volume structured-ish logs (auth, web, firewall), mine templates with Drain3. It groups lines by their fixed skeleton and treats the variable parts as parameters, with no training required. Millions of lines become a few hundred templates.

from drain3 import TemplateMiner

miner = TemplateMiner()
with open("/var/log/auth.log") as f:
    for line in f:
        miner.add_log_message(line.strip())

# A few hundred templates instead of millions of raw lines
for cluster in sorted(miner.drain.clusters, key=lambda c: c.size, reverse=True):
    print(cluster.size, cluster.get_template())

Now you have something a model can actually work with: send the templates, their counts, and a few example lines, and ask the model to label each cluster, flag which ones are security-relevant, and group them by activity. You are spending tokens on a few hundred patterns, not tens of millions of events.

Force Structured Output on the Messy Tail

Drain3 and Grok patterns handle the regular logs cheaply. The long tail is where the model earns its keep: free-text error messages, vendor appliance logs with no schema, application exceptions that never look the same twice.

Do not ask for prose back. Force a schema, the same way you would for any other enrichment callout. On the Anthropic Messages API, tool use doubles as a structured-output contract:

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

extract_tool = {
    "name": "extract_log_fields",
    "description": "Extract normalized fields from a single unstructured log line.",
    "input_schema": {
        "type": "object",
        "properties": {
            "event_type": {"type": "string"},
            "src_ip": {"type": "string"},
            "username": {"type": "string"},
            "outcome": {"type": "string", "enum": ["success", "failure", "unknown"]},
            "severity": {"type": "string", "enum": ["info", "low", "medium", "high"]},
        },
        "required": ["event_type", "outcome"],
    },
}

resp = client.messages.create(
    model="claude-haiku-4-5-20251001",   # cheap model for high-volume work
    max_tokens=512,
    tools=[extract_tool],
    tool_choice={"type": "tool", "name": "extract_log_fields"},
    system="Extract only fields present in the line. Do not invent values.",
    messages=[{"role": "user", "content": raw_line}],
)

fields = next(b.input for b in resp.content if b.type == "tool_use")

The enum constraints stop the model from inventing a new severity, and you validate every returned src_ip against an IPv4/IPv6 format before it enters your store. Route this high-volume extraction to a cheap model like Claude Haiku 4.5 (claude-haiku-4-5-20251001); save the expensive models for the investigations a human already cares about. The same routing-by-severity logic applies here as in a full SOC integration.

Cluster With Embeddings, Not Prompts

Two log lines can describe the same event with completely different wording. Embeddings catch that where string matching does not. Embed each message with a small local sentence-transformer, then cluster the vectors.

from sentence_transformers import SentenceTransformer
from sklearn.cluster import DBSCAN

model = SentenceTransformer("all-MiniLM-L6-v2")   # runs locally, no data leaves
vectors = model.encode(log_messages, normalize_embeddings=True)

labels = DBSCAN(eps=0.25, min_samples=5, metric="cosine").fit_predict(vectors)
# label == -1 marks the outliers worth an analyst's attention

Run the embedding model locally with sentence-transformers so you are not paying per token or exporting logs to analyze them. Store the vectors in pgvector and you get “show me logs similar to this one” during an investigation. The -1 outliers from DBSCAN are the rare events; hand only those to the LLM for explanation. This is the same feature-then-model discipline covered in feature engineering for security data.

Draft Queries, Do Not Run Them

Turning an analyst’s plain-English intent into first-draft Splunk SPL, KQL, or a Sigma rule is a real win. Generating a query and running it unattended is not. Models invent field names that do not exist in your schema, botch time-window boundaries, and write queries that look correct while matching the wrong events.

Give the model your actual field list so it stops guessing, and treat the output like generated code: review it, run it against a bounded time range, and sanity-check the hit count before it becomes a scheduled detection. A generated query that returns zero results or ten million both mean the same thing: read it before you trust it. This is the reverse of the SIEM-to-Jupyter workflow, where the analyst writes the logic and the notebook keeps it reproducible.

Where It Breaks

Plan for these from the first prototype:

  • Aggregation belongs in SQL. Counting failed logins per user across a week is a GROUP BY, not a prompt. Models cannot count reliably at scale and will confidently give you a wrong total.
  • Log fields are attacker-controlled. A User-Agent, a filename, an HTTP path can carry an indirect prompt injection (OWASP LLM01, MITRE ATLAS AML.T0054). Keep the model read-only and gate everything it returns.
  • Hallucinated fields reach your store. A model may return a plausible src_ip that was never in the line. Validate format and cross-check against the raw event before writing anything.
  • Non-determinism defeats detection. The same line can parse two ways on two runs. Anything feeding a detection rule needs a deterministic path or a validation gate, not a raw model output.
  • Cost scales with volume. Send representatives, not raw streams. If your token bill scales linearly with log volume, the architecture is wrong.

Where to Learn This

The hard part is not prompting. It is knowing which step is deterministic (templating, aggregation, format validation) and which is a language task (labeling clusters, explaining an anomaly, drafting a query), then wiring them so the model never sits where a wrong answer causes damage. Teams that get value here already understood their log schemas and detection logic; the model amplifies that engineering, it does not supply it.

GTK Cyber’s Applied Data Science and AI for Cybersecurity course is built for security practitioners who want to connect these pieces on real data, with the judgment to keep the model where it helps. The generative AI in security operations post covers the same split for the broader SOC.

Frequently Asked Questions

Can I just paste my logs into ChatGPT or Claude to analyze them?
For a handful of lines during an investigation, yes. As a pipeline, no. A single host can emit millions of log lines a day, and sending raw logs to a model API is slow, expensive, and leaks data you should not be shipping to a third party. The working pattern is to reduce first with deterministic tooling: mine log templates with Drain3 so millions of lines collapse to a few hundred patterns, or cluster with embeddings, then send only the representatives to the model. Counting and aggregation stay in SQL or your SIEM, never in a prompt. Use the API with an enterprise zero-retention agreement, and redact credentials and PII from any field before it leaves your environment.
What is the best way to parse unstructured logs with an LLM?
Do the structural parsing deterministically, then use the model for the language-heavy remainder. For high-volume templating, Drain3 mines log templates without training and turns variable-heavy lines into stable patterns. For the messy long tail (free-text error messages, vendor logs with no schema), force structured output: define a tool in the Anthropic Messages API or OpenAI function calling whose schema names the fields you want, force the call, and validate the returned JSON before it enters your store. That gives you a schema contract instead of prose you have to regex. The model handles the irregular cases; a Grok or regex pattern handles the regular ones far more cheaply.
Can an LLM write my Splunk or KQL detection queries?
It can draft them, and that is genuinely useful for turning an analyst's plain-English intent into first-draft SPL, KQL, or a Sigma rule. It should never run them unattended. Models routinely invent field names that do not exist in your schema, miss time-window edge cases, and write queries that look right but match the wrong events. Treat generated queries the way you treat generated code: review, run against a bounded time range, and confirm the hit count is sane before promoting the query into a scheduled detection. Give the model your actual field list in the prompt so it stops guessing column names.
Is it safe to feed attacker-controlled log data to an LLM?
Only if the model is read-only and everything it returns passes a validation gate. Log fields are frequently attacker-controlled: a User-Agent string, a filename, an HTTP path, a hostname. An attacker who can write to any of those can attempt indirect prompt injection (OWASP LLM01, MITRE ATLAS AML.T0054), embedding instructions in a field your analysis pipeline reads. Keep the analysis model to enrichment and drafting, never wire it to state-changing tools that run unattended, and validate the structure of its output rather than trusting the text. The model reads potentially hostile input, so treat its output as untrusted until checked.
Do embeddings help with log analysis, and which model should I use?
Yes, for clustering and similarity search across log lines that are worded differently but mean the same thing. Embed log messages with a small sentence-transformer like all-MiniLM-L6-v2 (fast, runs locally, no data leaves your environment), then cluster the vectors with DBSCAN or HDBSCAN to group near-duplicate events and surface rare outliers. Store the vectors in pgvector on Postgres and you can ask 'show me logs similar to this one' during an investigation. Run the embedding model locally rather than through an API so you are not paying per token or exporting log data to analyze it.

Related posts

Want to learn more?

Explore our hands-on AI and cybersecurity training courses.

View Courses