vidocq-runtime-core is the Vidocq Runtime runtime engine. It discovers the extensions through ServiceLoader, builds the configuration, boots the Vauban CDI container and drives the extension lifecycle.
Coordinates
Artefact |
|
Java module |
|
Source |
|
Responsibilities
-
Discover every
VidocqExtensionon the module path viajava.util.ServiceLoader(ExtensionLoader), sorted by ascendingpriority(). -
Build the configuration (
VidocqConfigImplover the built-inConfigSourcechain, wrapped by the legacyVidocqConfigurationImpl). -
Run the lifecycle in order (
VidocqBootstrap):-
configure — each extension reads its keys:
ext.configure(configuration). -
beforeStart — create the
VaubanContainerBuilder(bean index fromMETA-INF/vauban-beans.list, no classpath scanning) and let each extension enrich it:ext.beforeStart(builder). The Mansart pool extension opens its JDBC pools here, so that the extensions after it and the container find them open. -
CDI start —
builder.build()boots Vauban. -
onStart — each extension receives the
ExtensionContext(container, bean manager, config): Cassini mounts the REST routes, and the Chappe extension opens the HTTP listeners last.
-
-
Warn about configured
vidocq.*keys nobody consumes (ConfigKeyAudit, based on the core’s own keys andVidocqExtension.configKeys()), as VIDOCQ-CFG-003 — key read by nothing. -
Print the startup banner with the exact Vidocq version and build (Startup banner NEW).
-
Log the startup report, one record that says what has just started, just before
Vidocq - Started in, and report a boot that fails (Startup report NEW). -
Give the extensions a read-only view of that report,
ExtensionContext.startupReport()NEW, which the dev console shows while the application runs. -
Print readable console logs when the application has not configured logging (Console logging NEW).
-
Manage the shutdown cycle —
onStop()runs in reverse priority order, virtual threads drain cleanly. -
Install the Vauban application layer when the launch vehicle requires it (
VidocqAppLayer,Vidocq.run), including thevidocq:devhot-reload loop (VidocqDevReloadLoop).
Entry point
package io.example;
public class App {
public static void main(String[] args) {
io.vidocq.runtime.core.Vidocq.main(args);
}
}
Vidocq.main is a simple facade over VidocqBootstrap. User code does not need a @SpringBootApplication nor an application class — extensions and beans are discovered from the module path (ServiceLoader + the compile-time bean index). For a zero-config IDE launch, annotate a trampoline main with @VidocqMain and call Vidocq.run(App.class, args) — the application is then re-loaded inside the Vauban application layer.
Which main methods are accepted NEW
Vidocq chooses the application’s main itself whenever the application is handed over as a path rather than as a module to launch: mvn vidocq:run, vidocq:dev with -Dvidocq.dev.hotReload=false, and the packaged launcher of an application that has no @VidocqMain trampoline. All three pass -Dvidocq.app.main, and the runtime reaches that main by reflection, inside the application layer. (With a trampoline the script launches --module app/acme.App and the JVM launcher makes the choice; vidocq:dev’s hot-reload loop, the default, ignores `vidocq.app.main on purpose and reboots the layer instead.)
The choice accepts the four shapes the Java SE launcher accepts since Java 25 (JEP 512), and, like the launcher, never requires public:
static void main(String[] args) { } // and public, protected or package-private
static void main() { }
void main(String[] args) { } // an instance method: run on a fresh instance
void main() { }
Selection follows the launcher’s own algorithm, and its consequences are worth knowing:
-
main(String[])is searched beforemain(), and each search walks the class, then its superclasses, then the interfaces it implements. Adefault void main()on an implemented interface therefore starts the application; astaticmain declared in an interface never does. -
A main declared in the class ends the search whether or not it can be used. A
private main(String[])that hides a perfectly good inherited one sends the launch tomain()— which is whatjavadoes too. -
An instance main is run on an instance built with the class' no-argument constructor, which must not be private.
-
A method named
mainthat isprivateor that returns something other thanvoidis not a main method. The failure names it, and the class that declares it, rather than reporting that nothing was found.
This matters in an IDE on JDK 25: the inspection that flags the public of a main as an unnecessary modifier is right, and following it no longer breaks the launch. For an application module the layer defines, the controller exports and opens the package of the class named by vidocq.app.main and the package of the class that declares the selected method — two different packages, in two different modules, as soon as the main is inherited.
Lifecycle hooks
An extension subscribes to phases by overriding the VidocqExtension methods:
public class MyExtension implements VidocqExtension {
@Override
public String name() { return "my-extension"; }
@Override
public void onStart(ExtensionContext ctx) {
// the container is up — resolve beans, mount handlers
}
@Override
public void onStop() {
// reverse-order shutdown — release resources
}
}
All hooks have empty default implementations; priority() (default 1000, lower runs first) orders the extensions within every phase.
Threading
Boot itself is intentionally sequential on a single thread (easier diagnosis, readable stack traces). The virtual threads appear after onStart:
Startup banner NEW
Vidocq tells which Vidocq is starting, once per JVM, right after the configuration is loaded and before Vidocq - Configuration phase. Two 0.4.0-SNAPSHOT builds are told apart by their commit, so the version is in the output before anything later can fail.
When someone is watching (a terminal, a dev launch), the art and two lines go to standard output:
vidocq-runtime-cassini-rest-example in a terminal, launched with -Dvidocq.profile=dev__ ___ _
\ \ / (_) __| | ___ ___ __ _
\ \ / /| |/ _` |/ _ \ / __/ _` |
\ V / | | (_| | (_) | (_| (_| |
\_/ |_|\__,_|\___/ \___\__, |
|_|
Vidocq 0.4.0-SNAPSHOT (9beafc47, built 2026-09-17T16:36:42Z)
Java 25+36-LTS | dev | vidocq-runtime-cassini-rest-example 0.4.0-SNAPSHOT
Everywhere else (a container, CI, a pipe, > file, tests) there is no art, only one log line:
[INFO ][2026-09-17 18:38:35.246][main ][i.v.r.c.b.StartupBanner#show ] : Vidocq 0.4.0-SNAPSHOT (9beafc47, built 2026-09-17T16:36:42Z) | Java 25+36-LTS (Eclipse Adoptium) | prod (no dev or test signal) | vidocq-runtime-cassini-rest-example 0.4.0-SNAPSHOT
What it shows
The identity line is Vidocq <version>, followed by details only for a snapshot or a build from a modified working tree:
| Build | Identity line |
|---|---|
Clean release |
|
Snapshot |
|
Built from a modified tree |
|
Without a build time |
|
Jar without a build identity file (built with an older |
|
Classes recompiled by an IDE |
|
Classes directory with a version from the last Maven build |
|
Times are UTC, to the second. A directory of classes shows where the classes come from rather than a commit: an IDE recompiles without Maven, so its build identity file and its version may be older than the classes, hence the ?.
The context line is Java <version> (<vendor>) | <mode> (<reason>) | debug <address> | devconsole <address> | <application> <version>:
-
the Java version and vendor of the running JVM;
-
the launch mode —
dev,testorprod— with, in parentheses, the signal it was read from:dev (IntelliJ agent),prod (no dev or test signal); -
the debugger, when the JVM runs with a JDWP agent;
-
the dev console NEW, when it is on the module path and on for this launch:
devconsole :8888for a loopback host,devconsole <host>:<port>otherwise, nothing for port0. It is the configured address, a promise made before the bind; the console’s ownVidocq dev console: http://…/record, logged once it listens, is the one to trust; -
the application: the module of the class that calls
Vidocq.run, else the module of-Dvidocq.app.main, else the module launched withjava -m. It is named by theartifactIdof itspom.propertieswhen packaged, by its module name otherwise.
Both lines stay within 80 columns. For the context line, what is given up goes in this order: the JVM vendor, then the segments of a module name are abbreviated (i.v.t.l.mcptimeserver), then the name is cut with … — always before the application version — then the dev console NEW, and the name comes back whole if it then fits, then the debugger, then the reason of the launch mode, and with it the whole segment when the mode is a prod that no signal proves; the whole line is cut last. The vendor goes first because the line is read to know where the process runs and how to attach to it: 19 columns of (Eclipse Adoptium) never cost the address of a debugger, nor the mode and the reason it was read from. A long directory path drops its last Maven build label before being cut. The single INFO line is never cut: it carries the mode, its reason, the debugger and the dev console whatever their length.
The Vidocq bricks have their own versions. When a brick (Vauban, Chappe, Cassini, …) has another version than the runtime, was built from a modified tree, or is a snapshot without a commit, one more INFO record lists them:
Vidocq bricks: chappe 0.4.0-SNAPSHOT (2d8ec095+dirty, built 2026-09-16T08:48:26Z), vauban 0.3.0
Launch mode NEW
Vidocq says which of dev, test and prod it is running in, and in parentheses the signal it read it from — the claim and its evidence, never one without the other. It is an observation, not a switch: the container, the configuration and the extensions' lifecycle are the same in every mode. It decides what is printed — the art of the banner, and the default level of the startup report — so that a log or a screenshot says what the launch was. An extension reads it with ExtensionContext.launchMode() NEW to offer what only makes sense in development (Reading the launch mode).
The first signal found wins:
| Mode | Signal | Reason shown |
|---|---|---|
as set |
|
|
as set |
|
|
|
|
|
|
a JUnit, TestNG, Surefire or Arquillian frame on the booting thread, otherwise a JUnit Platform launcher on the class path |
|
|
an application archive that is a directory of a build tree: one ending in |
|
|
IntelliJ’s Run console, recognised by the |
|
|
nothing above matched |
|
A profile that is no mode (staging) is not a signal: the detection goes on without it. vidocq.launch.mode=auto NEW, the default, lets the detection decide too; any other value that names no mode is reported as VIDOCQ-CFG-001 — invalid value and does the same.
prod is the only mode that rests on an absence, and it never shows without saying so: when the context line is too narrow for prod (no dev or test signal), the whole segment goes rather than leave a bare prod claiming more than is known.
Reading the archives is bounded: at most four of them, their shape is a string comparison, and only one of them has its parents probed for a build file — a handful of stat calls, once per boot: every reload of the dev loop reads its launch again. Nothing in the resolution throws: a probe that fails is a signal that was not found, and the worst case is the prod above.
Debugger NEW
When the JVM runs with a JDWP agent (-agentlib:jdwp=…, or the older -Xrunjdwp:…), the context line also carries what a debugger has to attach to, in every mode and not only in dev:
Java 25+36-LTS | dev (IntelliJ agent) | debug *:5005 | mcp-time-server 0.1.0-SNAPSHOT
The address is repeated exactly as the agent was given it (address=:5005 shows :5005, address=127.0.0.1:5005 shows 127.0.0.1:5005), and suspend=y is added when the JVM waits for the debugger before running. An agent with no address shows debug alone: the JVM then chooses the port, and only its own Listening for transport dt_socket at address: … line tells which.
The debugger outlives the JVM vendor and the whole application name: it is given up only when even a cut name leaves no room — a long reason and a long address do not both fit behind a long module name in 80 columns. Whenever what was printed does not carry the address, one more INFO record repeats it, so a launch with a debugger never hides how to attach to it:
Vidocq debugger: attach to 127.0.0.1:18098
A custom banner that shows no context line gets that record too; an agent with no address says so instead of an address. The single INFO line of the log output always carries the segment itself, so it never needs the record.
When the art appears
vidocq.banner.mode |
Output |
|---|---|
|
One INFO line for an embedded deployment (Arquillian, the TCK: |
|
The art and the two lines on standard output, in one write. |
|
The art and the two lines in one INFO record, which starts with a line break so that the art stays in column 0. Never coloured. |
|
Nothing. |
A terminal means that System.console() exists and isTerminal() is true: redirecting standard input or standard output turns the art off. A dev launch is a mode of dev read from a deliberate signal — vidocq.launch.mode, a profile, the dev reload loop, or IntelliJ’s Run console, which is not a terminal and is what shows the art on an IDE Run. A dev read from the shape of a build tree does not show the art without a terminal: a piped CI job runs out of target/classes too.
The banner is printed once per JVM: a hot reload or a second deployment in the same JVM prints nothing more. It never fails the boot. Its logger, io.vidocq.runtime.core.banner.StartupBanner, can be silenced through the logging configuration.
Code that embeds Vidocq and owns its standard output (a tool whose output is data, a CLI plugin) sets the mode itself; this wins over the configuration:
VidocqBootstrap.create()
.banner(BannerMode.OFF)
.configure()
.start();
Configuration
| Key | Default | Description |
|---|---|---|
|
|
|
|
(empty) |
A custom banner: |
|
|
|
|
|
Colours, shared with the console logs (Console logging NEW). |
Like every key, they can be set with -D, an environment variable (VIDOCQ_BANNER_MODE), vidocq.properties or a profile (%dev.vidocq.banner.mode). There is no vidocq.banner.enabled: the startup audit reports it with the known keys of the namespace.
Colours
On a terminal or in IntelliJ’s Run console, the art is cyan, Vidocq <version> bold, the build details yellow and the context line faint. The policy is the one of the console logs: a non-empty NO_COLOR environment variable always wins, vidocq.console.color=always or never forces the choice, and auto needs TERM other than dumb and IntelliJ’s Run console or a terminal on an operating system other than Windows. Output piped to a file or a container log never carries escape sequences.
Custom banner
A vidocq-banner.txt file at the root of the application’s resources replaces the art, with no configuration; vidocq.banner.location points to another file. The file is read as UTF-8 through the context class loader, as vidocq.properties is. Its lines are printed as they are, after placeholder substitution:
| Placeholder | Value |
|---|---|
|
|
|
|
|
the whole identity line |
|
|
|
the application’s name and version |
|
|
|
|
|
the escape sequence, or empty when colours are off |
|
the configured value |
|
|
An unknown placeholder becomes empty, and a ${ without its closing brace on the same line is printed as is. Vidocq appends its identity and context lines after the file, so the version survives any custom banner, unless the file places ${vidocq.identity} itself.
src/main/resources/vidocq-banner.txt${ansi.cyan} T O D O S E R V I C E${ansi.reset}
port ${vidocq.chappe.listener.default.port:8080} - ${app.name} ${app.version}
Build identity NEW
Every jar built with io.vidocq:vidocq-parent (Vidocq, Vauban, Cassini, Chappe, and the applications created by vidocq create, through vidocq-runtime-parent) carries a build identity file, META-INF/vidocq/build-info/<artifactId>.properties, written by git-commit-id-maven-plugin at the initialize phase:
#Generated by Git-Commit-Id-Plugin
git.build.time=2026-09-17T14\:02\:11Z
git.build.version=0.4.0-SNAPSHOT
git.commit.id.abbrev=9beafc47
git.commit.time=2026-09-15T14\:08\:34Z
git.dirty=false
-
git.dirtyistruewhen tracked files had uncommitted changes; untracked files do not count. -
git.build.timeis the time of the build that wrote the file. On its own, the plugin leaves an existing file alone when only that time would change, so a build withoutmvn cleankeeps the time of the first build of the same commit and dirty state.vidocq-runtime-parent(the Vidocq runtime modules and the applications created byvidocq create) deletes the file at thevalidatephase, so every build writes its own time; for a brick built directly fromvidocq-parent,builtcan be that first build. -
Releases have no
git.build.timeand stay byte-for-byte reproducible (project.build.outputTimestampis the commit time in thereleaseprofile). -
The file name carries the artifactId: on the class path, a shared name would make every jar read the first one found. The banner reads each file inside the jar of its own module.
-
-Dmaven.gitcommitid.skip=truebuilds without it; the banner then falls back to the jar date.
To find the commit a snapshot was built from, read the file in the jar, then look the commit up in the repository of that brick:
unzip -p ~/.m2/repository/io/vidocq/runtime/vidocq-runtime-core/0.4.0-SNAPSHOT/vidocq-runtime-core-0.4.0-SNAPSHOT.jar \
META-INF/vidocq/build-info/vidocq-runtime-core.properties
git log -1 9beafc47
Startup report NEW
At the end of every boot, just before Vidocq - Started in, one INFO record says what has just started: the launch, the application layer, the configuration sources, the extensions, and a section for every library that contributes one. It is written for the moment something does not work — a key that nothing reads, an extension that did not start, a module that is not where it was expected — and for the bug report that has to say what ran. Vidocq - Started in stays the last line of a boot.
A packaged application gets the summary, one line per section:
vidocq-runtime-cassini-rest-example jlink image (prod)[INFO ][2026-09-18 14:18:32.141][main ][i.v.r.c.r.StartupRecorder#log ] : Vidocq startup report
launch prod (auto: no dev or test signal) | report summary | details -Dvidocq.startup.report=detailed
layer 2 modules (boot-layer detection from io.vidocq.runtime.examples.rest)
extensions chappe-engine, rest-cassini, chappe-mount-config, chappe-bootstrap
anomalies none
[INFO ][2026-09-18 14:18:32.142][main ][i.v.r.c.VidocqBootstrap#start ] : Vidocq - Started in 112.956 ms (process running for 539 ms)
The first boot of a dev launch gets the detailed report:
target/classes (dev), its library jar taken from the local repository[INFO ][2026-09-18 14:18:49.712][main ][i.v.r.c.r.StartupRecorder#log ] : Vidocq startup report
launch dev (auto: target/classes with a pom.xml above) | report detailed |
override -Dvidocq.launch.mode=prod
vidocq 0.4.0-SNAPSHOT on Java 25.0.3+9-LTS
phases configure 20 ms | weaving 2 ms | scan 4 ms | beforeStart 0 ms | build 55 ms |
extensions 24 ms | audit 0 ms | report 2 ms
layer 2 modules (boot-layer detection from io.vidocq.runtime.examples.rest)
io.vidocq.runtime.examples.rest target/classes directory
io.vidocq.runtime.examples.extlib ~/.m2/.../vidocq-runtime-external-rest-lib-0.4.0-SNAPSHOT.jar jar
weaving none
configuration
sources SystemProperties 400, Environment 300, ExternalFile(absent) 250, PropertiesFile 100
audited vidocq.banner.*, vidocq.chappe.*, vidocq.console.*, vidocq.http.*, vidocq.launch.*,
vidocq.log.*, vidocq.startup.*
extensions
100 chappe-engine onStart 0 ms io.vidocq.runtime.extensions.essentials.chappe
500 rest-cassini onStart 1 ms io.vidocq.runtime.extensions.jakartaee.core.cassini
7000 chappe-mount-config onStart 16 ms io.vidocq.runtime.extensions.essentials.chappe
10000 chappe-bootstrap onStart 7 ms io.vidocq.runtime.extensions.essentials.chappe vidocq.chappe.*, vidocq.http.*
anomalies none
[INFO ][2026-09-18 14:18:49.713][main ][i.v.r.c.VidocqBootstrap#start ] : Vidocq - Started in 113.185 ms (process running for 210 ms)
The whole report is one log record: one timestamp, printed in one piece, and nothing else can come between its lines. It is plain ASCII, indented with spaces and never coloured, so it reads the same in a terminal, a log file and a Windows console, through Vidocq’s console output or the JDK’s own formatter.
What it shows
| Section | summary |
detailed |
|---|---|---|
|
The launch mode and the signal it was read from, the report level, and how to see more: |
The same, with how to force the other mode instead: |
|
— |
The Vidocq version and the Java version, taken from the banner when it printed them. |
|
— ( |
The time each phase took: |
|
How many modules the application layer has and how they were found: |
The same count and origin; for |
|
— |
The configuration sources in lookup order, each with its ordinal; the provider whose sources replaced the native ones, if any; the namespaces whose keys are audited (VIDOCQ-CFG-003 — key read by nothing). Never a value. |
|
The extensions, in the order they started. |
One line per extension: its priority, its name, how long its |
a contributed section |
Its summary line, when it has one. |
Its title and how long it took to write ( |
|
|
One line per anomaly: its code and the first sentence of its message; after 50, |
The contributed sections follow the sections of the core: first those of the extensions that contribute one, in the order they started, then those of the other libraries. The report is made of what the boot already holds — the layer’s resolved modules, the loaded extensions, the configuration sources — and its own time is the report phase of the detailed report.
Report level
vidocq.startup.report chooses how much is printed: auto (the default), off, summary or detailed. An explicit level is honoured on every boot. auto follows the launch mode:
| Launch | auto gives |
|---|---|
|
|
|
|
|
|
|
|
An embedded deployment ( |
|
A dev launch shows everything once, then one line per section on every reload. An embedded deployment boots hundreds of times in one JVM, so it prints no report unless vidocq.startup.report asks for one — the banner is reduced to one line there too.
While the dev console is on NEW, the contributors are given the detailed level whatever the report prints, so that the console has every row even on a reload that logs the summary; what the log prints still follows the level above.
off only removes the report. The contributors are still called, and every anomaly is still logged as its own warning. A value that is none of the four is reported as VIDOCQ-CFG-001 — invalid value and read as auto. Like every key, it can be set with -D, an environment variable (VIDOCQ_STARTUP_REPORT) or vidocq.properties.
Anomalies
An anomaly is something wrong that does not stop the boot. Each one is its own WARNING record on the logger io.vidocq.startup.anomaly, logged the moment it is detected, its code first:
[WARN ][2026-09-18 14:19:46.775][main ][i.v.r.c.r.StartupAnomalies#warn ] : [VIDOCQ-CFG-003] Configuration key 'vidocq.startup.reprot' is read by nothing and has no effect. Known keys in this namespace: vidocq.startup.report
It is logged at every report level, off included, and before the report, so a boot that fails later still shows it, and a log pipeline can alert on its code. It is always one line: a line break or another control character in what it quotes, a configured value or a key name, becomes ?, so no anomaly can start a line that looks like another record. It is never cut. The report only recalls the codes, in its anomalies section. A library can report anomalies of its own, under its own codes, from its section of the report (Contributing to the startup report).
The core raises these codes.
VIDOCQ-CFG-001 — invalid value
vidocq.startup.report or vidocq.launch.mode has a value that it does not accept. The key is read as auto, and the boot goes on.
[VIDOCQ-CFG-001] Invalid value 'detaild' for vidocq.startup.report (auto, off, summary, detailed): using auto
It is logged while the configuration is read, before the banner. Fix the value; the message lists the accepted ones.
VIDOCQ-CFG-002 — audit failed
The audit of the configured keys could not run: a configuration source could not list its keys, or an extension’s configKeys() threw. Keys that nothing reads are then not reported on this boot, and the boot goes on.
[VIDOCQ-CFG-002] Configuration key audit failed: java.lang.IllegalStateException: ...; keys that nothing reads are not reported
VIDOCQ-CFG-003 — key read by nothing
A configured vidocq.* key, under a namespace that the core or a loaded extension claims, is read by nothing: the application keeps the default without a word, and the mistake stays invisible whenever the configured value happens to be the default. The message lists the keys known in that namespace.
[VIDOCQ-CFG-003] Configuration key 'vidocq.startup.reprot' is read by nothing and has no effect. Known keys in this namespace: vidocq.startup.report
The core claims vidocq.log., vidocq.console., vidocq.banner., vidocq.launch. and vidocq.startup.; every extension claims the namespaces of its configKeys() (Configuration keys audit). The detailed report lists the audited namespaces. Build-time keys such as vidocq.mainClass and the vidocq.dev.* keys are never reported.
VAUBAN-009 — load-time weaving failed
The beans of the bean index need load-time weaving, as those of an IDE build do, and it could not be prepared: vauban-weaver is not on the module path, META-INF/vauban-beans.list cannot be read, or the JVM refused the weaving agent. The message is Vauban’s reason; the layer section of the detailed report shows weaving failed (VAUBAN-009), and the beans that needed weaving may then fail the deployment.
[VAUBAN-009] Load-time weaving could not be installed (...). Either build with Maven/Gradle (build-time weaving), run with -javaagent:vauban-weaver.jar=<plan>, or add the (ProxyLink) constructors manually.
Build with Maven or Gradle, which weave at compile time, or launch through a @VidocqMain trampoline, whose application layer weaves without an agent.
VIDOCQ-RPT-001 — contributor failed
A startup report contributor could not be loaded or created, has no id (null or blank), threw from id(), title() or order(), or threw a RuntimeException or a LinkageError from contribute. The record carries the stack trace. Its section is left out, the anomalies it already reported stay, and the boot goes on. Any other Error is not caught.
[VIDOCQ-RPT-001] Startup report contributor 'translate' (com.example.translate.report.TranslateReport) failed; its section is skipped
VIDOCQ-RPT-002 — duplicate section id
Two contributors of different classes use the same id: the second one is skipped. So is a contributor whose id names a section of the core (launch, vidocq, phases, layer, configuration, extensions or anomalies), or one of the dev console's own panels, startup, config, cdi, logs, tests and jvm NEW. The same class found twice is not an anomaly: see Layer twins.
[VIDOCQ-RPT-002] Contributors com.example.translate.report.TranslateReport and com.acme.i18n.TranslateReport both use id 'translate'; com.acme.i18n.TranslateReport is skipped
When the boot fails
A boot that fails, in configure() or in start(), logs one WARNING record on io.vidocq.startup before the exception goes on, at every report level: what was running, the time since the bootstrap was created, the exception class and the codes of the anomalies already logged. In a dev launch, the partial report follows in the same record, unless the report level is off:
[WARN ][2026-09-18 14:19:58.822][main ][i.v.r.c.r.StartupRecorder#logFailure ] : Vidocq startup failed in onStart chappe-bootstrap after 1129 ms (io.vidocq.chappe.api.ChappeException$ServerException); anomalies already logged: none
Vidocq startup report (partial)
launch dev (auto: target/classes with a pom.xml above) | report detailed |
override -Dvidocq.launch.mode=prod
vidocq 0.4.0-SNAPSHOT on Java 25.0.3+9-LTS
phases configure 19 ms | weaving 2 ms | scan 4 ms | beforeStart 0 ms | build 54 ms
...
extensions
100 chappe-engine onStart 0 ms io.vidocq.runtime.extensions.essentials.chappe
500 rest-cassini onStart 0 ms io.vidocq.runtime.extensions.jakartaee.core.cassini
7000 chappe-mount-config onStart 15 ms io.vidocq.runtime.extensions.essentials.chappe
10000 chappe-bootstrap onStart failed io.vidocq.runtime.extensions.essentials.chappe vidocq.chappe.*, vidocq.http.*
anomalies none
Exception in thread "main" io.vidocq.chappe.api.ChappeException$ServerException: Failed to bind to 127.0.0.1:18082
at io.vidocq.chappe.core@0.4.0-SNAPSHOT/io.vidocq.chappe.core.ChappeServer.start(ChappeServer.java:102)
...
Caused by: java.net.BindException: Address already in use
What was running is a phase — configure, weaving, scan, beforeStart, build, extensions, audit, report — or, more precisely, the extension or the contributor it was calling: configure rest-cassini, beforeStart rest-cassini, onStart chappe-bootstrap, contributor mcp. The exception is the one that was thrown, untouched: not wrapped, nothing added to it, with its own stack trace. The contributors are not called when the boot fails before the report, since the container and the routes may be incomplete: a partial report has the sections of the core only.
The report of a boot that went well never fails it: if the report cannot be produced, one WARNING, Startup report skipped: …, says why, and the boot goes on.
What is never printed
The report goes to logs that are shared, pasted into issues and kept. It prints names and counts, never what an application keeps to itself:
-
No configuration value. The
configurationsection names the sources and their ordinals, never what they hold. The only configured values the core’s sections show are the launch mode, whenvidocq.launch.modeor avidocq.profilenamed after a mode set it, the report level, andvidocq.app.path, whose archives the summary prints as file names. A contributor shows a configured value that may carry a secret, such as a JDBC URL or a user name, in adevlaunch only, without its credentials NEW; outside it, it says what the value is, such as the database kind (the rule). -
No system property, environment variable or JVM argument as it is — a
-Dcan carry a password. Only what is derived from them:IntelliJ agent,target/classes with a pom.xml above. -
No secret. A contributor reports a secret as
configuredornot configured; the API has no way to pass its value. -
No directory in the summary, the default of a packaged application: the core’s sections print file names only (
vidocq.app.path=…/app). The detailed report prints a path relative to the working directory, or under~for the home directory, or else absolute; a path of more than four names keeps its first two and its last one:~/.m2/…/langchain4j-cdi-mcp-server-1.4.0-SNAPSHOT.jar. -
No forged line. Every value is cleaned before it is printed: control characters — line breaks,
ESC, and the invisible formatting characters such as bidirectional overrides — become?, so no value can start a line that looks like another record. A value longer than 200 characters is cut and ends with…; a list, a table or the anomalies of the detailed report stop after 50 items with… and N more. The anomaly records are cleaned the same way, and never cut.
Loggers
| Logger | Records |
|---|---|
|
The report, one INFO record; the failure of a boot, one WARNING record. |
|
One WARNING record per anomaly, |
To hide the report, prefer vidocq.startup.report=off, which keeps everything else. Through the logging configuration, io.vidocq.startup.level=WARNING hides the report and keeps the warnings; OFF would silence the failure record and, since io.vidocq.startup.anomaly inherits the level of io.vidocq.startup, the anomalies as well.
Configuration
| Key | Default | Description |
|---|---|---|
|
|
|
|
|
The launch mode that |
Console logging NEW
When the application has not configured logging, Vidocq prints one aligned line per record on standard output. The JDK default would print two lines per record on standard error, which an IDE shows in red. Vidocq keeps logging through System.Logger and java.util.logging: only the console handler of the root logger changes.
vidocq-runtime-cassini-rest-example boot (messages shortened)[INFO ][2026-09-17 18:10:10.880][main ][i.v.r.c.VidocqAppLayer#installLayer ] : Application layer ready: modules [...]
[INFO ][2026-09-17 18:10:10.885][main ][i.v.r.c.VidocqBootstrap#configure ] : Vidocq - Configuration phase
[INFO ][2026-09-17 18:10:10.890][main ][i.v.r.c.ExtensionLoader#load ] : Discovered 4 extension(s):
[INFO ][2026-09-17 18:10:10.937][main ][CassiniScopeExtension#addPathDefaultScope ] : @Path class ... defaulting to @RequestScoped
[INFO ][2026-09-17 18:10:10.987][main ][i.v.r.e.e.c.ChappeServerBootstrap#onStart ] : Chappe listener 'default' started on http://localhost:8080/
[INFO ][2026-09-17 18:10:10.989][main ][i.v.r.c.VidocqBootstrap#start ] : Vidocq - Started in 102.615 ms (process running for 183 ms)
Format
[LEVEL][timestamp][thread][source] : message, one line per record:
| Column | Content |
|---|---|
Level |
|
Timestamp |
|
Thread |
The thread that logged, padded to 15 columns. A longer name keeps its end after a |
Source |
|
Message |
Formatted with its |
Every level goes to standard output, flushed after each record: the logs stay in one stream, in order, with what the application prints.
When Vidocq steps back
Vidocq replaces the JDK’s default console handler first thing in Vidocq.main, in both Vidocq.run overloads and in VidocqBootstrap.create(), and only when the application has chosen nothing. The JDK default stays untouched when any of these holds:
-
java.util.logging.config.file,java.util.logging.config.class,java.util.logging.managerorjava.util.logging.SimpleFormatter.formatis set; -
System.LoggerFinderis not the JDK’sjava.util.loggingone, for example with an SLF4J or Log4jSystem.Loggerprovider on the module path; -
the root logger’s handlers are not exactly one
ConsoleHandlerwith aSimpleFormatter, for example afterSLF4JBridgeHandler.install()orLogManager.readConfiguration(…); -
vidocq.log.console=jdk.
An application that calls LogManager.readConfiguration after the boot still replaces Vidocq’s handler.
Configuration
| Key | Default | Description |
|---|---|---|
|
|
|
|
|
Colours of the level: |
The first install happens before the configuration is loaded, so only -D system properties and environment variables (VIDOCQ_LOG_CONSOLE, VIDOCQ_CONSOLE_COLOR) are visible then. Once vidocq.properties is loaded, both keys apply from the configuration as well: vidocq.log.console=jdk there puts the JDK handler back before any extension is configured. An unknown value logs a warning and falls back to auto.
Colours
Only the level is coloured: ERROR bold red, WARN yellow, INFO green, CONF cyan, DEBUG and TRACE faint.
-
A non-empty
NO_COLORenvironment variable always wins: no colour, even withalways. -
alwayscolours the level wherever the output goes;nevernever does. -
autoneedsTERMother thandumb, and either IntelliJ’s Run console or a terminal on an operating system other than Windows. IntelliJ’s Run console is not a terminal but decodes ANSI escapes; Vidocq recognises it by the-javaagent:…idea_rt.jarargument IntelliJ adds to the JVM. A terminal meansSystem.console()exists andisTerminal()is true, so output piped to a file or a container log stays plain.
Keeping full control
Vidocq never overrides a logging setup the application chose. To keep the JDK output or to use your own:
-
logging.properties— pass-Djava.util.logging.config.file=conf/logging.properties, or load it withLogManager.readConfiguration(…). -
SLF4J, Logback, Log4j — put a
System.Loggerprovider on the module path (slf4j-jdk-platform-logging,log4j-jpl), or routejava.util.loggingwithSLF4JBridgeHandler.removeHandlersForRootLogger()thenSLF4JBridgeHandler.install(). -
The JDK default, unchanged — set
vidocq.log.console=jdk(-Dvidocq.log.console=jdk,VIDOCQ_LOG_CONSOLE=jdkorvidocq.properties).