blob: 033ee1da9cc7f216087b2f87f2cbc4a681871a8b [file] [log] [blame]
Matthias Andreas Benkard80909242022-02-03 20:47:47 +01001// SPDX-FileCopyrightText: © 2021 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
2//
3// SPDX-License-Identifier: GFDL-1.3-or-later
4
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +02005= Quarkus Google Cloud JSON Logging
6Matthias Andreas Benkard
7// Meta
8:experimental:
9:data-uri:
10:sectnums:
11:toc:
12:stem:
13:toclevels: 2
14:description: Quarkus Google Cloud JSON Logging Manual
15:keywords: mulk
16// Settings
17:icons: font
18:source-highlighter: rouge
19
20
21Structured logging to standard output according to the Google Cloud
22Logging specification.
23
24
25== Summary
26
27This package contains a log formatter for JBoss Logging in the form of
28a Quarkus plugin that implements the
29https://cloud.google.com/logging/docs/structured-logging[Google Cloud
30Logging JSON format] on standard output.
31
32It is possible to log unstructured text, structured data, or a mixture
33of both depending on the situation.
34
35
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010036== Activation (Quarkus)
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020037
38Add the runtime POM to your dependency list. As long as the JAR is on
39the classpath at both build time and runtime, the log formatter
40automatically registers itself on startup.
41
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010042If you are using Maven:
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020043
44[source,xml]
45----
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010046<dependencies>
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020047
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010048 <dependency>
49 <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
50 <artifactId>quarkus-googlecloud-jsonlogging</artifactId>
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +020051 <version>7.0.0</version>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010052 </dependency>
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020053
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010054</dependencies>
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020055----
56
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010057If you are using Gradle:
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020058
59[source,groovy]
60----
61dependencies {
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +020062 implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging:7.0.0")
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020063}
64----
65
Matthias Andreas Benkard090b8852022-09-03 18:23:23 +020066By default the extension is turned on. You can turn the extension on
67or off explicitly by configuring the `quarkus.log.console.google` key
68in `application.properties`:
69
70[source,properties]
71----
72quarkus.log.console.google = true
73----
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +020074
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +020075
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010076== Activation (Other Frameworks)
77
78If you are not using Quarkus, you can still make use of the `-core`
79module and wire it into your application in a custom way. Read this
80section for hints on how to do so.
81
82
83=== Installation
84
85If you are using Maven:
86
87[source,xml]
88----
89<dependencies>
90
91 <dependency>
92 <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
93 <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId>
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +020094 <version>7.0.0</version>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +010095 </dependency>
96
97</dependencies>
98----
99
100If you are using Gradle:
101
102[source,groovy]
103----
104dependencies {
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200105 implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:7.0.0")
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100106}
107----
108
109
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200110=== Wiring (java.util.logging)
111
112This section explains how to use JBoss Log Manager as the log manager
113for `java.util.logging` and how to configure it to use the log
114formatter provided by this library.
115
116Add a dependency on JBoss Log Manager and the `-core` module to your
117project.
118
119If you are using Maven:
120
121[source,xml]
122----
123<dependencies>
124
125 <dependency>
126 <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
127 <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId>
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200128 <version>7.0.0</version>
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200129 </dependency>
130
131 <dependency>
132 <groupId>org.jboss.logmanager</groupId>
133 <artifactId>jboss-logmanager</artifactId>
Matthias Andreas Benkarda15a9c22025-04-12 22:13:01 +0200134 <version>3.1.2.Final</version>
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200135 </dependency>
136
137</dependencies>
138----
139
140If you are using Gradle:
141
142[source,groovy]
143----
144dependencies {
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200145 implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:7.0.0")
Matthias Andreas Benkarda15a9c22025-04-12 22:13:01 +0200146 implementation("org.jboss.logmanager:jboss-logmanager:3.1.2.Final")
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200147}
148----
149
150Create a text file called `org.jboss.logmanager.ConfiguratorFactory`
151in your `resources/META-INF/services/` directory and put the fully
152qualified name of this library's `DefaultConfiguratorFactory` into it:
153
154[source]
155----
156eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultConfiguratorFactory
157----
158
159Finally, set the `java.util.logging.manager` system property to
160`org.jboss.logmanager.LogManager` when running your application:
161
162[source,shell]
163----
164$ java -Djava.util.logging.manager=org.jboss.logmanager.LogManager ...
165----
166
167
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100168=== Wiring (Spring Boot)
169
170If you are using Spring Boot, the easiest way to integrate the log
171formatter is by relying on `spring-boot-starter-logging` (which is
172pulled in by `spring-boot-starter`), excluding Logback, and pulling in
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200173JBoss Log Manager as the back end for SLF4J. In addition, configure
174JBoss Log Manager as the log manager for `java.util.logging` by
175setting the `java.util.logging.manager` system property to
176`org.jboss.logmanager.LogManager`.
177
178If you are using Maven:
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100179
180[source,xml]
181----
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200182<project>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100183
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200184 <dependencies>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100185
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200186 <dependency>
187 <groupId>eu.mulk.quarkus-googlecloud-jsonlogging</groupId>
188 <artifactId>quarkus-googlecloud-jsonlogging-core</artifactId>
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200189 <version>7.0.0</version>
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200190 </dependency>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100191
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200192 <dependency>
193 <groupId>org.jboss.slf4j</groupId>
194 <artifactId>slf4j-jboss-logmanager</artifactId>
195 <version>2.0.1.Final</version>
196 </dependency>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100197
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200198 <dependency>
199 <groupId>org.jboss.logmanager</groupId>
200 <artifactId>jboss-logmanager</artifactId>
Matthias Andreas Benkarda15a9c22025-04-12 22:13:01 +0200201 <version>3.1.2.Final</version>
Matthias Andreas Benkardbe476482023-09-27 18:06:56 +0200202 </dependency>
203
204 <dependency>
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200205 <groupId>org.springframework.boot</groupId>
206 <artifactId>spring-boot-starter</artifactId>
207 <exclusions>
208 <exclusion>
209 <groupId>ch.qos.logback</groupId>
210 <artifactId>logback-classic</artifactId>
211 </exclusion>
212 </exclusions>
213 </dependency>
214
215 </dependencies>
216
217 <build>
218 <plugins>
219
220 <plugin>
221 <groupId>org.springframework.boot</groupId>
222 <artifactId>spring-boot-maven-plugin</artifactId>
223 <version>${spring-boot.version}</version>
224 <configuration>
225 <systemPropertyVariables>
226 <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
227 </systemPropertyVariables>
228 </configuration>
229 </plugin>
230
231 </plugins>
232 </build>
233
234</project>
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100235----
236
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200237If you are using Gradle:
238
239[source,groovy]
240----
241configurations {
242 all*.exclude(group: "ch.qos.logback", module: "logback-classic")
243}
244
245dependencies {
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200246 implementation("eu.mulk.quarkus-googlecloud-jsonlogging:quarkus-googlecloud-jsonlogging-core:7.0.0")
Matthias Andreas Benkarda15a9c22025-04-12 22:13:01 +0200247 implementation("org.jboss.logmanager:jboss-logmanager:3.1.2.Final")
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200248 implementation("org.jboss.slf4j:slf4j-jboss-logmanager:2.0.1.Final")
249}
250
251tasks.named("bootRun") {
252 systemProperty("java.util.logging.manager", "org.jboss.logmanager.LogManager")
253}
254----
255
256Create a text file called `org.jboss.logmanager.ConfiguratorFactory`
257in your `resources/META-INF/services/` directory and put the fully
258qualified name of this library's `DefaultConfiguratorFactory` into it:
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100259
260[source]
261----
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200262eu.mulk.quarkus.googlecloud.jsonlogging.logmanager.DefaultConfiguratorFactory
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100263----
264
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200265Because Spring Boot configures the system logger with a minimum log
266level of `SEVERE` by default, you may also want to configure the
267logger using a `logging.properties` file. To do so, first add an
268entry to `application.properties` that points to the file:
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100269
270[source,properties]
271----
272logging.config = classpath:logging.properties
273----
274
Matthias Andreas Benkardbe476482023-09-27 18:06:56 +0200275Create the `logging.properties` file in your `resources` directory and
276set the root logger level to something other than `SEVERE`:
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100277
278[source,properties]
279----
Matthias Andreas Benkard663163d2023-09-24 13:46:22 +0200280logger.level = INFO
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100281----
282
Matthias Andreas Benkardbe476482023-09-27 18:06:56 +0200283Finally, add a `static` block to your Spring Boot application class
284that disables the Tomcat URL stream handler factory, which conflicts
285with the URL stream handler factory registered by the JBoss Modules
286library:
287
288[source,java]
289----
290@SpringBootApplication
291public class Application {
292
293 static {
294 TomcatURLStreamHandlerFactory.disable();
295 }
296
297 // ...
298}
299----
Matthias Andreas Benkard348f2052022-01-15 16:13:01 +0100300
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200301== Usage
302
303Logging unstructured data requires no code changes. All logs are
304automatically converted to Google-Cloud-Logging-compatible JSON.
305
306Structured data can be logged in one of 3 different ways: by passing
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200307https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/Label.html[Label]s
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200308and
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200309https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameter.html[StructuredParameter]s
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200310as parameters to individual log entries, by supplying
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200311https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/LabelProvider.html[LabelProvider]s
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200312and
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200313https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameterProvider.html[StructuredParameterProvider]s,
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200314or by using the Mapped Diagnostic Context.
315
316
317=== Using Label and StructuredParameter
318
319Instances of
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200320https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/Label.html[Label]
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200321and
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200322https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameter.html[StructuredParameter]
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200323can be passed as log parameters to the `*f` family of logging
324functions on JBoss Logging's
325https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/Logger.html[Logger].
326
327Simple key–value pairs are represented by
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200328https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/KeyValueParameter.html[KeyValueParameter].
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200329
330**Example:**
331
332[source,java]
333----
334logger.logf(
335 "Request rejected: unauthorized.",
336 Label.of("requestId", "123"),
337 KeyValueParameter.of("resource", "/users/mulk"),
338 KeyValueParameter.of("method", "PATCH"),
339 KeyValueParameter.of("reason", "invalid token"));
340----
341
342Result:
343
344[source,json]
345----
346{
347 "jsonPayload": {
348 "message": "Request rejected: unauthorized.",
349 "resource": "/users/mulk",
350 "method": "PATCH",
351 "reason": "invalid token"
352 },
353 "labels": {
354 "requestId": "123"
355 }
356}
357----
358
359
360=== Using LabelProvider and StructuredParameterProvider
361
362Any CDI beans that implement
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200363https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/LabelProvider.html[LabelProvider]
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200364and
Matthias Andreas Benkardaed482c2025-04-12 20:26:10 +0200365https://javadocs.dev/eu.mulk.quarkus-googlecloud-jsonlogging/quarkus-googlecloud-jsonlogging-core/7.0.0/eu/mulk/quarkus/googlecloud/jsonlogging/StructuredParameterProvider.html[StructuredParameterProvider]
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200366are discovered at build time and consulted to provide labels and
367parameters for each message that is logged. This can be used to
368provide contextual information such as tracing and request IDs stored
369in thread-local storage.
370
Matthias Andreas Benkard93ecfd12022-01-15 14:03:41 +0100371Alternatively, you can also register providers through the Java
Matthias Andreas Benkard85341772024-06-23 18:19:33 +0200372https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/ServiceLoader.html[ServiceLoader]
Matthias Andreas Benkard93ecfd12022-01-15 14:03:41 +0100373mechanism.
374
Matthias Andreas Benkard37e804b2021-09-04 22:38:08 +0200375**Example:**
376
377[source,java]
378----
379@Singleton
380@Unremovable
381public final class TraceLogParameterProvider implements StructuredParameterProvider, LabelProvider {
382
383 @Override
384 public StructuredParameter getParameter() {
385 var b = Json.createObjectBuilder();
386 b.add("traceId", Span.current().getSpanContext().getTraceId());
387 b.add("spanId", Span.current().getSpanContext().getSpanId());
388 return () -> b;
389 }
390
391 @Override
392 public Collection<Label> getLabels() {
393 return List.of(Label.of("requestId", "123"));
394 }
395}
396----
397
398Result:
399
400[source,json]
401----
402{
403 "jsonPayload": {
404 "message": "Request rejected: unauthorized.",
405 "traceId": "39f9a49a9567a8bd7087b708f8932550",
406 "spanId": "c7431b14630b633d"
407 },
408 "labels": {
409 "requestId": "123"
410 }
411}
412----
413
414
415=== Using the Mapped Diagnostic Context
416
417Any key–value pairs in JBoss Logging's thread-local
418https://docs.jboss.org/jbosslogging/latest/org/jboss/logging/MDC.html[MDC]
419are added to the resulting JSON.
420
421**Example:**
422
423[source,java]
424----
425MDC.put("resource", "/users/mulk");
426MDC.put("method", "PATCH");
427logger.logf("Request rejected: unauthorized.");
428----
429
430Result:
431
432[source,json]
433----
434{
435 "jsonPayload": {
436 "message": "Request rejected: unauthorized.",
437 "resource": "/users/mulk",
438 "method": "PATCH"
439 }
440}
441----
Matthias Andreas Benkard47df8be2024-06-23 16:24:11 +0200442
443
444== Development
445
446=== Running the Tests
447
448To run the **test suite**, run:
449
450[source,shell]
451----
452$ mvn verify
453----
454
455To run the **benchmarks**, run:
456
457[source,shell]
458----
459$ mvn verify -Pbenchmark
460----