diff --git a/utility-logging/src/main/java/com/dua3/utility/logging/LogEntry.java b/utility-logging/src/main/java/com/dua3/utility/logging/LogEntry.java index 985403cf..65d4e409 100644 --- a/utility-logging/src/main/java/com/dua3/utility/logging/LogEntry.java +++ b/utility-logging/src/main/java/com/dua3/utility/logging/LogEntry.java @@ -1,165 +1,61 @@ package com.dua3.utility.logging; -import com.dua3.cabe.annotations.Nullable; - import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.time.Instant; import java.time.format.DateTimeFormatter; -import java.util.Objects; -import java.util.function.Consumer; -/** - * Represents a log entry with information about the log message, time, level, logger, and optional marker and throwable. - */ -public final class LogEntry { - private static final char[] NEW_LINE = String.format("%n").toCharArray(); - private final String loggerName; - private final Instant time; - private final LogLevel level; - private final String marker; - private Consumer messageFormatter; - private String formattedMessage; - private final Throwable throwable; +public interface LogEntry { + String message(); - /** - * Creates a new LogEntry object. - * - * @param loggerName The name of the logger. - * @param time The time the log entry was created, represented as an Instant object. - * @param level The log level of the entry. - * @param marker The marker associated with the log entry. Can be null. - * @param messageFormatter A Supplier that provides the formatted log message. - * @param throwable The throwable associated with the log entry. Can be null. - */ - public LogEntry(String loggerName, Instant time, LogLevel level, @Nullable String marker, Consumer messageFormatter, - @Nullable Throwable throwable) { - this.loggerName = loggerName; - this.time = time; - this.level = level; - this.marker = marker; - this.messageFormatter = messageFormatter; - this.throwable = throwable; - } + String loggerName(); - /** - * Formats the message using MessageFormatter.basicArrayFormat. - * - * @return the formatted message as a string. - */ - public String formatMessage() { - if (messageFormatter != null) { - StringBuilder sb = new StringBuilder(100); - messageFormatter.accept(sb); - formattedMessage = sb.toString(); - messageFormatter = null; - } - return formattedMessage; - } + Instant time(); - /** - * Formats the throwable object by printing its stack trace. - * If the throwable object is null, it returns an empty string. - * - * @return the formatted stack trace as a string. - */ - private void appendThrowable(StringBuilder sb) { - if (throwable == null) { - sb.append("null"); - } else { - try (StringWriter sw = new StringWriter(200); PrintWriter pw = new PrintWriter(sw)) { - throwable.printStackTrace(pw); - sb.append(sw); - } catch (IOException e) { - sb.append(throwable); - } - } - } + LogLevel level(); - @Override - public String toString() { - return format("", ""); - } + String marker(); + + Throwable throwable(); + + default String format(String prefix, String suffix) { + class Constants { + private static final char[] NEW_LINE = String.format("%n").toCharArray(); + }; - String format(String prefix, String suffix) { StringBuilder sb = new StringBuilder(100); sb.append(prefix); - sb.append('[').append(level).append(']'); + sb.append('[').append(level()).append(']'); sb.append(' '); - sb.append(DateTimeFormatter.ISO_INSTANT.format(time)); + sb.append(DateTimeFormatter.ISO_INSTANT.format(time())); sb.append(' '); - messageFormatter.accept(sb); - sb.append(suffix); + sb.append(loggerName()); + sb.append(' '); + sb.append(message()); if (throwable() != null) { - sb.append(NEW_LINE); + sb.append(Constants.NEW_LINE); appendThrowable(sb); } - sb.append(loggerName); + sb.append(suffix); return sb.toString(); } /** - * Returns the logger name. - * - * @return the name of the logger. - */ - public String loggerName() { - return loggerName; - } - - /** - * Returns the time when the log entry was created. - * - * @return the time when the log entry was created. - */ - public Instant time() { - return time; - } - - /** - * Returns the log level of the log entry. - * - * @return the log level of the log entry. - */ - public LogLevel level() { - return level; - } - - /** - * Returns the marker of the log entry. - * - * @return the marker of the log entry. - */ - public String marker() { - return marker; - } - - /** - * Returns the throwable associated with this log entry. - * - * @return the throwable associated with this log entry. + * Appends the throwable object to the supplied StringBuilder instance by printing its stack trace. + * @param sb the StringBuilder to append to */ - public Throwable throwable() { - return throwable; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (LogEntry) obj; - return Objects.equals(this.loggerName, that.loggerName) && - Objects.equals(this.time, that.time) && - Objects.equals(this.level, that.level) && - Objects.equals(this.marker, that.marker) && - Objects.equals(this.messageFormatter, that.messageFormatter) && - Objects.equals(this.throwable, that.throwable); - } - - @Override - public int hashCode() { - return Objects.hash(loggerName, time, level, marker, messageFormatter, throwable); + private void appendThrowable(StringBuilder sb) { + Throwable t = throwable(); + if (t == null) { + sb.append("null"); + } else { + try (StringWriter sw = new StringWriter(200); PrintWriter pw = new PrintWriter(sw)) { + t.printStackTrace(pw); + sb.append(sw); + } catch (IOException e) { + sb.append(t); + } + } } - } diff --git a/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogAppenderLog4j.java b/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogAppenderLog4j.java index 30e3d254..6f16a16b 100644 --- a/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogAppenderLog4j.java +++ b/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogAppenderLog4j.java @@ -1,12 +1,8 @@ package com.dua3.utility.logging.log4j; import com.dua3.cabe.annotations.Nullable; -import com.dua3.utility.logging.LogEntry; import com.dua3.utility.logging.LogEntryDispatcher; import com.dua3.utility.logging.LogEntryHandler; -import com.dua3.utility.logging.LogLevel; -import org.apache.logging.log4j.Level; -import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; @@ -15,16 +11,11 @@ import org.apache.logging.log4j.core.config.plugins.PluginElement; import org.apache.logging.log4j.core.config.plugins.PluginFactory; import org.apache.logging.log4j.core.layout.PatternLayout; -import org.apache.logging.log4j.message.Message; -import org.apache.logging.log4j.message.ReusableMessage; -import org.apache.logging.log4j.spi.StandardLevel; import java.io.Serializable; import java.lang.ref.WeakReference; -import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; /** * This class is an implementation of the Log4j Appender and LogEntryHandlerPool interfaces. @@ -102,27 +93,7 @@ public void append(LogEvent event) { if (handler==null) { cleanup = true; } else { - Marker marker = event.getMarker(); - Consumer messageAppender; - Message message = event.getMessage(); - if (message instanceof ReusableMessage rm) { - // for resusable messages, the message must be formatted instantly - StringBuilder sbMsg = new StringBuilder(80); - rm.formatTo(sbMsg); - String formattedMessage = sbMsg.toString(); - messageAppender = sb -> sb.append(formattedMessage); - } else { - messageAppender = sb -> sb.append(message.getFormattedMessage()); - } - Throwable thrown = event.getThrown(); - handler.handleEntry(new LogEntry( - event.getLoggerName(), - Instant.ofEpochMilli(event.getTimeMillis()), - translate(event.getLevel()), - marker == null ? null : marker.getName(), - messageAppender, - thrown - )); + handler.handleEntry(new LogEntryLog4J(event)); } } if (cleanup) { @@ -130,32 +101,6 @@ public void append(LogEvent event) { } } - /** - * Translates a Log4J Level object to a custom LogLevel object. - * - * This method takes a Log4J Level object as parameter and returns the corresponding custom LogLevel object. - * The translation is based on the integer level value of the Log4J Level object. - * - * @param level the Log4J Level object to be translated - * @return the custom LogLevel object that corresponds to the given Log4J Level object - */ - private static LogLevel translate(Level level) { - int levelInt = level.intLevel(); - if (levelInt > StandardLevel.DEBUG.intLevel()) { - return LogLevel.TRACE; - } - if (levelInt > StandardLevel.INFO.intLevel()) { - return LogLevel.DEBUG; - } - if (levelInt > StandardLevel.WARN.intLevel()) { - return LogLevel.INFO; - } - if (levelInt > StandardLevel.ERROR.intLevel()) { - return LogLevel.WARN; - } - return LogLevel.ERROR; - } - @Override public void addLogEntryHandler(LogEntryHandler handler) { handlers.add(new WeakReference<>(handler)); diff --git a/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogEntryLog4J.java b/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogEntryLog4J.java new file mode 100644 index 00000000..8938918d --- /dev/null +++ b/utility-logging/utility-logging-log4j/src/main/java/com/dua3/utility/logging/log4j/LogEntryLog4J.java @@ -0,0 +1,156 @@ +package com.dua3.utility.logging.log4j; + +import com.dua3.cabe.annotations.Nullable; +import com.dua3.utility.logging.LogEntry; +import com.dua3.utility.logging.LogLevel; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.ReusableMessage; +import org.apache.logging.log4j.spi.StandardLevel; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.Instant; +import java.time.InstantSource; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Represents a log entry with information about the log message, time, level, logger, and optional marker and throwable. + */ +public final class LogEntryLog4J implements LogEntry { + private final String loggerName; + private final Instant time; + private final LogLevel level; + private final String marker; + private Supplier messageFormatter; + private String formattedMessage; + private final Throwable throwable; + + /** + * Creates a new LogEntry object. + * + * @param event the log event. + */ + public LogEntryLog4J(LogEvent event) { + this.loggerName = event.getLoggerName(); + var instant = event.getInstant(); + this.time = Instant.ofEpochSecond(instant.getEpochSecond(), instant.getNanoOfSecond()); + this.level = translate(event.getLevel()); + var marker = event.getMarker(); + this.marker = marker == null ? "" : marker.getName(); + Message message = event.getMessage(); + if (message instanceof ReusableMessage rm) { + // for reusable messages, the message must be formatted instantly + StringBuilder sbMsg = new StringBuilder(80); + rm.formatTo(sbMsg); + this.messageFormatter = null; + this.formattedMessage = sbMsg.toString(); + } else { + this.messageFormatter = message::getFormattedMessage; + this.formattedMessage = null; + } + this.throwable = event.getThrown(); + } + + /** + * Formats the message using MessageFormatter.basicArrayFormat. + * + * @return the formatted message as a string. + */ + @Override + public String message() { + if (messageFormatter != null) { + formattedMessage = messageFormatter.get(); + messageFormatter = null; + } + return formattedMessage; + } + + @Override + public String toString() { + return format("", ""); + } + + /** + * Returns the logger name. + * + * @return the name of the logger. + */ + @Override + public String loggerName() { + return loggerName; + } + + /** + * Returns the time when the log entry was created. + * + * @return the time when the log entry was created. + */ + @Override + public Instant time() { + return time; + } + + /** + * Returns the log level of the log entry. + * + * @return the log level of the log entry. + */ + @Override + public LogLevel level() { + return level; + } + + /** + * Returns the marker of the log entry. + * + * @return the marker of the log entry. + */ + @Override + public String marker() { + return marker; + } + + /** + * Returns the throwable associated with this log entry. + * + * @return the throwable associated with this log entry. + */ + @Override + public Throwable throwable() { + return throwable; + } + + /** + * Translates a Log4J Level object to a custom LogLevel object. + * + * This method takes a Log4J Level object as parameter and returns the corresponding custom LogLevel object. + * The translation is based on the integer level value of the Log4J Level object. + * + * @param level the Log4J Level object to be translated + * @return the custom LogLevel object that corresponds to the given Log4J Level object + */ + private static LogLevel translate(Level level) { + int levelInt = level.intLevel(); + if (levelInt > StandardLevel.DEBUG.intLevel()) { + return LogLevel.TRACE; + } + if (levelInt > StandardLevel.INFO.intLevel()) { + return LogLevel.DEBUG; + } + if (levelInt > StandardLevel.WARN.intLevel()) { + return LogLevel.INFO; + } + if (levelInt > StandardLevel.ERROR.intLevel()) { + return LogLevel.WARN; + } + return LogLevel.ERROR; + } + +} diff --git a/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LogEntrySlf4j.java b/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LogEntrySlf4j.java new file mode 100644 index 00000000..e1930f80 --- /dev/null +++ b/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LogEntrySlf4j.java @@ -0,0 +1,131 @@ +package com.dua3.utility.logging.slf4j; + +import com.dua3.cabe.annotations.Nullable; +import com.dua3.utility.logging.LogEntry; +import com.dua3.utility.logging.LogLevel; +import org.slf4j.Marker; +import org.slf4j.event.Level; +import org.slf4j.spi.LocationAwareLogger; + +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Represents a log entry with information about the log message, time, level, logger, and optional marker and throwable. + */ +public final class LogEntrySlf4j implements LogEntry { + private static final char[] NEW_LINE = String.format("%n").toCharArray(); + private final String loggerName; + private final Instant time; + private final LogLevel level; + private final String marker; + private Supplier messageFormatter; + private String formattedMessage; + private final Throwable throwable; + + public LogEntrySlf4j(String loggerName, Level level, @Nullable Marker marker, Supplier messageFormatter, + @Nullable Throwable throwable) { + this.loggerName = loggerName; + this.time = Instant.now(); + this.level = translate(level); + this.marker = marker == null ? "" : marker.getName(); + this.messageFormatter = messageFormatter; + this.throwable = throwable; + } + + /** + * Formats the message using MessageFormatter.basicArrayFormat. + * + * @return the formatted message as a string. + */ + @Override + public String message() { + if (messageFormatter != null) { + formattedMessage = messageFormatter.get(); + messageFormatter = null; + } + return formattedMessage; + } + + @Override + public String toString() { + return format("", ""); + } + + /** + * Returns the logger name. + * + * @return the name of the logger. + */ + @Override + public String loggerName() { + return loggerName; + } + + /** + * Returns the time when the log entry was created. + * + * @return the time when the log entry was created. + */ + @Override + public Instant time() { + return time; + } + + /** + * Returns the log level of the log entry. + * + * @return the log level of the log entry. + */ + @Override + public LogLevel level() { + return level; + } + + /** + * Returns the marker of the log entry. + * + * @return the marker of the log entry. + */ + @Override + public String marker() { + return marker; + } + + /** + * Returns the throwable associated with this log entry. + * + * @return the throwable associated with this log entry. + */ + @Override + public Throwable throwable() { + return throwable; + } + + /** + * Translates a given SLF4J Level object to an equivalent LogLevel object. + * + * @param level the SLF4J Level object to be translated + * @return the translated LogLevel object + */ + private static LogLevel translate(Level level) { + int levelInt = level.toInt(); + if (levelInt < LocationAwareLogger.DEBUG_INT) { + return LogLevel.TRACE; + } + if (levelInt < LocationAwareLogger.INFO_INT) { + return LogLevel.DEBUG; + } + if (levelInt < LocationAwareLogger.WARN_INT) { + return LogLevel.INFO; + } + if (levelInt < LocationAwareLogger.ERROR_INT) { + return LogLevel.WARN; + } + return LogLevel.ERROR; + } + +} diff --git a/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LoggerSlf4j.java b/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LoggerSlf4j.java index d9d3a24f..2e3fc182 100644 --- a/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LoggerSlf4j.java +++ b/utility-logging/utility-logging-slf4j/src/main/java/com/dua3/utility/logging/slf4j/LoggerSlf4j.java @@ -1,7 +1,6 @@ package com.dua3.utility.logging.slf4j; import com.dua3.cabe.annotations.Nullable; -import com.dua3.utility.logging.LogEntry; import com.dua3.utility.logging.LogEntryHandler; import com.dua3.utility.logging.LogLevel; import org.slf4j.Marker; @@ -50,14 +49,13 @@ protected String getFullyQualifiedCallerName() { @Override protected void handleNormalizedLoggingCall(Level level, @Nullable Marker marker, String messagePattern, @Nullable Object[] arguments, @Nullable Throwable throwable) { - String markerName = marker != null ? marker.getName() : null; boolean cleanup = false; for (WeakReference ref: handlers) { LogEntryHandler handler = ref.get(); if (handler==null) { cleanup = true; } else { - handler.handleEntry(new LogEntry(name, Instant.now(), translate(level), markerName, sb -> sb.append(MessageFormatter.basicArrayFormat(messagePattern, arguments)), throwable)); + handler.handleEntry(new LogEntrySlf4j(name, level, marker, () -> MessageFormatter.basicArrayFormat(messagePattern, arguments), throwable)); } } if (cleanup) { @@ -65,29 +63,6 @@ protected void handleNormalizedLoggingCall(Level level, @Nullable Marker marker, } } - /** - * Translates a given SLF4J Level object to an equivalent LogLevel object. - * - * @param level the SLF4J Level object to be translated - * @return the translated LogLevel object - */ - private static LogLevel translate(Level level) { - int levelInt = level.toInt(); - if (levelInt < LocationAwareLogger.DEBUG_INT) { - return LogLevel.TRACE; - } - if (levelInt < LocationAwareLogger.INFO_INT) { - return LogLevel.DEBUG; - } - if (levelInt < LocationAwareLogger.WARN_INT) { - return LogLevel.INFO; - } - if (levelInt < LocationAwareLogger.ERROR_INT) { - return LogLevel.WARN; - } - return LogLevel.ERROR; - } - @Override public boolean isTraceEnabled() { return getLevel().toInt() <= Level.TRACE.toInt(); diff --git a/utility-samples/utility-samples-slf4j/src/main/java/com/dua3/utility/samples/slf4j/SwingComponentsSampleSlf4j.java b/utility-samples/utility-samples-slf4j/src/main/java/com/dua3/utility/samples/slf4j/SwingComponentsSampleSlf4j.java index d1c564a2..c15b937c 100644 --- a/utility-samples/utility-samples-slf4j/src/main/java/com/dua3/utility/samples/slf4j/SwingComponentsSampleSlf4j.java +++ b/utility-samples/utility-samples-slf4j/src/main/java/com/dua3/utility/samples/slf4j/SwingComponentsSampleSlf4j.java @@ -32,6 +32,11 @@ @SuppressWarnings({"ClassWithMultipleLoggers", "BusyWait"}) public class SwingComponentsSampleSlf4j extends JFrame { + static { + java.util.logging.LogManager.getLogManager().reset(); + SLF4JBridgeHandler.install(); + } + public static final String TASK_INDETERMINATE_1 = "Indeterminate Task"; public static final String TASK_INDETERMINATE_2 = "Another Indeterminate Task"; public static final int SLEEP_MILLIS = 25; @@ -39,11 +44,6 @@ public class SwingComponentsSampleSlf4j extends JFrame { private static final java.util.logging.Logger JUL_LOGGER = java.util.logging.Logger.getLogger("JUL." + SwingComponentsSampleSlf4j.class.getName()); private static final org.apache.logging.log4j.Logger LOG4J_LOGGER = org.apache.logging.log4j.LogManager.getLogger("LOG4J." + SwingComponentsSampleSlf4j.class.getName()); - static { - java.util.logging.LogManager.getLogManager().reset(); - SLF4JBridgeHandler.install(); - } - @SuppressWarnings("UnsecureRandomNumberGeneration") // used only to create a random sequence of log levels in tests private final Random random = new Random(); private final AtomicInteger n = new AtomicInteger(); @@ -164,13 +164,14 @@ private void init() { } int nr = n.incrementAndGet(); - String msg = "Message " + nr + "."; int implementation = random.nextInt(3); int bound = implementation == 1 ? 6 : 5; int levelInt = random.nextInt(bound); LogLevel level = LogLevel.values()[implementation == 1 ? Math.max(0, levelInt - 1) : levelInt]; + String msg = "Message #%d, imp %s, original integer level %d, level %s".formatted(nr, implementation, levelInt, level); + switch (implementation) { case 0 -> { switch (levelInt) { diff --git a/utility-swing/src/main/java/com/dua3/utility/swing/SwingLogPane.java b/utility-swing/src/main/java/com/dua3/utility/swing/SwingLogPane.java index a064b845..e9233a56 100644 --- a/utility-swing/src/main/java/com/dua3/utility/swing/SwingLogPane.java +++ b/utility-swing/src/main/java/com/dua3/utility/swing/SwingLogPane.java @@ -21,7 +21,6 @@ import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; -import javax.swing.RowFilter; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; @@ -376,7 +375,7 @@ public String get(LogEntry entry) { MESSAGE { @Override public String get(LogEntry entry) { - return entry.formatMessage(); + return entry.message(); } }, THROWABLE {