| // SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de> |
| // |
| // SPDX-License-Identifier: GFDL-1.3-or-later |
| |
| = 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>6.6.0</version> |
| </dependency> |
| |
| </dependencies> |
| ---- |
| |
| If you are using Gradle: |
| |
| [source,groovy] |
| ---- |
| dependencies { |
| implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging:6.6.0") |
| } |
| ---- |
| |
| By default the extension is turned on. You can turn the extension on |
| or off explicitly by configuring the `quarkus.log.console.google` key |
| in `application.properties`: |
| |
| [source,properties] |
| ---- |
| quarkus.log.console.google = true |
| ---- |
| |
| |
| == 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>6.6.0</version> |
| </dependency> |
| |
| </dependencies> |
| ---- |
| |
| If you are using Gradle: |
| |
| [source,groovy] |
| ---- |
| dependencies { |
| implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:6.6.0") |
| } |
| ---- |
| |
| |
| === Wiring (java.util.logging) |
| |
| This section explains how to use JBoss Log Manager as the log manager |
| for `java.util.logging` and how to configure it to use the log |
| formatter provided by this library. |
| |
| Add a dependency on JBoss Log Manager and the `-core` module to your |
| project. |
| |
| If you are using Maven: |
| |
| [source,xml] |
| ---- |
| <dependencies> |
| |
| <dependency> |
| <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId> |
| <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId> |
| <version>6.6.0</version> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.jboss.logmanager</groupId> |
| <artifactId>jboss-logmanager</artifactId> |
| <version>3.0.2.Final</version> |
| </dependency> |
| |
| </dependencies> |
| ---- |
| |
| If you are using Gradle: |
| |
| [source,groovy] |
| ---- |
| dependencies { |
| implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:6.6.0") |
| implementation("org.jboss.logmanager:jboss-logmanager:3.0.2.Final") |
| } |
| ---- |
| |
| Create a text file called `org.jboss.logmanager.ConfiguratorFactory` |
| in your `resources/META-INF/services/` directory and put the fully |
| qualified name of this library's `DefaultConfiguratorFactory` into it: |
| |
| [source] |
| ---- |
| eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultConfiguratorFactory |
| ---- |
| |
| Finally, set the `java.util.logging.manager` system property to |
| `org.jboss.logmanager.LogManager` when running your application: |
| |
| [source,shell] |
| ---- |
| $ java -Djava.util.logging.manager=org.jboss.logmanager.LogManager ... |
| ---- |
| |
| |
| === 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. In addition, configure |
| JBoss Log Manager as the log manager for `java.util.logging` by |
| setting the `java.util.logging.manager` system property to |
| `org.jboss.logmanager.LogManager`. |
| |
| If you are using Maven: |
| |
| [source,xml] |
| ---- |
| <project> |
| |
| <dependencies> |
| |
| <dependency> |
| <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId> |
| <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId> |
| <version>6.6.0</version> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.jboss.slf4j</groupId> |
| <artifactId>slf4j-jboss-logmanager</artifactId> |
| <version>2.0.1.Final</version> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.jboss.logmanager</groupId> |
| <artifactId>jboss-logmanager</artifactId> |
| <version>3.0.2.Final</version> |
| </dependency> |
| |
| <dependency> |
| <groupId>org.jboss.logmanager</groupId> |
| <artifactId>jboss-logmanager-embedded</artifactId> |
| <version>1.2.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> |
| |
| <build> |
| <plugins> |
| |
| <plugin> |
| <groupId>org.springframework.boot</groupId> |
| <artifactId>spring-boot-maven-plugin</artifactId> |
| <version>${spring-boot.version}</version> |
| <configuration> |
| <systemPropertyVariables> |
| <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> |
| </systemPropertyVariables> |
| </configuration> |
| </plugin> |
| |
| </plugins> |
| </build> |
| |
| </project> |
| ---- |
| |
| If you are using Gradle: |
| |
| [source,groovy] |
| ---- |
| configurations { |
| all*.exclude(group: "ch.qos.logback", module: "logback-classic") |
| } |
| |
| dependencies { |
| implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:6.6.0") |
| implementation("org.jboss.logmanager:jboss-logmanager:3.0.2.Final") |
| implementation("org.jboss.logmanager:jboss-logmanager-embedded:1.2.0.Final") |
| implementation("org.jboss.slf4j:slf4j-jboss-logmanager:2.0.1.Final") |
| } |
| |
| tasks.named("bootRun") { |
| systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager") |
| } |
| ---- |
| |
| Create a text file called `org.jboss.logmanager.ConfiguratorFactory` |
| in your `resources/META-INF/services/` directory and put the fully |
| qualified name of this library's `DefaultConfiguratorFactory` into it: |
| |
| [source] |
| ---- |
| eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultConfiguratorFactory |
| ---- |
| |
| Because Spring Boot configures the system logger with a minimum log |
| level of `SEVERE` by default, you may also want to configure the |
| logger using a `logging.properties` file. To do so, first add an |
| entry to `application.properties` that points to the file: |
| |
| [source,properties] |
| ---- |
| logging.config = classpath:logging.properties |
| ---- |
| |
| Create the `logging.properties` file in your `resources` directory and |
| set the root logger level to something other than `SEVERE`: |
| |
| [source,properties] |
| ---- |
| logger.level = INFO |
| ---- |
| |
| Finally, add a `static` block to your Spring Boot application class |
| that disables the Tomcat URL stream handler factory, which conflicts |
| with the URL stream handler factory registered by the JBoss Modules |
| library: |
| |
| [source,java] |
| ---- |
| @SpringBootApplication |
| public class Application { |
| |
| static { |
| TomcatURLStreamHandlerFactory.disable(); |
| } |
| |
| // ... |
| } |
| ---- |
| |
| == 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-core/6.6.0/eu/mulk/quarkus/googlecloud/jsonlogging/Label.html[Label]s |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/6.6.0/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-core/6.6.0/eu/mulk/quarkus/googlecloud/jsonlogging/LabelProvider.html[LabelProvider]s |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/6.6.0/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-core/6.6.0/eu/mulk/quarkus/googlecloud/jsonlogging/Label.html[Label] |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/6.6.0/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-core/6.6.0/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-core/6.6.0/eu/mulk/quarkus/googlecloud/jsonlogging/LabelProvider.html[LabelProvider] |
| and |
| https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/6.6.0/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/21/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" |
| } |
| } |
| ---- |
| |
| |
| == Development |
| |
| === Running the Tests |
| |
| To run the **test suite**, run: |
| |
| [source,shell] |
| ---- |
| $ mvn verify |
| ---- |
| |
| To run the **benchmarks**, run: |
| |
| [source,shell] |
| ---- |
| $ mvn verify -Pbenchmark |
| ---- |