From 5a5af1e881ed2f3a116eb0a44a2fc5d3e381636d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sat, 8 Aug 2026 03:18:56 +0200 Subject: [PATCH] Logging foundation: structured LogEvent, JUL handler, dependency cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the foundational logging infrastructure for the new build reporting pipeline: - LogEvent API: structured log event with level, message, loggerName, sourceClassName, sourceMethodName, threadId, and instant - MavenSimpleLogger: intercept SLF4J log calls and route them through LogSink for structured capture by ProjectBuildLogAppender - MavenJulHandler: custom JUL handler that bypasses SLF4J when LogSink is available, preserving JUL metadata (sourceClassName, threadId) that SLF4JBridgeHandler discards - Log.trace() and Log.child(): API additions for trace-level logging and hierarchical logger names (parentLogger.childName) - Remove dead logging dependencies: jul-to-slf4j, logback-classic, LogbackConfiguration, Log4j2Configuration — the LogSink pipeline bypasses SLF4J for log capture, making pluggable SLF4J backend shims unnecessary Co-Authored-By: Claude Opus 4.6 --- apache-maven/pom.xml | 7 - .../maven/api/build/report/LogEvent.java | 150 +++++++++++ .../maven/api/build/report/LogLevel.java | 32 +-- .../maven/api/build/report/package-info.java | 33 +++ .../java/org/apache/maven/api/plugin/Log.java | 66 +++++ compat/maven-embedder/pom.xml | 5 - impl/maven-cli/pom.xml | 9 - .../maven/cling/invoker/LookupInvoker.java | 7 +- .../logging/impl/LogbackConfiguration.java | 46 ---- .../maven/slf4j-configuration.properties | 2 - .../maven/internal/build/DefaultLogEvent.java | 75 ++++++ .../maven/internal/impl/DefaultLog.java | 47 +++- .../maven/logging/BuildEventListener.java | 3 +- .../logging/LoggingExecutionListener.java | 11 + .../logging/ProjectBuildLogAppender.java | 86 +++++- .../logging/SimpleBuildEventListener.java | 6 +- .../plugin/DefaultBuildPluginManager.java | 2 +- .../internal/DefaultMavenPluginManager.java | 2 +- .../apache/maven/slf4j/MavenBaseLogger.java | 36 ++- .../apache/maven/slf4j/MavenJulHandler.java | 249 ++++++++++++++++++ .../apache/maven/slf4j/MavenSimpleLogger.java | 132 +++++++++- pom.xml | 12 - 22 files changed, 896 insertions(+), 122 deletions(-) create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogEvent.java rename impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java => api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogLevel.java (56%) create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/build/report/package-info.java delete mode 100644 impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java create mode 100644 impl/maven-core/src/main/java/org/apache/maven/internal/build/DefaultLogEvent.java create mode 100644 impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenJulHandler.java diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml index 13d809a7b4cf..55fb4e61bb8e 100644 --- a/apache-maven/pom.xml +++ b/apache-maven/pom.xml @@ -78,13 +78,6 @@ under the License. ${slf4jVersion} runtime - - - org.slf4j - jul-to-slf4j - ${slf4jVersion} - runtime - org.apache.maven.resolver maven-resolver-connector-basic diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogEvent.java b/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogEvent.java new file mode 100644 index 000000000000..08bb7e190d37 --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogEvent.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.api.build.report; + +import java.time.Instant; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.annotations.Nullable; + +/** + * A structured log event captured during the build. + *

+ * Each event carries the log level, timestamp, message, and optionally + * the logger name and a stack trace. This replaces raw log line strings + * in the build report, enabling programmatic filtering by level and + * correlation by timestamp. + *

+ * Events originating from JUL ({@code java.util.logging}) may carry + * additional metadata that is normally lost when routed through the + * SLF4J bridge: the source class name, source method name, and thread + * identifier. These fields are populated by Maven's custom JUL + * {@link java.util.logging.Handler} and are {@code null} for events + * from other logging paths (Log API, SLF4J direct). + * + * @since 4.1.0 + */ +@Experimental +public interface LogEvent { + + /** + * When this log event was produced (wall-clock time). + * + * @return the event instant, never {@code null} + */ + @Nonnull + Instant timestamp(); + + /** + * The severity level of this log event. + * + * @return the log level, never {@code null} + */ + @Nonnull + LogLevel level(); + + /** + * The log message, without level prefix or timestamp formatting. + * + * @return the formatted message, never {@code null} + */ + @Nonnull + String message(); + + /** + * The name of the logger that produced this event + * (e.g. {@code "org.apache.maven.plugins.compiler.CompilerMojo"}). + * + * @return the logger name, or {@code null} if unavailable + */ + @Nullable + String loggerName(); + + /** + * The stack trace associated with this event, if an exception was logged. + *

+ * The trace is formatted as a multi-line string and may be truncated + * for very deep stack traces. + * + * @return the stack trace string, or {@code null} if no exception was logged + */ + @Nullable + String stackTrace(); + + /** + * The fully formatted log line as rendered for console output, including + * the level prefix, timestamp, and any ANSI styling applied by the logger. + *

+ * This is the string that would be printed to the terminal in verbose mode. + * Console renderers that just need pass-through output can use this directly, + * while renderers that apply custom formatting (e.g. rich mode) can use the + * structured fields ({@link #level()}, {@link #message()}) instead. + *

+ * May be {@code null} if the event was created outside the SLF4J pipeline + * (e.g. in tests or by programmatic construction). + * + * @return the formatted log line, or {@code null} + */ + @Nullable + String formattedMessage(); + + // ---- JUL metadata (populated only for events from java.util.logging) ---- + + /** + * The fully qualified class name of the source that issued the log call, + * as reported by JUL's {@code LogRecord.getSourceClassName()}. + *

+ * This field is only populated for events originating from JUL. For + * events from the Maven Log API or SLF4J, it is {@code null}. + * + * @return the source class name, or {@code null} + * @since 4.1.0 + */ + @Nullable + default String sourceClassName() { + return null; + } + + /** + * The method name of the source that issued the log call, + * as reported by JUL's {@code LogRecord.getSourceMethodName()}. + * + * @return the source method name, or {@code null} + * @since 4.1.0 + */ + @Nullable + default String sourceMethodName() { + return null; + } + + /** + * The thread identifier from which this log event originated, + * as reported by JUL's {@code LogRecord.getLongThreadID()}. + *

+ * Returns {@code -1} if the thread ID is not available (i.e. for + * non-JUL events). + * + * @return the thread ID, or {@code -1} if unavailable + * @since 4.1.0 + */ + default long threadId() { + return -1; + } +} diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java b/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogLevel.java similarity index 56% rename from impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java rename to api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogLevel.java index bbd487fa5f87..684ea610a5bc 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/Log4j2Configuration.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/LogLevel.java @@ -16,29 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.cling.logging.impl; +package org.apache.maven.api.build.report; -import org.apache.maven.cling.logging.BaseSlf4jConfiguration; +import org.apache.maven.api.annotations.Experimental; /** - * Configuration for slf4j-log4j2. + * Log severity levels, mirroring the standard SLF4J levels. * - * @since 3.1.0 + * @since 4.1.0 + * @see LogEvent#level() */ -public class Log4j2Configuration extends BaseSlf4jConfiguration { - @Override - public void setRootLoggerLevel(Level level) { - String value = - switch (level) { - case DEBUG -> "debug"; - case INFO -> "info"; - default -> "error"; - }; - System.setProperty("maven.logging.root.level", value); - } - - @Override - public void activate() { - // no op - } +@Experimental +public enum LogLevel { + TRACE, + DEBUG, + INFO, + WARN, + ERROR } diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/package-info.java b/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/package-info.java new file mode 100644 index 000000000000..8aee68ff901e --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/build/report/package-info.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Structured build report data model. + *

+ * This package provides structured representations of build execution + * data, including log events and (in future) full build reports. + * {@link org.apache.maven.api.build.report.LogEvent} is the foundational + * type representing a single structured log entry captured during the build. + * + * @since 4.1.0 + */ +@Experimental +package org.apache.maven.api.build.report; + +import org.apache.maven.api.annotations.Experimental; diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java index 50627efd86e3..9c020506f151 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/plugin/Log.java @@ -36,6 +36,51 @@ @Experimental @Provider public interface Log { + /** + * {@return true if the trace error level is enabled} + * @since 4.1.0 + */ + boolean isTraceEnabled(); + + /** + * Sends a message to the user in the trace error level. + *

+ * Trace is intended for Maven core internals and low-level framework + * diagnostics. Plugin authors should normally use {@link #debug} for + * developer-facing diagnostic output. + * + * @param content the message to log + * @since 4.1.0 + */ + void trace(CharSequence content); + + /** + * Sends a message (and accompanying exception) to the user at the trace error level. + * + * @param content the message to log + * @param error the error that caused this log + * @since 4.1.0 + */ + void trace(CharSequence content, Throwable error); + + /** + * Sends an exception to the user in the trace error level. + * + * @param error the error that caused this log + * @since 4.1.0 + */ + void trace(Throwable error); + + /** + * @since 4.1.0 + */ + void trace(Supplier content); + + /** + * @since 4.1.0 + */ + void trace(Supplier content, Throwable error); + /** * {@return true if the debug error level is enabled} */ @@ -43,6 +88,10 @@ public interface Log { /** * Sends a message to the user in the debug error level. + *

+ * Debug is the recommended level for diagnostic output that helps + * plugin users troubleshoot build problems (e.g. resolved paths, + * computed values). For Maven core internals, prefer {@link #trace}. * * @param content the message to log */ @@ -167,4 +216,21 @@ public interface Log { void error(Supplier content); void error(Supplier content, Throwable error); + + /** + * Returns a child logger whose name is derived from this logger's name + * by appending {@code "." + name}. This allows plugins to create + * sub-loggers for different concerns while keeping hierarchical level + * control (e.g. setting the level for the parent silences the children). + *

+ * The default implementation returns {@code this} so that existing + * implementations continue to work without changes. + * + * @param name the child logger name segment (must not be {@code null}) + * @return a child {@code Log}, never {@code null} + * @since 4.1.0 + */ + default Log child(String name) { + return this; + } } diff --git a/compat/maven-embedder/pom.xml b/compat/maven-embedder/pom.xml index 2df8588ec116..56730ccb4326 100644 --- a/compat/maven-embedder/pom.xml +++ b/compat/maven-embedder/pom.xml @@ -163,11 +163,6 @@ under the License. commons-cli - - ch.qos.logback - logback-classic - true - org.jline jansi-core diff --git a/impl/maven-cli/pom.xml b/impl/maven-cli/pom.xml index 5d7304af5e3a..56ac62ed6f3e 100644 --- a/impl/maven-cli/pom.xml +++ b/impl/maven-cli/pom.xml @@ -195,19 +195,10 @@ under the License. org.slf4j slf4j-api - - org.slf4j - jul-to-slf4j - commons-cli commons-cli - - ch.qos.logback - logback-classic - true - org.junit.jupiter diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index 40d3bb4926a4..44c7f89a258f 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -83,6 +83,7 @@ import org.apache.maven.logging.ProjectBuildLogAppender; import org.apache.maven.logging.SimpleBuildEventListener; import org.apache.maven.logging.api.LogLevelRecorder; +import org.apache.maven.slf4j.MavenJulHandler; import org.apache.maven.slf4j.MavenSimpleLogger; import org.codehaus.plexus.PlexusContainer; import org.jline.terminal.Terminal; @@ -91,7 +92,6 @@ import org.jline.terminal.spi.TerminalExt; import org.jline.utils.OSUtils; import org.slf4j.LoggerFactory; -import org.slf4j.bridge.SLF4JBridgeHandler; import org.slf4j.spi.LocationAwareLogger; import static java.util.Objects.requireNonNull; @@ -436,9 +436,8 @@ protected Consumer doDetermineWriter(C context) { } protected void activateLogging(C context) throws Exception { - if (!SLF4JBridgeHandler.isInstalled()) { - SLF4JBridgeHandler.removeHandlersForRootLogger(); - SLF4JBridgeHandler.install(); + if (!MavenJulHandler.isInstalled()) { + MavenJulHandler.install(); } context.slf4jConfiguration.activate(); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java deleted file mode 100644 index 67ee429d82ab..000000000000 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/logging/impl/LogbackConfiguration.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.maven.cling.logging.impl; - -import org.apache.maven.cling.logging.BaseSlf4jConfiguration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Configuration for slf4j-logback. - * - * @since 3.1.0 - */ -public class LogbackConfiguration extends BaseSlf4jConfiguration { - @Override - public void setRootLoggerLevel(Level level) { - ch.qos.logback.classic.Level value = - switch (level) { - case DEBUG -> ch.qos.logback.classic.Level.DEBUG; - case INFO -> ch.qos.logback.classic.Level.INFO; - default -> ch.qos.logback.classic.Level.ERROR; - }; - ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(value); - } - - @Override - public void activate() { - // no op - } -} diff --git a/impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties b/impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties index 369b0e6a266b..9580d5071ac9 100644 --- a/impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties +++ b/impl/maven-cli/src/main/resources/META-INF/maven/slf4j-configuration.properties @@ -19,5 +19,3 @@ # value = corresponding o.a.m.cli.logging.Slf4jConfiguration class org.slf4j.impl.SimpleLoggerFactory=org.apache.maven.cling.logging.impl.MavenSimpleConfiguration org.apache.maven.slf4j.MavenLoggerFactory=org.apache.maven.cling.logging.impl.MavenSimpleConfiguration -org.apache.logging.slf4j.Log4jLoggerFactory=org.apache.maven.cling.logging.impl.Log4j2Configuration -ch.qos.logback.classic.LoggerContext=org.apache.maven.cling.logging.impl.LogbackConfiguration diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/build/DefaultLogEvent.java b/impl/maven-core/src/main/java/org/apache/maven/internal/build/DefaultLogEvent.java new file mode 100644 index 000000000000..47139ff15e80 --- /dev/null +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/build/DefaultLogEvent.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.internal.build; + +import java.time.Instant; + +import org.apache.maven.api.build.report.LogEvent; +import org.apache.maven.api.build.report.LogLevel; + +/** + * Immutable implementation of {@link LogEvent}. + *

+ * Public to allow construction from other packages within the Maven + * implementation (e.g. {@code ProjectBuildLogAppender}). + * + * @param timestamp when the event was produced + * @param level the severity level + * @param message the clean log message (without level prefix or ANSI) + * @param loggerName the name of the logger, or {@code null} + * @param stackTrace the stack trace string, or {@code null} + * @param formattedMessage the fully formatted console line, or {@code null} + * @param sourceClassName the JUL source class name, or {@code null} + * @param sourceMethodName the JUL source method name, or {@code null} + * @param threadId the JUL thread ID, or {@code -1} if unavailable + */ +public record DefaultLogEvent( + Instant timestamp, + LogLevel level, + String message, + String loggerName, + String stackTrace, + String formattedMessage, + String sourceClassName, + String sourceMethodName, + long threadId) + implements LogEvent { + + /** + * Convenience constructor for events without JUL metadata + * (i.e. events from the SLF4J pipeline). + */ + public DefaultLogEvent( + Instant timestamp, + LogLevel level, + String message, + String loggerName, + String stackTrace, + String formattedMessage) { + this(timestamp, level, message, loggerName, stackTrace, formattedMessage, null, null, -1); + } + + /** + * Convenience constructor for events created without a formatted message + * (e.g. in tests or programmatic construction). + */ + DefaultLogEvent(Instant timestamp, LogLevel level, String message, String loggerName, String stackTrace) { + this(timestamp, level, message, loggerName, stackTrace, null, null, null, -1); + } +} diff --git a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLog.java b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLog.java index 1a11fe46fdb5..a70c2adb290a 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLog.java +++ b/impl/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultLog.java @@ -22,6 +22,7 @@ import org.apache.maven.api.plugin.Log; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import static java.util.Objects.requireNonNull; @@ -32,6 +33,44 @@ public DefaultLog(Logger logger) { this.logger = requireNonNull(logger); } + @Override + public boolean isTraceEnabled() { + return logger.isTraceEnabled(); + } + + @Override + public void trace(CharSequence content) { + if (isTraceEnabled()) { + logger.trace(toString(content)); + } + } + + @Override + public void trace(CharSequence content, Throwable error) { + if (isTraceEnabled()) { + logger.trace(toString(content), error); + } + } + + @Override + public void trace(Throwable error) { + logger.trace("", error); + } + + @Override + public void trace(Supplier content) { + if (isTraceEnabled()) { + logger.trace(content.get()); + } + } + + @Override + public void trace(Supplier content, Throwable error) { + if (isTraceEnabled()) { + logger.trace(content.get(), error); + } + } + @Override public void debug(CharSequence content) { if (isDebugEnabled()) { @@ -127,7 +166,7 @@ public void warn(Supplier content) { @Override public void warn(Supplier content, Throwable error) { if (isWarnEnabled()) { - logger.info(content.get(), error); + logger.warn(content.get(), error); } } @@ -184,6 +223,12 @@ public boolean isErrorEnabled() { return logger.isErrorEnabled(); } + @Override + public Log child(String name) { + requireNonNull(name, "name"); + return new DefaultLog(LoggerFactory.getLogger(logger.getName() + "." + name)); + } + private String toString(CharSequence content) { return content != null ? content.toString() : ""; } diff --git a/impl/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java b/impl/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java index 39573d061cd0..c1d771b5c11b 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java +++ b/impl/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java @@ -18,6 +18,7 @@ */ package org.apache.maven.logging; +import org.apache.maven.api.build.report.LogEvent; import org.apache.maven.execution.ExecutionEvent; import org.eclipse.aether.transfer.TransferEvent; @@ -30,7 +31,7 @@ public interface BuildEventListener { void projectStarted(String projectId); - void projectLogMessage(String projectId, String event); + void projectLogMessage(String projectId, LogEvent event); void projectFinished(String projectId); diff --git a/impl/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java b/impl/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java index 040a481455e2..6685351b362a 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java +++ b/impl/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java @@ -121,6 +121,7 @@ public void projectSkipped(ExecutionEvent event) { @Override public void mojoStarted(ExecutionEvent event) { setMdc(event); + setMojoMdc(event); buildEventListener.mojoStarted(event); delegate.mojoStarted(event); } @@ -128,12 +129,14 @@ public void mojoStarted(ExecutionEvent event) { @Override public void mojoSucceeded(ExecutionEvent event) { setMdc(event); + ProjectBuildLogAppender.setMojoId(null); delegate.mojoSucceeded(event); } @Override public void mojoFailed(ExecutionEvent event) { setMdc(event); + ProjectBuildLogAppender.setMojoId(null); delegate.mojoFailed(event); } @@ -187,4 +190,12 @@ private void setMdc(ExecutionEvent event) { ProjectBuildLogAppender.setProjectId(event.getProject().getArtifactId()); } } + + private void setMojoMdc(ExecutionEvent event) { + if (event.getMojoExecution() != null) { + String mojoId = event.getMojoExecution().getMojoDescriptor().getFullGoalName() + "@" + + event.getMojoExecution().getExecutionId(); + ProjectBuildLogAppender.setMojoId(mojoId); + } + } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java b/impl/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java index 8465df0cf060..e08976927fc7 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java +++ b/impl/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java @@ -18,16 +18,33 @@ */ package org.apache.maven.logging; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.time.Instant; + +import org.apache.maven.api.MonotonicClock; +import org.apache.maven.api.build.report.LogEvent; +import org.apache.maven.api.build.report.LogLevel; +import org.apache.maven.internal.build.DefaultLogEvent; +import org.apache.maven.slf4j.MavenJulHandler; import org.apache.maven.slf4j.MavenSimpleLogger; import org.slf4j.MDC; +import org.slf4j.spi.LocationAwareLogger; /** - * Forwards log messages to the client. + * Forwards log messages to the client as structured {@link LogEvent} objects. + *

+ * Installs itself as a {@link MavenSimpleLogger.LogSink} to intercept all + * SLF4J log output, enrich it with structured metadata (level, logger name, + * clean message, formatted output), and forward to the active + * {@link BuildEventListener}. */ public class ProjectBuildLogAppender implements AutoCloseable { private static final String KEY_PROJECT_ID = "maven.project.id"; + private static final String KEY_MOJO_ID = "maven.mojo.id"; private static final ThreadLocal PROJECT_ID = new InheritableThreadLocal<>(); + private static final ThreadLocal MOJO_ID = new InheritableThreadLocal<>(); private static final ThreadLocal FORKING_PROJECT_ID = new InheritableThreadLocal<>(); public static String getProjectId() { @@ -52,6 +69,31 @@ public static void setProjectId(String projectId) { } } + public static String getMojoId() { + return MOJO_ID.get(); + } + + /** + * Sets or clears the mojo execution identifier in both the thread-local + * and the SLF4J MDC. The value is available to any SLF4J appender via + * the MDC key {@code maven.mojo.id} and to JUL-bridged messages through + * the same MDC path. + *

+ * Format: {@code "prefix:goal@executionId"} + * (e.g. {@code "compiler:compile@default-compile"}). + * + * @param mojoId the mojo identifier, or {@code null} to clear + */ + public static void setMojoId(String mojoId) { + if (mojoId != null) { + MOJO_ID.set(mojoId); + MDC.put(KEY_MOJO_ID, mojoId); + } else { + MOJO_ID.remove(); + MDC.remove(KEY_MOJO_ID); + } + } + public static void setForkingProjectId(String forkingProjectId) { if (forkingProjectId != null) { FORKING_PROJECT_ID.set(forkingProjectId); @@ -76,13 +118,51 @@ public ProjectBuildLogAppender(BuildEventListener buildEventListener) { MavenSimpleLogger.setLogSink(this::accept); } - protected void accept(String message) { + protected void accept( + int level, String loggerName, String cleanMessage, String formattedMessage, Throwable throwable) { String projectId = MDC.get(KEY_PROJECT_ID); - buildEventListener.projectLogMessage(projectId, message); + Instant timestamp = MonotonicClock.now(); + LogLevel logLevel = toLogLevel(level); + String stackTrace = throwable != null ? formatStackTrace(throwable) : null; + + // Read JUL metadata if this event originated from java.util.logging + MavenJulHandler.JulMetadata julMeta = MavenJulHandler.getJulMetadata(); + LogEvent event; + if (julMeta != null) { + event = new DefaultLogEvent( + timestamp, + logLevel, + cleanMessage, + loggerName, + stackTrace, + formattedMessage, + julMeta.sourceClassName(), + julMeta.sourceMethodName(), + julMeta.threadId()); + } else { + event = new DefaultLogEvent(timestamp, logLevel, cleanMessage, loggerName, stackTrace, formattedMessage); + } + buildEventListener.projectLogMessage(projectId, event); } @Override public void close() throws Exception { MavenSimpleLogger.setLogSink(null); } + + private static LogLevel toLogLevel(int level) { + return switch (level) { + case LocationAwareLogger.TRACE_INT -> LogLevel.TRACE; + case LocationAwareLogger.DEBUG_INT -> LogLevel.DEBUG; + case LocationAwareLogger.INFO_INT -> LogLevel.INFO; + case LocationAwareLogger.WARN_INT -> LogLevel.WARN; + default -> LogLevel.ERROR; + }; + } + + private static String formatStackTrace(Throwable t) { + StringWriter sw = new StringWriter(); + t.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } } diff --git a/impl/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java b/impl/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java index 87f7baa1fd59..37c49a92c1c4 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java +++ b/impl/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java @@ -20,6 +20,7 @@ import java.util.function.Consumer; +import org.apache.maven.api.build.report.LogEvent; import org.apache.maven.execution.ExecutionEvent; import org.eclipse.aether.transfer.TransferEvent; @@ -38,8 +39,9 @@ public void sessionStarted(ExecutionEvent event) {} public void projectStarted(String projectId) {} @Override - public void projectLogMessage(String projectId, String event) { - log(event); + public void projectLogMessage(String projectId, LogEvent event) { + String formatted = event.formattedMessage(); + log(formatted != null ? formatted : event.message()); } @Override diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java index e395d1ed000b..476f2aadcdd3 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/DefaultBuildPluginManager.java @@ -125,7 +125,7 @@ public void executeMojo(MavenSession session, MojoExecution mojoExecution) scope.seed( org.apache.maven.api.plugin.Log.class, new DefaultLog(LoggerFactory.getLogger( - mojoExecution.getMojoDescriptor().getFullGoalName()))); + mojoExecution.getMojoDescriptor().getImplementation()))); InternalMavenSession sessionV4 = InternalMavenSession.from(session.getSession()); scope.seed(Project.class, sessionV4.getProject(project)); scope.seed(org.apache.maven.api.MojoExecution.class, new DefaultMojoExecution(sessionV4, mojoExecution)); diff --git a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java index 15f3d8df7b64..62a39f72fb8c 100644 --- a/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java +++ b/impl/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java @@ -555,7 +555,7 @@ private T loadV4Mojo( org.apache.maven.api.MojoExecution execution = new DefaultMojoExecution(sessionV4, mojoExecution); org.apache.maven.api.plugin.Log log = new DefaultLog( - LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getFullGoalName())); + LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getImplementation())); try { Injector injector = Injector.create(); injector.discover(pluginRealm); diff --git a/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java b/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java index 20a5e7ab666b..d14167dfea51 100644 --- a/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java +++ b/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java @@ -232,6 +232,23 @@ protected void write(StringBuilder buf, Throwable t) { } } + /** + * Context-aware write that includes the log level, logger name, and + * clean message alongside the formatted output. Subclasses can override + * to forward structured data to a log sink. + *

+ * The default implementation delegates to {@link #write(StringBuilder, Throwable)}. + * + * @param level the SLF4J log level constant + * @param loggerName the name of the logger + * @param cleanMessage the formatted message without level/timestamp prefix + * @param formattedBuf the fully formatted log line + * @param t the throwable, may be {@code null} + */ + protected void write(int level, String loggerName, String cleanMessage, StringBuilder formattedBuf, Throwable t) { + write(formattedBuf, t); + } + protected void writeThrowable(Throwable t, PrintStream targetStream) { if (t != null) { t.printStackTrace(targetStream); @@ -375,7 +392,24 @@ private void innerHandleNormalizedLoggingCall( // Append the message buf.append(formattedMessage); - write(buf, t); + write(level.toInt(), name, formattedMessage, buf, t); + + // Notify structured log event listener (for build report capture) + onLogEvent(level.toInt(), name, formattedMessage, t); + } + + /** + * Hook called after every log event with structured data. + * Subclasses can override to forward to a structured event sink + * (e.g. for build report log capture). + * + * @param level the log level (one of the {@code LOG_LEVEL_*} constants) + * @param loggerName the name of the logger that produced the event + * @param message the formatted log message (without level/timestamp prefix) + * @param throwable the associated throwable, or {@code null} + */ + protected void onLogEvent(int level, String loggerName, String message, Throwable throwable) { + // no-op — overridden by MavenSimpleLogger } protected String renderLevel(int levelInt) { diff --git a/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenJulHandler.java b/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenJulHandler.java new file mode 100644 index 000000000000..9be299faf0fd --- /dev/null +++ b/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenJulHandler.java @@ -0,0 +1,249 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.slf4j; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import org.apache.maven.api.services.MessageBuilder; +import org.slf4j.LoggerFactory; +import org.slf4j.spi.LocationAwareLogger; + +import static org.apache.maven.jline.MessageUtils.builder; + +/** + * A JUL {@link Handler} that routes {@code java.util.logging} events into + * Maven's structured logging pipeline, preserving the rich {@link LogRecord} + * metadata that the standard {@code SLF4JBridgeHandler} silently drops + * (source class name, source method name, thread ID). + *

+ * When a {@link MavenSimpleLogger.LogSink LogSink} is installed (i.e. during + * a build), JUL events are sent directly to the sink — bypassing SLF4J + * entirely. The JUL metadata is stashed in a thread-local so downstream + * consumers (e.g. {@code ProjectBuildLogAppender}) can read it when + * constructing a structured {@code LogEvent}. + *

+ * When no LogSink is installed (e.g. during early bootstrap), the handler + * falls back to routing through SLF4J for console output. + *

+ * Usage — replace the standard SLF4J bridge in {@code LookupInvoker}: + *

+ *     MavenJulHandler.install();
+ * 
+ * + * @since 4.1.0 + * @see #install() + * @see #getJulMetadata() + */ +public class MavenJulHandler extends Handler { + + /** + * JUL metadata captured from a {@link LogRecord} that would otherwise + * be lost when bridging to SLF4J. + * + * @param sourceClassName the source class, or {@code null} + * @param sourceMethodName the source method, or {@code null} + * @param threadId the originating thread ID + */ + public record JulMetadata(String sourceClassName, String sourceMethodName, long threadId) {} + + private static final ThreadLocal METADATA = new ThreadLocal<>(); + + /** + * Returns the JUL metadata for the current log event being processed, + * or {@code null} if the current log event did not originate from JUL. + *

+ * This method is intended to be called from within a + * {@link MavenSimpleLogger.LogSink} callback (e.g. in + * {@code ProjectBuildLogAppender.accept()}). + * + * @return the current JUL metadata, or {@code null} + */ + public static JulMetadata getJulMetadata() { + return METADATA.get(); + } + + /** + * Installs this handler on the JUL root logger, removing any + * previously installed handlers. This replaces the standard + * {@code SLF4JBridgeHandler.install()} call. + */ + public static void install() { + Logger rootLogger = LogManager.getLogManager().getLogger(""); + // Remove all existing handlers (including any SLF4JBridgeHandler) + for (Handler handler : rootLogger.getHandlers()) { + rootLogger.removeHandler(handler); + } + rootLogger.addHandler(new MavenJulHandler()); + // Accept all levels — filtering is done by SLF4J + rootLogger.setLevel(Level.ALL); + } + + /** + * Returns {@code true} if a {@code MavenJulHandler} is installed + * on the JUL root logger. + */ + public static boolean isInstalled() { + Logger rootLogger = LogManager.getLogManager().getLogger(""); + for (Handler handler : rootLogger.getHandlers()) { + if (handler instanceof MavenJulHandler) { + return true; + } + } + return false; + } + + @Override + public void publish(LogRecord record) { + if (record == null) { + return; + } + + String loggerName = record.getLoggerName(); + org.slf4j.Logger slf4jLogger = LoggerFactory.getLogger(loggerName); + int slf4jLevel = julLevelToSlf4j(record.getLevel()); + + // Quick exit if this level is not enabled + if (!isLevelEnabled(slf4jLogger, slf4jLevel)) { + return; + } + + String message = formatMessage(record); + Throwable throwable = record.getThrown(); + + // If a LogSink is installed, bypass SLF4J entirely: call the sink + // directly with the JUL metadata so no information is lost in transit. + MavenSimpleLogger.LogSink sink = MavenSimpleLogger.getLogSink(); + if (sink != null) { + METADATA.set(new JulMetadata( + record.getSourceClassName(), record.getSourceMethodName(), record.getLongThreadID())); + try { + String formatted = formatForConsole(slf4jLevel, message); + sink.accept(slf4jLevel, loggerName, message, formatted, throwable); + } finally { + METADATA.remove(); + } + } else { + // No LogSink — fall through to SLF4J for console output + logToSlf4j(slf4jLogger, slf4jLevel, message, throwable); + } + } + + @Override + public void flush() { + // nothing to flush + } + + @Override + public void close() throws SecurityException { + // nothing to close + } + + /** + * Formats the log message, applying i18n resource bundle lookup and + * {@link MessageFormat} parameter substitution, matching the behavior + * of {@code SLF4JBridgeHandler}. + */ + private static String formatMessage(LogRecord record) { + String message = record.getMessage(); + if (message == null) { + return ""; + } + + // Try resource bundle lookup + ResourceBundle bundle = record.getResourceBundle(); + if (bundle != null) { + try { + message = bundle.getString(message); + } catch (MissingResourceException e) { + // use raw message + } + } + + // Apply MessageFormat parameters + Object[] params = record.getParameters(); + if (params != null && params.length > 0) { + try { + message = MessageFormat.format(message, params); + } catch (IllegalArgumentException e) { + // use message as-is if formatting fails + } + } + + return message; + } + + /** + * Formats a JUL message for console output, matching the {@code [LEVEL] message} + * style used by MavenSimpleLogger with ANSI coloring when available. + */ + private static String formatForConsole(int level, String message) { + MessageBuilder mb = builder(); + String levelStr = + switch (level) { + case LocationAwareLogger.TRACE_INT -> mb.trace("TRACE").build(); + case LocationAwareLogger.DEBUG_INT -> mb.debug("DEBUG").build(); + case LocationAwareLogger.INFO_INT -> mb.info("INFO").build(); + case LocationAwareLogger.WARN_INT -> mb.warning("WARNING").build(); + default -> mb.error("ERROR").build(); + }; + return "[" + levelStr + "] " + message; + } + + private static int julLevelToSlf4j(Level julLevel) { + int value = julLevel.intValue(); + if (value <= Level.FINEST.intValue()) { + return LocationAwareLogger.TRACE_INT; + } else if (value <= Level.FINE.intValue()) { + return LocationAwareLogger.DEBUG_INT; + } else if (value <= Level.INFO.intValue()) { + return LocationAwareLogger.INFO_INT; + } else if (value <= Level.WARNING.intValue()) { + return LocationAwareLogger.WARN_INT; + } else { + return LocationAwareLogger.ERROR_INT; + } + } + + private static boolean isLevelEnabled(org.slf4j.Logger logger, int level) { + return switch (level) { + case LocationAwareLogger.TRACE_INT -> logger.isTraceEnabled(); + case LocationAwareLogger.DEBUG_INT -> logger.isDebugEnabled(); + case LocationAwareLogger.INFO_INT -> logger.isInfoEnabled(); + case LocationAwareLogger.WARN_INT -> logger.isWarnEnabled(); + default -> logger.isErrorEnabled(); + }; + } + + private static void logToSlf4j(org.slf4j.Logger logger, int level, String message, Throwable throwable) { + switch (level) { + case LocationAwareLogger.TRACE_INT -> logger.trace(message, throwable); + case LocationAwareLogger.DEBUG_INT -> logger.debug(message, throwable); + case LocationAwareLogger.INFO_INT -> logger.info(message, throwable); + case LocationAwareLogger.WARN_INT -> logger.warn(message, throwable); + default -> logger.error(message, throwable); + } + } +} diff --git a/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java b/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java index 02767987e2a1..5d44a0637555 100644 --- a/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java +++ b/impl/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java @@ -39,14 +39,65 @@ public class MavenSimpleLogger extends MavenBaseLogger { private String warnRenderedLevel; private String errorRenderedLevel; - static Consumer logSink; + /** + * Structured log sink that receives level, logger name, clean message, + * formatted console output, and throwable for each log event. + *

+ * This replaces the previous {@code Consumer} sink to enable + * console renderers (e.g. rich mode) to filter by log level and access + * the clean message independently of ANSI formatting. + * + * @since 4.1.0 + */ + @FunctionalInterface + public interface LogSink { + void accept(int level, String loggerName, String cleanMessage, String formattedMessage, Throwable throwable); + } + + static volatile LogSink logSink; + + /** + * Structured log event sink for build report capture. + * Receives level, logger name, clean message, and throwable + * independently of the formatted console output. + */ + @FunctionalInterface + public interface LogEventSink { + void accept(int level, String loggerName, String message, Throwable throwable); + } + + private static volatile LogEventSink logEventSink; public static final String DEFAULT_LOG_LEVEL_KEY = "org.slf4j.simpleLogger.defaultLogLevel"; - public static void setLogSink(Consumer logSink) { + /** + * Sets the structured log sink. + * + * @param logSink the sink, or {@code null} to remove + * @since 4.1.0 + */ + public static void setLogSink(LogSink logSink) { MavenSimpleLogger.logSink = logSink; } + public static void setLogEventSink(LogEventSink sink) { + MavenSimpleLogger.logEventSink = sink; + } + + public static LogEventSink getLogEventSink() { + return logEventSink; + } + + /** + * Returns the current log sink, or {@code null} if none is set. + * + * @return the current log sink, or {@code null} + * @since 4.1.0 + */ + public static LogSink getLogSink() { + return logSink; + } + MavenSimpleLogger(String name) { super(name); } @@ -70,15 +121,74 @@ protected String renderLevel(int level) { } @Override - protected void write(StringBuilder buf, Throwable t) { - Consumer sink = logSink; + protected void write(int level, String loggerName, String cleanMessage, StringBuilder formattedBuf, Throwable t) { + LogSink sink = logSink; if (sink != null) { - sink.accept(buf.toString()); + // Build the full formatted output including throwable rendering + String formatted = formattedBuf.toString(); if (t != null) { - writeThrowable(t, sink); + StringBuilder full = new StringBuilder(formatted); + full.append(System.lineSeparator()); + appendFormattedThrowable(full, t, ""); + formatted = full.toString(); } + sink.accept(level, loggerName, cleanMessage, formatted, t); } else { - super.write(buf, t); + super.write(formattedBuf, t); + } + } + + /** + * Append a colorized throwable rendering to the given builder. + * Reuses the existing formatting logic for consistency with console output. + */ + private void appendFormattedThrowable(StringBuilder sb, Throwable t, String prefix) { + MessageBuilder builder = builder().a(prefix).failure(t.getClass().getName()); + if (t.getMessage() != null) { + builder.a(": ").failure(t.getMessage()); + } + sb.append(builder.toString()).append(System.lineSeparator()); + appendStackTrace(sb, t, prefix); + } + + private void appendStackTrace(StringBuilder sb, Throwable t, String prefix) { + MessageBuilder builder = builder(); + for (StackTraceElement e : t.getStackTrace()) { + builder.a(prefix); + builder.a(" "); + builder.strong("at"); + builder.a(" "); + builder.a(e.getClassName()); + builder.a("."); + builder.a(e.getMethodName()); + builder.a("("); + builder.strong(getLocation(e)); + builder.a(")"); + sb.append(builder.toString()).append(System.lineSeparator()); + builder.setLength(0); + } + for (Throwable se : t.getSuppressed()) { + builder.a(prefix) + .a(" ") + .strong("Suppressed") + .a(": ") + .a(se.getClass().getName()); + if (se.getMessage() != null) { + builder.a(": ").failure(se.getMessage()); + } + sb.append(builder.toString()).append(System.lineSeparator()); + builder.setLength(0); + appendStackTrace(sb, se, prefix + " "); + } + Throwable cause = t.getCause(); + if (cause != null && t != cause) { + builder.a(prefix).strong("Caused by").a(": ").a(cause.getClass().getName()); + if (cause.getMessage() != null) { + builder.a(": ").failure(cause.getMessage()); + } + sb.append(builder.toString()).append(System.lineSeparator()); + builder.setLength(0); + appendStackTrace(sb, cause, prefix); } } @@ -167,4 +277,12 @@ public void configure(int defaultLogLevel) { public void setLogLevel(int logLevel) { this.currentLogLevel = logLevel; } + + @Override + protected void onLogEvent(int level, String loggerName, String message, Throwable throwable) { + LogEventSink sink = logEventSink; + if (sink != null) { + sink.accept(level, loggerName, message, throwable); + } + } } diff --git a/pom.xml b/pom.xml index b7c907d764cb..35f23020dcee 100644 --- a/pom.xml +++ b/pom.xml @@ -157,7 +157,6 @@ under the License. 1.37 6.1.2 1.4.0 - 1.6.1 5.23.0 1.5.1 1.29 @@ -508,17 +507,6 @@ under the License. ${slf4jVersion} true - - org.slf4j - jul-to-slf4j - ${slf4jVersion} - - - ch.qos.logback - logback-classic - ${logbackClassicVersion} - true - org.apache.maven.wagon