summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2021-05-03 08:04:53 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2021-05-11 21:49:21 +0200
commitc8144a9c60279f8dc81b2506794acd24df31b9a3 (patch)
tree495231540cd6f04f02d7d7f22db38f36d716833a /runtime
parent25a6ef32df15d05904622326328882549095532f (diff)
Initial checkin: Quarkus Google Cloud JSON Logging.
Change-Id: I264211f56c2bed4002ecdb6ead8a5321ada855fd
Diffstat (limited to 'runtime')
-rw-r--r--runtime/pom.xml59
-rw-r--r--runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLogEntry.java41
-rw-r--r--runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingFormatter.java115
-rw-r--r--runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingRecorder.java16
-rw-r--r--runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/KeyValueParameter.java3
-rw-r--r--runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/Label.java3
-rw-r--r--runtime/src/main/resources/META-INF/quarkus-extension.yaml9
7 files changed, 246 insertions, 0 deletions
diff --git a/runtime/pom.xml b/runtime/pom.xml
new file mode 100644
index 0000000..e8254f9
--- /dev/null
+++ b/runtime/pom.xml
@@ -0,0 +1,59 @@
+<?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-observability</groupId>
+ <artifactId>quarkus-googlecloud-jsonlogging-parent</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ </parent>
+ <artifactId>quarkus-googlecloud-jsonlogging</artifactId>
+ <name>Quarkus Google Cloud JSON Logging Extension - Runtime</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-arc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-jsonb</artifactId>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-bootstrap-maven-plugin</artifactId>
+ <version>${quarkus.version}</version>
+ <executions>
+ <execution>
+ <phase>compile</phase>
+ <goals>
+ <goal>extension-descriptor</goal>
+ </goals>
+ <configuration>
+ <deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <annotationProcessorPaths>
+ <path>
+ <groupId>io.quarkus</groupId>
+ <artifactId>quarkus-extension-processor</artifactId>
+ <version>${quarkus.version}</version>
+ </path>
+ </annotationProcessorPaths>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLogEntry.java b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLogEntry.java
new file mode 100644
index 0000000..3f5a836
--- /dev/null
+++ b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLogEntry.java
@@ -0,0 +1,41 @@
+package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
+
+import io.smallrye.common.constraint.Nullable;
+import java.time.Instant;
+import java.util.Map;
+import javax.json.bind.annotation.JsonbProperty;
+
+/**
+ * A JSON log entry compatible with Google Cloud Logging.
+ *
+ * <p>Roughly (but not quite) corresponds to Google Cloud Logging's
+ * <a href="https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry">LogEntry</a>
+ * structure.
+ */
+public record GoogleCloudLogEntry(
+ String getMessage,
+ String getSeverity,
+ Timestamp getTimestamp,
+ @Nullable String getTrace,
+ @Nullable String getSpanId,
+ @Nullable SourceLocation getSourceLocation,
+ @Nullable Map<String, String> getLabels,
+ @Nullable Map<String, Object> getParameters,
+ @Nullable Map<String, String> getMappedDiagnosticContext,
+ @Nullable String getNestedDiagnosticContext,
+ @Nullable @JsonbProperty("@type") String getType) {
+
+ static public record SourceLocation(
+ @Nullable String getFile,
+ @Nullable String getLine,
+ @Nullable String getFunction) {}
+
+ static public record Timestamp(
+ long getSeconds,
+ int getNanos) {
+
+ public Timestamp(Instant t) {
+ this(t.getEpochSecond(), t.getNano());
+ }
+ }
+}
diff --git a/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingFormatter.java b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingFormatter.java
new file mode 100644
index 0000000..a44e8b5
--- /dev/null
+++ b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingFormatter.java
@@ -0,0 +1,115 @@
+package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.logging.Level;
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbException;
+import org.jboss.logmanager.ExtFormatter;
+import org.jboss.logmanager.ExtLogRecord;
+
+/**
+ * Formats log records as JSON for consumption by Google Cloud Logging.
+ *
+ * <p>Meant to be used in containers running on Google Kubernetes Engine (GKE).
+ *
+ * @see GoogleCloudLogEntry
+ */
+class GoogleCloudLoggingFormatter extends ExtFormatter {
+
+ private static final String TRACE_LEVEL = "TRACE";
+ private static final String DEBUG_LEVEL = "DEBUG";
+ private static final String INFO_LEVEL = "INFO";
+ private static final String WARNING_LEVEL = "WARNING";
+ private static final String ERROR_LEVEL = "ERROR";
+
+ private static final String ERROR_EVENT_TYPE =
+ "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
+
+ private final Jsonb jsonb;
+
+ GoogleCloudLoggingFormatter(Jsonb jsonb) {
+ this.jsonb = jsonb;
+ }
+
+ @Override
+ public String format(ExtLogRecord logRecord) {
+ var message = formatMessageWithStackTrace(logRecord);
+
+ var parameters = new HashMap<String, Object>();
+ var labels = new HashMap<String, String>();
+ if (logRecord.getParameters() != null) {
+ for (var parameter : logRecord.getParameters()) {
+ if (parameter instanceof KeyValueParameter kvparam) {
+ parameters.put(kvparam.key(), kvparam.value());
+ } else if (parameter instanceof Label label) {
+ labels.put(label.key(), label.value());
+ }
+ }
+ }
+
+ var mdc = logRecord.getMdcCopy();
+ var ndc = logRecord.getNdc();
+
+ var sourceLocation =
+ new GoogleCloudLogEntry.SourceLocation(
+ logRecord.getSourceFileName(), String.valueOf(logRecord.getSourceLineNumber()), String.format("%s.%s", logRecord.getSourceClassName(), logRecord.getSourceMethodName()));
+
+ var entry =
+ new GoogleCloudLogEntry(
+ message,
+ severityOf(logRecord.getLevel()),
+ new GoogleCloudLogEntry.Timestamp(logRecord.getInstant()),
+ null,
+ null,
+ sourceLocation,
+ labels.isEmpty() ? null : labels,
+ parameters.isEmpty() ? null : parameters,
+ mdc.isEmpty() ? null : mdc,
+ ndc.isEmpty() ? null : ndc,
+ logRecord.getLevel().intValue() >= 1000 ? ERROR_EVENT_TYPE : null);
+
+ try {
+ return jsonb.toJson(entry) + "\n";
+ } catch (JsonbException e) {
+ e.printStackTrace();
+ return message + "\n";
+ }
+ }
+
+ /**
+ * Formats the log message corresponding to {@code logRecord} including a stack trace of the
+ * {@link ExtLogRecord#getThrown()} exception if any.
+ */
+ private String formatMessageWithStackTrace(ExtLogRecord logRecord) {
+ var messageStringWriter = new StringWriter();
+ var messagePrintWriter = new PrintWriter(messageStringWriter);
+ messagePrintWriter.append(this.formatMessage(logRecord));
+
+ if (logRecord.getThrown() != null) {
+ messagePrintWriter.println();
+ logRecord.getThrown().printStackTrace(messagePrintWriter);
+ }
+
+ messagePrintWriter.close();
+ return messageStringWriter.toString();
+ }
+
+ /**
+ * Computes the Google Cloud Logging severity corresponding to a given {@link Level}.
+ */
+ private static String severityOf(Level level) {
+ if (level.intValue() < 500) {
+ return TRACE_LEVEL;
+ } else if (level.intValue() < 700) {
+ return DEBUG_LEVEL;
+ } else if (level.intValue() < 900) {
+ return INFO_LEVEL;
+ } else if (level.intValue() < 1000) {
+ return WARNING_LEVEL;
+ } else {
+ return ERROR_LEVEL;
+ }
+ }
+}
diff --git a/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingRecorder.java b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingRecorder.java
new file mode 100644
index 0000000..9ae3ae1
--- /dev/null
+++ b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/GoogleCloudLoggingRecorder.java
@@ -0,0 +1,16 @@
+package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
+
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.runtime.annotations.Recorder;
+import java.util.Optional;
+import java.util.logging.Formatter;
+import javax.json.bind.spi.JsonbProvider;
+
+@Recorder
+public class GoogleCloudLoggingRecorder {
+
+ public RuntimeValue<Optional<Formatter>> initialize() {
+ var jsonb = JsonbProvider.provider().create().build();
+ return new RuntimeValue<>(Optional.of(new GoogleCloudLoggingFormatter(jsonb)));
+ }
+}
diff --git a/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/KeyValueParameter.java b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/KeyValueParameter.java
new file mode 100644
index 0000000..358e470
--- /dev/null
+++ b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/KeyValueParameter.java
@@ -0,0 +1,3 @@
+package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
+
+public record KeyValueParameter(String key, Object value) {}
diff --git a/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/Label.java b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/Label.java
new file mode 100644
index 0000000..0c13739
--- /dev/null
+++ b/runtime/src/main/java/eu/mulk/quarkus/observability/googlecloud/jsonlogging/Label.java
@@ -0,0 +1,3 @@
+package eu.mulk.quarkus.observability.googlecloud.jsonlogging;
+
+public record Label(String key, String value) {}
diff --git a/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000..7aa7a2a
--- /dev/null
+++ b/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,9 @@
+name: Google Cloud JSON Logging
+description: Logs to standard output in Google-Cloud-compatible JSON format.
+metadata:
+ keywords:
+ - logging
+ categories:
+ - "miscellaneous"
+ status: "preview"
+ # guide: ...