Changelog¶
All notable changes to LogAperture are recorded here. The format is based on
Keep a Changelog, and this project aims
to follow Semantic Versioning once it
reaches 1.0. Pre-1.0 alpha builds are numbered 0.1.0-alpha.N.
Unreleased¶
Added¶
-
logctl help <command>, and--helpafter any command (logctl set logger --help), explain one command: its forms, what it does, its options, and examples.logctl help setlists thesetcommands to pick from. The guide's command reference (reference/logctl.md) is generated from the same text (issue #159;doc/specs/user-documentation.mdslice 2). -
The user guide, at https://logaperture.org/ and in the release zip's
docs/folder (opendocs/index.html; it works with no network access). Written so far: Home with what 1.0 supports, installing on WildFly and on a plain JVM, a Quick start, Configuration layers, and thelogctlcommand reference. The other pages are being written for 1.0 (issue #159;doc/specs/user-documentation.md). -
logctl enable stormsandlogctl disable stormsturn storm detection on and off in a running JVM, in every context. With no tier that lasts until the JVM stops;for 30mswitches it to the other position after 30 minutes, andstickykeeps it across restarts, ahead of the agent argument. Running one twice is the same as running it once. Each change is audited (switch=storm-detection) and needs the newdiagnosticscapability.logctl storms,statusanddoctorsay when it is disabled; a report taken after disabling is kept as it was at that moment, and enabling again starts a new measurement window. The agent argument--storm-detection=onstarts a JVM with it enabled (issue #151;doc/specs/storm-detection-toggle.md). The state file goes to schema 12. -
logctl set logger <name> <level> --forcealso sets the loggers under<name>that have a level of their own, which the logging framework never lets inherit. Afterset logger com.acme TRACE,com.acme.otherconfigured atINFOstays atINFO. With--forceit goes toTRACEtoo, andreset logger com.acmeputs it back toINFO. A logger you set yourself is kept, and so is anything changed since the force. Without--force,set loggernow ends with a note naming the loggers that didn't follow and the command that sets them too. Guidedset loggerasks.list loggersshowsforced by com.acmeon a forced override. The state file goes to schema 11 (issue #142;doc/specs/set-logger-force.md). logctl list recipes— the logging recipes on offer in the running application: named, documented sets of logger levels for watching one thing, e.g.io.undertow:sessions. They come from the libraries it has loaded (META-INF/logaperture/recipes.yamlon a class path, including a war'sWEB-INF/classesandWEB-INF/libjars), from the vendor defaults file's newrecipes:section, and from a recipes folder (~/.logaperture/recipes, or-Dlogaperture.recipes=<dir>), read afresh each time. A folder or vendor recipe with a library's id takes its place;--verboseshows full sources and any recipe file that couldn't be read, whichlogctl doctoralso reports (issue #92;doc/specs/recipes.md).logctl show recipe <id>— what a recipe is for, where it came from, and each change it would make against the live level (INFO -> DEBUG). A library's recipe may only raise levels: an entry that would lower one is shown as skipped.--from <source>picks among recipes sharing an id (issue #92).logctl apply recipe <id> [session | for <duration> | sticky]— switch a recipe on: shows the changes, asks, then makes them (--yesto skip asking;for 4hunless a tier is given). Every change is checked first, and if any would be refused nothing changes. Each change remembers the recipe:list loggers,list handlersandlist rulesshow aRECIPEcolumn,list recipesanAPPLIEDcolumn (for, 22m left (2 of 3)), and the audit log names the recipe and where it came from. Applying it again replaces its rules rather than adding them twice (issue #92).logctl reset recipe <id>— switch it off: puts back every change that still carries the recipe, leaving anything changed by hand since alone; sticky ones need--include-sticky. Works for a recipe whose library has since been undeployed. On a terminal,logctl applyandlogctl reset recipealone pick from a list (issue #92).- Vendor defaults file:
namespace:andrecipes:, and multi-line text written as YAML'skey: |block text (issue #92).logctl export vendor-defaultscarries both sections over.
Changed¶
-
The release zip's
docs/INSTALL-wildfly.mdis replaced by the user guide indocs/(docs/get-started/install-wildfly.html).doc/wildfly-test-drive.mdbecame the guide's Quick start (issue #159). -
logctl --helpis a one-screen overview: every command form, the options every command takes, and how tiers work. The per-command options and explanations moved tologctl help <command>. A usage error now shows only the forms of the command you typed, not the whole help (issue #159). -
Storm detection is disabled by default. Through alpha.3 it was always on, and it added a measurable cost to every log call that reached a handler (#150). Start the agent with
-javaagent:logaperture-agent.jar=--storm-detection=on, or runlogctl enable storms, to turn it on (issue #151). - Audit lines label a handler
handler=, notlogger=(issue #137). A handler change used to print aslogger=FILE, which read like a logger called FILE. Handler levels and theDEFAULT_HANDLERSmembership now printhandler=<name>, and the records naming the vendor defaults file loaded or the state file resumed from printfile=<path>. Logger and rule records still printlogger=. If you grep the audit log forlogger=to find handler changes, grephandler=instead. - Much quieter startup and drop summaries (issue #123;
doc/specs/quieter-output.md). A start prints one line,[logaperture] LogAperture <version> active (WildFly): vendor defaults <file> (…); N sticky settings restored, in place of several INFO lines. Loading a vendor defaults file is audited once, with the file's SHA-256, and a restart's resume once, with counts -- no longer one record per entry. Drop summaries are one line for every rule, every 10 minutes (-Dlogaperture.drop.summaryInterval), none during the first interval; on WildFly they go through the server's own logging at INFO underorg.logaperture.dropinstead of appearing asERROR [stderr]. No rule can match or be attached toorg.logaperture.drop. Every LogAperture message now honours-Dlogaperture.diagnostics.level, whose default is nowWARN, in one format ([logaperture] LEVEL message); a failure repeated on every log event is written at most once a minute. - Guided
logctl listalso offersrecipes. A one-letter answer to a guided question now counts only when no other choice starts with that letter, sorasks again (rules or recipes).
Fixed¶
- A long-running server no longer ends up throwing
StackOverflowErrorfrom every log call. On java.util.logging and JBoss LogManager (WildFly), the agent's two filters on each handler, for storm detection and for rules, wrapped each other again on every 30-second check. So every log record went through a longer chain the longer the server ran. Logging got slower and storm counts were inflated. After a few days, any thread that logged ran out of stack, and a timer thread that died that way never came back. The filters now keep a single fixed layering however often the check runs. Affects 0.1.0-alpha.3 (issue #145). - The guided logger prompt accepts a trailing
.*to pick from a subtree. Typing*.hibernate.*ororg.hibernate.*atlogctl set loggerorlogctl add ruleused to be refused ("A trailing '.*' isn't needed"), though the prompt's own example was*.deployment.*. It now lists the package and every logger under it to pick from, and sets only the names picked. A complete command such aslogctl set logger 'org.hibernate.*' DEBUGis still refused, since there it would be a standing wildcard (issue #139). - A package name with loggers under it is no longer reported as missing. Just after a server
start, the guided prompt could say
No logger named org.wildfly exists yet; set it anyway? [y/N]even thoughlist loggers org.wildfly --show-allshowed dozens of loggers under it: the framework hadn't created theorg.wildflylogger itself yet. The prompt now uses the name and says what it reaches (org.wildfly isn't a logger itself yet; 37 loggers under it inherit from it ...). A name with nothing at or under it is still asked about (issue #140). - A handler set on its own under a group setting no longer flips every 30 seconds. After
logctl handler ALL_HANDLERS TRACEthenlogctl handler FILE DEBUG, the verification sweep used to putFILEback toTRACEand then toDEBUGon every tick, auditing both changes each time. Now the most specific setting wins and stays: a handler's own setting, thenDEFAULT_HANDLERS, thenALL_HANDLERS.logctl handler FILE resethandsFILEback to the group's level rather than its original one, andlogctl statusnotes which wins when a group is listed with a more specific setting. The sweep also no longer writes an audit row for a group member that was already at the right level (previous=TRACE new=TRACE) (issue #135). logaperture-agent.jarlisted twice on-javaagentno longer starts LogAperture twice. The second copy used to fail to lock the state file and to register its control surface, logging both at startup; now LogAperture starts once, from the first entry, and every later entry writes one line,LogAperture is already started in this JVM; ignoring the duplicate -javaagent entry.Attaching the agent to a JVM that already runs it is ignored the same way.logctl doctorstill flags the duplicate entry (issue #120).- Storm detection is much cheaper per log line. Every message that reaches a handler is
normalized for storm fingerprinting (
order 4821andorder 9134count as one message). That was four regular-expression passes; it's now one pass over the text with the same result, and a message seen recently, such as a log statement's template, is looked up rather than normalized again.logctl stormsshows the same normalized messages as before, except that a non-ASCII letter touching a hex run now always counts as part of the word (édeadbeefstays as it is; on Java 19 and later it used to becomeé<hex>). The lookup keeps up to 1,024 recent messages per context;-Dlogaperture.storm.normalizationCacheSizechanges that, and0turns it off (issue #129). - Storm detection is cheaper again, most of all for messages built by string concatenation.
The fingerprint is now a hash of the normalized message, computed in one pass without building
the normalized text; the text is built only the first time a message is seen. A message that
never repeats no longer takes a slot in the recent-message lookup, so it can't push a template
out. On the benchmark machine, storm detection adds about 35 ns to a templated log call (was
about 110 ns) and about 290 ns to a concatenated one (was about 760 ns).
logctl stormsshows the same fingerprints and messages as before (issue #147). - The rule check costs nothing when no rule applies. Every log line that reaches a handler
went through the
drop/trimrule check, which took a lock shared by every logging thread and recorded the line in a table, even with no rules attached. Now a line from a logger no rule can reach is passed straight through, and lines that a rule does apply to share the remaining bookkeeping across 16 locks instead of one (issue #130).
0.1.0-alpha.3 — 2026-09-28¶
The first suppression features: drop and trim rules, report-only storm detection, a vendor
defaults file, and guided (ask-for-what's-missing) logctl commands.
Evaluation only — not for production. The override store's and vendor defaults file's on-disk
formats may still change between builds without a migration path.
Added¶
logctl storms— lists the log storms the agent has detected: a burst of near-identical events from one logger (1,000 within 10 s by default,-Dlogaperture.storm.*to tune). For each: the logger, exception type and normalized message, when it started, how many events, the current rate, and whether it is still going. The first occurrence is kept in full, stack trace included.--limit Nshows the worst N;--jsonfor scripts. Report-only: nothing is suppressed or delayed.logctl doctoradds a one-line pointer while a storm is ongoing (issue #26;doc/specs/storm-detection.md).logctl add rule drop <logger> …— stop a known-noisy message from being logged without touching its logger's level, e.g.add rule drop com.acme.batch.Worker --message-contains "This happens a lot". Match on the log message (--message-contains,--message-contains-ignore-case), the exception (--throwable,--throwable-message-contains,--any-cause), or both; at least one is required. Only events below--below(defaultERROR) are dropped, and FATAL never is. One full event is let through every 5 minutes (--sample-full <duration>, or--no-sample-full), and a periodic summary line on the JVM's stderr counts what was dropped (issue #72;doc/specs/drop-rule.md).logctl add rule trim <logger> …— keep logging a noisy exception but shorten its stack trace: a matching event below--belowis written with a one-line trace and a marker, or the top--frames Nframes;--collapse-causesalso folds theCaused by:chain. Same matchers asdrop, none required. Text formatters only; JSON/XML handlers are left untouched (issue #34;doc/specs/trim-rule.md).- Rules are managed like overrides — each gets a short id (
r1,r2, …) and the samesession/for <duration>/stickytiers asset logger(defaultfor 4h). A rule reaches the logger's descendants, including ones created later.logctl list rulesshows each rule with its tier, expiry and hit count;logctl reset rule <id>andlogctl reset rulesremove them, andlogctl reset logger Xalso removes the rules attached directly toX. Rules and storm detection need the JUL / JBoss LogManager adapter (WildFly); Logback is not yet supported (issue #71;doc/specs/rule-pipeline-foundation.md). - Vendor defaults file — start the agent with
-javaagent:logaperture-agent.jar=--vendor-defaults=/path/vendor-defaults.yamlto ship baseline logger levels, handler levels, a default-handler list anddrop/trimrules with a product. The file's settings apply from startup and become the baseline:logctl resetreturns to them, not to the application's own logging configuration.logctl listshows them in aVENDORcolumn;logctl status,envanddoctorreport whether the file loaded. A file with errors is rejected as a whole, with every problem listed, and never stops the application from starting;doctorwarns if the JVM's account can write the file. Vendor rules havevendor:ids and reset the way loggers do (seelogctl alter rulebelow) (issues #60, #61;doc/specs/vendor-defaults.md). logctl reset … --to-native— onreset logger,loggers,handler,handlers,default-handler,ruleandrules: return to the application's own logging configuration, ignoring the vendor defaults until restart; a plainresetputs the vendor default back (issue #94;doc/specs/reset-to-native.md). The layers and terms (native configuration, vendor defaults, baseline, override, effective level) are now defined in one place, spec §6.6.logctl alter rule <id>— change a rule in place, giving only what changes:alter rule r3 --below WARN,alter rule r3 --message-contains green. It keeps its id; the--no-options (--no-throwable,--no-message-contains, …) remove an optional part, and a tier (alter rule r3 sticky) changes how long it lasts. The hit count starts over when what the rule matches changes. A vendor rule can be altered too:reset rule vendor:<id>puts the vendor's definition back, andreset rule vendor:<id> --to-nativeswitches it off until restart (issue #96;doc/specs/alter-rule.md).logctl export vendor-defaults [--out <file>] [--force]— write the next vendor defaults file from a running application: the file it started with plus everystickychange made on top of it (set … sticky,alter rule … sticky,add rule … sticky). Anything reset with--to-nativeis left out, andsession/forchanges never reach the file. The agent checks the file loads before handing it over;--outnever overwrites an existing file without--force(issue #62;doc/specs/vendor-defaults-export.md). Restarting the same application with the exported file hands those sticky settings over to it: each is active once, from the file (nor7running next to its exported copy), later edits to the file take effect, and the startup log lists what was handed over. Exported entries carry astateId:line for this (issue #107;doc/specs/export-round-trip.md).logctl list rules --verbose— adds anEXPRESSIONcolumn: each rule's defining options, written asadd rule drop|trimtakes them (e.g.--message-contains "Can't connect" --throwable java.net.ConnectException --below WARN --sample-full 5m), with every default spelled out and shell-safe quoting.list rules --jsonalways includes it asexpression(issue #98;doc/specs/list-rules-verbose.md).- Guided
logctl add rule— on a terminal, leave out anythingadd ruleneeds and it asks.logctl add rule '*.Deployer'lists the matching loggers to pick from (1,3-5,all), then asks drop or trim, what to match, below which level, and how long the rule lasts, with the default shown for each. A bare name typed at the prompt, likeDeployer, finds*.Deployer, the way a log line prints it. Before applying, it prints the equivalent one-line command to copy into a script. Complete commands, scripts without a terminal, and--yesnever prompt (issue #104;doc/specs/guided-add-rule.md). - Pick the JVM from a list — with several LogAperture JVMs running, any
logctlcommand on a terminal lists them numbered and asks which one, then prints the--pidto skip the question next time. The list, and the "several candidates" table scripts still get (exit 4, unchanged), now show when each JVM started and its working directory, so two WildFly servers can be told apart (issue #106;doc/specs/pick-jvm.md). - Pattern targets for
add rule drop|trim—add rule trim '*.Deployer' …picks from the matching loggers on a terminal, or adds one rule to each with--yes;--jsonthen prints an array (issue #104). - Guided
logctl listandlogctl set— on a terminal,logctl listalone asks whether to list loggers, handlers or rules; for loggers, typing a name such asDeployerfinds every matching logger, not only overridden ones.logctl setalone,set logger Deployer,set handlerandset default-handlerask for whatever is missing. Loggers and handlers are picked from a numbered list that shows each one's current level, and the equivalent one-line command is printed before anything changes. Scripts,--yesand--jsonnever prompt (issue #116;doc/specs/guided-commands.md). - Guided
logctl reset— on a terminal,logctl resetalone lists everything currently changed (overridden loggers and handlers, rules, an assigned default-handler membership) to pick from;reset logger,reset handlerorreset rulewith no name lists only that kind. Picking a sticky item asks once whether to reset it too, and with a vendor defaults file it asks whether to go back to the vendor defaults or the application's own configuration, so neither--include-stickynor--to-nativehas to be remembered. Onelogctl reset …line per item is printed before anything changes (issue #116). - Guided
logctl alter rule— on a terminal,logctl alter rulealone lists the rules to pick one, andlogctl alter rule r3with no changes lists the rule's parts with their current values (message, exception, level, sampling or stack frames, lifetime, reason); pick the ones to change, answer only those, and thealter rulecommand naming just those changes is printed before it's applied.-removes an optional part; answers that change nothing apply nothing (issue #116).
Changed¶
logctl set logger '<pattern>' <level>on a terminal lists the matches to pick from (1,3-5,all, Enter to cancel) rather than asking once to apply to all of them, the same asadd rule. A pattern matching a single logger is applied without asking. More than 30 matches still get the all-or-nothing[y/N]. Without a terminal, and with--yes, nothing changes (issue #116).
Fixed¶
-
Piped or scripted
logctlcould be treated as interactive on JDK 22–24, whoseSystem.console()returns a console even when input is redirected:set logger's pattern confirmation could read its answer from the pipe, and an incompleteadd ruleasked questions instead of failing.logctlnow also checksConsole.isTerminal()where the JDK has it (issue #106). -
WildFly could abort at startup (
ModuleNotFoundException: org.jboss.as.standalone) on launches where the JBoss LogManager is on the system class path (jboss.modules.system.pkgsnamingorg.jboss.logmanager) and the agent installs duringpremain. Cause: resolving WildFly's handler names asked JBoss Modules for its boot module loader beforeorg.jboss.modules.Mainhad setmodule.path, which fixed that loader with no module roots. The resolver now waits formodule.path(issues #86, #87).drop/trimrules keep applying from the moment the agent installs, including to other agents' startup logging that reaches the server's handlers. A new-Dlogaperture.handlerInstallDelaySeconds=<n>(default0) can hold back the handler-level installs (trim rendering,top's byte counting, storm detection, the rule pipeline) if a launch ever needs it. Seedoc/spikes/early-handler-install.mdanddoc/specs/wildfly-deferred-handler-install.md.
Known limitations¶
add rule drop|trimattaches in the first logging context; multi-context attachment is #79.trimdoesn't apply to structured (JSON/XML) formatters (#83).- Storm detection can merge two concurrent throw sites that share a fingerprint (#77).
0.1.0-alpha.2 — 2026-09-20¶
Developer handler workflow, a reworked command surface, and WildFly fixes. Evaluation only — not for production. The override store's on-disk format may still change between builds without a migration path.
Changed¶
logctlcommand surface refactor.set,resetandlistare now namespaced by target:logctl set logger <logger> <level>,logctl set handler <name> <level>,logctl reset logger|loggers|handler|handlers,logctl list loggers|handlers. The level-named verbs (debug,trace, ...) are retired.list loggersshows overrides only by default (--show-allfor everything);reset loggers --include-stickyalso clears sticky overrides. Breaking: commands and scripts written for alpha.1 (e.g.logctl debug <logger>) will stop working and must be updated to the new forms.- Pattern targeting is pure selection. Glob patterns select the loggers a command applies to; standing rules are retired.
Added¶
AUTOhandler level —logctl set handler CONSOLE AUTOmakes a handler track the lowest active logger override.DEFAULT_HANDLERS— a deterministic default handler group for handler targeting.- Squelch warning — warns when raising a handler's level would silence an active logger override.
logctl env— read-only environment report for bug reports, including the fully-qualified state file path.- WildFly test-drive walkthrough (
doc/wildfly-test-drive.md).
Fixed¶
- WildFly handler-name resolution on newer WildFly (#39) and the readiness gate that never passed (#64); boot-time resolver noise (#66); an intermittent JBoss LogManager install race.
- Handler override resume resilience across restarts: pending overrides and baseline-key migration (#29).
StateStorebatches removals in reset and expiry sweeps (#17).- The adapter's handler-ref maps are pruned when a handler is detached, so repeated
/subsystem=loggingreconfiguration no longer grows them without bound (#31). WildFlyContainerITruns against any WildFly image (#65).
Known limitations¶
- Still no log suppression; see the alpha.1 "Not yet in this build" list.
0.1.0-alpha.1 — 2026-09-07¶
First tagged build. Evaluation only — not for production. The override store's on-disk format may still change between builds without a migration path.
Added¶
- Runtime log-level control.
logctl set <logger> <level>and the named forms (debug/trace/info/warn/error), acrossjava.util.logging(incl. JBoss LogManager) and Logback, on plainjava -jarand standalone WildFly.--include-childrenfans a change out over a subtree. - Enforced expiry and persistence tiers. A change reverts on its own timer
(
for 30m), lasts the JVM's lifetime (session), or survives a restart and a WildFly redeploy (sticky). The agent re-applies persisted overrides on startup and re-asserts them after a/subsystem=loggingchange. logctl handler <name> <level>— set a handler's own level, up or down, with the same lifetime tokens; the fix when a raised logger still shows nothing because a handler is pinned stricter. A blocking handler on a level raise is named in a warning with the exact command to clear it.ALL_HANDLERS— a stable, restart-safe target that fans a handler-level change out over every real handler in a context.- Real WildFly handler names.
CONSOLE,FILE, and any dedicated handler resolve to their configured names, read in-VM from the running server's own/subsystem=loggingmodel — no socket, no credentials. Degrades toALL_HANDLERS-only where the model can't be read. logctl handlers— the addressable handler catalogue: name, level, sink, target file, and any active override.logctl doctor— read-only diagnosis of common logging-config problems: unbounded file-handler growth, verbosity left on, duplicate output to two persistent handlers, autoflush, and disk headroom vs. current write rate.logctl top— bytes written per logger over the agent's lifetime, worst-first, with a projected daily total and the stack-trace-byte fraction.logctl statusandlogctl reset/reset --all— inspect and undo what LogAperture has changed.- Governance. Every mutation is capability-checked and written to a
hash-chained, tamper-evident audit trail. The agent opens no network
connections;
logctlreaches it over the local attach API, UID-gated by the operating system. --jsonon every read command, for scripting and monitoring checks.- Evaluation bundle.
logaperture-<version>.zip— the agent jar,logctl, and a WildFly install guide, all marked pre-production.
Not yet in this build¶
- Automatic storm collapse or any log suppression, per-rule squelching, budgets, the disk guard, capture profiles.
- The Log4j 2 adapter; Spring Boot, Tomcat, and Quarkus JVM mode at depth.
- Branch protection, Maven Central publishing, a signed release.
Known limitations¶
- A persisted per-handler
stickyoverride, and asticky ALL_HANDLERSoverride, can be lost or mis-reverted across a WildFly restart if handler-name resolution loses the race with override resume (#29). - The adapter's handler-ref maps are not pruned across repeated
/subsystem=loggingreconfiguration (#31).