feat: Add InsertId class.

Change-Id: Id620972fea28922a453cbe95ad8467d84df8eca8
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 4aa8f9f..0b2003d 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
@@ -123,6 +123,8 @@
       }
     }
 
+    String insertId = null;
+
     if (logRecord.getParameters() != null) {
       for (var parameter : logRecord.getParameters()) {
         if (parameter instanceof StructuredParameter) {
@@ -130,6 +132,8 @@
         } else if (parameter instanceof Label) {
           var label = (Label) parameter;
           labels.put(label.key(), label.value());
+        } else if (parameter instanceof InsertId) {
+          insertId = ((InsertId) parameter).value();
         }
       }
     }
@@ -151,7 +155,8 @@
             parameters,
             mdc,
             ndc,
-            logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
+            logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null,
+            insertId);
 
     var b = stringBuilder.get();
     b.delete(0, b.length());
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/InsertId.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/InsertId.java
new file mode 100644
index 0000000..9c2bb23
--- /dev/null
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/InsertId.java
@@ -0,0 +1,82 @@
+// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+package eu.mulk.quarkus.googlecloud.jsonlogging;
+
+import java.util.Objects;
+
+/**
+ * A unique identifier for a log entry.
+ *
+ * <p>Prevents the duplicate insertion of log entries.  Also serves as a discriminator to order log entries that carry
+ * the same time stamp.
+ *
+ * <p>Will be generated by Google Cloud Logging if not provided.
+ *
+ * <p>Instances of {@link InsertId} can be passed as log parameters to the {@code *f} family of logging
+ * functions on {@link org.jboss.logging.Logger}.
+ *
+ * <p><strong>Example:</strong>
+ *
+ * {@snippet :
+ * logger.logf("Request rejected: unauthorized.", InsertId.of("123"));
+ * }
+ *
+ * <p>Result:
+ *
+ * {@snippet lang="json" :
+ * {
+ *   "textPayload": "Request rejected: unauthorized.",
+ *   "logging.googleapis.com/insertId": "123"
+ * }
+ * }
+ *
+ * @see Label
+ * @see StructuredParameter
+ */
+public final class InsertId {
+
+  private final String value;
+
+  private InsertId(String value) {
+    this.value = value;
+  }
+
+  /**
+   * Constructs an {@link InsertId} from a string.
+   *
+   * @param value the value of the insertion ID.
+   * @return the newly constructed {@link InsertId}, ready to be passed to a logging function.
+   */
+  public static InsertId of(String value) {
+    return new InsertId(value);
+  }
+
+  /**
+   * The value of the label.
+   *
+   * @return the value of the label.
+   */
+  public String value() {
+    return value;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == this) return true;
+    if (obj == null || obj.getClass() != this.getClass()) return false;
+    var that = (InsertId) obj;
+    return Objects.equals(this.value, that.value);
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(value);
+  }
+
+  @Override
+  public String toString() {
+    return "InsertId[value=" + value + ']';
+  }
+}
diff --git a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java
index 19e6a3f..8859cb6 100644
--- a/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java
+++ b/core/src/main/java/eu/mulk/quarkus/googlecloud/jsonlogging/LogEntry.java
@@ -35,6 +35,7 @@
   private final Map<String, String> mappedDiagnosticContext;
   @Nullable private final String nestedDiagnosticContext;
   @Nullable private final String type;
+  @Nullable private final String insertId;
 
   LogEntry(
       String message,
@@ -47,7 +48,8 @@
       List<StructuredParameter> parameters,
       Map<String, String> mappedDiagnosticContext,
       @Nullable String nestedDiagnosticContext,
-      @Nullable String type) {
+      @Nullable String type,
+      @Nullable String insertId) {
     this.message = message;
     this.severity = severity;
     this.timestamp = timestamp;
@@ -59,6 +61,7 @@
     this.mappedDiagnosticContext = mappedDiagnosticContext;
     this.nestedDiagnosticContext = nestedDiagnosticContext;
     this.type = type;
+    this.insertId = insertId;
   }
 
   static final class SourceLocation {
@@ -124,6 +127,13 @@
   }
 
   void json(StringBuilder b) {
+
+    if (insertId != null) {
+      b.append("\"logging.googleapis.com/insertId\":");
+      appendEscapedString(b, insertId);
+      b.append(",");
+    }
+
     if (trace != null) {
       b.append("\"logging.googleapis.com/trace\":");
       appendEscapedString(b, trace);
diff --git a/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java b/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java
index 16fc537..d202fd9 100644
--- a/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java
+++ b/core/src/test/java/eu/mulk/quarkus/googlecloud/jsonlogging/FormatterTest.java
@@ -67,6 +67,7 @@
     assertLinesMatch(
         List.of(
             "\\{"
+                + "\"logging.googleapis.com/insertId\":\"123-456-789\","
                 + "\"logging.googleapis.com/labels\":\\{\"a\":\"b\",\"requestId\":\"123\"\\},"
                 + "\"traceId\":\"39f9a49a9567a8bd7087b708f8932550\","
                 + "\"spanId\":\"c7431b14630b633d\","
@@ -91,7 +92,8 @@
         new Object[] {
           (StructuredParameter)
               () -> JSON.createObjectBuilder().add("one", 1).add("two", 2.0).add("yes", true),
-          Label.of("a", "b")
+          Label.of("a", "b"),
+          InsertId.of("123-456-789"),
         });
     return logRecord;
   }