| = Quarkus Google Cloud JSON Logging |
| Matthias Andreas Benkard |
| // Meta |
| :experimental: |
| :data-uri: |
| :sectnums: |
| :toc: |
| :stem: |
| :toclevels: 2 |
| :description: Quarkus Google Cloud JSON Logging Manual |
| :keywords: mulk |
| // Settings |
| :icons: font |
| :source-highlighter: rouge |
| |
| |
| Structured logging to standard output according to the Google Cloud |
| Logging specification. |
| |
| |
| == Summary |
| |
| This package contains a log formatter for JBoss Logging in the form of |
| a Quarkus plugin that implements the |
| https://cloud.google.com/logging/docs/structured-logging[Google Cloud |
| Logging JSON format] on standard output. |
| |
| It is possible to log unstructured text, structured data, or a mixture |
| of both depending on the situation. |
| |
| |
| == 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. |
| |
| If you are using Maven: |
| |
| [source,xml] |
| ---- |
| <dependencies> |
| |
| <dependency> |
| <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId> |
| <artifactId>quarkus-googlecloud-jsonlogging</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: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 |
| automatically converted to Google-Cloud-Logging-compatible JSON. |
| |
| Structured data can be logged in one of 3 different ways: by passing |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/Label.html[Label]s |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameter.html[StructuredParameter]s |
| as parameters to individual log entries, by supplying |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/LabelProvider.html[LabelProvider]s |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameterProvider.html[StructuredParameterProvider]s, |
| or by using the Mapped Diagnostic Context. |
| |
| |
| === Using Label and StructuredParameter |
| |
| Instances of |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/Label.html[Label] |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameter.html[StructuredParameter] |
| can be passed as log parameters to the `*f` family of logging |
| functions on JBoss Logging's |
| https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/Logger.html[Logger]. |
| |
| Simple key–value pairs are represented by |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/KeyValueParameter.html[KeyValueParameter]. |
| |
| **Example:** |
| |
| [source,java] |
| ---- |
| logger.logf( |
| "Request rejected: unauthorized.", |
| Label.of("requestId", "123"), |
| KeyValueParameter.of("resource", "/users/mulk"), |
| KeyValueParameter.of("method", "PATCH"), |
| KeyValueParameter.of("reason", "invalid token")); |
| ---- |
| |
| Result: |
| |
| [source,json] |
| ---- |
| { |
| "jsonPayload": { |
| "message": "Request rejected: unauthorized.", |
| "resource": "/users/mulk", |
| "method": "PATCH", |
| "reason": "invalid token" |
| }, |
| "labels": { |
| "requestId": "123" |
| } |
| } |
| ---- |
| |
| |
| === Using LabelProvider and StructuredParameterProvider |
| |
| Any CDI beans that implement |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/LabelProvider.html[LabelProvider] |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging/3.1.0/eu.mulk.quarkus.googlecloud.jsonlogging/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameterProvider.html[StructuredParameterProvider] |
| are discovered at build time and consulted to provide labels and |
| 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. |
| |
| 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] |
| ---- |
| @Singleton |
| @Unremovable |
| public final class TraceLogParameterProvider implements StructuredParameterProvider, LabelProvider { |
| |
| @Override |
| public StructuredParameter getParameter() { |
| var b = Json.createObjectBuilder(); |
| b.add("traceId", Span.current().getSpanContext().getTraceId()); |
| b.add("spanId", Span.current().getSpanContext().getSpanId()); |
| return () -> b; |
| } |
| |
| @Override |
| public Collection<Label> getLabels() { |
| return List.of(Label.of("requestId", "123")); |
| } |
| } |
| ---- |
| |
| Result: |
| |
| [source,json] |
| ---- |
| { |
| "jsonPayload": { |
| "message": "Request rejected: unauthorized.", |
| "traceId": "39f9a49a9567a8bd7087b708f8932550", |
| "spanId": "c7431b14630b633d" |
| }, |
| "labels": { |
| "requestId": "123" |
| } |
| } |
| ---- |
| |
| |
| === Using the Mapped Diagnostic Context |
| |
| Any key–value pairs in JBoss Logging's thread-local |
| https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/MDC.html[MDC] |
| are added to the resulting JSON. |
| |
| **Example:** |
| |
| [source,java] |
| ---- |
| MDC.put("resource", "/users/mulk"); |
| MDC.put("method", "PATCH"); |
| logger.logf("Request rejected: unauthorized."); |
| ---- |
| |
| Result: |
| |
| [source,json] |
| ---- |
| { |
| "jsonPayload": { |
| "message": "Request rejected: unauthorized.", |
| "resource": "/users/mulk", |
| "method": "PATCH" |
| } |
| } |
| ---- |