skip to content

How does Logback find and load its configuration at startup, what is the precedence between logback-test.xml, logback.xml and the logback.configurationFile system property, and how do you debug a configuration that appears to be ignored?

level: middleimportance: must knowfreq 55%

answer

  1. order: system property > logback-test.xml > logback.xml > Configurator > BasicConfigurator
  2. lazy: first LoggerContext touch, not JVM start
  3. Joran + reflection: typos warn, not fail
  4. status listener prints the resolved URL
  5. scan="true" scanPeriod rebuilds the context

basics

~20 s

On first use of a logger, Logback auto-configures: the logback.configurationFile system property wins, else logback-test.xml, else logback.xml on the classpath; with none, it falls back to a console appender at DEBUG. Its own parse errors go to an internal status list, printable via a status listener.

solid answer

~50 s

Configuration happens once, lazily, the first time a `LoggerContext` is initialised — usually the first `getLogger` call, not at class load of your app. The lookup order is: the `logback.configurationFile` system property (a path or classpath resource), then `logback-test.xml`, then `logback.xml`, taking the first match on the classpath; a service-loader `Configurator` may also supply configuration. With nothing found, Logback installs a default console appender at DEBUG. The usual failure is a second `logback.xml` shadowing yours from a dependency jar, or `logback-test.xml` still present on the runtime classpath. Because Logback cannot use itself to report its own problems, parse errors land in an internal **status** list. Turn it on with `<configuration debug="true">`, an `OnConsoleStatusListener`, or the `logback.statusListenerClass` property, and it prints the resolved file URL — which settles the shadowing question immediately. `<configuration scan="true" scanPeriod="30 seconds">` re-reads the file periodically and rebuilds the context, allowing level changes without restart.

code

text · 5 lines
text
java -Dlogback.statusListenerClass=ch.qos.logback.core.status.OnConsoleStatusListener -jar app.jar

... Found resource [logback.xml] at [jar:file:/opt/app/lib/some-library.jar!/logback.xml]
... Resource [logback.xml] occurs multiple times on the classpath
=> a dependency is shadowing the application config

go deeper

for a junior

Know the three-way precedence, that logback-test.xml is for tests, and that nothing configured still gives console output at DEBUG.

for a middle

Explain the lazy initialisation, Joran's reflective binding and its tolerance of typos, and how to enable status output to find the loaded URL.

for a senior

Own the deployment story: force the file via the system property, keep config out of libraries, choose between scanning and JMX/API level changes, and know reconfiguration's cost on appenders.

for a principal

Standardise one configuration mechanism across services — externalised file plus environment substitution, structured encoder by policy — and treat a library shipping its own logback.xml as a build-level defect to enforce.

## Lazy, once, per context Logback does not configure at JVM start. The first call that touches the `LoggerContext` triggers `ContextInitializer`, which runs the lookup and applies whatever it finds. Everything before that point is uninitialised, which is why a property set programmatically in `main()` after some static logger has already been touched has no effect. ## The resolution order 1. The system property `logback.configurationFile` — a filesystem path, a URL, or a classpath resource name. This is the deployment-time override, and the only one that lets a container point at a file outside the artifact. 2. `logback-test.xml` on the classpath. 3. `logback.xml` on the classpath. 4. A `Configurator` implementation discovered through the JDK service loader (`META-INF/services`). 5. Otherwise `BasicConfigurator`: one console appender, root at DEBUG. This default is why an unconfigured app is unexpectedly chatty. Only the **first** matching classpath resource is used, and classpath order across jars is not something you control reliably. Shipping `logback.xml` inside a library is therefore a known anti-pattern: it may win over the application's own file. `logback-test.xml` exists precisely so tests can win without editing the production file — its risk is leaking into a runtime classpath through a badly scoped test dependency. ## Parsing The XML is interpreted by **Joran**, a rule-based configurator that maps element paths to actions and instantiates classes reflectively, setting properties from attributes and child elements via JavaBean setters. Two consequences follow. First, an unknown element or a misspelled property is usually a *warning*, not a hard failure — the surrounding configuration still applies, so a silently wrong appender is entirely possible. Second, any class named in the file must be on the classpath at configuration time, so a missing encoder dependency shows up as a status error rather than an exception in your code. Substitution uses `${name}` resolved against, in order, local `<property>` and `<variable>` definitions, system properties, and environment variables; `${VAR:-default}` supplies a fallback. `<springProfile>`-style conditionals are not native — plain Logback offers `<if>/<then>/<else>` conditional blocks (requiring the Janino library) and `<include>` for composing files. ## Status: how you actually debug it Logback keeps an internal `StatusManager` list because it cannot log its own bootstrap through itself. Nothing prints from it unless an error occurred or you asked. Three ways to ask: - `<configuration debug="true">` — prints status from the point the file is parsed. - `-Dlogback.statusListenerClass=ch.qos.logback.core.status.OnConsoleStatusListener` — prints from *before* the file is located, which is the version you want when the question is "which file did it even load?". - Add `<statusListener class="...OnConsoleStatusListener"/>` as the first child element. The first status line names the resolved URL. If it points into a dependency jar, you have your answer without further guessing. ## Reloading `scan="true"` attaches a `ReconfigureOnChangeTask` that checks the file's modification time every `scanPeriod` (default one minute) and reconfigures the whole context on change. Reconfiguration replaces appenders, so file handles are reopened and any in-flight async queue is drained or dropped depending on the appender's stop semantics — it is not free, and a scan period of a few seconds on a large config is a real cost. If the new file fails to parse, Logback falls back to a safe configuration rather than leaving you with nothing, and says so in status. For runtime level changes without file editing, the `LoggerContext` API (`getLogger(name).setLevel(...)`) or a JMX configurator is the more surgical tool; scanning is the fire-and-forget option.

  • Your logback.xml changes have no effect in production. How do you prove where the configuration came from?
    Start the JVM with -Dlogback.statusListenerClass set to OnConsoleStatusListener, which prints Logback's internal status from before the file is located. The first lines name the resolved resource URL and warn when logback.xml occurs multiple times on the classpath. If the URL points inside a dependency jar, the fix is to exclude or repackage that library, or to force the file explicitly with -Dlogback.configurationFile.
  • Is scan="true" a good idea in production?
    It is convenient for changing levels during an incident without a restart, but reconfiguration rebuilds the whole context: appenders are stopped and recreated, files reopened, and async queues disrupted. A short scanPeriod also means repeated stat calls and a window where a half-written file fails to parse. Prefer a longer period, or drive level changes through the LoggerContext or JMX where you want surgical, auditable control.

saying these in an interview costs you the question

  • Believing configuration is read at JVM start rather than on first logger use
  • Assuming a misspelled property fails loudly — Joran mostly warns
  • Shipping logback.xml inside a shared library and expecting the app's file to win
  • Thinking no config file means no logging; the default is a console appender at DEBUG
  • Treating debug="true" as sufficient to diagnose which file was found — it only prints once the file is already parsed

context