DEV Community

Thilo Barth
Thilo Barth

Posted on

Your AI System Already Has Logging. The EU AI Act Wants Something More Specific Than That.

Your AI system almost certainly has logging. Structured logs, maybe a request ID, maybe already shipped to a log aggregator. If someone asked "do you log what your model does," most engineering teams would say yes without thinking twice.

Article 12 of the EU AI Act asks a narrower, more specific question than "do you have logs" — and a lot of systems that would confidently answer yes to the first question fail the second one anyway.

What Article 12 actually requires

The text: a high-risk AI system must technically support automatic logging of events over its lifetime, to a degree that supports traceability, incident investigation, and post-market monitoring. (Remote biometric identification systems under Annex III point 1(a) have their own additional minimum logging content, which this post doesn't cover.)

Read that again slowly, because the specific words are doing real work. Not "logging exists somewhere." Automatic — it has to happen without a human remembering to trigger it. Over its lifetime — not just at request time, but across the system's whole operational history. Supports traceability — someone has to be able to reconstruct, after the fact, what the system saw and what it decided. Supports incident investigation — when something goes wrong, the logs need to be the thing that explains why, not a dead end. Supports post-market monitoring — the same logs are supposed to feed the ongoing obligation to watch for problems after deployment, not just exist for debugging.

Most application logging was built to answer a different question: "is the service healthy, and can I debug a crash." That's a legitimate, useful thing to log for. It is not the same question as "can I reconstruct, six months from now, exactly what this system decided about a specific person and why." A log line that says INFO: scored applicant, result=0.82 answers the first question fine. It's close to useless for the second one — there's no record of what inputs produced that score, no reference to which model version ran, no way to tie it back to a specific decision that got acted on.

Where this actually shows up in code

The pattern isn't "no logging." It's logging that exists but doesn't capture the right thing, at the right durability, next to the decision that actually matters. A few shapes this takes in practice:

  • A model call wrapped in generic app-level logging (logger.info(f"prediction: {result}")) with no persisted record of the input, the model version, or which downstream action the result triggered.
  • Logs that exist but live only in ephemeral container stdout with a short retention window — technically "automatic," but not "over its lifetime" if they age out in a week and the system runs for years.
  • A decision-making function that logs that it ran, but not the specific record needed to reconstruct why it produced the output it did for a specific case under investigation.

Here's a simplified rule (real Semgrep syntax, not the literal production rule) that looks for the first shape — a high-risk inference call whose output flows into application logic with no accompanying structured, persisted audit record nearby:

`rules:

  • id: eu-ai-act-example.article-12-missing-audit-log languages: [python] severity: WARNING message: > High-risk AI inference result is used without a structured audit-log call nearby. EU AI Act Article 12 requires automatic logging that supports traceability, incident investigation, and post-market monitoring -- not just generic application logging. metadata: article: "12" category: record-keeping patterns:
    • pattern-either:
      • pattern: $RESULT = $MODEL.predict(...)
      • pattern: $RESULT = $CLIENT.chat.completions.create(...)
    • pattern-not-inside: | $RESULT = $MODEL.predict(...) ... audit_log(...)
    • pattern-not-inside: | $RESULT = $MODEL.predict(...) ... persist_decision_record(...)`

The rule isn't looking for the absence of any log call — most of this code already has one, which is exactly the trap. It's looking for the absence of a call that persists a structured decision record: something that could actually answer "what did this system decide, about what input, using which model version, and what happened next" if someone asked that question a year later.

The passing version doesn't require ripping anything out — usually one additional call next to logic that's already there:

result = model.predict(applicant_data)
persist_decision_record(
input_ref=applicant_data.id,
model_version=model.version,
output=result,
timestamp=now(),
)
logger.info(f"prediction: {result}") # the old logging can stay -- it just isn't sufficient alone

The generic logger.info line isn't wrong. It's just answering a different question than Article 12 is asking, and a team that only has the first line has reasonable-looking logs and an actual compliance gap at the same time — which is exactly why this one gets missed more than a system with genuinely zero logging would.

This isn't hypothetical. We've already published one real scan result: a public résumé-screening repo came back with three findings and a 91% score (see our Sept 1 post on this). Gaps shaped like the one above — logging that exists but doesn't persist a reconstructable decision record — are a common contributor to scores landing short of 100, even on codebases that already take logging seriously.

Why fail-closed matters here specifically

There's a second trap worth naming: a compliance check that silently passes when it can't actually verify the logging exists (a timeout, a missing permission, a check that errors out and defaults to "OK"). That produces an audit trail claiming something was verified when nothing was. For a requirement whose entire point is "can you reconstruct what happened," a check that fails open is actively worse than no check — it's a false record layered on top of a real gap. Any tooling built against Article 12 specifically needs to fail closed: if the check can't run, that's a finding, not a pass.

Try it

Free tier, self-serve, no sales call: https://scanara.io/en/

Where I might be wrong

I'd like actual pushback on this one. Is "persist a structured decision record next to the inference call" really what auditors will look for under Article 12, or is that my own engineering intuition about what should satisfy traceability, filling in a gap the regulation itself leaves genuinely open? How much retention is actually "over its lifetime" in practice — is there real guidance on this anywhere, or is everyone guessing at a number right now? And for teams already shipping to observability platforms with long retention (Datadog, a data warehouse) — does that already satisfy this, or does the structure of what's captured matter more than where it's stored? Genuinely don't know the answer to that last one myself.

Top comments (0)