Add a Spring Boot example and integration code.

Change-Id: Ia11dea607c74d9b4cc9a698e9ec92e930bd03f37
diff --git a/README.adoc b/README.adoc
index 494f44c..506ba63 100644
--- a/README.adoc
+++ b/README.adoc
@@ -29,51 +29,137 @@
 of both depending on the situation.
 
 
-== Activation
+== Activation (Quarkus)
 
 Add the runtime POM to your dependency list. As long as the JAR is on
 the classpath at both build time and runtime, the log formatter
 automatically registers itself on startup.
 
-
-=== Activation with Maven
+If you are using Maven:
 
 [source,xml]
 ----
-<project>
-  ...
+<dependencies>
 
-  <dependencies>
-    ...
+  <dependency>
+    <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
+    <artifactId>quarkus-googlecloud-jsonlogging</artifactId>
+    <version>4.0.0</version>
+  </dependency>
 
-    <dependency>
-      <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
-      <artifactId>quarkus-googlecloud-jsonlogging</artifactId>
-      <version>4.0.0</version>
-    </dependency>
-
-    ...
-  </dependencies>
-
-  ...
-</project>
+</dependencies>
 ----
 
-
-=== Activation with Gradle
+If you are using Gradle:
 
 [source,groovy]
 ----
 dependencies {
-  ...
-
   implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging:4.0.0")
-
-  ...
 }
 ----
 
 
+== Activation (Other Frameworks)
+
+If you are not using Quarkus, you can still make use of the `-core`
+module and wire it into your application in a custom way.  Read this
+section for hints on how to do so.
+
+
+=== Installation
+
+If you are using Maven:
+
+[source,xml]
+----
+<dependencies>
+
+  <dependency>
+    <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
+    <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId>
+    <version>4.0.0</version>
+  </dependency>
+
+</dependencies>
+----
+
+If you are using Gradle:
+
+[source,groovy]
+----
+dependencies {
+  implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:4.0.0")
+}
+----
+
+
+=== Wiring (Spring Boot)
+
+If you are using Spring Boot, the easiest way to integrate the log
+formatter is by relying on `spring-boot-starter-logging` (which is
+pulled in by `spring-boot-starter`), excluding Logback, and pulling in
+JBoss Log Manager as the back end for SLF4J:
+
+[source,xml]
+----
+
+<dependencies>
+
+  <dependency>
+    <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
+    <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId>
+    <version>4.0.0</version>
+  </dependency>
+
+  <dependency>
+    <groupId>org.jboss.slf4j</groupId>
+    <artifactId>slf4j-jboss-logmanager</artifactId>
+    <version>1.1.0.Final</version>
+  </dependency>
+
+  <dependency>
+    <groupId>org.springframework.boot</groupId>
+    <artifactId>spring-boot-starter</artifactId>
+    <exclusions>
+      <exclusion>
+        <groupId>ch.qos.logback</groupId>
+        <artifactId>logback-classic</artifactId>
+      </exclusion>
+    </exclusions>
+  </dependency>
+
+</dependencies>
+----
+
+Create a text file called
+`META-INF/services/org.jboss.logmanager.EmbeddedConfigurator` in your
+`resources` directory and put the fully qualified name of
+`DefaultEmbeddedConfigurator` into it:
+
+[source]
+----
+eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultEmbeddedConfigurator
+----
+
+To configure `java.util.logging`, which is used by Tomcat by default,
+create an entry in `application.properties` that points to
+`logging.properties`:
+
+[source,properties]
+----
+logging.config = classpath:logging.properties
+----
+
+Create the `logging.properties` file in your `resources` directory and
+name `DefaultConsoleHandler` as a handler:
+
+[source,properties]
+----
+handlers = eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.ConsoleHandler
+----
+
+
 == Usage
 
 Logging unstructured data requires no code changes. All logs are
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java
index 61a2dea..e759ff0 100644
--- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/Formatter.java
@@ -8,7 +8,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.ServiceLoader;
+import java.util.ServiceLoader.Provider;
 import java.util.logging.Level;
+import java.util.stream.Collectors;
 import org.jboss.logmanager.ExtFormatter;
 import org.jboss.logmanager.ExtLogRecord;
 
@@ -34,7 +36,7 @@
   private final List<LabelProvider> labelProviders;
 
   /**
-   * Constructs a {@link Formatter}.
+   * Constructs a {@link Formatter} with custom configuration.
    *
    * <p><strong>Note:</strong> This constructor does not automatically discover providers using the
    * {@link ServiceLoader} mechanism. See {@link #load} for this case use.
@@ -59,21 +61,33 @@
    *
    * @param parameterProviders the {@link StructuredParameterProvider}s to apply to each log entry.
    * @param labelProviders the {@link LabelProvider}s to apply to each log entry.
+   * @return a new formatter.
    */
   public static Formatter load(
       Collection<StructuredParameterProvider> parameterProviders,
       Collection<LabelProvider> labelProviders) {
     parameterProviders = new ArrayList<>(parameterProviders);
-    ServiceLoader.load(StructuredParameterProvider.class, Formatter.class.getClassLoader())
-        .forEach(parameterProviders::add);
+    parameterProviders.addAll(loadStructuredParameterProviders());
 
     labelProviders = new ArrayList<>(labelProviders);
-    ServiceLoader.load(LabelProvider.class, Formatter.class.getClassLoader())
-        .forEach(labelProviders::add);
+    labelProviders.addAll(loadLabelProviders());
 
     return new Formatter(parameterProviders, labelProviders);
   }
 
+  private static List<StructuredParameterProvider> loadStructuredParameterProviders() {
+    return ServiceLoader.load(StructuredParameterProvider.class, Formatter.class.getClassLoader())
+        .stream()
+        .map(Provider::get)
+        .collect(Collectors.toList());
+  }
+
+  private static List<LabelProvider> loadLabelProviders() {
+    return ServiceLoader.load(LabelProvider.class, Formatter.class.getClassLoader()).stream()
+        .map(Provider::get)
+        .collect(Collectors.toList());
+  }
+
   @Override
   public String format(ExtLogRecord logRecord) {
     var message = formatMessageWithStackTrace(logRecord);
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/DefaultConsoleHandler.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/DefaultConsoleHandler.java
new file mode 100644
index 0000000..a1dbde7
--- /dev/null
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/DefaultConsoleHandler.java
@@ -0,0 +1,68 @@
+package eu.mulk.quarkus.googlecloud.jsonlogging.logmanager;
+
+import eu.mulk.quarkus.googlecloud.jsonlogging.Formatter;
+import java.io.InputStream;
+import java.util.Collections;
+import org.jboss.logmanager.handlers.ConsoleHandler;
+
+/**
+ * A {@link ConsoleHandler} preconfigured with {@link Formatter}.
+ *
+ * <p>Useful as a handler for {@link java.util.logging}.
+ *
+ * <p>If you have a {@code logging.properties} file (see {@link
+ * java.util.logging.LogManager#readConfiguration(InputStream)}), you can use this handler by
+ * setting the following properties:
+ *
+ * <pre>{@code
+ * handlers = eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.ConsoleHandler
+ * }</pre>
+ *
+ * <p><strong>Note:</strong> You can use {@code org.slf4j.bridge.SLF4JBridgeHandler} from {@code
+ * org.slf4j:jul-to-slf4j} instead if you also have {@code org.jboss.slf4j:slf4j-jboss-logmanager}
+ * on the class path. In comparison to this class, which relies on the relatively efficient {@link
+ * org.jboss.logmanager.ExtLogRecord#wrap}, routing through SLF4J incurs additional overhead because
+ * of the necessary conversions between SLF4J's log entry structure and {@link
+ * java.util.logging.LogRecord}.
+ *
+ * <h2>Usage with Spring Boot</h2>
+ *
+ * <p>In case you are using Spring Boot, note that in addition to ensuring that {@code
+ * org.springframework.boot.logging.java.JavaLoggingSystem} is the logging system in use (see
+ * below), you need to accompany this with an entry in {@code application.properties} that points to
+ * your {@code logging.properties} file:
+ *
+ * <pre>{@code
+ * logging.config = classpath:logging.properties
+ * }</pre>
+ *
+ * <p>In order to ensure that Spring Boot chooses {@code JavaLoggingSystem} over other
+ * implementations, make sure that no other logging backends are present on the class path. A simple
+ * way of doing this is by relying on {@code spring-boot-starter-logging} while excluding Logback:
+ *
+ * <pre>{@code
+ * <dependency>
+ *   <groupId>org.springframework.boot</groupId>
+ *   <artifactId>spring-boot-starter</artifactId>
+ *   <exclusions>
+ *     <exclusion>
+ *       <groupId>ch.qos.logback</groupId>
+ *       <artifactId>logback-classic</artifactId>
+ *     </exclusion>
+ *   </exclusions>
+ * </dependency>
+ * }</pre>
+ *
+ * <p>You will probably want to include at least {@code org.jboss.slf4j:slf4j-jboss-logmanager} as
+ * well. In addition, {@code org.slf4j:jcl-over-slf4j}, {@code
+ * org.jboss.logmanager:log4j-jboss-logmanager}, and {@code
+ * org.jboss.logmanager:log4j2-jboss-logmanager} may be useful, but are not required.
+ */
+@SuppressWarnings("java:S110")
+public final class DefaultConsoleHandler extends ConsoleHandler {
+
+  /** Constructs console handler with a formatter created by {@link Formatter#load}. */
+  public DefaultConsoleHandler() {
+    super(Formatter.load(Collections.emptyList(), Collections.emptyList()));
+  }
+}
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/DefaultEmbeddedConfigurator.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/DefaultEmbeddedConfigurator.java
new file mode 100644
index 0000000..8d9d4d8
--- /dev/null
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/DefaultEmbeddedConfigurator.java
@@ -0,0 +1,46 @@
+package eu.mulk.quarkus.googlecloud.jsonlogging.logmanager;
+
+import eu.mulk.quarkus.googlecloud.jsonlogging.Formatter;
+import java.util.logging.Handler;
+import org.jboss.logmanager.EmbeddedConfigurator;
+import org.jboss.logmanager.handlers.ConsoleHandler;
+
+/**
+ * A convenient {@link EmbeddedConfigurator} for JBoss Log Manager.
+ *
+ * <p>You can register this class through the {@link java.util.ServiceLoader} mechanism as a
+ * provider of the {@link EmbeddedConfigurator} interface (under the name of {@code
+ * org.jboss.logmanager.EmbeddedConfigurator}) to automatically register a {@link ConsoleHandler}
+ * using {@link Formatter} as the default log output method for the application.
+ */
+public final class DefaultEmbeddedConfigurator implements EmbeddedConfigurator {
+
+  private final Handler[] rootHandlers;
+
+  /**
+   * Constructs a JBoss Log Manager configuration that uses {@link Formatter} and {@link
+   * ConsoleHandler} for log output.
+   */
+  @SuppressWarnings("java:S2095")
+  public DefaultEmbeddedConfigurator() {
+    rootHandlers = new Handler[] {createConsoleHandler()};
+  }
+
+  /**
+   * Creates a {@link ConsoleHandler} that uses {@link Formatter} for formatting.
+   *
+   * @return a preconfigured {@link ConsoleHandler}.
+   */
+  public static ConsoleHandler createConsoleHandler() {
+    return new DefaultConsoleHandler();
+  }
+
+  @Override
+  public Handler[] getHandlersOf(String loggerName) {
+    if (loggerName.isEmpty()) {
+      return rootHandlers;
+    } else {
+      return EmbeddedConfigurator.NO_HANDLERS;
+    }
+  }
+}
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/package-info.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/package-info.java
new file mode 100644
index 0000000..87eb71e
--- /dev/null
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/logmanager/package-info.java
@@ -0,0 +1,18 @@
+/**
+ * Integration with JBoss Log Manager and {@link java.util.logging}.
+ *
+ * <p>Provides classes that can be used to conveniently configure the JBoss Log Manager ({@link
+ * org.jboss.logmanager.LogManager}) as well as {@link java.util.logging} to use the Google Cloud
+ * JSON Logging formatter ({@link eu.mulk.quarkus.googlecloud.jsonlogging.Formatter}).
+ *
+ * <p>{@link eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultEmbeddedConfigurator} can be
+ * set as a provided implementation of {@link org.jboss.logmanager.EmbeddedConfigurator} via the
+ * standard {@link java.util.ServiceLoader} mechanism.
+ *
+ * <p>{@link eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultConsoleHandler} can be used
+ * as the target of the {@code handlers} property key in {@link
+ * java.util.logging.LogManager#readConfiguration(java.io.InputStream)}. This is particularly useful
+ * when used in conjunction with frameworks other than Quarkus (such as Spring Boot). See the class
+ * documentation for details.
+ */
+package eu.mulk.quarkus.googlecloud.jsonlogging.logmanager;
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java
index 3617b8c..e684bfd 100644
--- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/package-info.java
@@ -4,7 +4,7 @@
  *
  * <ul>
  *   <li><a href="#sect-summary">Summary</a>
- *   <li><a href="#sect-activation">Activation</a>
+ *   <li><a href="#sect-installation">Installation</a>
  *   <li><a href="#sect-usage">Usage</a>
  * </ul>
  *
@@ -17,7 +17,7 @@
  * <p>It is possible to log unstructured text, structured data, or a mixture of both depending on
  * the situation.
  *
- * <h2 id="sect-activation">Installation</h2>
+ * <h2 id="sect-installation">Installation</h2>
  *
  * <ul>
  *   <li><a href="#sect-installation-maven">Installation with Maven</a>
diff --git a/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/GoogleCloudLoggingProcessor.java b/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/GoogleCloudLoggingProcessor.java
index 668128f..fe012ee 100644
--- a/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/GoogleCloudLoggingProcessor.java
+++ b/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/GoogleCloudLoggingProcessor.java
@@ -7,18 +7,34 @@
 import io.quarkus.deployment.builditem.FeatureBuildItem;
 import io.quarkus.deployment.builditem.LogConsoleFormatBuildItem;
 
-class GoogleCloudLoggingProcessor {
+/**
+ * Registers {@link eu.mulk.quarkus.googlecloud.jsonlogging.Formatter} as the formatter for the
+ * embedded JBoss Log Manager.
+ */
+public class GoogleCloudLoggingProcessor {
 
   private static final String FEATURE = "googlecloud-jsonlogging";
 
+  /**
+   * Returns the feature name of {@code "googlecloud-jsonlogging"}.
+   *
+   * @return the feature {@code "googlecloud-jsonlogging"}
+   */
   @BuildStep
-  FeatureBuildItem feature() {
+  public FeatureBuildItem feature() {
     return new FeatureBuildItem(FEATURE);
   }
 
+  /**
+   * Constructs a {@link eu.mulk.quarkus.googlecloud.jsonlogging.Formatter} at runtime and returns
+   * it.
+   *
+   * @param recorder the recorder that implements the construction process at runtime.
+   * @return an instance of {@link eu.mulk.quarkus.googlecloud.jsonlogging.Formatter}.
+   */
   @BuildStep
   @Record(ExecutionTime.RUNTIME_INIT)
-  LogConsoleFormatBuildItem setUpFormatter(GoogleCloudJsonLoggingRecorder recorder) {
+  public LogConsoleFormatBuildItem setUpFormatter(GoogleCloudJsonLoggingRecorder recorder) {
     return new LogConsoleFormatBuildItem(recorder.initialize());
   }
 }
diff --git a/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/package-info.java b/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/package-info.java
new file mode 100644
index 0000000..eba03ab
--- /dev/null
+++ b/deployment/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/deployment/package-info.java
@@ -0,0 +1,2 @@
+/** Quarkus deployment integration for the Google Cloud JSON Logging extension. */
+package eu.mulk.quarkus.googlecloud.jsonlogging.deployment;
diff --git a/example/pom.xml b/examples/quarkus/pom.xml
similarity index 88%
rename from example/pom.xml
rename to examples/quarkus/pom.xml
index 546a47c..a26f782 100644
--- a/example/pom.xml
+++ b/examples/quarkus/pom.xml
@@ -10,8 +10,8 @@
     <version>3.1.4-SNAPSHOT</version>
   </parent>
 
-  <artifactId>quarkus-googlecloud-jsonlogging-example</artifactId>
-  <name>Quarkus Google Cloud JSON Logging Extension - Example</name>
+  <artifactId>quarkus-googlecloud-jsonlogging-quarkus-example</artifactId>
+  <name>Quarkus Google Cloud JSON Logging Extension - Quarkus Example</name>
 
   <dependencies>
     <dependency>
diff --git a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java b/examples/quarkus/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
similarity index 100%
rename from example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
rename to examples/quarkus/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
diff --git a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java b/examples/quarkus/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
similarity index 97%
rename from example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
rename to examples/quarkus/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
index 6fa42cd..90cd587 100644
--- a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
+++ b/examples/quarkus/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
@@ -32,5 +32,6 @@
         Label.of("app", "foo"),
         KeyValueParameter.of("version", 10));
     throw new IllegalStateException();
+    // return "ok";
   }
 }
diff --git a/example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider b/examples/quarkus/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
similarity index 100%
rename from example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
rename to examples/quarkus/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
diff --git a/example/src/main/resources/application.properties b/examples/quarkus/src/main/resources/application.properties
similarity index 100%
rename from example/src/main/resources/application.properties
rename to examples/quarkus/src/main/resources/application.properties
diff --git a/examples/spring-boot/pom.xml b/examples/spring-boot/pom.xml
new file mode 100644
index 0000000..29e17d3
--- /dev/null
+++ b/examples/spring-boot/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
+  xmlns="http://maven.apache.org/POM/4.0.0"
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
+    <artifactId>quarkus-googlecloud-jsonlogging-parent</artifactId>
+    <version>3.1.4-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>quarkus-googlecloud-jsonlogging-spring-boot-example</artifactId>
+  <name>Quarkus Google Cloud JSON Logging Extension - Spring Boot Example</name>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-dependencies</artifactId>
+        <version>2.6.2</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.jboss.slf4j</groupId>
+      <artifactId>slf4j-jboss-logmanager</artifactId>
+      <version>1.1.0.Final</version>
+    </dependency>
+    <!-- *** optional ***
+    <dependency>
+      <groupId>org.jboss.logmanager</groupId>
+      <artifactId>log4j2-jboss-logmanager</artifactId>
+      <version>1.0.0.Final</version>
+    </dependency>
+    -->
+    <!-- *** optional ***
+    <dependency>
+      <groupId>org.jboss.logmanager</groupId>
+      <artifactId>log4j-jboss-logmanager</artifactId>
+      <version>1.2.2.Final</version>
+    </dependency>
+    -->
+    <!-- *** optional ***
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>jcl-over-slf4j</artifactId>
+    </dependency>
+    -->
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-web</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>ch.qos.logback</groupId>
+          <artifactId>logback-classic</artifactId>
+        </exclusion>
+        <!--
+        <exclusion>
+          <groupId>org.springframework.boot</groupId>
+          <artifactId>spring-boot-starter-logging</artifactId>
+        </exclusion>
+        -->
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
+      <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <version>2.6.2</version>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
diff --git a/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/Application.java b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/Application.java
new file mode 100644
index 0000000..d003b3f
--- /dev/null
+++ b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/Application.java
@@ -0,0 +1,12 @@
+package eu.mulk.quarkus.googlecloud.jsonlogging.example;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+  public static void main(String[] args) {
+    SpringApplication.run(Application.class, args);
+  }
+}
diff --git a/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/ApplicationLoggingSystem.java b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/ApplicationLoggingSystem.java
new file mode 100644
index 0000000..4050b99
--- /dev/null
+++ b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/ApplicationLoggingSystem.java
@@ -0,0 +1,22 @@
+package eu.mulk.quarkus.googlecloud.jsonlogging.example;
+
+import org.springframework.boot.logging.LogFile;
+import org.springframework.boot.logging.LoggingInitializationContext;
+import org.springframework.boot.logging.Slf4JLoggingSystem;
+
+public class ApplicationLoggingSystem extends Slf4JLoggingSystem {
+
+  public ApplicationLoggingSystem(ClassLoader classLoader) {
+    super(classLoader);
+  }
+
+  @Override
+  protected String[] getStandardConfigLocations() {
+    return new String[0];
+  }
+
+  @Override
+  protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
+    /* no configuration necessary */
+  }
+}
diff --git a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
similarity index 100%
copy from example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
copy to examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
diff --git a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
similarity index 78%
copy from example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
copy to examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
index 6fa42cd..b3197fd 100644
--- a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
+++ b/examples/spring-boot/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RootResource.java
@@ -3,16 +3,12 @@
 import eu.mulk.quarkus.googlecloud.jsonlogging.KeyValueParameter;
 import eu.mulk.quarkus.googlecloud.jsonlogging.Label;
 import javax.annotation.PostConstruct;
-import javax.enterprise.context.ApplicationScoped;
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.Produces;
 import org.jboss.logging.Logger;
 import org.jboss.logging.MDC;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
 
-@Produces("text/plain")
-@Path("/")
-@ApplicationScoped
+@RestController("/")
 public class RootResource {
 
   static final Logger log = Logger.getLogger(RootResource.class);
@@ -22,7 +18,7 @@
     log.warn("Hey!");
   }
 
-  @GET
+  @GetMapping(produces = "text/plain")
   public String hello() {
     MDC.put("requestMethod", "GET");
     log.infof(
@@ -32,5 +28,6 @@
         Label.of("app", "foo"),
         KeyValueParameter.of("version", 10));
     throw new IllegalStateException();
+    // return "ok";
   }
 }
diff --git a/example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider b/examples/spring-boot/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
similarity index 100%
copy from example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
copy to examples/spring-boot/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
diff --git a/examples/spring-boot/src/main/resources/META-INF/services/org.jboss.logmanager.EmbeddedConfigurator b/examples/spring-boot/src/main/resources/META-INF/services/org.jboss.logmanager.EmbeddedConfigurator
new file mode 100644
index 0000000..6d937e5
--- /dev/null
+++ b/examples/spring-boot/src/main/resources/META-INF/services/org.jboss.logmanager.EmbeddedConfigurator
@@ -0,0 +1 @@
+eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultEmbeddedConfigurator
diff --git a/examples/spring-boot/src/main/resources/application.properties b/examples/spring-boot/src/main/resources/application.properties
new file mode 100644
index 0000000..c87d7c3
--- /dev/null
+++ b/examples/spring-boot/src/main/resources/application.properties
@@ -0,0 +1 @@
+logging.config = classpath:logging.properties
diff --git a/examples/spring-boot/src/main/resources/logging.properties b/examples/spring-boot/src/main/resources/logging.properties
new file mode 100644
index 0000000..e80e0c6
--- /dev/null
+++ b/examples/spring-boot/src/main/resources/logging.properties
@@ -0,0 +1,2 @@
+handlers = eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultConsoleHandler
+.level = INFO