From 1d41e835756d51977ea8af244bfd1d1681978ce4 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sun, 30 Aug 2026 11:12:15 +0200 Subject: [PATCH 1/2] Log API enhancements and mojo MDC (#12690) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Backport Log API enhancements and mojo MDC to 4.0.x Backport four Log-related improvements from master to the 4.0.x branch for inclusion in rc-7: 1. Log.trace() — new trace level (maps to SLF4J TRACE / JUL FINEST) to separate Maven core internals from user-facing debug messages. Currently -X floods debug output with resolver/interpolation details that drown user-relevant diagnostics. 2. Log.child(name) — creates a sub-logger with an independently filterable name (e.g. "CompilerMojo.diagnostics"), letting plugin sub-components log under their own namespace. 3. Logger name alignment — Maven 4 Log now uses the mojo implementation class name (e.g. "org.apache.maven.plugins.compiler.CompilerMojo") instead of the goal name ("compiler:compile"). This matches what Maven 3 mojos already use and enables standard SLF4J hierarchical level configuration. 4. Mojo MDC propagation — sets "maven.mojo.id" (prefix:goal@executionId) in the SLF4J MDC during mojo execution. All log messages — including those arriving through the JUL-to-SLF4J bridge — now carry mojo context, available to any SLF4J appender via %X{maven.mojo.id}. Also fixes a pre-existing bug in DefaultLog where warn(Supplier, Throwable) incorrectly delegated to logger.info() instead of logger.warn(). Co-Authored-By: Claude Opus 4.6 * Add isXxxEnabled() guards to Throwable-only log overloads Align with master by wrapping the five xxx(Throwable) overloads in level-enabled checks, avoiding unnecessary method calls and empty string construction when the level is disabled. Co-Authored-By: Claude Opus 4.6 * Address review: default trace methods and fork-aware mojoId Apply review fixes from #12694 to align the backport: - Log.java: make all 6 trace methods default (no-ops) to prevent AbstractMethodError for existing third-party Log implementors. isTraceEnabled() returns false by default. - ProjectBuildLogAppender: add FORKING_MOJO_ID ThreadLocal mirroring the existing FORKING_PROJECT_ID pattern. When setMojoId(null) is called, the forking mojo's ID is restored instead of clearing. - LoggingExecutionListener: save current mojoId in forkStarted(), clear forking mojoId in forkSucceeded/forkFailed. Fix cleanup ordering in mojoSucceeded/mojoFailed — delegate runs first, then MDC is cleared. Co-Authored-By: Claude Opus 4.6 * Address review: add DefaultLogTest and clear MDC on mojoSkipped - Add DefaultLogTest with 5 tests: warn/supplier regression, trace delegation, trace no-op guard, child() sub-logger, and default trace methods (AbstractMethodError prevention). - Clear mojo MDC in mojoSkipped() to prevent stale mojo context from leaking into subsequent log messages. Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- .../java/org/apache/maven/api/plugin/Log.java | 95 +++++++++ .../maven/internal/impl/DefaultLog.java | 65 +++++- .../logging/LoggingExecutionListener.java | 16 ++ .../logging/ProjectBuildLogAppender.java | 49 +++++ .../plugin/DefaultBuildPluginManager.java | 2 +- .../internal/DefaultMavenPluginManager.java | 2 +- .../maven/internal/impl/DefaultLogTest.java | 196 ++++++++++++++++++ 7 files changed, 418 insertions(+), 7 deletions(-) create mode 100644 impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultLogTest.java 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..3a12fcd27939 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,73 @@ @Experimental @Provider public interface Log { + /** + * {@return true if the trace error level is enabled} + *

+ * The default implementation returns {@code false} for backward + * compatibility with existing {@code Log} implementations. + */ + default boolean isTraceEnabled() { + return false; + } + + /** + * 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). + *

+ * The default implementation is a no-op for backward compatibility. + * + * @param content the message to log + */ + default 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. + *

+ * The default implementation is a no-op for backward compatibility. + * + * @param content the message to log + * @param error the error that caused this log + */ + default 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. + *

+ * The default implementation is a no-op for backward compatibility. + * + * @param error the error that caused this log + */ + default void trace(Throwable error) {} + + /** + * Sends a lazily-computed message in the trace error level. + * The supplier is only evaluated if trace is enabled. + *

+ * The default implementation is a no-op for backward compatibility. + * + * @param content the message supplier + */ + default 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. + *

+ * The default implementation is a no-op for backward compatibility. + * + * @param content the message supplier + * @param error the error that caused this log + */ + default void trace(Supplier content, Throwable error) {} + /** * {@return true if the debug error level is enabled} */ @@ -43,6 +110,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 +239,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..700834eb80df 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); } @@ -129,18 +130,21 @@ public void mojoStarted(ExecutionEvent event) { public void mojoSucceeded(ExecutionEvent event) { setMdc(event); delegate.mojoSucceeded(event); + ProjectBuildLogAppender.setMojoId(null); } @Override public void mojoFailed(ExecutionEvent event) { setMdc(event); delegate.mojoFailed(event); + ProjectBuildLogAppender.setMojoId(null); } @Override public void mojoSkipped(ExecutionEvent event) { setMdc(event); delegate.mojoSkipped(event); + ProjectBuildLogAppender.setMojoId(null); } @Override @@ -148,18 +152,22 @@ public void forkStarted(ExecutionEvent event) { setMdc(event); delegate.forkStarted(event); ProjectBuildLogAppender.setForkingProjectId(event.getProject().getArtifactId()); + // Save the forking mojo's ID so it can be restored when the fork completes + ProjectBuildLogAppender.setForkingMojoId(ProjectBuildLogAppender.getMojoId()); } @Override public void forkSucceeded(ExecutionEvent event) { delegate.forkSucceeded(event); ProjectBuildLogAppender.setForkingProjectId(null); + ProjectBuildLogAppender.setForkingMojoId(null); } @Override public void forkFailed(ExecutionEvent event) { delegate.forkFailed(event); ProjectBuildLogAppender.setForkingProjectId(null); + ProjectBuildLogAppender.setForkingMojoId(null); } @Override @@ -187,4 +195,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..dc82a2f89848 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,8 +27,11 @@ 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<>(); + private static final ThreadLocal FORKING_MOJO_ID = new InheritableThreadLocal<>(); public static String getProjectId() { return PROJECT_ID.get(); @@ -52,6 +55,37 @@ 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. When clearing ({@code null}), if a forking mojo ID + * was saved, it is restored — mirroring the fork-aware project ID pattern. + *

+ * 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 { + // Restore the forking mojo's ID if one was saved + String forkingMojoId = FORKING_MOJO_ID.get(); + if (forkingMojoId != null) { + MOJO_ID.set(forkingMojoId); + MDC.put(KEY_MOJO_ID, forkingMojoId); + } else { + MOJO_ID.remove(); + MDC.remove(KEY_MOJO_ID); + } + } + } + public static void setForkingProjectId(String forkingProjectId) { if (forkingProjectId != null) { FORKING_PROJECT_ID.set(forkingProjectId); @@ -60,6 +94,21 @@ public static void setForkingProjectId(String forkingProjectId) { } } + /** + * Saves or clears the mojo ID of the forking mojo, so it can be + * restored when the fork completes. Mirrors the {@link #setForkingProjectId} + * pattern for project IDs. + * + * @param forkingMojoId the forking mojo identifier, or {@code null} to clear + */ + public static void setForkingMojoId(String forkingMojoId) { + if (forkingMojoId != null) { + FORKING_MOJO_ID.set(forkingMojoId); + } else { + FORKING_MOJO_ID.remove(); + } + } + public static void updateMdc() { String id = getProjectId(); if (id != null) { 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 1af5e5effdd2..915d3d996822 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 @@ -556,7 +556,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-core/src/test/java/org/apache/maven/internal/impl/DefaultLogTest.java b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultLogTest.java new file mode 100644 index 000000000000..eb290734fb98 --- /dev/null +++ b/impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultLogTest.java @@ -0,0 +1,196 @@ +/* + * 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.impl; + +import org.apache.maven.api.plugin.Log; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link DefaultLog}. + */ +class DefaultLogTest { + + /** + * Regression test: {@code warn(Supplier, Throwable)} was incorrectly + * calling {@code logger.info()} instead of {@code logger.warn()}. + */ + @Test + void warnWithSupplierAndThrowableDelegatesToWarn() { + Logger mockLogger = mock(Logger.class); + when(mockLogger.isWarnEnabled()).thenReturn(true); + + DefaultLog log = new DefaultLog(mockLogger); + RuntimeException ex = new RuntimeException("test"); + log.warn(() -> "warning message", ex); + + verify(mockLogger).warn("warning message", ex); + } + + /** + * Verify trace methods delegate to the SLF4J logger correctly. + */ + @Test + void traceMethodsDelegateToSlf4jTrace() { + Logger mockLogger = mock(Logger.class); + when(mockLogger.isTraceEnabled()).thenReturn(true); + + DefaultLog log = new DefaultLog(mockLogger); + log.trace("trace message"); + + verify(mockLogger).trace("trace message"); + } + + /** + * Verify that trace methods are no-ops when trace is disabled. + */ + @Test + void traceIsNoOpWhenDisabled() { + Logger mockLogger = mock(Logger.class); + when(mockLogger.isTraceEnabled()).thenReturn(false); + + DefaultLog log = new DefaultLog(mockLogger); + log.trace("should not be logged"); + + verify(mockLogger).isTraceEnabled(); + verifyNoMoreInteractions(mockLogger); + } + + /** + * Verify that {@code child()} creates a new logger with the + * expected hierarchical name. + */ + @Test + void childCreatesSubLogger() { + Logger mockLogger = mock(Logger.class); + when(mockLogger.getName()).thenReturn("org.apache.maven.plugins.compiler.CompilerMojo"); + + DefaultLog parent = new DefaultLog(mockLogger); + Log child = parent.child("diagnostics"); + + assertNotSame(parent, child); + assertTrue(child instanceof DefaultLog, "child should be a DefaultLog"); + } + + /** + * Verify that the default {@code Log.isTraceEnabled()} returns false, + * preventing {@code AbstractMethodError} for third-party implementors. + */ + @Test + void defaultTraceIsDisabled() { + // Use a minimal Log implementation that relies on defaults + Log minimal = new Log() { + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public void debug(CharSequence c) {} + + @Override + public void debug(CharSequence c, Throwable e) {} + + @Override + public void debug(Throwable e) {} + + @Override + public void debug(java.util.function.Supplier c) {} + + @Override + public void debug(java.util.function.Supplier c, Throwable e) {} + + @Override + public boolean isInfoEnabled() { + return false; + } + + @Override + public void info(CharSequence c) {} + + @Override + public void info(CharSequence c, Throwable e) {} + + @Override + public void info(Throwable e) {} + + @Override + public void info(java.util.function.Supplier c) {} + + @Override + public void info(java.util.function.Supplier c, Throwable e) {} + + @Override + public boolean isWarnEnabled() { + return false; + } + + @Override + public void warn(CharSequence c) {} + + @Override + public void warn(CharSequence c, Throwable e) {} + + @Override + public void warn(Throwable e) {} + + @Override + public void warn(java.util.function.Supplier c) {} + + @Override + public void warn(java.util.function.Supplier c, Throwable e) {} + + @Override + public boolean isErrorEnabled() { + return false; + } + + @Override + public void error(CharSequence c) {} + + @Override + public void error(CharSequence c, Throwable e) {} + + @Override + public void error(Throwable e) {} + + @Override + public void error(java.util.function.Supplier c) {} + + @Override + public void error(java.util.function.Supplier c, Throwable e) {} + }; + + // These should NOT throw AbstractMethodError — they use defaults + assertEquals(false, minimal.isTraceEnabled()); + minimal.trace("should be a no-op"); + minimal.trace("no-op", new RuntimeException()); + minimal.trace(new RuntimeException()); + minimal.trace(() -> "no-op"); + minimal.trace(() -> "no-op", new RuntimeException()); + } +} From b5b179c1a13bebb27ece3c8145bc26f1ad70362d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Sun, 30 Aug 2026 22:37:54 +0200 Subject: [PATCH 2/2] Address review: javadoc wording in trace methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "in the trace error level" → "at the trace error level" - "e.g." → "for instance," Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/org/apache/maven/api/plugin/Log.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 3a12fcd27939..23175d07b1f7 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 @@ -47,13 +47,13 @@ default boolean isTraceEnabled() { } /** - * Sends a message to the user in the trace error level. + * Sends a message to the user at 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). + * (for instance, why a module was recompiled). *

* The default implementation is a no-op for backward compatibility. * @@ -73,7 +73,7 @@ default void trace(CharSequence content) {} default void trace(CharSequence content, Throwable error) {} /** - * Sends an exception to the user in the trace error level. + * Sends an exception to the user at the trace error level. * The stack trace for this exception will be output when this error level is enabled. *

* The default implementation is a no-op for backward compatibility. @@ -83,7 +83,7 @@ default void trace(CharSequence content, Throwable error) {} default void trace(Throwable error) {} /** - * Sends a lazily-computed message in the trace error level. + * Sends a lazily-computed message at the trace error level. * The supplier is only evaluated if trace is enabled. *

* The default implementation is a no-op for backward compatibility. @@ -93,7 +93,7 @@ default void trace(Throwable error) {} default void trace(Supplier content) {} /** - * Sends a lazily-computed message (and accompanying exception) in the trace error level. + * Sends a lazily-computed message (and accompanying exception) at the trace error level. * The supplier is only evaluated if trace is enabled. *

* The default implementation is a no-op for backward compatibility.