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 f2968295e456..a0cd78fd39d4 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,58 @@
@Experimental
@Provider
public interface Log {
+ /**
+ * {@return true if the trace error level is enabled}
+ */
+ boolean isTraceEnabled();
+
+ /**
+ * Sends a message to the user in the trace error level.
+ *
+ * Trace is the most verbose level, intended for Maven core internals
+ * such as resolver negotiation, model interpolation, and lifecycle
+ * ordering details. Use {@link #debug(CharSequence)} instead for
+ * messages that help users investigate their build
+ * (e.g. why a module was recompiled).
+ *
+ * @param content the message to log
+ */
+ void trace(CharSequence content);
+
+ /**
+ * Sends a message (and accompanying exception) to the user at the trace error level.
+ * The error's stacktrace will be output when this error level is enabled.
+ *
+ * @param content the message to log
+ * @param error the error that caused this log
+ */
+ void trace(CharSequence content, Throwable error);
+
+ /**
+ * Sends an exception to the user in the trace error level.
+ * The stack trace for this exception will be output when this error level is enabled.
+ *
+ * @param error the error that caused this log
+ */
+ void trace(Throwable error);
+
+ /**
+ * Sends a lazily-computed message in the trace error level.
+ * The supplier is only evaluated if trace is enabled.
+ *
+ * @param content the message supplier
+ */
+ void trace(Supplier content);
+
+ /**
+ * Sends a lazily-computed message (and accompanying exception) in the trace error level.
+ * The supplier is only evaluated if trace is enabled.
+ *
+ * @param content the message supplier
+ * @param error the error that caused this log
+ */
+ void trace(Supplier content, Throwable error);
+
/**
* {@return true if the debug error level is enabled}
*/
@@ -43,6 +95,11 @@ public interface Log {
/**
* Sends a message to the user in the debug error level.
+ *
+ * Debug is intended for messages that help users investigate
+ * their build — for example, why a module was recompiled or what
+ * classpath was resolved. For Maven core internals, use
+ * {@link #trace(CharSequence)} instead.
*
* @param content the message to log
*/
@@ -167,4 +224,27 @@ 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 a dot and the given suffix.
+ *
+ * For example, if a plugin's logger is named
+ * {@code "org.apache.maven.plugins.compiler.CompilerMojo"},
+ * then {@code child("diagnostics")} returns a logger named
+ * {@code "org.apache.maven.plugins.compiler.CompilerMojo.diagnostics"}.
+ * This lets sub-components log under an independently filterable name
+ * without requiring a separate injection point.
+ *
+ * The default implementation returns {@code this}, so existing
+ * {@code Log} implementations continue to work without changes.
+ * Implementations that wrap a hierarchical logging backend (such as
+ * SLF4J) should override this to create a real child logger.
+ *
+ * @param name the suffix to append (must not be {@code null} or blank)
+ * @return a child logger — never {@code null}
+ */
+ default Log child(String name) {
+ return this;
+ }
}
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..b1cf40cc4059 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,46 @@ 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) {
+ if (isTraceEnabled()) {
+ 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()) {
@@ -48,7 +89,9 @@ public void debug(CharSequence content, Throwable error) {
@Override
public void debug(Throwable error) {
- logger.debug("", error);
+ if (isDebugEnabled()) {
+ logger.debug("", error);
+ }
}
@Override
@@ -81,7 +124,9 @@ public void info(CharSequence content, Throwable error) {
@Override
public void info(Throwable error) {
- logger.info("", error);
+ if (isInfoEnabled()) {
+ logger.info("", error);
+ }
}
@Override
@@ -114,7 +159,9 @@ public void warn(CharSequence content, Throwable error) {
@Override
public void warn(Throwable error) {
- logger.warn("", error);
+ if (isWarnEnabled()) {
+ logger.warn("", error);
+ }
}
@Override
@@ -127,7 +174,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);
}
}
@@ -147,7 +194,9 @@ public void error(CharSequence content, Throwable error) {
@Override
public void error(Throwable error) {
- logger.error("", error);
+ if (isErrorEnabled()) {
+ logger.error("", error);
+ }
}
@Override
@@ -184,6 +233,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/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..947d09474f21 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
@@ -27,7 +27,9 @@
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 +54,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);
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);