Skip to content

Commit

Permalink
4.x: JSON Formatter for JUL (#9260)
Browse files Browse the repository at this point in the history
JSON Formatter for JUL and tests.
A fix of escaping when writing in HSON.
Switched one packaging test to use JSON format.

Signed-off-by: Tomas Langer <[email protected]>
  • Loading branch information
tomas-langer authored Sep 20, 2024
1 parent c91a232 commit e7ad9de
Show file tree
Hide file tree
Showing 9 changed files with 539 additions and 52 deletions.
4 changes: 2 additions & 2 deletions logging/jul/etc/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2021 Oracle and/or its affiliates.
Copyright (c) 2021, 2024 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand All @@ -25,7 +25,7 @@
<Match>
<!-- Stack trace is written to log file -->
<Class name="io.helidon.logging.jul.HelidonFormatter"/>
<Method name="formatRow"/>
<Method name="parameters"/>
<Bug pattern="INFORMATION_EXPOSURE_THROUGH_AN_ERROR_MESSAGE"/>
</Match>

Expand Down
9 changes: 9 additions & 0 deletions logging/jul/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
<groupId>io.helidon.common</groupId>
<artifactId>helidon-common</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.metadata</groupId>
<artifactId>helidon-metadata-hson</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand All @@ -54,6 +58,11 @@
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.common.testing</groupId>
<artifactId>helidon-common-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,7 @@
package io.helidon.logging.jul;

import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.StreamHandler;

Expand All @@ -35,9 +36,15 @@ public class HelidonConsoleHandler extends StreamHandler {
* </ul>.
*/
public HelidonConsoleHandler() {
super();
setOutputStream(System.out);
setLevel(Level.ALL); // Handlers should not filter, loggers should
setFormatter(new HelidonFormatter());
if (LogManager.getLogManager().getProperty(HelidonConsoleHandler.class.getName() + ".level") == null) {
setLevel(Level.ALL); // Handlers should not filter, loggers should
}
// only set this if none set
if (LogManager.getLogManager().getProperty(HelidonConsoleHandler.class.getName() + ".formatter") == null) {
setFormatter(new HelidonFormatter());
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020, 2021 Oracle and/or its affiliates.
* Copyright (c) 2020, 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -36,12 +36,13 @@
* It also supports replacement of {@code "!thread!"} with the current thread.
*/
public class HelidonFormatter extends SimpleFormatter {
private static final String THREAD = "thread";
private static final String THREAD_TOKEN = "!" + THREAD + "!";
private static final Pattern THREAD_PATTERN = Pattern.compile(THREAD_TOKEN);
private static final Pattern X_VALUE = Pattern.compile("(\\s?%X\\{)(\\S*?)(})");
private static final Map<String, Pattern> PATTERN_CACHE = new HashMap<>();
private static final String JUL_FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format";
static final String THREAD = "thread";
static final String THREAD_TOKEN = "!" + THREAD + "!";
static final Pattern THREAD_PATTERN = Pattern.compile(THREAD_TOKEN);
static final Pattern X_VALUE = Pattern.compile("(\\s?%X\\{)(\\S*?)(})");
static final Map<String, Pattern> PATTERN_CACHE = new HashMap<>();
static final String JUL_FORMAT_PROP_KEY = "java.util.logging.SimpleFormatter.format";

private final String format = LogManager.getLogManager().getProperty(JUL_FORMAT_PROP_KEY);
private final Set<String> parsedProps = new HashSet<>();
private final boolean thread;
Expand All @@ -57,28 +58,22 @@ public HelidonFormatter() {
}
}

@Override
public String format(LogRecord record) {
String message = thread ? thread() : format;
for (String parsedKey : parsedProps) {
String value = HelidonMdc.get(parsedKey).orElse("");
message = PATTERN_CACHE.computeIfAbsent(parsedKey, key -> Pattern.compile("%X\\{" + key + "}"))
.matcher(message).replaceAll(value);
}
return formatRow(record, message);
}

private String thread() {
/*
Replace the thread pattern in the format with the current thread value
*/
static String thread(String format) {
String currentThread = Thread.currentThread().toString();
String message = PATTERN_CACHE.computeIfAbsent(THREAD, key -> Pattern.compile("%X\\{" + THREAD + "}"))
.matcher(format).replaceAll(currentThread);
message = THREAD_PATTERN.matcher(message).replaceAll(currentThread);
return message;
}

//Copied from SimpleFormatter
private String formatRow(LogRecord record, String format) {
ZonedDateTime zdt = ZonedDateTime.ofInstant(
/*
All parameters expected by simple formatter (and json formatter as well)
*/
static Object[] parameters(LogRecord record, String formattedMessage) {
var timestamp = ZonedDateTime.ofInstant(
record.getInstant(), ZoneId.systemDefault());
String source;
if (record.getSourceClassName() != null) {
Expand All @@ -89,7 +84,7 @@ private String formatRow(LogRecord record, String format) {
} else {
source = record.getLoggerName();
}
String message = formatMessage(record);

String throwable = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
Expand All @@ -99,12 +94,31 @@ private String formatRow(LogRecord record, String format) {
pw.close();
throwable = sw.toString();
}

Object[] result = new Object[6];
result[0] = timestamp;
result[1] = source;
result[2] = record.getLoggerName();
result[3] = record.getLevel().getName();
result[4] = formattedMessage;
result[5] = throwable;

return result;
}

@Override
public String format(LogRecord record) {
String message = thread ? thread(format) : format;
for (String parsedKey : parsedProps) {
String value = HelidonMdc.get(parsedKey).orElse("");
message = PATTERN_CACHE.computeIfAbsent(parsedKey, key -> Pattern.compile("%X\\{" + key + "}"))
.matcher(message).replaceAll(value);
}
return formatRow(record, message);
}

private String formatRow(LogRecord record, String format) {
return String.format(format,
zdt,
source,
record.getLoggerName(),
record.getLevel().getLocalizedName(),
message,
throwable);
parameters(record, super.formatMessage(record)));
}
}
Loading

1 comment on commit e7ad9de

@hrstoyanov
Copy link
Contributor

@hrstoyanov hrstoyanov commented on e7ad9de Sep 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @tomas-langer - json structured JUL logging is a very interesting thing! Is there any existing or upcoming documentation around it?

Please sign in to comment.