Structured logs
Goblin writes every event of a chaos experiment — each assault, chaos switched on or off, a configuration change — as a structured line in the io.quarkiverse.goblin.events log category. The goal is a log you can reason about while the experiment runs, whether you read it yourself or hand it to an agent: which request was hit, by what, with which value, under which configuration.
This is not log aggregation for compliance: Goblin never reaches production (see Does Goblin run in production?), and these events only exist in dev and test mode.
Format
Each event is a single line: a goblin marker followed by key=value pairs, always in the same order. A value holding a space, a quote or an = is quoted, and control characters are escaped (\n, \t, \u0007…), so an event never spans two lines.
WARN goblin event=activated timestamp=2026-10-21T10:00:00.004Z origin=startup profile=NONE level=100 layers=HTTP_OUT,HTTP_IN config="latency enabled (500 - 1000 ms)"
INFO goblin event=assault timestamp=2026-10-21T10:02:03.120Z source=server method=GreetingResource.hello type=latency assault-type=latency value=842ms config="latency enabled (500 - 1000 ms)"
WARN goblin event=config-change timestamp=2026-10-21T10:03:10.511Z profile=NONE level=100 layers=HTTP_OUT,HTTP_IN config="latency enabled (500 - 1000 ms), httpStatus enabled (503: \"Service Unavailable\")" previous="latency enabled (500 - 1000 ms)"
INFO goblin event=assault timestamp=2026-10-21T10:03:12.087Z source=server method=GreetingResource.hello type=http-status assault-type=http-status value="HTTP 503" config="latency enabled (500 - 1000 ms), httpStatus enabled (503: \"Service Unavailable\")"
WARN goblin event=deactivated timestamp=2026-10-21T10:05:00.000Z reason=auto-off
Events
| Event | Level | Fields after event and timestamp |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
timestamp is an ISO-8601 instant in UTC. A configuration change comes from anywhere — the Dev UI, a JSON-RPC or Dev MCP call, a test calling MutableAssaultConfig — and is logged once per published change, like the AssaultObserver.onConfigChange notification it mirrors. The configuration loaded at startup is reported by the activated event, not as a change.
The injected value
value says what the assault did, read from the configuration it ran with:
| Assault | Value |
|---|---|
Latency |
the delay actually applied, e.g. |
Exception |
the class and message of the exception thrown, e.g. |
HTTP status |
|
Dependency degradation |
|
Response body |
the mode and percentage, e.g. |
Response header |
|
Custom |
the latency it recorded, empty otherwise |
JSON logs
While an event is logged, its fields are also in the MDC, prefixed with goblin.: goblin.event, goblin.timestamp, goblin.method, goblin.value… With quarkus-logging-json, they come out as fields of the JSON log record, so an agent can filter the events without parsing the message:
quarkus.log.console.json.enabled=true
Tuning the volume
At a 100% target level, every request produces an assault event. Assaults are logged at INFO and the lifecycle at WARN, so raising the category level keeps the lifecycle and silences the assaults:
quarkus.log.category."io.quarkiverse.goblin.events".level=WARN
Set it to OFF to silence all events. The history (AssaultEngine.getHistory()), the counters and the observers are not affected.