Java error logging · reimagined for AI

stacktale

stack traces that tell the tale

Java error logs were built in the ’90s for a human with grep — and for that reader they work. But the reader is changing: an AI assistant or an automated agent reads an error with no muscle memory, and every line is context and token cost. stacktale captures the failure at the source, structured for that reader — alongside your untouched logs.

Maven Central 0.3.1 Java 17+ Logback · Log4j2 Apache-2.0
A quiet revolution

The Java stack trace hasn't changed since 1995. The reader has.

For thirty years the format was aimed at one reader: a human at a terminal with grep and a hunch. Then, quietly, the most frequent reader of an error log became an AI assistant — and nobody redesigned the log for it. Every pasted stack trace becomes an interrogation: what happened before? which values? which version? — ten messages of the AI asking for context that existed at the moment of the error and was thrown away.

1995
The stack trace is born. A human reads it with grep.
2005–2020
JSON logs, MDC, tracing. Still shaped for dashboards and humans.
Today
An AI reads your errors. stacktale writes the log it actually needs.

Post-processing can't fix this — by the time the log is written, the story is already gone. The only place to capture it is at the source. That's the whole idea, and it's why a 1-line stack trace from 1995 and a stacktale report are not the same artifact at all.

The same NullPointerException

One error, two ways to read it

A checkout dies on an NPE. The root cause — a cache miss returning null — happened three log lines earlier, buried under other threads.

classic stack trace — what you paste today
ERROR o.a.c.c.C.[dispatcherServlet]
java.lang.NullPointerException: Cannot invoke
 "Customer.getEmail()" because "customer" is null
	at com.shop.OrderService.confirm(OrderService.java:87)
	at jdk.internal.reflect.DirectMethodHandle...
	at org.springframework.web.method.support.In...
	at org.springframework.web.servlet.mvc.method...
	at org.springframework.web.servlet.Dispatcher...
	at org.apache.catalina.core.ApplicationFilter...
	at org.apache.catalina.core.StandardWrapper...
	… 49 more framework frames …

(what happened before? which values?
 which version? — you'll be asked all of it)
✕  where it died. Nothing about why.
errors-ai.log — what stacktale writes
━━━ ERROR #cddfa41c ━━━ thread=exec-5
NullPointerException: "customer" is null
at OrderService.confirm(:87) ← YOUR CODE
log: "checkout failed for order {}" args=[889]
mdc: traceId=70e1dfe7
fields: orderId=889 retryable=false

story (traceId=70e1dfe7, last 4, 412ms):
  INFO  OrderController  POST /orders/889/confirm
  INFO  CustomerClient   fetch customer 555 → 404
  WARN  CustomerCache    miss 555, returns null
  ERROR OrderService     checkout failed 889 

stack (distilled, 2 of 56):
  OrderService.confirm(:87) ← culprit
  … 54 collapsed (spring ×24, tomcat ×13)
env: app=shop-api 1.4.2 | java 21 | linux
━━━ END #cddfa41c ━━━
✓  the highlighted line is the root cause — one read, solved.
Measured, not estimated

Less to read, more to know

Counted with a real tokenizer on the artifacts of an actual dogfooding session — a Spring Boot app hit over HTTP until it failed seven ways.

98.3%
fewer tokens for a whole session of logs
80.6%
fewer per single error — carrying more context
137ns
overhead per log event (JMH, story capture)
7
modules — core, Logback, Log4j2, Spring, MCP, agent

a classic log grows with traffic · a report file grows only with distinct errors

Anatomy of a report

Everything the moment of failure knew

Captured at the source and never thrown away — because by the time the log is written, the story is already gone.

headline
Root cause first — wrappers shrink to one line each
← YOUR CODE
The culprit frame: your code, not the framework
log · args
The pattern and the actual values, not guesses
mdc · fields
Trace ids and the state the exception carried
story
The events that led here — the narrative, correlated
stack
Distilled — 40 framework frames become one counted line
captured
Method arguments at the throw site — even if nothing was logged
env
Version, git sha, Java, profile — asked in half of debug sessions
redacted
Secrets, tokens, PII masked by default before anything hits disk
One core, every surface

Seven modules, pick what you run

stacktale-core
The framework-agnostic report pipeline: story, distiller, dedup, redaction.
stacktale
The Logback appender — a thin adapter over the core.
stacktale-log4j2
The Log4j2 appender. Same format, ThreadContext correlation.
…-spring-boot-starter
Zero config. Auto-registers, opens each story with the request line.
stacktale-mcp
An MCP server — your AI queries reports as tools, not files.
stacktale-agent
A -javaagent capturing method args at the throw site.
Quickstart

One dependency. No config.

pom.xml — Spring Boot
<dependency>
  <groupId>io.github.gabrielbbaldez</groupId>
  <artifactId>stacktale-spring-boot-starter</artifactId>
  <version>0.3.1</version>
</dependency>

That's it. Reports land in errors-ai.log; your console stays exactly as it was.