Load providers registered through the ServiceLoader mechanism.

Change-Id: I392e78b34c8330e9b4c06d57b1423ca552ba6fc1
diff --git a/README.adoc b/README.adoc
index 79407cf..494f44c 100644
--- a/README.adoc
+++ b/README.adoc
@@ -144,6 +144,10 @@
 provide contextual information such as tracing and request IDs stored
 in thread-local storage.
 
+Alternatively, you can also register providers through the Java
+https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ServiceLoader.html[ServiceLoader]
+mechanism.
+
 **Example:**
 
 [source,java]
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 066f709..61a2dea 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
@@ -7,6 +7,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.ServiceLoader;
 import java.util.logging.Level;
 import org.jboss.logmanager.ExtFormatter;
 import org.jboss.logmanager.ExtLogRecord;
@@ -35,6 +36,9 @@
   /**
    * Constructs a {@link Formatter}.
    *
+   * <p><strong>Note:</strong> This constructor does not automatically discover providers using the
+   * {@link ServiceLoader} mechanism. See {@link #load} for this case use.
+   *
    * @param parameterProviders the {@link StructuredParameterProvider}s to apply to each log entry.
    * @param labelProviders the {@link LabelProvider}s to apply to each log entry.
    */
@@ -45,6 +49,31 @@
     this.labelProviders = List.copyOf(labelProviders);
   }
 
+  /**
+   * Constructs a {@link Formatter} with parameter and label providers supplied by {@link
+   * ServiceLoader}.
+   *
+   * <p>In addition to the providers supplied as parameters, this factory method loads all {@link
+   * StructuredParameterProvider}s and {@link LabelProvider}s found through the {@link
+   * ServiceLoader} mechanism.
+   *
+   * @param parameterProviders the {@link StructuredParameterProvider}s to apply to each log entry.
+   * @param labelProviders the {@link LabelProvider}s to apply to each log entry.
+   */
+  public static Formatter load(
+      Collection<StructuredParameterProvider> parameterProviders,
+      Collection<LabelProvider> labelProviders) {
+    parameterProviders = new ArrayList<>(parameterProviders);
+    ServiceLoader.load(StructuredParameterProvider.class, Formatter.class.getClassLoader())
+        .forEach(parameterProviders::add);
+
+    labelProviders = new ArrayList<>(labelProviders);
+    ServiceLoader.load(LabelProvider.class, Formatter.class.getClassLoader())
+        .forEach(labelProviders::add);
+
+    return new Formatter(parameterProviders, labelProviders);
+  }
+
   @Override
   public String format(ExtLogRecord logRecord) {
     var message = formatMessageWithStackTrace(logRecord);
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 2f4c7ce..3617b8c 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
@@ -123,8 +123,14 @@
  * parameters for each message that is logged. This can be used to provide contextual information
  * such as tracing and request IDs stored in thread-local storage.
  *
- * <p>If you are using the Quarkus extension, CDI beans that implement these interfaces are
- * automatically detected at build time and passed to the formatter on startup.
+ * <p><strong>Service provider support:</strong> Providers can be registered using the {@link
+ * java.util.ServiceLoader} mechanism, in which case {@link
+ * eu.mulk.quarkus.googlecloud.jsonlogging.Formatter#load} picks them up automatically.
+ *
+ * <p><strong>CDI support:</strong> If you are using the Quarkus extension, CDI beans that implement
+ * one of the provider interfaces are automatically detected at build time and passed to the
+ * formatter on startup. In addition, providers using the {@link java.util.ServiceLoader} mechanism
+ * are detected and passed to the formatter as well.
  *
  * <p><strong>Example:</strong>
  *
diff --git a/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java b/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
new file mode 100644
index 0000000..7e4158c
--- /dev/null
+++ b/example/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/example/RandomNumberParameterProvider.java
@@ -0,0 +1,14 @@
+package eu.mulk.quarkus.googlecloud.jsonlogging.example;
+
+import eu.mulk.quarkus.googlecloud.jsonlogging.KeyValueParameter;
+import eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameter;
+import eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class RandomNumberParameterProvider implements StructuredParameterProvider {
+
+  @Override
+  public StructuredParameter getParameter() {
+    return KeyValueParameter.of("randomNumber", ThreadLocalRandom.current().nextInt());
+  }
+}
diff --git a/example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider b/example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
new file mode 100644
index 0000000..c0017e6
--- /dev/null
+++ b/example/src/main/resources/META-INF/services/eu.mulk.quarkus.googlecloud.jsonlogging.StructuredParameterProvider
@@ -0,0 +1 @@
+eu.mulk.quarkus.googlecloud.jsonlogging.example.RandomNumberParameterProvider
diff --git a/runtime/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/runtime/GoogleCloudJsonLoggingRecorder.java b/runtime/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/runtime/GoogleCloudJsonLoggingRecorder.java
index 661b69f..84d9112 100644
--- a/runtime/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/runtime/GoogleCloudJsonLoggingRecorder.java
+++ b/runtime/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/runtime/GoogleCloudJsonLoggingRecorder.java
@@ -31,6 +31,6 @@
     var labelProviders =
         Arc.container().select(LabelProvider.class).stream().collect(Collectors.toList());
 
-    return new RuntimeValue<>(Optional.of(new Formatter(parameterProviders, labelProviders)));
+    return new RuntimeValue<>(Optional.of(Formatter.load(parameterProviders, labelProviders)));
   }
 }