Class LogbackPrecompiler

java.lang.Object
io.micronaut.aot.logback.LogbackPrecompiler

@Internal public final class LogbackPrecompiler extends Object
Compiles an application's logback.xml into a Logback Configurator at build time, so that the application does not parse XML and run Joran on the main thread at every start.

It is the engine the Micronaut Gradle and Maven plugins and Micronaut Runner share: they call precompile(Request) in-process with plain paths and get class files back. It is internal to them, not an API for applications or other build tools, and may change in any release. It fails closed: whenever it cannot prove that the generated code reproduces Joran's result exactly, it generates nothing, says why in LogbackPrecompiler.Result.message(), and Logback reads logback.xml with Joran at startup exactly as it would without this class.

What it generates

Entries under "io/micronaut/aot/logback/generated/": a LogbackConfigurator emitted with the ClassFile API, the JoranFallback class and, unless the class path is closed, the ClassPathGuard class, both copied byte for byte from the front end jar this module carries, and a META-INF/services/ch.qos.logback.classic.spi.Configurator file naming the configurator. logback.xml has to stay in the application for the fallbacks. At runtime the configurator, on every call:

  1. hands over to Logback's own default lookup when -Dlogback.configurationFile is set;
  2. on a second or later call (Micronaut's LoggingSystem.refresh()), configures from the first of these that is set, as Micronaut does without a configurator: an environment variable named logback.configurationFile, the logger.config system property, the LOGGER_CONFIG environment variable, an environment variable named logger.config;
  3. hands over to Logback's default lookup when logback.debug or logback.statusListenerClass asks for Logback's status output, or "micronaut.logback.precompiled" is false;
  4. unless the class path is closed, hands over to Logback's default lookup when a logback-test.xml is visible, or the first visible logback.xml is missing or is not the file that was compiled, which it checks again on every call;
  5. otherwise applies the configuration literally, in Joran's order, and returns DO_NOT_INVOKE_NEXT_IF_ANY.

A location that only Micronaut's own configuration holds, such as a --logger.config program argument, is not applied: Micronaut's refresh calls a registered configurator with the logger context alone.

Build policy

It never loads, initialises or runs application classes. Its front end runs Logback and slf4j-api classes, and only those, taken from the runtime class path it is given and never from the caller's own, in an isolated class loader whose parent is the platform class loader, and instantiates only the ch.qos.logback.* classes the configuration names. That loader is also the thread's context class loader while the front end runs, so a service lookup in it, such as JAXP's for an XML parser, finds the JDK's provider and nothing from the build tool's class path. It never starts an appender and never opens a file or a stream of the application.

When it generates nothing

When the target release is below 25; when logback-classic, logback-core or slf4j-api is missing, the application output carries Logback or SLF4J classes of its own, or either Logback version is missing, differs from the other or is outside "1.5.37" to "1.6" (exclusive); when there is no logback.xml or more than one class path entry or application root has one, or any entry has a logback-test.xml, a logback.groovy or a versioned Logback file; when any entry of the class path it is given already registers a Configurator service, which covers the application's own configurator and an application that the logback.xml.to.java optimizer has already optimized; when a packaged application* or bootstrap* file, of the application output or of any entry of the runtime class path, might set logger.config or logback.configurationFile, which the generated code cannot see, or might import configuration with micronaut.config.import, which is not followed; and when the front end finds the file outside its literal subset. Each of these is LogbackPrecompiler.Status.STOOD_DOWN. A generated name that is already taken, which includes the output of an earlier call that is still in the application output, a class path that cannot be read, or an unexpected failure, is LogbackPrecompiler.Status.FAILED.

When it warns

One case still generates, as LogbackPrecompiler.Status.GENERATED, but adds a line to LogbackPrecompiler.Result.warnings(): a packaged application* or bootstrap* file, of the application output or of any entry of the runtime class path, that may enable Micronaut's distributed configuration client (micronaut.config-client.enabled set to true, or to any placeholder, which the run time may resolve to true). A logger.config or logback.configurationFile that distributed configuration supplies is not applied while the generated configurator is registered, and the build cannot know whether the configuration server supplies one. A client that is enabled in another way is not seen: by an environment variable, a system property or properties passed to ApplicationContext.builder(); by a file that is not on the class path given here, or that the scan does not read, such as a bootstrap file of another name that the micronaut.bootstrap.name system property selects; or by a value that a packaged Groovy file computes, such as from System.getenv. A stand-down or a failure has no warnings.

Since:
3.2.0
  • Method Details

    • precompile

      public static LogbackPrecompiler.Result precompile(LogbackPrecompiler.Request request)
      Precompiles the first logback.xml on a class path, or reports why it does not.

      It never throws for anything about the inputs: a missing class path entry, an unreadable file and any failure of the front end or of the emitter, Errors included, are a LogbackPrecompiler.Result that generated nothing. So a build that calls it never fails because of Logback. Only a VirtualMachineError propagates.

      Before it returns, the isolated class loader of the front end is closed and the caller's context class loader is restored. Each call unpacks the front end into a temporary directory of its own, which it deletes, so calls are independent of each other.

      Parameters:
      request - what to compile and for which class path
      Returns:
      what was generated, or why nothing was
    • applicationInputs

      public static List<String> applicationInputs()
      The entries of the application output that can change the result, as Ant-style patterns relative to a root (* matches within a path segment, **/ any number of directories). Everything else in a root is ignored: its name is read and nothing more, its META-INF/MANIFEST.MF included.

      This is a contract a build plugin's input declaration may rely on: a task that declares the application output filtered by these patterns, plus the runtime class path, stays up to date when ordinary classes and resources change. They are logback.xml, logback-test.xml, logback.groovy and the versioned Logback files; the Configurator service file; classes under ch/qos/logback/ and org/slf4j/; the application* and bootstrap* configuration files at the root and under config/; and the generated names.

      Returns:
      the patterns, unmodifiable