From 623653f2aa4a9dabe4cffc9a90a86b61023ee157 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 9 Oct 2024 20:47:35 +0200 Subject: [PATCH 01/14] wip --- ...ion.java => MavenSimpleConfiguration.java} | 12 +- .../main/java/org/slf4j/MavenSlf4jFriend.java | 32 -- .../slf4j/simple/MavenSlf4jSimpleFriend.java | 36 -- .../maven/slf4j-configuration.properties | 4 +- .../apache/maven/slf4j/MavenBaseLogger.java | 474 ++++++++++++++++++ .../maven/slf4j/MavenLoggerFactory.java | 31 +- .../maven/slf4j/MavenServiceProvider.java | 8 +- .../apache/maven/slf4j/MavenSimpleLogger.java | 25 +- .../org/apache/maven/slf4j/OutputChoice.java | 75 +++ .../slf4j/SimpleLoggerConfiguration.java | 193 +++++++ .../org/slf4j/simple/ExtSimpleLogger.java | 39 -- 11 files changed, 803 insertions(+), 126 deletions(-) rename maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/{Slf4jSimpleConfiguration.java => MavenSimpleConfiguration.java} (81%) delete mode 100644 maven-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.java delete mode 100644 maven-embedder/src/main/java/org/slf4j/simple/MavenSlf4jSimpleFriend.java create mode 100644 maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java create mode 100644 maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/OutputChoice.java create mode 100644 maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java delete mode 100644 maven-slf4j-provider/src/main/java/org/slf4j/simple/ExtSimpleLogger.java diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/Slf4jSimpleConfiguration.java b/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/MavenSimpleConfiguration.java similarity index 81% rename from maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/Slf4jSimpleConfiguration.java rename to maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/MavenSimpleConfiguration.java index 42a85b0f4194..aa732397554c 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/Slf4jSimpleConfiguration.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/logging/impl/MavenSimpleConfiguration.java @@ -19,14 +19,16 @@ package org.apache.maven.cli.logging.impl; import org.apache.maven.cli.logging.BaseSlf4jConfiguration; -import org.slf4j.simple.MavenSlf4jSimpleFriend; +import org.apache.maven.slf4j.MavenLoggerFactory; +import org.slf4j.ILoggerFactory; +import org.slf4j.LoggerFactory; /** * Configuration for slf4j-simple. * * @since 3.1.0 */ -public class Slf4jSimpleConfiguration extends BaseSlf4jConfiguration { +public class MavenSimpleConfiguration extends BaseSlf4jConfiguration { @Override public void setRootLoggerLevel(Level level) { String value; @@ -48,7 +50,9 @@ public void setRootLoggerLevel(Level level) { @Override public void activate() { - // property for root logger level or System.out redirection need to be taken into account - MavenSlf4jSimpleFriend.init(); + ILoggerFactory lf = LoggerFactory.getILoggerFactory(); + if (lf instanceof MavenLoggerFactory mlf) { + mlf.reconfigure(); + } } } diff --git a/maven-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.java b/maven-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.java deleted file mode 100644 index b729879b0e3b..000000000000 --- a/maven-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.java +++ /dev/null @@ -1,32 +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.slf4j; - -/** - * Utility for Maven to access Slf4j internals through package access. - * Use with precaution, since this is not normally intended for production use. - */ -public class MavenSlf4jFriend { - /** - * Reset Slf4j internal state. - */ - public static void reset() { - LoggerFactory.reset(); - } -} diff --git a/maven-embedder/src/main/java/org/slf4j/simple/MavenSlf4jSimpleFriend.java b/maven-embedder/src/main/java/org/slf4j/simple/MavenSlf4jSimpleFriend.java deleted file mode 100644 index e27d85f003e7..000000000000 --- a/maven-embedder/src/main/java/org/slf4j/simple/MavenSlf4jSimpleFriend.java +++ /dev/null @@ -1,36 +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.slf4j.simple; - -import org.slf4j.ILoggerFactory; -import org.slf4j.LoggerFactory; - -/** - * Utility for Maven to access Slf4j-Simple internals through package access. - * Use with precaution, since this is not normally intended for production use. - */ -public class MavenSlf4jSimpleFriend { - public static void init() { - SimpleLogger.init(); - ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - if (loggerFactory instanceof SimpleLoggerFactory) { - ((SimpleLoggerFactory) loggerFactory).reset(); - } - } -} diff --git a/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties b/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties index 7431cb8f091d..ba7b675ea965 100644 --- a/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties +++ b/maven-embedder/src/main/resources/META-INF/maven/slf4j-configuration.properties @@ -17,7 +17,7 @@ # key = Slf4j effective logger factory implementation # value = corresponding o.a.m.cli.logging.Slf4jConfiguration class -org.slf4j.impl.SimpleLoggerFactory=org.apache.maven.cli.logging.impl.Slf4jSimpleConfiguration -org.apache.maven.slf4j.MavenLoggerFactory=org.apache.maven.cli.logging.impl.Slf4jSimpleConfiguration +org.slf4j.impl.SimpleLoggerFactory=org.apache.maven.cli.logging.impl.MavenSimpleConfiguration +org.apache.maven.slf4j.MavenLoggerFactory=org.apache.maven.cli.logging.impl.MavenSimpleConfiguration org.apache.logging.slf4j.Log4jLoggerFactory=org.apache.maven.cli.logging.impl.Log4j2Configuration ch.qos.logback.classic.LoggerContext=org.apache.maven.cli.logging.impl.LogbackConfiguration diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java new file mode 100644 index 000000000000..273f13ebc72c --- /dev/null +++ b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java @@ -0,0 +1,474 @@ +/* + * 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.io.PrintStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.Marker; +import org.slf4j.event.Level; +import org.slf4j.event.LoggingEvent; +import org.slf4j.helpers.LegacyAbstractLogger; +import org.slf4j.helpers.MessageFormatter; +import org.slf4j.helpers.NormalizedParameters; +import org.slf4j.spi.LocationAwareLogger; + +/** + *

+ * Simple implementation of {@link Logger} that sends all enabled log messages, + * for all defined loggers, to the console ({@code System.err}). The following + * system properties are supported to configure the behavior of this logger: + * + * + *

+ * + *

+ * In addition to looking for system properties with the names specified above, + * this implementation also checks for a class loader resource named + * "simplelogger.properties", and includes any matching definitions + * from this resource (if it exists). + * + * + *

+ * With no configuration, the default output includes the relative time in + * milliseconds, thread name, the level, logger name, and the message followed + * by the line separator for the host. In log4j terms it amounts to the "%r [%t] + * %level %logger - %m%n" pattern. + * + *

+ * Sample output follows. + * + * + *

+ * 176 [main] INFO examples.Sort - Populating an array of 2 elements in reverse order.
+ * 225 [main] INFO examples.SortAlgo - Entered the sort method.
+ * 304 [main] INFO examples.SortAlgo - Dump of integer array:
+ * 317 [main] INFO examples.SortAlgo - Element [0] = 0
+ * 331 [main] INFO examples.SortAlgo - Element [1] = 1
+ * 343 [main] INFO examples.Sort - The next log statement should be an error message.
+ * 346 [main] ERROR examples.SortAlgo - Tried to dump an uninitialized array.
+ *   at org.log4j.examples.SortAlgo.dump(SortAlgo.java:58)
+ *   at org.log4j.examples.Sort.main(Sort.java:64)
+ * 467 [main] INFO  examples.Sort - Exiting main method.
+ * 
+ * + *

+ * This implementation is heavily inspired by + * Apache Commons Logging's + * SimpleLog. + * + * + * @author Ceki Gülcü + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @author Cédrik LIME + */ +public class MavenBaseLogger extends LegacyAbstractLogger { + + private static final long serialVersionUID = -632788891211436180L; + + private static final long START_TIME = System.currentTimeMillis(); + + protected static final int LOG_LEVEL_TRACE = LocationAwareLogger.TRACE_INT; + protected static final int LOG_LEVEL_DEBUG = LocationAwareLogger.DEBUG_INT; + protected static final int LOG_LEVEL_INFO = LocationAwareLogger.INFO_INT; + protected static final int LOG_LEVEL_WARN = LocationAwareLogger.WARN_INT; + protected static final int LOG_LEVEL_ERROR = LocationAwareLogger.ERROR_INT; + + static final char SP = ' '; + static final String TID_PREFIX = "tid="; + + // The OFF level can only be used in configuration files to disable logging. + // It has + // no printing method associated with it in o.s.Logger interface. + protected static final int LOG_LEVEL_OFF = LOG_LEVEL_ERROR + 10; + + static final SimpleLoggerConfiguration CONFIG_PARAMS = new SimpleLoggerConfiguration(); + + private static boolean initialized = false; + + static void lazyInit() { + if (initialized) { + return; + } + initialized = true; + init(); + } + + // external software might be invoking this method directly. Do not rename + // or change its semantics. + static void init() { + CONFIG_PARAMS.init(); + } + + /** The current log level */ + protected int currentLogLevel = LOG_LEVEL_INFO; + /** The short name of this simple log instance */ + private transient String shortLogName = null; + + /** + * All system properties used by SimpleLogger start with this + * prefix + */ + public static final String SYSTEM_PREFIX = "org.slf4j.simpleLogger."; + + public static final String LOG_KEY_PREFIX = MavenBaseLogger.SYSTEM_PREFIX + "log."; + + public static final String CACHE_OUTPUT_STREAM_STRING_KEY = MavenBaseLogger.SYSTEM_PREFIX + "cacheOutputStream"; + + public static final String WARN_LEVEL_STRING_KEY = MavenBaseLogger.SYSTEM_PREFIX + "warnLevelString"; + + public static final String LEVEL_IN_BRACKETS_KEY = MavenBaseLogger.SYSTEM_PREFIX + "levelInBrackets"; + + public static final String LOG_FILE_KEY = MavenBaseLogger.SYSTEM_PREFIX + "logFile"; + + public static final String SHOW_SHORT_LOG_NAME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showShortLogName"; + + public static final String SHOW_LOG_NAME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showLogName"; + + public static final String SHOW_THREAD_NAME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showThreadName"; + + public static final String SHOW_THREAD_ID_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showThreadId"; + + public static final String DATE_TIME_FORMAT_KEY = MavenBaseLogger.SYSTEM_PREFIX + "dateTimeFormat"; + + public static final String SHOW_DATE_TIME_KEY = MavenBaseLogger.SYSTEM_PREFIX + "showDateTime"; + + public static final String DEFAULT_LOG_LEVEL_KEY = MavenBaseLogger.SYSTEM_PREFIX + "defaultLogLevel"; + + /** + * Protected access allows only {@link MavenLoggerFactory} and also derived classes to instantiate + * MavenLoggerFactory instances. + */ + protected MavenBaseLogger(String name) { + this.name = name; + + String levelString = recursivelyComputeLevelString(); + if (levelString != null) { + this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString); + } else { + this.currentLogLevel = CONFIG_PARAMS.defaultLogLevel; + } + } + + String recursivelyComputeLevelString() { + String tempName = name; + String levelString = null; + int indexOfLastDot = tempName.length(); + while ((levelString == null) && (indexOfLastDot > -1)) { + tempName = tempName.substring(0, indexOfLastDot); + levelString = CONFIG_PARAMS.getStringProperty(MavenBaseLogger.LOG_KEY_PREFIX + tempName, null); + indexOfLastDot = String.valueOf(tempName).lastIndexOf("."); + } + return levelString; + } + + /** + * To avoid intermingling of log messages and associated stack traces, the two + * operations are done in a synchronized block. + * + * @param buf + * @param t + */ + protected void write(StringBuilder buf, Throwable t) { + PrintStream targetStream = CONFIG_PARAMS.outputChoice.getTargetPrintStream(); + + synchronized (CONFIG_PARAMS) { + targetStream.println(buf.toString()); + writeThrowable(t, targetStream); + targetStream.flush(); + } + } + + protected void writeThrowable(Throwable t, PrintStream targetStream) { + if (t != null) { + t.printStackTrace(targetStream); + } + } + + protected String getFormattedDate() { + Date now = new Date(); + String dateText; + synchronized (CONFIG_PARAMS.dateFormatter) { + dateText = CONFIG_PARAMS.dateFormatter.format(now); + } + return dateText; + } + + protected String computeShortName() { + return name.substring(name.lastIndexOf(".") + 1); + } + + // /** + // * For formatted messages, first substitute arguments and then log. + // * + // * @param level + // * @param format + // * @param arg1 + // * @param arg2 + // */ + // private void formatAndLog(int level, String format, Object arg1, Object arg2) { + // if (!isLevelEnabled(level)) { + // return; + // } + // FormattingTuple tp = MessageFormatter.format(format, arg1, arg2); + // log(level, tp.getMessage(), tp.getThrowable()); + // } + + // /** + // * For formatted messages, first substitute arguments and then log. + // * + // * @param level + // * @param format + // * @param arguments + // * a list of 3 ore more arguments + // */ + // private void formatAndLog(int level, String format, Object... arguments) { + // if (!isLevelEnabled(level)) { + // return; + // } + // FormattingTuple tp = MessageFormatter.arrayFormat(format, arguments); + // log(level, tp.getMessage(), tp.getThrowable()); + // } + + /** + * Is the given log level currently enabled? + * + * @param logLevel is this level enabled? + * @return whether the logger is enabled for the given level + */ + protected boolean isLevelEnabled(int logLevel) { + // log level are numerically ordered so can use simple numeric + // comparison + return (logLevel >= currentLogLevel); + } + + /** Are {@code trace} messages currently enabled? */ + public boolean isTraceEnabled() { + return isLevelEnabled(LOG_LEVEL_TRACE); + } + + /** Are {@code debug} messages currently enabled? */ + public boolean isDebugEnabled() { + return isLevelEnabled(LOG_LEVEL_DEBUG); + } + + /** Are {@code info} messages currently enabled? */ + public boolean isInfoEnabled() { + return isLevelEnabled(LOG_LEVEL_INFO); + } + + /** Are {@code warn} messages currently enabled? */ + public boolean isWarnEnabled() { + return isLevelEnabled(LOG_LEVEL_WARN); + } + + /** Are {@code error} messages currently enabled? */ + public boolean isErrorEnabled() { + return isLevelEnabled(LOG_LEVEL_ERROR); + } + + /** + * SimpleLogger's implementation of + * {@link org.slf4j.helpers.AbstractLogger#handleNormalizedLoggingCall(Level, Marker, String, Object[], Throwable) AbstractLogger#handleNormalizedLoggingCall} + * } + * + * @param level the SLF4J level for this event + * @param marker The marker to be used for this event, may be null. + * @param messagePattern The message pattern which will be parsed and formatted + * @param arguments the array of arguments to be formatted, may be null + * @param throwable The exception whose stack trace should be logged, may be null + */ + @Override + protected void handleNormalizedLoggingCall( + Level level, Marker marker, String messagePattern, Object[] arguments, Throwable throwable) { + + List markers = null; + + if (marker != null) { + markers = new ArrayList<>(); + markers.add(marker); + } + + innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable); + } + + private void innerHandleNormalizedLoggingCall( + Level level, List markers, String messagePattern, Object[] arguments, Throwable t) { + + StringBuilder buf = new StringBuilder(32); + + // Append date-time if so configured + if (CONFIG_PARAMS.showDateTime) { + if (CONFIG_PARAMS.dateFormatter != null) { + buf.append(getFormattedDate()); + buf.append(SP); + } else { + buf.append(System.currentTimeMillis() - START_TIME); + buf.append(SP); + } + } + + // Append current thread name if so configured + if (CONFIG_PARAMS.showThreadName) { + buf.append('['); + buf.append(Thread.currentThread().getName()); + buf.append("] "); + } + + if (CONFIG_PARAMS.showThreadId) { + buf.append(TID_PREFIX); + buf.append(Thread.currentThread().getId()); + buf.append(SP); + } + + if (CONFIG_PARAMS.levelInBrackets) { + buf.append('['); + } + + // Append a readable representation of the log level + String levelStr = renderLevel(level.toInt()); + buf.append(levelStr); + if (CONFIG_PARAMS.levelInBrackets) { + buf.append(']'); + } + buf.append(SP); + + // Append the name of the log instance if so configured + if (CONFIG_PARAMS.showShortLogName) { + if (shortLogName == null) { + shortLogName = computeShortName(); + } + buf.append(shortLogName).append(" - "); + } else if (CONFIG_PARAMS.showLogName) { + buf.append(name).append(" - "); + } + + if (markers != null) { + buf.append(SP); + for (Marker marker : markers) { + buf.append(marker.getName()).append(SP); + } + } + + String formattedMessage = MessageFormatter.basicArrayFormat(messagePattern, arguments); + + // Append the message + buf.append(formattedMessage); + + write(buf, t); + } + + protected String renderLevel(int levelInt) { + switch (levelInt) { + case LOG_LEVEL_TRACE: + return "TRACE"; + case LOG_LEVEL_DEBUG: + return ("DEBUG"); + case LOG_LEVEL_INFO: + return "INFO"; + case LOG_LEVEL_WARN: + return "WARN"; + case LOG_LEVEL_ERROR: + return "ERROR"; + default: + throw new IllegalStateException("Unrecognized level [" + levelInt + "]"); + } + } + + public void log(LoggingEvent event) { + int levelInt = event.getLevel().toInt(); + + if (!isLevelEnabled(levelInt)) { + return; + } + + NormalizedParameters np = NormalizedParameters.normalize(event); + + innerHandleNormalizedLoggingCall( + event.getLevel(), event.getMarkers(), np.getMessage(), np.getArguments(), event.getThrowable()); + } + + @Override + protected String getFullyQualifiedCallerName() { + return null; + } +} diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java index ea0b98272bcc..a142f9b3a33d 100644 --- a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java +++ b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java @@ -19,19 +19,23 @@ package org.apache.maven.slf4j; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import org.apache.maven.logwrapper.LogLevelRecorder; import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; import org.slf4j.Logger; -import org.slf4j.simple.SimpleLoggerFactory; /** * LogFactory for Maven which can create a simple logger or one which, if set, fails the build on a severity threshold. */ -public class MavenLoggerFactory extends SimpleLoggerFactory implements MavenSlf4jWrapperFactory { +public class MavenLoggerFactory implements MavenSlf4jWrapperFactory { private LogLevelRecorder logLevelRecorder = null; + private final ConcurrentMap loggerMap = new ConcurrentHashMap<>(); - public MavenLoggerFactory() {} + public MavenLoggerFactory() { + MavenSimpleLogger.lazyInit(); + } @Override public void setLogLevelRecorder(LogLevelRecorder logLevelRecorder) { @@ -39,7 +43,6 @@ public void setLogLevelRecorder(LogLevelRecorder logLevelRecorder) { throw new IllegalStateException("LogLevelRecorder has already been set."); } this.logLevelRecorder = logLevelRecorder; - reset(); } @Override @@ -47,11 +50,29 @@ public Optional getLogLevelRecorder() { return Optional.ofNullable(logLevelRecorder); } - protected Logger createLogger(String name) { + /** + * Return an appropriate {@link Logger} instance by name. + */ + @Override + public Logger getLogger(String name) { + return loggerMap.computeIfAbsent(name, this::getNewLoggingInstance); + } + + protected Logger getNewLoggingInstance(String name) { if (logLevelRecorder == null) { return new MavenSimpleLogger(name); } else { return new MavenFailOnSeverityLogger(name, logLevelRecorder); } } + + public void reconfigure() { + SimpleLoggerConfiguration config = MavenSimpleLogger.CONFIG_PARAMS; + config.init(); + loggerMap.values().forEach(l -> { + if (l instanceof MavenSimpleLogger msl) { + msl.configure(config.defaultLogLevel); + } + }); + } } diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java index 11e2f9371506..c20ddf6e1818 100644 --- a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java +++ b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java @@ -18,6 +18,8 @@ */ package org.apache.maven.slf4j; +import java.util.ServiceLoader; + import org.slf4j.ILoggerFactory; import org.slf4j.IMarkerFactory; import org.slf4j.helpers.BasicMarkerFactory; @@ -35,10 +37,14 @@ public class MavenServiceProvider implements SLF4JServiceProvider { @SuppressWarnings({"checkstyle:StaticVariableName", "checkstyle:VisibilityModifier"}) public static String REQUESTED_API_VERSION = "2.0.99"; // !final - private MavenLoggerFactory loggerFactory = new MavenLoggerFactory(); + private MavenLoggerFactory loggerFactory = loadMavenLoggerFactory(); private IMarkerFactory markerFactory = new BasicMarkerFactory(); private MDCAdapter mdcAdapter = new NOPMDCAdapter(); + protected MavenLoggerFactory loadMavenLoggerFactory() { + return ServiceLoader.load(MavenLoggerFactory.class).findFirst().orElseGet(MavenLoggerFactory::new); + } + public ILoggerFactory getLoggerFactory() { return loggerFactory; } diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java index 749d60de883a..584da25c4c51 100644 --- a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java +++ b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java @@ -22,7 +22,6 @@ import java.util.function.Consumer; import org.apache.maven.api.services.MessageBuilder; -import org.slf4j.simple.ExtSimpleLogger; import static org.apache.maven.jline.MessageUtils.builder; @@ -32,7 +31,7 @@ * * @since 3.5.0 */ -public class MavenSimpleLogger extends ExtSimpleLogger { +public class MavenSimpleLogger extends MavenBaseLogger { private String traceRenderedLevel; private String debugRenderedLevel; @@ -74,13 +73,12 @@ protected String renderLevel(int level) { } } - @Override - protected void doWrite(StringBuilder buf, Throwable t) { + protected void write(StringBuilder buf, Throwable t) { Consumer sink = logSink; if (sink != null) { sink.accept(buf.toString()); } else { - super.doWrite(buf, t); + super.write(buf, t); } } @@ -98,7 +96,7 @@ protected void writeThrowable(Throwable t, PrintStream stream) { printStackTrace(t, stream, ""); } - private void printStackTrace(Throwable t, PrintStream stream, String prefix) { + protected void printStackTrace(Throwable t, PrintStream stream, String prefix) { MessageBuilder builder = builder(); for (StackTraceElement e : t.getStackTrace()) { builder.a(prefix); @@ -123,7 +121,7 @@ private void printStackTrace(Throwable t, PrintStream stream, String prefix) { } } - private void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { + protected void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { MessageBuilder builder = builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName()); if (t.getMessage() != null) { @@ -147,4 +145,17 @@ protected String getLocation(final StackTraceElement e) { return e.getFileName(); } } + + public void configure(int defaultLogLevel) { + String levelString = recursivelyComputeLevelString(); + if (levelString != null) { + this.currentLogLevel = SimpleLoggerConfiguration.stringToLevel(levelString); + } else { + this.currentLogLevel = defaultLogLevel; + } + } + + public void setLogLevel(int logLevel) { + this.currentLogLevel = logLevel; + } } diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/OutputChoice.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/OutputChoice.java new file mode 100644 index 000000000000..fa491b596dca --- /dev/null +++ b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/OutputChoice.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.slf4j; + +import java.io.PrintStream; + +/** + * This class encapsulates the user's choice of output target. + * + * @author Ceki Gülcü + * + */ +class OutputChoice { + + enum OutputChoiceType { + SYS_OUT, + CACHED_SYS_OUT, + SYS_ERR, + CACHED_SYS_ERR, + FILE; + } + + final OutputChoiceType outputChoiceType; + final PrintStream targetPrintStream; + + OutputChoice(OutputChoiceType outputChoiceType) { + if (outputChoiceType == OutputChoiceType.FILE) { + throw new IllegalArgumentException(); + } + this.outputChoiceType = outputChoiceType; + if (outputChoiceType == OutputChoiceType.CACHED_SYS_OUT) { + this.targetPrintStream = System.out; + } else if (outputChoiceType == OutputChoiceType.CACHED_SYS_ERR) { + this.targetPrintStream = System.err; + } else { + this.targetPrintStream = null; + } + } + + OutputChoice(PrintStream printStream) { + this.outputChoiceType = OutputChoiceType.FILE; + this.targetPrintStream = printStream; + } + + PrintStream getTargetPrintStream() { + switch (outputChoiceType) { + case SYS_OUT: + return System.out; + case SYS_ERR: + return System.err; + case CACHED_SYS_ERR: + case CACHED_SYS_OUT: + case FILE: + return targetPrintStream; + default: + throw new IllegalArgumentException(); + } + } +} diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java new file mode 100644 index 000000000000..02ca0f464e2a --- /dev/null +++ b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java @@ -0,0 +1,193 @@ +/* + * 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.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Properties; + +import org.apache.maven.slf4j.OutputChoice.OutputChoiceType; +import org.slf4j.helpers.Reporter; + +/** + * This class holds configuration values for {@link MavenBaseLogger}. The + * values are computed at runtime. See {@link MavenBaseLogger} documentation for + * more information. + * + * + * @author Ceki Gülcü + * @author Scott Sanders + * @author Rod Waldhoff + * @author Robert Burrell Donkin + * @author Cédrik LIME + * + * @since 1.7.25 + */ +public class SimpleLoggerConfiguration { + + private static final String CONFIGURATION_FILE = "simplelogger.properties"; + + static final int DEFAULT_LOG_LEVEL_DEFAULT = MavenBaseLogger.LOG_LEVEL_INFO; + int defaultLogLevel = DEFAULT_LOG_LEVEL_DEFAULT; + + private static final boolean SHOW_DATE_TIME_DEFAULT = false; + boolean showDateTime = SHOW_DATE_TIME_DEFAULT; + + private static final String DATE_TIME_FORMAT_STR_DEFAULT = null; + private static String dateTimeFormatStr = DATE_TIME_FORMAT_STR_DEFAULT; + + DateFormat dateFormatter = null; + + private static final boolean SHOW_THREAD_NAME_DEFAULT = true; + boolean showThreadName = SHOW_THREAD_NAME_DEFAULT; + + /** + * See https://jira.qos.ch/browse/SLF4J-499 + * @since 1.7.33 and 2.0.0-alpha6 + */ + private static final boolean SHOW_THREAD_ID_DEFAULT = false; + + boolean showThreadId = SHOW_THREAD_ID_DEFAULT; + + static final boolean SHOW_LOG_NAME_DEFAULT = true; + boolean showLogName = SHOW_LOG_NAME_DEFAULT; + + private static final boolean SHOW_SHORT_LOG_NAME_DEFAULT = false; + boolean showShortLogName = SHOW_SHORT_LOG_NAME_DEFAULT; + + private static final boolean LEVEL_IN_BRACKETS_DEFAULT = false; + boolean levelInBrackets = LEVEL_IN_BRACKETS_DEFAULT; + + private static final String LOG_FILE_DEFAULT = "System.err"; + private String logFile = LOG_FILE_DEFAULT; + OutputChoice outputChoice = null; + + private static final boolean CACHE_OUTPUT_STREAM_DEFAULT = false; + private boolean cacheOutputStream = CACHE_OUTPUT_STREAM_DEFAULT; + + private static final String WARN_LEVELS_STRING_DEFAULT = "WARN"; + String warnLevelString = WARN_LEVELS_STRING_DEFAULT; + + private final Properties properties = new Properties(); + + void init() { + loadProperties(); + + String defaultLogLevelString = getStringProperty(MavenBaseLogger.DEFAULT_LOG_LEVEL_KEY, null); + if (defaultLogLevelString != null) { + defaultLogLevel = stringToLevel(defaultLogLevelString); + } + + showLogName = + getBooleanProperty(MavenBaseLogger.SHOW_LOG_NAME_KEY, SimpleLoggerConfiguration.SHOW_LOG_NAME_DEFAULT); + showShortLogName = getBooleanProperty(MavenBaseLogger.SHOW_SHORT_LOG_NAME_KEY, SHOW_SHORT_LOG_NAME_DEFAULT); + showDateTime = getBooleanProperty(MavenBaseLogger.SHOW_DATE_TIME_KEY, SHOW_DATE_TIME_DEFAULT); + showThreadName = getBooleanProperty(MavenBaseLogger.SHOW_THREAD_NAME_KEY, SHOW_THREAD_NAME_DEFAULT); + showThreadId = getBooleanProperty(MavenBaseLogger.SHOW_THREAD_ID_KEY, SHOW_THREAD_ID_DEFAULT); + dateTimeFormatStr = getStringProperty(MavenBaseLogger.DATE_TIME_FORMAT_KEY, DATE_TIME_FORMAT_STR_DEFAULT); + levelInBrackets = getBooleanProperty(MavenBaseLogger.LEVEL_IN_BRACKETS_KEY, LEVEL_IN_BRACKETS_DEFAULT); + warnLevelString = getStringProperty(MavenBaseLogger.WARN_LEVEL_STRING_KEY, WARN_LEVELS_STRING_DEFAULT); + + logFile = getStringProperty(MavenBaseLogger.LOG_FILE_KEY, logFile); + + cacheOutputStream = + getBooleanProperty(MavenBaseLogger.CACHE_OUTPUT_STREAM_STRING_KEY, CACHE_OUTPUT_STREAM_DEFAULT); + outputChoice = computeOutputChoice(logFile, cacheOutputStream); + + if (dateTimeFormatStr != null) { + try { + dateFormatter = new SimpleDateFormat(dateTimeFormatStr); + } catch (IllegalArgumentException e) { + Reporter.error("Bad date format in " + CONFIGURATION_FILE + "; will output relative time", e); + } + } + } + + private void loadProperties() { + // Add props from the resource simplelogger.properties + ClassLoader threadCL = Thread.currentThread().getContextClassLoader(); + ClassLoader toUseCL = (threadCL != null ? threadCL : ClassLoader.getSystemClassLoader()); + try (InputStream in = toUseCL.getResourceAsStream(CONFIGURATION_FILE)) { + if (in != null) { + properties.load(in); + } + } catch (java.io.IOException e) { + // ignored + } + } + + String getStringProperty(String name, String defaultValue) { + String prop = getStringProperty(name); + return (prop == null) ? defaultValue : prop; + } + + boolean getBooleanProperty(String name, boolean defaultValue) { + String prop = getStringProperty(name); + return (prop == null) ? defaultValue : "true".equalsIgnoreCase(prop); + } + + String getStringProperty(String name) { + String prop = null; + try { + prop = System.getProperty(name); + } catch (SecurityException e) { + // Ignore + } + return (prop == null) ? properties.getProperty(name) : prop; + } + + static int stringToLevel(String levelStr) { + if ("trace".equalsIgnoreCase(levelStr)) { + return MavenBaseLogger.LOG_LEVEL_TRACE; + } else if ("debug".equalsIgnoreCase(levelStr)) { + return MavenBaseLogger.LOG_LEVEL_DEBUG; + } else if ("info".equalsIgnoreCase(levelStr)) { + return MavenBaseLogger.LOG_LEVEL_INFO; + } else if ("warn".equalsIgnoreCase(levelStr)) { + return MavenBaseLogger.LOG_LEVEL_WARN; + } else if ("error".equalsIgnoreCase(levelStr)) { + return MavenBaseLogger.LOG_LEVEL_ERROR; + } else if ("off".equalsIgnoreCase(levelStr)) { + return MavenBaseLogger.LOG_LEVEL_OFF; + } + // assume INFO by default + return MavenBaseLogger.LOG_LEVEL_INFO; + } + + private static OutputChoice computeOutputChoice(String logFile, boolean cacheOutputStream) { + if ("System.err".equalsIgnoreCase(logFile)) { + return new OutputChoice(cacheOutputStream ? OutputChoiceType.CACHED_SYS_ERR : OutputChoiceType.SYS_ERR); + } else if ("System.out".equalsIgnoreCase(logFile)) { + return new OutputChoice(cacheOutputStream ? OutputChoiceType.CACHED_SYS_OUT : OutputChoiceType.SYS_OUT); + } else { + try { + FileOutputStream fos = new FileOutputStream(logFile); + PrintStream printStream = new PrintStream(fos); + return new OutputChoice(printStream); + } catch (FileNotFoundException e) { + Reporter.error("Could not open [" + logFile + "]. Defaulting to System.err", e); + return new OutputChoice(OutputChoiceType.SYS_ERR); + } + } + } +} diff --git a/maven-slf4j-provider/src/main/java/org/slf4j/simple/ExtSimpleLogger.java b/maven-slf4j-provider/src/main/java/org/slf4j/simple/ExtSimpleLogger.java deleted file mode 100644 index 973f75a7e823..000000000000 --- a/maven-slf4j-provider/src/main/java/org/slf4j/simple/ExtSimpleLogger.java +++ /dev/null @@ -1,39 +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.slf4j.simple; - -/** - * Class inheriting SimpleLogger to work around the fact that the {@link #write(StringBuilder, Throwable)} - * method is package private. - */ -public class ExtSimpleLogger extends SimpleLogger { - - public ExtSimpleLogger(String name) { - super(name); - } - - @Override - void write(StringBuilder buf, Throwable t) { - doWrite(buf, t); - } - - protected void doWrite(StringBuilder buf, Throwable t) { - super.write(buf, t); - } -} From e4a27880596b0f7e21cfb617c1bca10e76480caa Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 11 Oct 2024 06:46:41 +0200 Subject: [PATCH 02/14] wip 2 --- .../concurrent/BuildPlanExecutor.java | 1 + .../maven/logging/BuildEventListener.java | 79 +++++++ .../ConcurrentLogOutput.java | 2 +- .../logging/LoggingExecutionListener.java | 195 ++++++++++++++++++ .../maven/logging/LoggingOutputStream.java | 97 +++++++++ .../logging/ProjectBuildLogAppender.java | 88 ++++++++ .../apache/maven/cli/logging/Slf4jLogger.java | 19 ++ 7 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java rename maven-core/src/main/java/org/apache/maven/{lifecycle/internal/concurrent => logging}/ConcurrentLogOutput.java (97%) create mode 100644 maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java create mode 100644 maven-core/src/main/java/org/apache/maven/logging/LoggingOutputStream.java create mode 100644 maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java index d40e2e5ac47e..990b5850a2ad 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java @@ -71,6 +71,7 @@ import org.apache.maven.lifecycle.internal.ReactorContext; import org.apache.maven.lifecycle.internal.Task; import org.apache.maven.lifecycle.internal.TaskSegment; +import org.apache.maven.logging.ConcurrentLogOutput; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.MavenPluginManager; diff --git a/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java b/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java new file mode 100644 index 000000000000..54ccb1aa1a3e --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java @@ -0,0 +1,79 @@ +/* + * 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.logging; + +import org.apache.maven.execution.ExecutionEvent; +import org.eclipse.aether.transfer.TransferEvent; + +/** + * An abstract build event sink. + */ +public abstract class BuildEventListener { + private static final BuildEventListener DUMMY = new BuildEventListener() { + + public void sessionStarted(ExecutionEvent event) {} + + public void projectStarted(String projectId) {} + + public void projectLogMessage(String projectId, String event) {} + + public void projectFinished(String projectId) {} + + public void executionFailure(String projectId, boolean halted, String exception) {} + + public void mojoStarted(ExecutionEvent event) {} + + public void finish(int exitCode) throws Exception {} + + public void fail(Throwable t) throws Exception {} + + public void log(String msg) {} + + public void transfer(String projectId, TransferEvent e) {} + }; + + /** + * @return a dummy {@link BuildEventListener} that just swallows the messages and does not send them anywhere + */ + public static BuildEventListener dummy() { + return DUMMY; + } + + protected BuildEventListener() {} + + public abstract void sessionStarted(ExecutionEvent event); + + public abstract void projectStarted(String projectId); + + public abstract void projectLogMessage(String projectId, String event); + + public abstract void projectFinished(String projectId); + + public abstract void executionFailure(String projectId, boolean halted, String exception); + + public abstract void mojoStarted(ExecutionEvent event); + + public abstract void finish(int exitCode) throws Exception; + + public abstract void fail(Throwable t) throws Exception; + + public abstract void log(String msg); + + public abstract void transfer(String projectId, TransferEvent e); +} diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLogOutput.java b/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java similarity index 97% rename from maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLogOutput.java rename to maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java index 5c5342ba8ef8..5339a7bf035f 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLogOutput.java +++ b/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.lifecycle.internal.concurrent; +package org.apache.maven.logging; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; diff --git a/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java b/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java new file mode 100644 index 000000000000..b2f2d3c2a81c --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java @@ -0,0 +1,195 @@ +/* + * 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.logging; + +import javax.inject.Named; +import javax.inject.Singleton; + +import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.execution.ExecutionListener; +import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.execution.ProjectExecutionEvent; +import org.apache.maven.execution.ProjectExecutionListener; +import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.lifecycle.internal.ReactorBuildStatus; + +@Singleton +@Named +public class LoggingExecutionListener implements ExecutionListener, ProjectExecutionListener { + + private ExecutionListener delegate; + private BuildEventListener buildEventListener; + + public void init(ExecutionListener delegate, BuildEventListener buildEventListener) { + this.delegate = delegate; + this.buildEventListener = buildEventListener; + } + + @Override + public void beforeProjectExecution(ProjectExecutionEvent projectExecutionEvent) + throws LifecycleExecutionException {} + + @Override + public void beforeProjectLifecycleExecution(ProjectExecutionEvent projectExecutionEvent) + throws LifecycleExecutionException {} + + @Override + public void afterProjectExecutionSuccess(ProjectExecutionEvent projectExecutionEvent) + throws LifecycleExecutionException {} + + @Override + public void afterProjectExecutionFailure(ProjectExecutionEvent projectExecutionEvent) { + MavenSession session = projectExecutionEvent.getSession(); + boolean halted; + // The ReactorBuildStatus is only available if the SmartBuilder is used + ReactorBuildStatus status = + (ReactorBuildStatus) session.getRepositorySession().getData().get(ReactorBuildStatus.class); + if (status != null) { + halted = status.isHalted(); + } else { + // assume sensible default + Throwable t = projectExecutionEvent.getCause(); + halted = (t instanceof RuntimeException || !(t instanceof Exception)) + || !MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(session.getReactorFailureBehavior()) + && !MavenExecutionRequest.REACTOR_FAIL_AT_END.equals(session.getReactorFailureBehavior()); + } + Throwable cause = projectExecutionEvent.getCause(); + buildEventListener.executionFailure( + projectExecutionEvent.getProject().getArtifactId(), halted, cause != null ? cause.toString() : null); + } + + @Override + public void projectDiscoveryStarted(ExecutionEvent event) { + setMdc(event); + delegate.projectDiscoveryStarted(event); + } + + @Override + public void sessionStarted(ExecutionEvent event) { + setMdc(event); + buildEventListener.sessionStarted(event); + delegate.sessionStarted(event); + } + + @Override + public void sessionEnded(ExecutionEvent event) { + setMdc(event); + delegate.sessionEnded(event); + } + + @Override + public void projectStarted(ExecutionEvent event) { + setMdc(event); + buildEventListener.projectStarted(event.getProject().getArtifactId()); + delegate.projectStarted(event); + } + + @Override + public void projectSucceeded(ExecutionEvent event) { + setMdc(event); + delegate.projectSucceeded(event); + buildEventListener.projectFinished(event.getProject().getArtifactId()); + } + + @Override + public void projectFailed(ExecutionEvent event) { + setMdc(event); + delegate.projectFailed(event); + buildEventListener.projectFinished(event.getProject().getArtifactId()); + } + + @Override + public void projectSkipped(ExecutionEvent event) { + setMdc(event); + buildEventListener.projectStarted(event.getProject().getArtifactId()); + delegate.projectSkipped(event); + buildEventListener.projectFinished(event.getProject().getArtifactId()); + } + + @Override + public void mojoStarted(ExecutionEvent event) { + setMdc(event); + buildEventListener.mojoStarted(event); + delegate.mojoStarted(event); + } + + @Override + public void mojoSucceeded(ExecutionEvent event) { + setMdc(event); + delegate.mojoSucceeded(event); + } + + @Override + public void mojoFailed(ExecutionEvent event) { + setMdc(event); + delegate.mojoFailed(event); + } + + @Override + public void mojoSkipped(ExecutionEvent event) { + setMdc(event); + delegate.mojoSkipped(event); + } + + @Override + public void forkStarted(ExecutionEvent event) { + setMdc(event); + delegate.forkStarted(event); + ProjectBuildLogAppender.setForkingProjectId(event.getProject().getArtifactId()); + } + + @Override + public void forkSucceeded(ExecutionEvent event) { + delegate.forkSucceeded(event); + ProjectBuildLogAppender.setForkingProjectId(null); + } + + @Override + public void forkFailed(ExecutionEvent event) { + delegate.forkFailed(event); + ProjectBuildLogAppender.setForkingProjectId(null); + } + + @Override + public void forkedProjectStarted(ExecutionEvent event) { + setMdc(event); + delegate.forkedProjectStarted(event); + } + + @Override + public void forkedProjectSucceeded(ExecutionEvent event) { + setMdc(event); + delegate.forkedProjectSucceeded(event); + ProjectBuildLogAppender.setProjectId(null); + } + + @Override + public void forkedProjectFailed(ExecutionEvent event) { + setMdc(event); + delegate.forkedProjectFailed(event); + ProjectBuildLogAppender.setProjectId(null); + } + + private void setMdc(ExecutionEvent event) { + if (event.getProject() != null) { + ProjectBuildLogAppender.setProjectId(event.getProject().getArtifactId()); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/logging/LoggingOutputStream.java b/maven-core/src/main/java/org/apache/maven/logging/LoggingOutputStream.java new file mode 100644 index 000000000000..5d7ee4b107ea --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/LoggingOutputStream.java @@ -0,0 +1,97 @@ +/* + * 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.logging; + +import java.io.ByteArrayOutputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.function.Consumer; + +public class LoggingOutputStream extends FilterOutputStream { + + static final byte[] LINE_SEP = System.lineSeparator().getBytes(); + + final EolBaos buf; + final Consumer consumer; + + public LoggingOutputStream(Consumer consumer) { + this(new EolBaos(), consumer); + } + + LoggingOutputStream(EolBaos out, Consumer consumer) { + super(out); + this.buf = out; + this.consumer = consumer; + } + + public PrintStream printStream() { + return new LoggingPrintStream(this); + } + + @Override + public void write(int b) throws IOException { + super.write(b); + if (buf.isEol()) { + String line = new String(buf.toByteArray(), 0, buf.size() - LINE_SEP.length); + ProjectBuildLogAppender.updateMdc(); + consumer.accept(line); + buf.reset(); + } + } + + public void forceFlush() { + if (buf.size() > 0) { + String line = new String(buf.toByteArray(), 0, buf.size()); + ProjectBuildLogAppender.updateMdc(); + consumer.accept(line); + buf.reset(); + } + } + + static class EolBaos extends ByteArrayOutputStream { + boolean isEol() { + if (count >= LINE_SEP.length) { + for (int i = 0; i < LINE_SEP.length; i++) { + if (buf[count - LINE_SEP.length + i] != LINE_SEP[i]) { + return false; + } + } + return true; + } + return false; + } + } + + public static class LoggingPrintStream extends PrintStream { + public LoggingPrintStream(LoggingOutputStream out) { + super(out, true); + } + + public void forceFlush() { + ((LoggingOutputStream) out).forceFlush(); + } + } + + public static void forceFlush(PrintStream ps) { + if (ps instanceof LoggingPrintStream) { + ((LoggingPrintStream) ps).forceFlush(); + } + } +} diff --git a/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java b/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java new file mode 100644 index 000000000000..8465df0cf060 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/ProjectBuildLogAppender.java @@ -0,0 +1,88 @@ +/* + * 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.logging; + +import org.apache.maven.slf4j.MavenSimpleLogger; +import org.slf4j.MDC; + +/** + * Forwards log messages to the client. + */ +public class ProjectBuildLogAppender implements AutoCloseable { + + private static final String KEY_PROJECT_ID = "maven.project.id"; + private static final ThreadLocal PROJECT_ID = new InheritableThreadLocal<>(); + private static final ThreadLocal FORKING_PROJECT_ID = new InheritableThreadLocal<>(); + + public static String getProjectId() { + return PROJECT_ID.get(); + } + + public static void setProjectId(String projectId) { + String forkingProjectId = FORKING_PROJECT_ID.get(); + if (forkingProjectId != null) { + if (projectId != null) { + projectId = forkingProjectId + "/" + projectId; + } else { + projectId = forkingProjectId; + } + } + if (projectId != null) { + PROJECT_ID.set(projectId); + MDC.put(KEY_PROJECT_ID, projectId); + } else { + PROJECT_ID.remove(); + MDC.remove(KEY_PROJECT_ID); + } + } + + public static void setForkingProjectId(String forkingProjectId) { + if (forkingProjectId != null) { + FORKING_PROJECT_ID.set(forkingProjectId); + } else { + FORKING_PROJECT_ID.remove(); + } + } + + public static void updateMdc() { + String id = getProjectId(); + if (id != null) { + MDC.put(KEY_PROJECT_ID, id); + } else { + MDC.remove(KEY_PROJECT_ID); + } + } + + private final BuildEventListener buildEventListener; + + public ProjectBuildLogAppender(BuildEventListener buildEventListener) { + this.buildEventListener = buildEventListener; + MavenSimpleLogger.setLogSink(this::accept); + } + + protected void accept(String message) { + String projectId = MDC.get(KEY_PROJECT_ID); + buildEventListener.projectLogMessage(projectId, message); + } + + @Override + public void close() throws Exception { + MavenSimpleLogger.setLogSink(null); + } +} diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java b/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java index 9cca0a940d1a..0dbce41f1263 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java @@ -18,6 +18,7 @@ */ package org.apache.maven.cli.logging; +import org.apache.maven.logging.ProjectBuildLogAppender; import org.codehaus.plexus.logging.Logger; /** @@ -29,16 +30,20 @@ public class Slf4jLogger implements Logger { private org.slf4j.Logger logger; + private String projectId; public Slf4jLogger(org.slf4j.Logger logger) { this.logger = logger; + this.projectId = ProjectBuildLogAppender.getProjectId(); } public void debug(String message) { + setMdc(); logger.debug(message); } public void debug(String message, Throwable throwable) { + setMdc(); logger.debug(message, throwable); } @@ -47,10 +52,12 @@ public boolean isDebugEnabled() { } public void info(String message) { + setMdc(); logger.info(message); } public void info(String message, Throwable throwable) { + setMdc(); logger.info(message, throwable); } @@ -59,10 +66,12 @@ public boolean isInfoEnabled() { } public void warn(String message) { + setMdc(); logger.warn(message); } public void warn(String message, Throwable throwable) { + setMdc(); logger.warn(message, throwable); } @@ -71,10 +80,12 @@ public boolean isWarnEnabled() { } public void error(String message) { + setMdc(); logger.error(message); } public void error(String message, Throwable throwable) { + setMdc(); logger.error(message, throwable); } @@ -83,10 +94,12 @@ public boolean isErrorEnabled() { } public void fatalError(String message) { + setMdc(); logger.error(message); } public void fatalError(String message, Throwable throwable) { + setMdc(); logger.error(message, throwable); } @@ -116,4 +129,10 @@ public Logger getChildLogger(String name) { public String getName() { return logger.getName(); } + + private void setMdc() { + if (projectId != null && ProjectBuildLogAppender.getProjectId() == null) { + ProjectBuildLogAppender.setProjectId(projectId); + } + } } From bc434428380b3a5c26de09fc9456bb584d05a147 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 11 Oct 2024 16:25:34 +0200 Subject: [PATCH 03/14] wip --- apache-maven/pom.xml | 2 +- .../org/apache/maven/api/cli/Options.java | 8 + maven-bom/pom.xml | 2 +- .../cling/invoker/CommonsCliOptions.java | 14 + .../maven/cling/invoker/LayeredOptions.java | 5 + .../maven/cling/invoker/LookupInvoker.java | 76 +++- .../invoker/mvn/DefaultMavenInvoker.java | 16 +- .../invoker/mvn/MavenInvokerTestSupport.java | 7 +- maven-core/pom.xml | 2 +- .../multithreaded/MultiThreadedBuilder.java | 22 +- .../multithreaded/ThreadOutputMuxer.java | 390 ------------------ .../maven/logging/BuildEventListener.java | 53 +-- .../maven/logging/ConcurrentLogOutput.java | 8 +- .../logging/SimpleBuildEventListener.java | 76 ++++ .../multithreaded/ThreadOutputMuxerTest.java | 152 ------- maven-embedder/pom.xml | 2 +- .../java/org/apache/maven/cli/MavenCli.java | 19 +- .../maven/cli/event/ExecutionEventLogger.java | 32 +- .../org/apache/maven/jline/MessageUtils.java | 4 + .../pom.xml | 26 +- .../maven/logging/api/LogLevelRecorder.java | 23 +- .../maven/slf4j/DefaultLogLevelRecorder.java | 102 +++++ .../apache/maven/slf4j/MavenBaseLogger.java | 0 .../slf4j/MavenFailOnSeverityLogger.java | 5 +- .../maven/slf4j/MavenLoggerFactory.java | 30 +- .../maven/slf4j/MavenServiceProvider.java | 0 .../apache/maven/slf4j/MavenSimpleLogger.java | 0 .../org/apache/maven/slf4j/OutputChoice.java | 0 .../slf4j/SimpleLoggerConfiguration.java | 0 .../org.slf4j.spi.SLF4JServiceProvider | 0 .../src/site/apt/index.apt | 0 .../src/site/site.xml | 0 .../maven/slf4j}/LogLevelRecorderTest.java | 10 +- .../maven/slf4j/MavenLoggerFactoryTest.java | 25 +- .../maven/slf4j/MavenSimpleLoggerTest.java | 4 +- maven-slf4j-provider/pom.xml | 113 ----- .../maven/logwrapper/LogLevelRecorder.java | 64 --- maven-slf4j-wrapper/src/site/site.xml | 38 -- pom.xml | 18 +- 39 files changed, 403 insertions(+), 945 deletions(-) delete mode 100644 maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxer.java create mode 100644 maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java delete mode 100644 maven-core/src/test/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxerTest.java rename {maven-slf4j-wrapper => maven-logging}/pom.xml (74%) rename maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java => maven-logging/src/main/java/org/apache/maven/logging/api/LogLevelRecorder.java (69%) create mode 100644 maven-logging/src/main/java/org/apache/maven/slf4j/DefaultLogLevelRecorder.java rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java (100%) rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java (95%) rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java (73%) rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java (100%) rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java (100%) rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/OutputChoice.java (100%) rename {maven-slf4j-provider => maven-logging}/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java (100%) rename {maven-slf4j-provider => maven-logging}/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider (100%) rename {maven-slf4j-provider => maven-logging}/src/site/apt/index.apt (100%) rename {maven-slf4j-provider => maven-logging}/src/site/site.xml (100%) rename {maven-slf4j-wrapper/src/test/java/org/apache/maven/logwrapper => maven-logging/src/test/java/org/apache/maven/slf4j}/LogLevelRecorderTest.java (84%) rename {maven-slf4j-provider => maven-logging}/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java (72%) rename {maven-slf4j-provider => maven-logging}/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java (97%) delete mode 100644 maven-slf4j-provider/pom.xml delete mode 100644 maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/LogLevelRecorder.java delete mode 100644 maven-slf4j-wrapper/src/site/site.xml diff --git a/apache-maven/pom.xml b/apache-maven/pom.xml index 45b017fbc56c..857e2d88cc14 100644 --- a/apache-maven/pom.xml +++ b/apache-maven/pom.xml @@ -99,7 +99,7 @@ under the License. org.apache.maven - maven-slf4j-provider + maven-logging org.jline diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java index 5a486bf2152f..128e23f630a8 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/Options.java @@ -166,6 +166,14 @@ public interface Options { @Nonnull Optional logFile(); + /** + * Returns whether raw streams should be logged. + * + * @return a boolean indicating whether raw streams should be logged + */ + @Nonnull + Optional rawStreams(); + /** * Returns the color setting for console output. * diff --git a/maven-bom/pom.xml b/maven-bom/pom.xml index 9e4f5c757391..164027d419c9 100644 --- a/maven-bom/pom.xml +++ b/maven-bom/pom.xml @@ -175,7 +175,7 @@ under the License. org.apache.maven - maven-slf4j-wrapper + maven-logging ${project.version} diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java index c3adcb5ee10f..cdde46f32588 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java @@ -181,6 +181,14 @@ public Optional logFile() { return Optional.empty(); } + @Override + public Optional rawStreams() { + if (commandLine.hasOption(CLIManager.RAW_STREAMS)) { + return Optional.of(Boolean.TRUE); + } + return Optional.empty(); + } + @Override public Optional color() { if (commandLine.hasOption(CLIManager.COLOR)) { @@ -262,6 +270,7 @@ protected static class CLIManager { public static final String ALTERNATE_INSTALLATION_TOOLCHAINS = "it"; public static final String LOG_FILE = "l"; + public static final String RAW_STREAMS = "raw-streams"; public static final String COLOR = "color"; public static final String HELP = "h"; @@ -348,6 +357,11 @@ protected void prepareOptions(org.apache.commons.cli.Options options) { .hasArg() .desc("Log file where all build output will go (disables output color)") .build()); + options.addOption(Option.builder() + .longOpt(RAW_STREAMS) + .hasArg() + .desc("Do not decorate standard output and error streams") + .build()); options.addOption(Option.builder(SHOW_VERSION) .longOpt("show-version") .desc("Display version information WITHOUT stopping build") diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java index e702c76dde69..83219eb27b3d 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LayeredOptions.java @@ -122,6 +122,11 @@ public Optional logFile() { return returnFirstPresentOrEmpty(Options::logFile); } + @Override + public Optional rawStreams() { + return returnFirstPresentOrEmpty(Options::rawStreams); + } + @Override public Optional color() { return returnFirstPresentOrEmpty(Options::color); diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index b4995b6c0e13..d43d7af4d48a 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -19,14 +19,15 @@ package org.apache.maven.cling.invoker; import java.io.FileNotFoundException; -import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.function.Function; @@ -54,8 +55,11 @@ import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.jline.MessageUtils; -import org.apache.maven.logwrapper.LogLevelRecorder; -import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; +import org.apache.maven.logging.BuildEventListener; +import org.apache.maven.logging.LoggingOutputStream; +import org.apache.maven.logging.ProjectBuildLogAppender; +import org.apache.maven.logging.SimpleBuildEventListener; +import org.apache.maven.logging.api.LogLevelRecorder; import org.apache.maven.settings.Mirror; import org.apache.maven.settings.Profile; import org.apache.maven.settings.Proxy; @@ -68,9 +72,11 @@ import org.apache.maven.settings.building.SettingsBuildingRequest; import org.apache.maven.settings.building.SettingsBuildingResult; import org.apache.maven.settings.building.SettingsProblem; +import org.apache.maven.slf4j.MavenSimpleLogger; import org.eclipse.aether.transfer.TransferListener; import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; +import org.slf4j.spi.LocationAwareLogger; import static java.util.Objects.requireNonNull; import static org.apache.maven.cling.invoker.Utils.toFile; @@ -149,10 +155,27 @@ protected LookupInvokerContext(LookupInvoker invoker, R invokerRequest) public Path userSettingsPath; public Settings effectiveSettings; + public final List closeables = new ArrayList<>(); + @Override public void close() throws InvokerException { - if (containerCapsule != null) { - containerCapsule.close(); + List causes = null; + for (AutoCloseable c : closeables) { + if (c != null) { + try { + c.close(); + } catch (Exception e) { + if (causes == null) { + causes = new ArrayList<>(); + } + causes.add(e); + } + } + } + if (causes != null) { + InvokerException exception = new InvokerException("Unable to close context"); + causes.forEach(exception::addSuppressed); + throw exception; } } } @@ -273,17 +296,27 @@ protected void configureLogging(C context) throws Exception { // see https://issues.apache.org/jira/browse/MNG-2570 // LOG STREAMS + PrintStream sysOut = System.out; + PrintStream sysErr = System.err; + if (mavenOptions.rawStreams().isEmpty() || !mavenOptions.rawStreams().get()) { + MavenSimpleLogger stdout = (MavenSimpleLogger) context.loggerFactory.getLogger("stdout"); + MavenSimpleLogger stderr = (MavenSimpleLogger) context.loggerFactory.getLogger("stderr"); + stdout.setLogLevel(LocationAwareLogger.INFO_INT); + stderr.setLogLevel(LocationAwareLogger.INFO_INT); + System.setOut(new LoggingOutputStream(s -> stdout.info("[stdout] " + s)).printStream()); + System.setErr(new LoggingOutputStream(s -> stderr.warn("[stderr] " + s)).printStream()); + context.closeables.add(() -> System.setOut(sysOut)); + context.closeables.add(() -> System.setErr(sysErr)); + } + BuildEventListener bel; if (mavenOptions.logFile().isPresent()) { Path logFile = context.cwdResolver.apply(mavenOptions.logFile().get()); - // redirect stdout and stderr to file - try { - PrintStream ps = new PrintStream(Files.newOutputStream(logFile)); - System.setOut(ps); - System.setErr(ps); - } catch (IOException e) { - throw new InvokerException("Cannot set up log " + e.getMessage(), e); - } + bel = new SimpleBuildEventListener(new PrintWriter(Files.newBufferedWriter(logFile))); + } else { + bel = new SimpleBuildEventListener(MessageUtils.getTerminal().writer(), true); } + ProjectBuildLogAppender projectBuildLogAppender = new ProjectBuildLogAppender(bel); + context.closeables.add(projectBuildLogAppender); } protected void activateLogging(C context) throws Exception { @@ -298,13 +331,19 @@ protected void activateLogging(C context) throws Exception { if (mavenOptions.failOnSeverity().isPresent()) { String logLevelThreshold = mavenOptions.failOnSeverity().get(); - - if (context.loggerFactory instanceof MavenSlf4jWrapperFactory) { - LogLevelRecorder logLevelRecorder = new LogLevelRecorder(logLevelThreshold); - ((MavenSlf4jWrapperFactory) context.loggerFactory).setLogLevelRecorder(logLevelRecorder); + if (context.loggerFactory instanceof LogLevelRecorder recorder) { + LogLevelRecorder.Level level = + switch (logLevelThreshold.toLowerCase(Locale.ENGLISH)) { + case "warn", "warning" -> LogLevelRecorder.Level.WARN; + case "error" -> LogLevelRecorder.Level.ERROR; + default -> throw new IllegalArgumentException( + logLevelThreshold + + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR."); + }; + recorder.setMaxLevelAllowed(level); context.logger.info("Enabled to break the build on log level " + logLevelThreshold + "."); } else { - context.logger.warn("Expected LoggerFactory to be of type '" + MavenSlf4jWrapperFactory.class.getName() + context.logger.warn("Expected LoggerFactory to be of type '" + LogLevelRecorder.class.getName() + "', but found '" + context.loggerFactory.getClass().getName() + "' instead. " + "The --fail-on-severity flag will not take effect."); @@ -337,6 +376,7 @@ protected void preCommands(C context) throws Exception { protected void container(C context) throws Exception { context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(context); + context.closeables.add(context.containerCapsule); context.lookup = context.containerCapsule.getLookup(); context.settingsBuilder = context.lookup.lookup(SettingsBuilder.class); diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java index 3c8f6bb8f9d1..54d8525ef45c 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java @@ -37,6 +37,7 @@ import org.apache.maven.api.cli.mvn.MavenInvoker; import org.apache.maven.api.cli.mvn.MavenInvokerRequest; import org.apache.maven.api.cli.mvn.MavenOptions; +import org.apache.maven.api.services.LookupException; import org.apache.maven.building.FileSource; import org.apache.maven.building.Problem; import org.apache.maven.cli.CLIReportingUtils; @@ -56,6 +57,8 @@ import org.apache.maven.execution.ProjectActivation; import org.apache.maven.jline.MessageUtils; import org.apache.maven.lifecycle.LifecycleExecutionException; +import org.apache.maven.logging.BuildEventListener; +import org.apache.maven.logging.LoggingExecutionListener; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.building.SettingsBuildingRequest; @@ -359,12 +362,17 @@ protected String determineGlobalChecksumPolicy(C context) { } protected ExecutionListener determineExecutionListener(C context) { - ExecutionListener executionListener = new ExecutionEventLogger(context.invokerRequest.messageBuilderFactory()); + ExecutionListener listener = new ExecutionEventLogger(context.invokerRequest.messageBuilderFactory()); if (context.eventSpyDispatcher != null) { - return context.eventSpyDispatcher.chainListener(executionListener); - } else { - return executionListener; + listener = context.eventSpyDispatcher.chainListener(listener); + } + try { + LoggingExecutionListener leListener = context.lookup.lookup(LoggingExecutionListener.class); + leListener.init(listener, context.lookup.lookup(BuildEventListener.class)); + listener = leListener; + } catch (LookupException ignore) { } + return listener; } protected String determineMakeBehavior(C context) { diff --git a/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java index da6b4deb0be4..e2dd847053a8 100644 --- a/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java +++ b/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvn/MavenInvokerTestSupport.java @@ -39,8 +39,11 @@ public abstract class MavenInvokerTestSupport goals) throws Exception { // works only in recent Maven4 - Assumptions.assumeTrue(Files.isRegularFile( - Paths.get(System.getProperty("maven.home")).resolve("conf").resolve("maven.properties"))); + Assumptions.assumeTrue( + Files.isRegularFile(Paths.get(System.getProperty("maven.home")) + .resolve("conf") + .resolve("maven.properties")), + "${maven.home}/conf/maven.properties must be a file"); Files.createDirectory(cwd.resolve(".mvn")); diff --git a/maven-core/pom.xml b/maven-core/pom.xml index 6c44bac11cf6..c3baf1706c64 100644 --- a/maven-core/pom.xml +++ b/maven-core/pom.xml @@ -93,7 +93,7 @@ under the License. org.apache.maven - maven-slf4j-provider + maven-logging org.apache.maven.resolver diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java index 2a76afb50d92..6f13050ebd62 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/MultiThreadedBuilder.java @@ -93,9 +93,6 @@ public void build( ExecutorService executor = Executors.newFixedThreadPool(nThreads, new BuildThreadFactory()); CompletionService service = new ExecutorCompletionService<>(executor); - // Currently disabled - ThreadOutputMuxer muxer = null; // new ThreadOutputMuxer( analyzer.getProjectBuilds(), System.out ); - for (TaskSegment taskSegment : taskSegments) { ProjectBuildList segmentProjectBuilds = projectBuilds.getByTaskSegment(taskSegment); Map projectBuildMap = projectBuilds.selectSegment(taskSegment); @@ -103,7 +100,7 @@ public void build( ConcurrencyDependencyGraph analyzer = new ConcurrencyDependencyGraph(segmentProjectBuilds, session.getProjectDependencyGraph()); multiThreadedProjectTaskSegmentBuild( - analyzer, reactorContext, session, service, taskSegment, projectBuildMap, muxer); + analyzer, reactorContext, session, service, taskSegment, projectBuildMap); if (reactorContext.getReactorBuildStatus().isHalted()) { break; } @@ -123,8 +120,7 @@ private void multiThreadedProjectTaskSegmentBuild( MavenSession rootSession, CompletionService service, TaskSegment taskSegment, - Map projectBuildList, - ThreadOutputMuxer muxer) { + Map projectBuildList) { // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId Set duplicateArtifactIds = projectBuildList.keySet().stream() .map(MavenProject::getArtifactId) @@ -139,8 +135,8 @@ private void multiThreadedProjectTaskSegmentBuild( for (MavenProject mavenProject : analyzer.getRootSchedulableBuilds()) { ProjectSegment projectSegment = projectBuildList.get(mavenProject); logger.debug("Scheduling: {}", projectSegment.getProject()); - Callable cb = createBuildCallable( - rootSession, projectSegment, reactorContext, taskSegment, muxer, duplicateArtifactIds); + Callable cb = + createBuildCallable(rootSession, projectSegment, reactorContext, taskSegment, duplicateArtifactIds); service.submit(cb); } @@ -160,12 +156,7 @@ private void multiThreadedProjectTaskSegmentBuild( ProjectSegment scheduledDependent = projectBuildList.get(mavenProject); logger.debug("Scheduling: {}", scheduledDependent); Callable cb = createBuildCallable( - rootSession, - scheduledDependent, - reactorContext, - taskSegment, - muxer, - duplicateArtifactIds); + rootSession, scheduledDependent, reactorContext, taskSegment, duplicateArtifactIds); service.submit(cb); } } @@ -185,7 +176,6 @@ private Callable createBuildCallable( final ProjectSegment projectBuild, final ReactorContext reactorContext, final TaskSegment taskSegment, - final ThreadOutputMuxer muxer, final Set duplicateArtifactIds) { return () -> { final Thread currentThread = Thread.currentThread(); @@ -198,10 +188,8 @@ private Callable createBuildCallable( currentThread.setName("mvn-builder-" + threadNameSuffix); try { - // muxer.associateThreadWithProjectSegment( projectBuild ); lifecycleModuleBuilder.buildProject( projectBuild.getSession(), rootSession, reactorContext, project, taskSegment); - // muxer.setThisModuleComplete( projectBuild ); return projectBuild; } finally { diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxer.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxer.java deleted file mode 100644 index 45ab841d0bc3..000000000000 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxer.java +++ /dev/null @@ -1,390 +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.lifecycle.internal.builder.multithreaded; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -import org.apache.maven.lifecycle.internal.ProjectBuildList; -import org.apache.maven.lifecycle.internal.ProjectSegment; - -/** - * NOTE: This class is not part of any public api and can be changed or deleted without prior notice. - * This class in particular may spontaneously self-combust and be replaced by a plexus-compliant thread aware - * logger implementation at any time. - * - * @since 3.0 - */ -@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"}) -public class ThreadOutputMuxer { - private final Iterator projects; - - private final ThreadLocal projectBuildThreadLocal = new ThreadLocal<>(); - - private final Map streams = new HashMap<>(); - - private final Map printStreams = new HashMap<>(); - - private final ByteArrayOutputStream defaultOutputStreamForUnknownData = new ByteArrayOutputStream(); - - private final PrintStream defaultPrintStream = new PrintStream(defaultOutputStreamForUnknownData); - - private final Set completedBuilds = Collections.synchronizedSet(new HashSet<>()); - - private volatile ProjectSegment currentBuild; - - private final PrintStream originalSystemOUtStream; - - private final ConsolePrinter printer; - - /** - * A simple but safe solution for printing to the console. - */ - class ConsolePrinter implements Runnable { - private volatile boolean running; - - private final ProjectBuildList projectBuildList; - - ConsolePrinter(ProjectBuildList projectBuildList) { - this.projectBuildList = projectBuildList; - } - - public void run() { - running = true; - for (ProjectSegment projectBuild : projectBuildList) { - final PrintStream projectStream = printStreams.get(projectBuild); - ByteArrayOutputStream projectOs = streams.get(projectBuild); - - do { - synchronized (projectStream) { - try { - projectStream.wait(100); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - try { - projectOs.writeTo(originalSystemOUtStream); - } catch (IOException e) { - throw new RuntimeException(e); - } - - projectOs.reset(); - } - } while (!completedBuilds.contains(projectBuild)); - } - running = false; - } - - /* - Wait until we are sure the print-stream thread is running. - */ - - public void waitUntilRunning(boolean expect) { - while (!running == expect) { - try { - Thread.sleep(10); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - } - - public ThreadOutputMuxer(ProjectBuildList segmentChunks, PrintStream originalSystemOut) { - projects = segmentChunks.iterator(); - for (ProjectSegment segmentChunk : segmentChunks) { - final ByteArrayOutputStream value = new ByteArrayOutputStream(); - streams.put(segmentChunk, value); - printStreams.put(segmentChunk, new PrintStream(value)); - } - setNext(); - this.originalSystemOUtStream = originalSystemOut; - System.setOut(new ThreadBoundPrintStream(this.originalSystemOUtStream)); - printer = new ConsolePrinter(segmentChunks); - new Thread(printer).start(); - printer.waitUntilRunning(true); - } - - public void close() { - printer.waitUntilRunning(false); - System.setOut(this.originalSystemOUtStream); - } - - private void setNext() { - currentBuild = projects.hasNext() ? projects.next() : null; - } - - private boolean ownsRealOutputStream(ProjectSegment projectBuild) { - return projectBuild.equals(currentBuild); - } - - private PrintStream getThreadBoundPrintStream() { - ProjectSegment threadProject = projectBuildThreadLocal.get(); - if (threadProject == null) { - return defaultPrintStream; - } - if (ownsRealOutputStream(threadProject)) { - return originalSystemOUtStream; - } - return printStreams.get(threadProject); - } - - public void associateThreadWithProjectSegment(ProjectSegment projectBuild) { - projectBuildThreadLocal.set(projectBuild); - } - - public void setThisModuleComplete(ProjectSegment projectBuild) { - completedBuilds.add(projectBuild); - PrintStream stream = printStreams.get(projectBuild); - synchronized (stream) { - stream.notifyAll(); - } - disconnectThreadFromProject(); - } - - private void disconnectThreadFromProject() { - projectBuildThreadLocal.remove(); - } - - private class ThreadBoundPrintStream extends PrintStream { - - ThreadBoundPrintStream(PrintStream systemOutStream) { - super(systemOutStream); - } - - private PrintStream getOutputStreamForCurrentThread() { - return getThreadBoundPrintStream(); - } - - @Override - public void println() { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(); - currentStream.notifyAll(); - } - } - - @Override - public void print(char c) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(c); - currentStream.notifyAll(); - } - } - - @Override - public void println(char x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(double d) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(d); - currentStream.notifyAll(); - } - } - - @Override - public void println(double x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(float f) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(f); - currentStream.notifyAll(); - } - } - - @Override - public void println(float x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(int i) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(i); - currentStream.notifyAll(); - } - } - - @Override - public void println(int x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(long l) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(l); - currentStream.notifyAll(); - } - } - - @Override - public void println(long x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(boolean b) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(b); - currentStream.notifyAll(); - } - } - - @Override - public void println(boolean x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(char s[]) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(s); - currentStream.notifyAll(); - } - } - - @Override - public void println(char x[]) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(Object obj) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(obj); - currentStream.notifyAll(); - } - } - - @Override - public void println(Object x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(x); - currentStream.notifyAll(); - } - } - - @Override - public void print(String s) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.print(s); - currentStream.notifyAll(); - } - } - - @Override - public void println(String x) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.println(x); - currentStream.notifyAll(); - } - } - - @Override - public void write(byte b[], int off, int len) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.write(b, off, len); - currentStream.notifyAll(); - } - } - - @Override - public void close() { - getOutputStreamForCurrentThread().close(); - } - - @Override - public void flush() { - getOutputStreamForCurrentThread().flush(); - } - - @Override - public void write(int b) { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.write(b); - currentStream.notifyAll(); - } - } - - @Override - public void write(byte b[]) throws IOException { - final PrintStream currentStream = getOutputStreamForCurrentThread(); - synchronized (currentStream) { - currentStream.write(b); - currentStream.notifyAll(); - } - } - } -} diff --git a/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java b/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java index 54ccb1aa1a3e..39573d061cd0 100644 --- a/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java +++ b/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java @@ -24,56 +24,25 @@ /** * An abstract build event sink. */ -public abstract class BuildEventListener { - private static final BuildEventListener DUMMY = new BuildEventListener() { +public interface BuildEventListener { - public void sessionStarted(ExecutionEvent event) {} + void sessionStarted(ExecutionEvent event); - public void projectStarted(String projectId) {} + void projectStarted(String projectId); - public void projectLogMessage(String projectId, String event) {} + void projectLogMessage(String projectId, String event); - public void projectFinished(String projectId) {} + void projectFinished(String projectId); - public void executionFailure(String projectId, boolean halted, String exception) {} + void executionFailure(String projectId, boolean halted, String exception); - public void mojoStarted(ExecutionEvent event) {} + void mojoStarted(ExecutionEvent event); - public void finish(int exitCode) throws Exception {} + void finish(int exitCode) throws Exception; - public void fail(Throwable t) throws Exception {} + void fail(Throwable t) throws Exception; - public void log(String msg) {} + void log(String msg); - public void transfer(String projectId, TransferEvent e) {} - }; - - /** - * @return a dummy {@link BuildEventListener} that just swallows the messages and does not send them anywhere - */ - public static BuildEventListener dummy() { - return DUMMY; - } - - protected BuildEventListener() {} - - public abstract void sessionStarted(ExecutionEvent event); - - public abstract void projectStarted(String projectId); - - public abstract void projectLogMessage(String projectId, String event); - - public abstract void projectFinished(String projectId); - - public abstract void executionFailure(String projectId, boolean halted, String exception); - - public abstract void mojoStarted(ExecutionEvent event); - - public abstract void finish(int exitCode) throws Exception; - - public abstract void fail(Throwable t) throws Exception; - - public abstract void log(String msg); - - public abstract void transfer(String projectId, TransferEvent e); + void transfer(String projectId, TransferEvent e); } diff --git a/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java b/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java index 5339a7bf035f..c0f87bd405b9 100644 --- a/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java +++ b/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java @@ -22,7 +22,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.apache.maven.project.MavenProject; -import org.apache.maven.slf4j.MavenSimpleLogger; /** * Forwards log messages to the client. @@ -32,7 +31,7 @@ public class ConcurrentLogOutput implements AutoCloseable { private static final ThreadLocal CONTEXT = new InheritableThreadLocal<>(); public ConcurrentLogOutput() { - MavenSimpleLogger.setLogSink(this::accept); + // MavenSimpleLogger.setLogSink(this::accept); } protected void accept(String message) { @@ -46,11 +45,12 @@ protected void accept(String message) { @Override public void close() { - MavenSimpleLogger.setLogSink(null); + // MavenSimpleLogger.setLogSink(null); } public AutoCloseable build(MavenProject project) { - return new ProjectExecutionContext(project); + return () -> {}; + // return new ProjectExecutionContext(project); } private static class ProjectExecutionContext implements AutoCloseable { diff --git a/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java b/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java new file mode 100644 index 000000000000..b5858fb6636d --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java @@ -0,0 +1,76 @@ +/* + * 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.logging; + +import java.io.PrintWriter; + +import org.apache.maven.execution.ExecutionEvent; +import org.eclipse.aether.transfer.TransferEvent; + +public class SimpleBuildEventListener implements BuildEventListener { + + final PrintWriter writer; + final boolean autoflush; + + public SimpleBuildEventListener(PrintWriter writer) { + this(writer, false); + } + + public SimpleBuildEventListener(PrintWriter writer, boolean autoflush) { + this.writer = writer; + this.autoflush = autoflush; + } + + @Override + public void sessionStarted(ExecutionEvent event) {} + + @Override + public void projectStarted(String projectId) {} + + @Override + public void projectLogMessage(String projectId, String event) { + log(event); + } + + @Override + public void projectFinished(String projectId) {} + + @Override + public void executionFailure(String projectId, boolean halted, String exception) {} + + @Override + public void mojoStarted(ExecutionEvent event) {} + + @Override + public void finish(int exitCode) throws Exception {} + + @Override + public void fail(Throwable t) throws Exception {} + + @Override + public void log(String msg) { + writer.println(msg); + if (autoflush) { + writer.flush(); + } + } + + @Override + public void transfer(String projectId, TransferEvent e) {} +} diff --git a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxerTest.java b/maven-core/src/test/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxerTest.java deleted file mode 100644 index 69eda5f2d70f..000000000000 --- a/maven-core/src/test/java/org/apache/maven/lifecycle/internal/builder/multithreaded/ThreadOutputMuxerTest.java +++ /dev/null @@ -1,152 +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.lifecycle.internal.builder.multithreaded; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ExecutorCompletionService; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.apache.maven.execution.MavenSession; -import org.apache.maven.lifecycle.LifecycleNotFoundException; -import org.apache.maven.lifecycle.LifecyclePhaseNotFoundException; -import org.apache.maven.lifecycle.internal.ProjectBuildList; -import org.apache.maven.lifecycle.internal.ProjectSegment; -import org.apache.maven.lifecycle.internal.stub.ProjectDependencyGraphStub; -import org.apache.maven.plugin.InvalidPluginDescriptorException; -import org.apache.maven.plugin.MojoNotFoundException; -import org.apache.maven.plugin.PluginDescriptorParsingException; -import org.apache.maven.plugin.PluginNotFoundException; -import org.apache.maven.plugin.PluginResolutionException; -import org.apache.maven.plugin.prefix.NoPluginFoundForPrefixException; -import org.apache.maven.plugin.version.PluginVersionResolutionException; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - */ -class ThreadOutputMuxerTest { - - final String paid = "Paid"; - - final String in = "In"; - - final String full = "Full"; - - @Test - void testSingleThreaded() throws Exception { - ProjectBuildList src = getProjectBuildList(); - ProjectBuildList projectBuildList = new ProjectBuildList(Arrays.asList(src.get(0), src.get(1), src.get(2))); - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - PrintStream systemOut = new PrintStream(byteArrayOutputStream); - ThreadOutputMuxer threadOutputMuxer = new ThreadOutputMuxer(projectBuildList, systemOut); - - threadOutputMuxer.associateThreadWithProjectSegment(projectBuildList.get(0)); - System.out.print(paid); // No, this does not print to system.out. It's part of the test - assertEquals(paid.length(), byteArrayOutputStream.size()); - threadOutputMuxer.associateThreadWithProjectSegment(projectBuildList.get(1)); - System.out.print(in); // No, this does not print to system.out. It's part of the test - assertEquals(paid.length(), byteArrayOutputStream.size()); - threadOutputMuxer.associateThreadWithProjectSegment(projectBuildList.get(2)); - System.out.print(full); // No, this does not print to system.out. It's part of the test - assertEquals(paid.length(), byteArrayOutputStream.size()); - - threadOutputMuxer.setThisModuleComplete(projectBuildList.get(0)); - threadOutputMuxer.setThisModuleComplete(projectBuildList.get(1)); - threadOutputMuxer.setThisModuleComplete(projectBuildList.get(2)); - threadOutputMuxer.close(); - assertEquals((paid + in + full).length(), byteArrayOutputStream.size()); - } - - @Test - void testMultiThreaded() throws Exception { - ProjectBuildList projectBuildList = getProjectBuildList(); - - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - PrintStream systemOut = new PrintStream(byteArrayOutputStream); - final ThreadOutputMuxer threadOutputMuxer = new ThreadOutputMuxer(projectBuildList, systemOut); - - final List stringList = Arrays.asList( - "Thinkin", "of", "a", "master", "plan", "Cuz", "ain’t", "nuthin", "but", "sweat", "inside", "my", - "hand"); - Iterator lyrics = stringList.iterator(); - - ExecutorService executor = Executors.newFixedThreadPool(10); - CompletionService service = new ExecutorCompletionService<>(executor); - - List> futures = new ArrayList<>(); - for (ProjectSegment projectBuild : projectBuildList) { - final Future buildFuture = - service.submit(new Outputter(threadOutputMuxer, projectBuild, lyrics.next())); - futures.add(buildFuture); - } - - for (Future future : futures) { - future.get(); - } - int expectedLength = 0; - for (int i = 0; i < projectBuildList.size(); i++) { - expectedLength += stringList.get(i).length(); - } - - threadOutputMuxer.close(); - final byte[] bytes = byteArrayOutputStream.toByteArray(); - String result = new String(bytes); - assertEquals(expectedLength, bytes.length, result); - } - - class Outputter implements Callable { - private final ThreadOutputMuxer threadOutputMuxer; - - private final ProjectSegment item; - - private final String response; - - Outputter(ThreadOutputMuxer threadOutputMuxer, ProjectSegment item, String response) { - this.threadOutputMuxer = threadOutputMuxer; - this.item = item; - this.response = response; - } - - public ProjectSegment call() throws Exception { - threadOutputMuxer.associateThreadWithProjectSegment(item); - System.out.print(response); - threadOutputMuxer.setThisModuleComplete(item); - return item; - } - } - - private ProjectBuildList getProjectBuildList() - throws InvalidPluginDescriptorException, PluginVersionResolutionException, PluginDescriptorParsingException, - NoPluginFoundForPrefixException, MojoNotFoundException, PluginNotFoundException, - PluginResolutionException, LifecyclePhaseNotFoundException, LifecycleNotFoundException { - final MavenSession session = ProjectDependencyGraphStub.getMavenSession(); - return ProjectDependencyGraphStub.getProjectBuildList(session); - } -} diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml index a5fb2351e0a8..71b1545befea 100644 --- a/maven-embedder/pom.xml +++ b/maven-embedder/pom.xml @@ -74,7 +74,7 @@ under the License. org.apache.maven - maven-slf4j-wrapper + maven-logging org.apache.maven.resolver diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java index 180c03eb65b0..2a11634602d1 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/MavenCli.java @@ -38,6 +38,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.ListIterator; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; @@ -99,8 +100,7 @@ import org.apache.maven.jline.JLineMessageBuilderFactory; import org.apache.maven.jline.MessageUtils; import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.logwrapper.LogLevelRecorder; -import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; +import org.apache.maven.logging.api.LogLevelRecorder; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.model.root.RootLocator; import org.apache.maven.project.MavenProject; @@ -541,15 +541,22 @@ void logging(CliRequest cliRequest) throws ExitException { if (commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) { String logLevelThreshold = commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY); - if (slf4jLoggerFactory instanceof MavenSlf4jWrapperFactory) { - LogLevelRecorder logLevelRecorder = new LogLevelRecorder(logLevelThreshold); - ((MavenSlf4jWrapperFactory) slf4jLoggerFactory).setLogLevelRecorder(logLevelRecorder); + if (slf4jLoggerFactory instanceof LogLevelRecorder recorder) { + LogLevelRecorder.Level level = + switch (logLevelThreshold.toLowerCase(Locale.ENGLISH)) { + case "warn", "warning" -> LogLevelRecorder.Level.WARN; + case "error" -> LogLevelRecorder.Level.ERROR; + default -> throw new IllegalArgumentException( + logLevelThreshold + + " is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR."); + }; + recorder.setMaxLevelAllowed(level); slf4jLogger.info("Enabled to break the build on log level {}.", logLevelThreshold); } else { slf4jLogger.warn( "Expected LoggerFactory to be of type '{}', but found '{}' instead. " + "The --fail-on-severity flag will not take effect.", - MavenSlf4jWrapperFactory.class.getName(), + LogLevelRecorder.class.getName(), slf4jLoggerFactory.getClass().getName()); } } diff --git a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java index dcc27022541b..e53e74bda617 100644 --- a/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java +++ b/maven-embedder/src/main/java/org/apache/maven/cli/event/ExecutionEventLogger.java @@ -32,8 +32,6 @@ import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenSession; -import org.apache.maven.logwrapper.LogLevelRecorder; -import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.project.MavenProject; @@ -79,13 +77,7 @@ public ExecutionEventLogger(MessageBuilderFactory messageBuilderFactory, Logger } private static String chars(char c, int count) { - StringBuilder buffer = new StringBuilder(count); - - for (int i = count; i > 0; i--) { - buffer.append(c); - } - - return buffer.toString(); + return String.valueOf(c).repeat(Math.max(0, count)); } private void infoLine(char c) { @@ -137,9 +129,8 @@ public void sessionStarted(ExecutionEvent event) { } final List allProjects = event.getSession().getAllProjects(); - final int projectsSkipped = allProjects.size() - projects.size(); - currentVisitedProjectCount = projectsSkipped; + currentVisitedProjectCount = allProjects.size() - projects.size(); totalProjects = allProjects.size(); } } @@ -154,16 +145,13 @@ public void sessionEnded(ExecutionEvent event) { ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); - if (iLoggerFactory instanceof MavenSlf4jWrapperFactory) { - MavenSlf4jWrapperFactory loggerFactory = (MavenSlf4jWrapperFactory) iLoggerFactory; - loggerFactory - .getLogLevelRecorder() - .filter(LogLevelRecorder::metThreshold) - .ifPresent(recorder -> event.getSession() - .getResult() - .addException(new Exception( - "Build failed due to log statements with a higher severity than allowed. " - + "Fix the logged issues or remove flag --fail-on-severity (-fos)."))); + if (iLoggerFactory instanceof org.apache.maven.logging.api.LogLevelRecorder recorder + && recorder.hasReachedMaxLevel()) { + event.getSession() + .getResult() + .addException( + new Exception("Build failed due to log statements with a higher severity than allowed. " + + "Fix the logged issues or remove flag --fail-on-severity (-fos).")); } logResult(event.getSession()); @@ -297,7 +285,7 @@ public void projectSkipped(ExecutionEvent event) { infoLine('-'); String name = event.getProject().getName(); infoMain("Skipping " + name); - logger.info(name + " was not built because a module it depends on failed to build."); + logger.info("{} was not built because a module it depends on failed to build.", name); infoLine('-'); } diff --git a/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java index 164d4bee84c4..43a75aa3239f 100644 --- a/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java +++ b/maven-jline/src/main/java/org/apache/maven/jline/MessageUtils.java @@ -98,4 +98,8 @@ public static int getTerminalWidth() { public static MessageBuilder builder() { return messageBuilderFactory.builder(); } + + public static Terminal getTerminal() { + return terminal; + } } diff --git a/maven-slf4j-wrapper/pom.xml b/maven-logging/pom.xml similarity index 74% rename from maven-slf4j-wrapper/pom.xml rename to maven-logging/pom.xml index 6fa47292aa51..8b6cd681c976 100644 --- a/maven-slf4j-wrapper/pom.xml +++ b/maven-logging/pom.xml @@ -19,19 +19,25 @@ under the License. --> 4.0.0 - org.apache.maven maven 4.0.0-beta-5-SNAPSHOT - maven-slf4j-wrapper - - Maven SLF4J Wrapper - This modules provides an ILoggerFactory interface which avoids a cyclic dependency between maven-embedder and maven-slf4j-provider. + maven-logging + Maven Logging + Provides the Maven Logging infrastructure + + org.apache.maven + maven-api-core + + + org.apache.maven + maven-jline + org.slf4j slf4j-api @@ -48,4 +54,14 @@ under the License. test + + + + + org.eclipse.sisu + sisu-maven-plugin + + + + diff --git a/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java b/maven-logging/src/main/java/org/apache/maven/logging/api/LogLevelRecorder.java similarity index 69% rename from maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java rename to maven-logging/src/main/java/org/apache/maven/logging/api/LogLevelRecorder.java index afa8bb11f565..25ccc266e229 100644 --- a/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java +++ b/maven-logging/src/main/java/org/apache/maven/logging/api/LogLevelRecorder.java @@ -16,17 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.logwrapper; +package org.apache.maven.logging.api; -import java.util.Optional; +public interface LogLevelRecorder { -import org.slf4j.ILoggerFactory; + enum Level { + DEBUG, + INFO, + WARN, + ERROR + } -/** - * Wrapper for creating loggers which can have a log level threshold. - */ -public interface MavenSlf4jWrapperFactory extends ILoggerFactory { - void setLogLevelRecorder(LogLevelRecorder logLevelRecorder); + boolean hasReachedMaxLevel(); + + Level getMaxLevelReached(); + + Level getMaxLevelAllowed(); - Optional getLogLevelRecorder(); + void setMaxLevelAllowed(Level level); } diff --git a/maven-logging/src/main/java/org/apache/maven/slf4j/DefaultLogLevelRecorder.java b/maven-logging/src/main/java/org/apache/maven/slf4j/DefaultLogLevelRecorder.java new file mode 100644 index 000000000000..b1b9b7de668e --- /dev/null +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/DefaultLogLevelRecorder.java @@ -0,0 +1,102 @@ +/* + * 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.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.maven.logging.api.LogLevelRecorder; + +/** + * Responsible for keeping state of whether the threshold of the --fail-on-severity flag has been hit. + */ +public class DefaultLogLevelRecorder implements LogLevelRecorder { + private static final Map ACCEPTED_LEVELS = new HashMap<>(); + + static { + ACCEPTED_LEVELS.put("WARN", Level.WARN); + ACCEPTED_LEVELS.put("WARNING", Level.WARN); + ACCEPTED_LEVELS.put("ERROR", Level.ERROR); + } + + private Level maxAllowed; + private final AtomicReference maxReached = new AtomicReference<>(Level.DEBUG); + + public DefaultLogLevelRecorder(String threshold) { + this(determineThresholdLevel(threshold)); + } + + public DefaultLogLevelRecorder(Level maxAllowed) { + this.maxAllowed = maxAllowed; + } + + @Override + public boolean hasReachedMaxLevel() { + return maxReached.get().ordinal() > maxAllowed.ordinal(); + } + + @Override + public Level getMaxLevelReached() { + return maxReached.get(); + } + + @Override + public Level getMaxLevelAllowed() { + return maxAllowed; + } + + @Override + public void setMaxLevelAllowed(Level level) { + this.maxAllowed = level; + } + + private static Level determineThresholdLevel(String input) { + final Level result = ACCEPTED_LEVELS.get(input); + if (result == null) { + String message = String.format( + "%s is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.", input); + throw new IllegalArgumentException(message); + } + return result; + } + + public void record(org.slf4j.event.Level logLevel) { + Level level = + switch (logLevel) { + case TRACE, DEBUG -> Level.DEBUG; + case INFO -> Level.INFO; + case WARN -> Level.WARN; + case ERROR -> Level.ERROR; + }; + while (true) { + Level r = maxReached.get(); + if (level.ordinal() > r.ordinal()) { + if (!maxReached.compareAndSet(r, level)) { + continue; + } + } + break; + } + } + + public boolean metThreshold() { + return maxReached.get().ordinal() >= maxAllowed.ordinal(); + } +} diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java similarity index 100% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java similarity index 95% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java index 65b317314047..8f2e7c6d3447 100644 --- a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenFailOnSeverityLogger.java @@ -18,7 +18,6 @@ */ package org.apache.maven.slf4j; -import org.apache.maven.logwrapper.LogLevelRecorder; import org.slf4j.event.Level; /** @@ -26,9 +25,9 @@ * Currently only support WARN and ERROR states, since it's been used for the --fail-on-severity flag. */ public class MavenFailOnSeverityLogger extends MavenSimpleLogger { - private final LogLevelRecorder logLevelRecorder; + private final DefaultLogLevelRecorder logLevelRecorder; - MavenFailOnSeverityLogger(String name, LogLevelRecorder logLevelRecorder) { + MavenFailOnSeverityLogger(String name, DefaultLogLevelRecorder logLevelRecorder) { super(name); this.logLevelRecorder = logLevelRecorder; } diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java similarity index 73% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java index a142f9b3a33d..9ea6c18bc412 100644 --- a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java @@ -18,38 +18,42 @@ */ package org.apache.maven.slf4j; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.apache.maven.logwrapper.LogLevelRecorder; -import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; +import org.slf4j.ILoggerFactory; import org.slf4j.Logger; /** * LogFactory for Maven which can create a simple logger or one which, if set, fails the build on a severity threshold. */ -public class MavenLoggerFactory implements MavenSlf4jWrapperFactory { - private LogLevelRecorder logLevelRecorder = null; - private final ConcurrentMap loggerMap = new ConcurrentHashMap<>(); +public class MavenLoggerFactory implements org.apache.maven.logging.api.LogLevelRecorder, ILoggerFactory { + DefaultLogLevelRecorder logLevelRecorder = null; + final ConcurrentMap loggerMap = new ConcurrentHashMap<>(); public MavenLoggerFactory() { MavenSimpleLogger.lazyInit(); } @Override - public void setLogLevelRecorder(LogLevelRecorder logLevelRecorder) { - if (this.logLevelRecorder != null) { - throw new IllegalStateException("LogLevelRecorder has already been set."); - } - this.logLevelRecorder = logLevelRecorder; + public boolean hasReachedMaxLevel() { + return logLevelRecorder != null && logLevelRecorder.metThreshold(); + } + + @Override + public Level getMaxLevelReached() { + return null; } @Override - public Optional getLogLevelRecorder() { - return Optional.ofNullable(logLevelRecorder); + public Level getMaxLevelAllowed() { + return null; } + @Override + public void setMaxLevelAllowed(Level level) { + this.logLevelRecorder = new DefaultLogLevelRecorder(level.name()); + } /** * Return an appropriate {@link Logger} instance by name. */ diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java similarity index 100% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java similarity index 100% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/OutputChoice.java b/maven-logging/src/main/java/org/apache/maven/slf4j/OutputChoice.java similarity index 100% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/OutputChoice.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/OutputChoice.java diff --git a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java b/maven-logging/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java similarity index 100% rename from maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java rename to maven-logging/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java diff --git a/maven-slf4j-provider/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider b/maven-logging/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider similarity index 100% rename from maven-slf4j-provider/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider rename to maven-logging/src/main/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider diff --git a/maven-slf4j-provider/src/site/apt/index.apt b/maven-logging/src/site/apt/index.apt similarity index 100% rename from maven-slf4j-provider/src/site/apt/index.apt rename to maven-logging/src/site/apt/index.apt diff --git a/maven-slf4j-provider/src/site/site.xml b/maven-logging/src/site/site.xml similarity index 100% rename from maven-slf4j-provider/src/site/site.xml rename to maven-logging/src/site/site.xml diff --git a/maven-slf4j-wrapper/src/test/java/org/apache/maven/logwrapper/LogLevelRecorderTest.java b/maven-logging/src/test/java/org/apache/maven/slf4j/LogLevelRecorderTest.java similarity index 84% rename from maven-slf4j-wrapper/src/test/java/org/apache/maven/logwrapper/LogLevelRecorderTest.java rename to maven-logging/src/test/java/org/apache/maven/slf4j/LogLevelRecorderTest.java index e806fb620799..264be86f635b 100644 --- a/maven-slf4j-wrapper/src/test/java/org/apache/maven/logwrapper/LogLevelRecorderTest.java +++ b/maven-logging/src/test/java/org/apache/maven/slf4j/LogLevelRecorderTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.logwrapper; +package org.apache.maven.slf4j; import org.junit.jupiter.api.Test; import org.slf4j.event.Level; @@ -29,7 +29,7 @@ class LogLevelRecorderTest { @Test void createsLogLevelRecorder() { - LogLevelRecorder logLevelRecorder = new LogLevelRecorder("WARN"); + DefaultLogLevelRecorder logLevelRecorder = new DefaultLogLevelRecorder("WARN"); logLevelRecorder.record(Level.ERROR); assertTrue(logLevelRecorder.metThreshold()); @@ -37,12 +37,12 @@ void createsLogLevelRecorder() { @Test void failsOnLowerThanWarn() { - assertThrows(IllegalArgumentException.class, () -> new LogLevelRecorder("INFO")); + assertThrows(IllegalArgumentException.class, () -> new DefaultLogLevelRecorder("INFO")); } @Test void createsLogLevelRecorderWithWarning() { - LogLevelRecorder logLevelRecorder = new LogLevelRecorder("WARNING"); + DefaultLogLevelRecorder logLevelRecorder = new DefaultLogLevelRecorder("WARNING"); logLevelRecorder.record(Level.ERROR); assertTrue(logLevelRecorder.metThreshold()); @@ -50,7 +50,7 @@ void createsLogLevelRecorderWithWarning() { @Test void failsOnUnknownLogLevel() { - Throwable thrown = assertThrows(IllegalArgumentException.class, () -> new LogLevelRecorder("SEVERE")); + Throwable thrown = assertThrows(IllegalArgumentException.class, () -> new DefaultLogLevelRecorder("SEVERE")); String message = thrown.getMessage(); assertThat(message, containsString("SEVERE is not a valid log severity threshold")); assertThat(message, containsString("WARN")); diff --git a/maven-slf4j-provider/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java b/maven-logging/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java similarity index 72% rename from maven-slf4j-provider/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java rename to maven-logging/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java index 04475427d8e6..6ee870dc62cc 100644 --- a/maven-slf4j-provider/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java +++ b/maven-logging/src/test/java/org/apache/maven/slf4j/MavenLoggerFactoryTest.java @@ -18,7 +18,6 @@ */ package org.apache.maven.slf4j; -import org.apache.maven.logwrapper.LogLevelRecorder; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -28,7 +27,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; class MavenLoggerFactoryTest { @@ -58,31 +56,18 @@ void loggerCachingWorks() { @Test void reportsWhenFailOnSeverityThresholdHasBeenHit() { MavenLoggerFactory mavenLoggerFactory = new MavenLoggerFactory(); - mavenLoggerFactory.setLogLevelRecorder(new LogLevelRecorder("ERROR")); - - assertTrue(mavenLoggerFactory.getLogLevelRecorder().isPresent()); - LogLevelRecorder logLevelRecorder = - mavenLoggerFactory.getLogLevelRecorder().get(); + mavenLoggerFactory.logLevelRecorder = new DefaultLogLevelRecorder("ERROR"); MavenFailOnSeverityLogger logger = (MavenFailOnSeverityLogger) mavenLoggerFactory.getLogger("Test"); - assertFalse(logLevelRecorder.metThreshold()); + assertFalse(mavenLoggerFactory.logLevelRecorder.metThreshold()); logger.warn("This should not hit the fail threshold"); - assertFalse(logLevelRecorder.metThreshold()); + assertFalse(mavenLoggerFactory.logLevelRecorder.metThreshold()); logger.error("This should hit the fail threshold"); - assertTrue(logLevelRecorder.metThreshold()); + assertTrue(mavenLoggerFactory.logLevelRecorder.metThreshold()); logger.warn("This should not reset the fail threshold"); - assertTrue(logLevelRecorder.metThreshold()); - } - - @Test - void failOnSeverityThresholdCanOnlyBeSetOnce() { - MavenLoggerFactory mavenLoggerFactory = new MavenLoggerFactory(); - mavenLoggerFactory.setLogLevelRecorder(new LogLevelRecorder("WARN")); - assertThrows( - IllegalStateException.class, - () -> mavenLoggerFactory.setLogLevelRecorder(new LogLevelRecorder("ERROR"))); + assertTrue(mavenLoggerFactory.logLevelRecorder.metThreshold()); } } diff --git a/maven-slf4j-provider/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java b/maven-logging/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java similarity index 97% rename from maven-slf4j-provider/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java rename to maven-logging/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java index bd2e1dd4a9ec..e26ea811830d 100644 --- a/maven-slf4j-provider/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java +++ b/maven-logging/src/test/java/org/apache/maven/slf4j/MavenSimpleLoggerTest.java @@ -49,7 +49,7 @@ void tearDown() { } @Test - void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo testInfo) throws Exception { + void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo testInfo) { Exception causeOfSuppressed = new NoSuchElementException("cause of suppressed"); Exception suppressed = new IllegalStateException("suppressed", causeOfSuppressed); suppressed.addSuppressed(new IllegalArgumentException( @@ -62,7 +62,7 @@ void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo testInfo new MavenSimpleLogger("logger").writeThrowable(throwable, new PrintStream(output)); - String actual = output.toString(UTF_8.name()); + String actual = output.toString(UTF_8); List actualLines = Arrays.asList(actual.split(System.lineSeparator())); Class testClass = testInfo.getTestClass().get(); diff --git a/maven-slf4j-provider/pom.xml b/maven-slf4j-provider/pom.xml deleted file mode 100644 index 40a6091e27d3..000000000000 --- a/maven-slf4j-provider/pom.xml +++ /dev/null @@ -1,113 +0,0 @@ - - - - 4.0.0 - - - org.apache.maven - maven - 4.0.0-beta-5-SNAPSHOT - - - maven-slf4j-provider - - Maven SLF4J Simple Provider - Maven SLF4J provider based on SLF4J's simple provider, extended to support Maven styled colors - for levels and stacktrace rendering. - - - - org.slf4j - slf4j-api - - - org.apache.maven - maven-slf4j-wrapper - - - org.apache.maven - maven-api-core - - - org.apache.maven - maven-jline - - - - org.junit.jupiter - junit-jupiter-api - test - - - org.hamcrest - hamcrest - test - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - - - - org.slf4j - slf4j-simple - ${slf4jVersion} - jar - sources - false - ${project.build.directory}/generated-sources/slf4j-simple - org/slf4j/simple/*.java - - - - - - unzip-slf4j-simple - - unpack - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-slf4j-simple - - add-source - - generate-sources - - - ${project.build.directory}/generated-sources/slf4j-simple - - - - - - - - diff --git a/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/LogLevelRecorder.java b/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/LogLevelRecorder.java deleted file mode 100644 index d77ca4df1f51..000000000000 --- a/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/LogLevelRecorder.java +++ /dev/null @@ -1,64 +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.logwrapper; - -import java.util.HashMap; -import java.util.Map; - -import org.slf4j.event.Level; - -/** - * Responsible for keeping state of whether the threshold of the --fail-on-severity flag has been hit. - */ -public class LogLevelRecorder { - private static final Map ACCEPTED_LEVELS = new HashMap<>(); - - static { - ACCEPTED_LEVELS.put("WARN", Level.WARN); - ACCEPTED_LEVELS.put("WARNING", Level.WARN); - ACCEPTED_LEVELS.put("ERROR", Level.ERROR); - } - - private final Level logThreshold; - private boolean metThreshold = false; - - public LogLevelRecorder(String threshold) { - logThreshold = determineThresholdLevel(threshold); - } - - private Level determineThresholdLevel(String input) { - final Level result = ACCEPTED_LEVELS.get(input); - if (result == null) { - String message = String.format( - "%s is not a valid log severity threshold. Valid severities are WARN/WARNING and ERROR.", input); - throw new IllegalArgumentException(message); - } - return result; - } - - public void record(Level logLevel) { - if (!metThreshold && logLevel.toInt() >= logThreshold.toInt()) { - metThreshold = true; - } - } - - public boolean metThreshold() { - return metThreshold; - } -} diff --git a/maven-slf4j-wrapper/src/site/site.xml b/maven-slf4j-wrapper/src/site/site.xml deleted file mode 100644 index 8ffe43d07c30..000000000000 --- a/maven-slf4j-wrapper/src/site/site.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - ${project.scm.url} - - -

- - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1526eb14d32b..8f6352e71ddb 100644 --- a/pom.xml +++ b/pom.xml @@ -109,14 +109,13 @@ under the License. maven-di maven-xml-impl maven-jline + maven-logging maven-core maven-settings maven-settings-builder maven-artifact maven-resolver-provider maven-repository-metadata - maven-slf4j-provider - maven-slf4j-wrapper maven-embedder maven-cli maven-compat @@ -216,6 +215,11 @@ under the License. maven-jline ${project.version} + + org.apache.maven + maven-logging + ${project.version} + org.apache.maven maven-core @@ -341,11 +345,6 @@ under the License. maven-toolchain-builder ${project.version} - - org.apache.maven - maven-slf4j-wrapper - ${project.version} - org.apache.maven maven-xml-impl @@ -356,11 +355,6 @@ under the License. maven-compat ${project.version} - - org.apache.maven - maven-slf4j-provider - ${project.version} - org.codehaus.plexus From c8b8a497a8f84e16c44a15bc8457c3e26350cee9 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 11 Oct 2024 16:26:41 +0200 Subject: [PATCH 04/14] Remove ConcurrentLogOutput --- .../concurrent/BuildPlanExecutor.java | 7 +- .../maven/logging/ConcurrentLogOutput.java | 81 ------------------- 2 files changed, 1 insertion(+), 87 deletions(-) delete mode 100644 maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java diff --git a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java index 990b5850a2ad..ce8efc8bd5be 100644 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java +++ b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java @@ -71,7 +71,6 @@ import org.apache.maven.lifecycle.internal.ReactorContext; import org.apache.maven.lifecycle.internal.Task; import org.apache.maven.lifecycle.internal.TaskSegment; -import org.apache.maven.logging.ConcurrentLogOutput; import org.apache.maven.model.Plugin; import org.apache.maven.model.PluginExecution; import org.apache.maven.plugin.MavenPluginManager; @@ -165,7 +164,6 @@ class BuildContext implements AutoCloseable { final MavenSession session; final ReactorContext reactorContext; final PhasingExecutor executor; - final ConcurrentLogOutput appender; final Map clocks = new ConcurrentHashMap<>(); final ReadWriteLock lock = new ReentrantReadWriteLock(); final int threads; @@ -180,7 +178,6 @@ class BuildContext implements AutoCloseable { // Propagate the parallel flag to the root session session.setParallel(threads > 1); this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory())); - this.appender = new ConcurrentLogOutput(); // build initial plan this.plan = buildInitialPlan(taskSegments); @@ -191,7 +188,6 @@ class BuildContext implements AutoCloseable { this.reactorContext = null; this.threads = 1; this.executor = null; - this.appender = null; this.plan = null; } @@ -313,7 +309,6 @@ void execute() { @Override public void close() { - this.appender.close(); this.executor.close(); } @@ -332,7 +327,7 @@ private void executePlan() { .forEach(step -> { boolean nextIsPlanning = step.successors.stream().anyMatch(st -> PLAN.equals(st.name)); executor.execute(() -> { - try (AutoCloseable ctx = appender.build(step.project)) { + try { executeStep(step); if (nextIsPlanning) { lock.writeLock().lock(); diff --git a/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java b/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java deleted file mode 100644 index c0f87bd405b9..000000000000 --- a/maven-core/src/main/java/org/apache/maven/logging/ConcurrentLogOutput.java +++ /dev/null @@ -1,81 +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.logging; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.apache.maven.project.MavenProject; - -/** - * Forwards log messages to the client. - */ -public class ConcurrentLogOutput implements AutoCloseable { - - private static final ThreadLocal CONTEXT = new InheritableThreadLocal<>(); - - public ConcurrentLogOutput() { - // MavenSimpleLogger.setLogSink(this::accept); - } - - protected void accept(String message) { - ProjectExecutionContext context = CONTEXT.get(); - if (context != null) { - context.accept(message); - } else { - System.out.println(message); - } - } - - @Override - public void close() { - // MavenSimpleLogger.setLogSink(null); - } - - public AutoCloseable build(MavenProject project) { - return () -> {}; - // return new ProjectExecutionContext(project); - } - - private static class ProjectExecutionContext implements AutoCloseable { - final MavenProject project; - final List messages = new CopyOnWriteArrayList<>(); - boolean closed; - - ProjectExecutionContext(MavenProject project) { - this.project = project; - CONTEXT.set(this); - } - - void accept(String message) { - if (!closed) { - this.messages.add(message); - } else { - System.out.println(message); - } - } - - @Override - public void close() { - closed = true; - CONTEXT.set(null); - this.messages.forEach(System.out::println); - } - } -} From f073a3451e372c8cbeeb93391fef5ed82cec05b4 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 11 Oct 2024 16:50:40 +0200 Subject: [PATCH 05/14] Fix invoker --- .../apache/maven/cling/invoker/LookupInvoker.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index d43d7af4d48a..9df8d8f1e059 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -19,6 +19,7 @@ package org.apache.maven.cling.invoker; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.io.PrintWriter; @@ -308,15 +309,21 @@ protected void configureLogging(C context) throws Exception { context.closeables.add(() -> System.setOut(sysOut)); context.closeables.add(() -> System.setErr(sysErr)); } + BuildEventListener bel = determineBuildEventListener(context); + ProjectBuildLogAppender projectBuildLogAppender = new ProjectBuildLogAppender(bel); + context.closeables.add(projectBuildLogAppender); + } + + protected BuildEventListener determineBuildEventListener(C context) throws IOException { BuildEventListener bel; - if (mavenOptions.logFile().isPresent()) { - Path logFile = context.cwdResolver.apply(mavenOptions.logFile().get()); + O options = context.invokerRequest.options(); + if (options.logFile().isPresent()) { + Path logFile = context.cwdResolver.apply(options.logFile().get()); bel = new SimpleBuildEventListener(new PrintWriter(Files.newBufferedWriter(logFile))); } else { bel = new SimpleBuildEventListener(MessageUtils.getTerminal().writer(), true); } - ProjectBuildLogAppender projectBuildLogAppender = new ProjectBuildLogAppender(bel); - context.closeables.add(projectBuildLogAppender); + return bel; } protected void activateLogging(C context) throws Exception { From a32e29f06be3f8b0661f561c1ea639c50fe45b17 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 14 Oct 2024 10:59:54 +0200 Subject: [PATCH 06/14] Fix --- .../maven/cling/invoker/LookupInvoker.java | 21 +++++++++++++++---- .../invoker/mvn/DefaultMavenInvoker.java | 9 +------- .../logging/LoggingExecutionListener.java | 11 +++------- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index 9df8d8f1e059..adfb04481b45 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -40,6 +40,7 @@ import org.apache.maven.api.cli.Logger; import org.apache.maven.api.cli.Options; import org.apache.maven.api.services.Lookup; +import org.apache.maven.api.services.MavenException; import org.apache.maven.api.services.MessageBuilder; import org.apache.maven.artifact.InvalidRepositoryException; import org.apache.maven.artifact.repository.ArtifactRepository; @@ -144,6 +145,7 @@ protected LookupInvokerContext(LookupInvoker invoker, R invokerRequest) public ILoggerFactory loggerFactory; public Slf4jConfiguration slf4jConfiguration; public Slf4jConfiguration.Level loggerLevel; + public BuildEventListener buildEventListener; public ClassLoader currentThreadContextClassLoader; public ContainerCapsule containerCapsule; public Lookup lookup; @@ -309,17 +311,28 @@ protected void configureLogging(C context) throws Exception { context.closeables.add(() -> System.setOut(sysOut)); context.closeables.add(() -> System.setErr(sysErr)); } - BuildEventListener bel = determineBuildEventListener(context); - ProjectBuildLogAppender projectBuildLogAppender = new ProjectBuildLogAppender(bel); + ProjectBuildLogAppender projectBuildLogAppender = + new ProjectBuildLogAppender(determineBuildEventListener(context)); context.closeables.add(projectBuildLogAppender); } - protected BuildEventListener determineBuildEventListener(C context) throws IOException { + protected BuildEventListener determineBuildEventListener(C context) { + if (context.buildEventListener == null) { + context.buildEventListener = doDetermineBuildEventListener(context); + } + return context.buildEventListener; + } + + protected BuildEventListener doDetermineBuildEventListener(C context) { BuildEventListener bel; O options = context.invokerRequest.options(); if (options.logFile().isPresent()) { Path logFile = context.cwdResolver.apply(options.logFile().get()); - bel = new SimpleBuildEventListener(new PrintWriter(Files.newBufferedWriter(logFile))); + try { + bel = new SimpleBuildEventListener(new PrintWriter(Files.newBufferedWriter(logFile))); + } catch (IOException e) { + throw new MavenException("Unable to redirect logging to " + logFile, e); + } } else { bel = new SimpleBuildEventListener(MessageUtils.getTerminal().writer(), true); } diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java index 54d8525ef45c..e3f7d5b959b8 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java @@ -37,7 +37,6 @@ import org.apache.maven.api.cli.mvn.MavenInvoker; import org.apache.maven.api.cli.mvn.MavenInvokerRequest; import org.apache.maven.api.cli.mvn.MavenOptions; -import org.apache.maven.api.services.LookupException; import org.apache.maven.building.FileSource; import org.apache.maven.building.Problem; import org.apache.maven.cli.CLIReportingUtils; @@ -57,7 +56,6 @@ import org.apache.maven.execution.ProjectActivation; import org.apache.maven.jline.MessageUtils; import org.apache.maven.lifecycle.LifecycleExecutionException; -import org.apache.maven.logging.BuildEventListener; import org.apache.maven.logging.LoggingExecutionListener; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.project.MavenProject; @@ -366,12 +364,7 @@ protected ExecutionListener determineExecutionListener(C context) { if (context.eventSpyDispatcher != null) { listener = context.eventSpyDispatcher.chainListener(listener); } - try { - LoggingExecutionListener leListener = context.lookup.lookup(LoggingExecutionListener.class); - leListener.init(listener, context.lookup.lookup(BuildEventListener.class)); - listener = leListener; - } catch (LookupException ignore) { - } + listener = new LoggingExecutionListener(listener, determineBuildEventListener(context)); return listener; } diff --git a/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java b/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java index b2f2d3c2a81c..040a481455e2 100644 --- a/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java +++ b/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java @@ -18,9 +18,6 @@ */ package org.apache.maven.logging; -import javax.inject.Named; -import javax.inject.Singleton; - import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.execution.MavenExecutionRequest; @@ -30,14 +27,12 @@ import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.internal.ReactorBuildStatus; -@Singleton -@Named public class LoggingExecutionListener implements ExecutionListener, ProjectExecutionListener { - private ExecutionListener delegate; - private BuildEventListener buildEventListener; + private final ExecutionListener delegate; + private final BuildEventListener buildEventListener; - public void init(ExecutionListener delegate, BuildEventListener buildEventListener) { + public LoggingExecutionListener(ExecutionListener delegate, BuildEventListener buildEventListener) { this.delegate = delegate; this.buildEventListener = buildEventListener; } From 4700e119616fb2913dbb0d2f9215a4f29596fbfe Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 14 Oct 2024 11:12:38 +0200 Subject: [PATCH 07/14] Also fix transfer listener --- .../invoker/mvn/DefaultMavenInvoker.java | 7 ++ .../maven/logging/MavenTransferListener.java | 70 +++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 maven-core/src/main/java/org/apache/maven/logging/MavenTransferListener.java diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java index e3f7d5b959b8..e8d4ed23d10c 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvn/DefaultMavenInvoker.java @@ -57,6 +57,7 @@ import org.apache.maven.jline.MessageUtils; import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.logging.LoggingExecutionListener; +import org.apache.maven.logging.MavenTransferListener; import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.building.SettingsBuildingRequest; @@ -66,6 +67,7 @@ import org.apache.maven.toolchain.building.ToolchainsBuildingResult; import org.codehaus.plexus.PlexusContainer; import org.eclipse.aether.DefaultRepositoryCache; +import org.eclipse.aether.transfer.TransferListener; import static java.util.Comparator.comparing; import static org.apache.maven.cling.invoker.Utils.toProperties; @@ -368,6 +370,11 @@ protected ExecutionListener determineExecutionListener(C context) { return listener; } + protected TransferListener determineTransferListener(C context, boolean noTransferProgress) { + TransferListener delegate = super.determineTransferListener(context, noTransferProgress); + return new MavenTransferListener(delegate, determineBuildEventListener(context)); + } + protected String determineMakeBehavior(C context) { MavenOptions mavenOptions = context.invokerRequest.options(); if (mavenOptions.alsoMake().isPresent() diff --git a/maven-core/src/main/java/org/apache/maven/logging/MavenTransferListener.java b/maven-core/src/main/java/org/apache/maven/logging/MavenTransferListener.java new file mode 100644 index 000000000000..d422f33e8b03 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/MavenTransferListener.java @@ -0,0 +1,70 @@ +/* + * 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.logging; + +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferListener; + +public class MavenTransferListener implements TransferListener { + + private final TransferListener delegate; + private final BuildEventListener dispatcher; + + public MavenTransferListener(TransferListener delegate, BuildEventListener dispatcher) { + this.delegate = delegate; + this.dispatcher = dispatcher; + } + + @Override + public void transferInitiated(TransferEvent event) throws TransferCancelledException { + dispatcher.transfer(ProjectBuildLogAppender.getProjectId(), event); + delegate.transferInitiated(event); + } + + @Override + public void transferStarted(TransferEvent event) throws TransferCancelledException { + dispatcher.transfer(ProjectBuildLogAppender.getProjectId(), event); + delegate.transferStarted(event); + } + + @Override + public void transferProgressed(TransferEvent event) throws TransferCancelledException { + dispatcher.transfer(ProjectBuildLogAppender.getProjectId(), event); + delegate.transferProgressed(event); + } + + @Override + public void transferCorrupted(TransferEvent event) throws TransferCancelledException { + dispatcher.transfer(ProjectBuildLogAppender.getProjectId(), event); + delegate.transferCorrupted(event); + } + + @Override + public void transferSucceeded(TransferEvent event) { + dispatcher.transfer(ProjectBuildLogAppender.getProjectId(), event); + delegate.transferSucceeded(event); + } + + @Override + public void transferFailed(TransferEvent event) { + dispatcher.transfer(ProjectBuildLogAppender.getProjectId(), event); + delegate.transferFailed(event); + } +} From e228baca991029324e2f350cbd13bd1bef52b7a0 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 14 Oct 2024 15:25:49 +0200 Subject: [PATCH 08/14] Render stack traces --- .../maven/plugin/PluginResolutionException.java | 3 +-- .../apache/maven/slf4j/MavenSimpleLogger.java | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/maven-core/src/main/java/org/apache/maven/plugin/PluginResolutionException.java b/maven-core/src/main/java/org/apache/maven/plugin/PluginResolutionException.java index c837fd87214a..b40dea8c38c6 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/PluginResolutionException.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/PluginResolutionException.java @@ -44,8 +44,7 @@ public PluginResolutionException(Plugin plugin, List exceptions, Thro + System.lineSeparator() + "\t" + exceptions.stream() .map(Throwable::getMessage) - .collect(Collectors.joining(System.lineSeparator() + "\t")) - + System.lineSeparator(), + .collect(Collectors.joining(System.lineSeparator() + "\t")), cause); this.plugin = plugin; } diff --git a/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java index 584da25c4c51..14b81aff4923 100644 --- a/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java @@ -77,6 +77,9 @@ protected void write(StringBuilder buf, Throwable t) { Consumer sink = logSink; if (sink != null) { sink.accept(buf.toString()); + if (t != null) { + writeThrowable(t, sink); + } } else { super.write(buf, t); } @@ -84,6 +87,10 @@ protected void write(StringBuilder buf, Throwable t) { @Override protected void writeThrowable(Throwable t, PrintStream stream) { + writeThrowable(t, stream::println); + } + + protected void writeThrowable(Throwable t, Consumer stream) { if (t == null) { return; } @@ -91,12 +98,12 @@ protected void writeThrowable(Throwable t, PrintStream stream) { if (t.getMessage() != null) { builder.a(": ").failure(t.getMessage()); } - stream.println(builder); + stream.accept(builder.toString()); printStackTrace(t, stream, ""); } - protected void printStackTrace(Throwable t, PrintStream stream, String prefix) { + protected void printStackTrace(Throwable t, Consumer stream, String prefix) { MessageBuilder builder = builder(); for (StackTraceElement e : t.getStackTrace()) { builder.a(prefix); @@ -109,7 +116,7 @@ protected void printStackTrace(Throwable t, PrintStream stream, String prefix) { builder.a("("); builder.strong(getLocation(e)); builder.a(")"); - stream.println(builder); + stream.accept(builder.toString()); builder.setLength(0); } for (Throwable se : t.getSuppressed()) { @@ -121,13 +128,13 @@ protected void printStackTrace(Throwable t, PrintStream stream, String prefix) { } } - protected void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { + protected void writeThrowable(Throwable t, Consumer stream, String caption, String prefix) { MessageBuilder builder = builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName()); if (t.getMessage() != null) { builder.a(": ").failure(t.getMessage()); } - stream.println(builder); + stream.accept(builder.toString()); printStackTrace(t, stream, prefix); } From 75a48ce0b10992f3dcb94b131332a0d0d8c52eb6 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 14 Oct 2024 17:32:11 +0200 Subject: [PATCH 09/14] Fix raw-streams option --- .../java/org/apache/maven/cling/invoker/CommonsCliOptions.java | 1 - 1 file changed, 1 deletion(-) diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java index cdde46f32588..3e99afa8f044 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java @@ -359,7 +359,6 @@ protected void prepareOptions(org.apache.commons.cli.Options options) { .build()); options.addOption(Option.builder() .longOpt(RAW_STREAMS) - .hasArg() .desc("Do not decorate standard output and error streams") .build()); options.addOption(Option.builder(SHOW_VERSION) From f3c338e1e3b48202966feabb46a8078ddfee2200 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 14 Oct 2024 17:58:08 +0200 Subject: [PATCH 10/14] Move all related output/logging config inside the LookupInvoker.configureLogging --- .../apache/maven/api/cli/InvokerRequest.java | 8 --- .../org/apache/maven/cling/ClingSupport.java | 11 +-- .../cling/invoker/BaseInvokerRequest.java | 5 -- .../maven/cling/invoker/LookupInvoker.java | 69 ++++++++++++------- .../logging/SimpleBuildEventListener.java | 19 ++--- 5 files changed, 52 insertions(+), 60 deletions(-) diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java index 34418a5fcc2c..f43dc66a193b 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java @@ -149,14 +149,6 @@ default Lookup lookup() { @Nonnull Optional out(); - /** - * Returns the error stream for the Maven execution, if running in embedded mode. - * - * @return an {@link Optional} containing the error stream, or empty if not applicable - */ - @Nonnull - Optional err(); - /** * Returns a list of core extensions, if configured in the .mvn/extensions.xml file. * diff --git a/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java b/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java index 5ea9ec72dbae..ba65331388fe 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java @@ -25,7 +25,6 @@ import org.apache.maven.api.cli.InvokerRequest; import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.ParserException; -import org.apache.maven.jline.MessageUtils; import org.codehaus.plexus.classworlds.ClassWorld; import static java.util.Objects.requireNonNull; @@ -65,8 +64,6 @@ private ClingSupport(ClassWorld classWorld, boolean classWorldManaged) { * The main entry point. */ public int run(String[] args) throws IOException { - MessageUtils.systemInstall(); - MessageUtils.registerShutdownHook(); try (Invoker invoker = createInvoker()) { return invoker.invoke(parseArguments(args)); } catch (ParserException e) { @@ -75,12 +72,8 @@ public int run(String[] args) throws IOException { } catch (InvokerException e) { return 1; } finally { - try { - if (classWorldManaged) { - classWorld.close(); - } - } finally { - MessageUtils.systemUninstall(); + if (classWorldManaged) { + classWorld.close(); } } } diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java index 1aa26ca7be1d..0f799bd7d5f9 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java @@ -128,11 +128,6 @@ public Optional out() { return Optional.ofNullable(out); } - @Override - public Optional err() { - return Optional.ofNullable(err); - } - @Override public Optional> coreExtensions() { return Optional.ofNullable(coreExtensions); diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index adfb04481b45..78f34c86d04a 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -20,8 +20,6 @@ import java.io.FileNotFoundException; import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; @@ -56,6 +54,7 @@ import org.apache.maven.cli.transfer.SimplexTransferListener; import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; import org.apache.maven.execution.MavenExecutionRequest; +import org.apache.maven.jline.FastTerminal; import org.apache.maven.jline.MessageUtils; import org.apache.maven.logging.BuildEventListener; import org.apache.maven.logging.LoggingOutputStream; @@ -76,6 +75,9 @@ import org.apache.maven.settings.building.SettingsProblem; import org.apache.maven.slf4j.MavenSimpleLogger; import org.eclipse.aether.transfer.TransferListener; +import org.jline.jansi.AnsiConsole; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; import org.slf4j.ILoggerFactory; import org.slf4j.LoggerFactory; import org.slf4j.spi.LocationAwareLogger; @@ -119,9 +121,6 @@ public static class LookupInvokerContext< public final Function cwdResolver; public final Function installationResolver; public final Function userResolver; - public final InputStream stdIn; - public final PrintWriter stdOut; - public final PrintWriter stdErr; protected LookupInvokerContext(LookupInvoker invoker, R invokerRequest) { this.invoker = invoker; @@ -135,9 +134,6 @@ protected LookupInvokerContext(LookupInvoker invoker, R invokerRequest) .toAbsolutePath(); this.userResolver = s -> invokerRequest.userHomeDirectory().resolve(s).normalize().toAbsolutePath(); - this.stdIn = invokerRequest.in().orElse(System.in); - this.stdOut = new PrintWriter(invokerRequest.out().orElse(System.out), true); - this.stdErr = new PrintWriter(invokerRequest.err().orElse(System.err), true); this.logger = invokerRequest.parserRequest().logger(); } @@ -145,6 +141,7 @@ protected LookupInvokerContext(LookupInvoker invoker, R invokerRequest) public ILoggerFactory loggerFactory; public Slf4jConfiguration slf4jConfiguration; public Slf4jConfiguration.Level loggerLevel; + public Terminal terminal; public BuildEventListener buildEventListener; public ClassLoader currentThreadContextClassLoader; public ContainerCapsule containerCapsule; @@ -298,22 +295,39 @@ protected void configureLogging(C context) throws Exception { // else fall back to default log level specified in conf // see https://issues.apache.org/jira/browse/MNG-2570 - // LOG STREAMS - PrintStream sysOut = System.out; - PrintStream sysErr = System.err; - if (mavenOptions.rawStreams().isEmpty() || !mavenOptions.rawStreams().get()) { + // JLine is quite slow to start due to the native library unpacking and loading + // so boot it asynchronously + context.terminal = new FastTerminal( + () -> TerminalBuilder.builder() + .name("Maven") + .streams( + context.invokerRequest.in().orElse(null), + context.invokerRequest.out().orElse(null)) + .dumb(true) + .build(), + terminal -> doConfigureWithTerminal(context, terminal)); + ProjectBuildLogAppender projectBuildLogAppender = + new ProjectBuildLogAppender(determineBuildEventListener(context)); + context.closeables.add(projectBuildLogAppender); + } + + protected void doConfigureWithTerminal(C context, Terminal terminal) { + MessageUtils.systemInstall(terminal); + AnsiConsole.setTerminal(terminal); + AnsiConsole.systemInstall(); + context.closeables.add(MessageUtils::systemUninstall); + MessageUtils.registerShutdownHook(); // safety belt + + O options = context.invokerRequest.options(); + if (options.rawStreams().isEmpty() || !options.rawStreams().get()) { MavenSimpleLogger stdout = (MavenSimpleLogger) context.loggerFactory.getLogger("stdout"); MavenSimpleLogger stderr = (MavenSimpleLogger) context.loggerFactory.getLogger("stderr"); stdout.setLogLevel(LocationAwareLogger.INFO_INT); stderr.setLogLevel(LocationAwareLogger.INFO_INT); System.setOut(new LoggingOutputStream(s -> stdout.info("[stdout] " + s)).printStream()); System.setErr(new LoggingOutputStream(s -> stderr.warn("[stderr] " + s)).printStream()); - context.closeables.add(() -> System.setOut(sysOut)); - context.closeables.add(() -> System.setErr(sysErr)); + // no need to set them back, this is already handled by MessageUtils.systemUninstall() above } - ProjectBuildLogAppender projectBuildLogAppender = - new ProjectBuildLogAppender(determineBuildEventListener(context)); - context.closeables.add(projectBuildLogAppender); } protected BuildEventListener determineBuildEventListener(C context) { @@ -329,12 +343,19 @@ protected BuildEventListener doDetermineBuildEventListener(C context) { if (options.logFile().isPresent()) { Path logFile = context.cwdResolver.apply(options.logFile().get()); try { - bel = new SimpleBuildEventListener(new PrintWriter(Files.newBufferedWriter(logFile))); + PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(logFile)); + bel = new SimpleBuildEventListener(printWriter::println); } catch (IOException e) { throw new MavenException("Unable to redirect logging to " + logFile, e); } } else { - bel = new SimpleBuildEventListener(MessageUtils.getTerminal().writer(), true); + // Given the terminal creation has been offloaded to a different thread, + // do not pass directory the terminal writer + bel = new SimpleBuildEventListener(msg -> { + PrintWriter pw = context.terminal.writer(); + pw.println(msg); + pw.flush(); + }); } return bel; } @@ -374,14 +395,14 @@ protected void activateLogging(C context) throws Exception { protected void helpOrVersionAndMayExit(C context) throws Exception { R invokerRequest = context.invokerRequest; if (invokerRequest.options().help().isPresent()) { - invokerRequest.options().displayHelp(context.invokerRequest.parserRequest(), context.stdOut); + invokerRequest.options().displayHelp(context.invokerRequest.parserRequest(), context.terminal.writer()); throw new ExitException(0); } if (invokerRequest.options().showVersionAndExit().isPresent()) { if (invokerRequest.options().quiet().orElse(false)) { - context.stdOut.println(CLIReportingUtils.showVersionMinimal()); + context.terminal.writer().println(CLIReportingUtils.showVersionMinimal()); } else { - context.stdOut.println(CLIReportingUtils.showVersion()); + context.terminal.writer().println(CLIReportingUtils.showVersion()); } throw new ExitException(0); } @@ -390,7 +411,7 @@ protected void helpOrVersionAndMayExit(C context) throws Exception { protected void preCommands(C context) throws Exception { Options mavenOptions = context.invokerRequest.options(); if (mavenOptions.verbose().orElse(false) || mavenOptions.showVersion().orElse(false)) { - context.stdOut.println(CLIReportingUtils.showVersion()); + context.terminal.writer().println(CLIReportingUtils.showVersion()); } } @@ -764,7 +785,7 @@ protected TransferListener determineTransferListener(C context, boolean noTransf } else if (context.interactive && !logFile) { return new SimplexTransferListener(new ConsoleMavenTransferListener( context.invokerRequest.messageBuilderFactory(), - context.stdOut, + context.terminal.writer(), context.invokerRequest.options().verbose().orElse(false))); } else { return new Slf4jMavenTransferListener(); diff --git a/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java b/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java index b5858fb6636d..87f7baa1fd59 100644 --- a/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java +++ b/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java @@ -18,23 +18,17 @@ */ package org.apache.maven.logging; -import java.io.PrintWriter; +import java.util.function.Consumer; import org.apache.maven.execution.ExecutionEvent; import org.eclipse.aether.transfer.TransferEvent; public class SimpleBuildEventListener implements BuildEventListener { - final PrintWriter writer; - final boolean autoflush; + final Consumer output; - public SimpleBuildEventListener(PrintWriter writer) { - this(writer, false); - } - - public SimpleBuildEventListener(PrintWriter writer, boolean autoflush) { - this.writer = writer; - this.autoflush = autoflush; + public SimpleBuildEventListener(Consumer output) { + this.output = output; } @Override @@ -65,10 +59,7 @@ public void fail(Throwable t) throws Exception {} @Override public void log(String msg) { - writer.println(msg); - if (autoflush) { - writer.flush(); - } + output.accept(msg); } @Override From fa4e7288858b25c8549d70d4d4fd8d24b9287c5d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 14 Oct 2024 22:08:43 +0200 Subject: [PATCH 11/14] Extract createTerminal --- .../maven/cling/invoker/LookupInvoker.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java index 78f34c86d04a..d38077238dd0 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/LookupInvoker.java @@ -24,6 +24,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -160,7 +161,9 @@ protected LookupInvokerContext(LookupInvoker invoker, R invokerRequest) @Override public void close() throws InvokerException { List causes = null; - for (AutoCloseable c : closeables) { + List cs = new ArrayList<>(closeables); + Collections.reverse(cs); + for (AutoCloseable c : cs) { if (c != null) { try { c.close(); @@ -297,7 +300,17 @@ protected void configureLogging(C context) throws Exception { // JLine is quite slow to start due to the native library unpacking and loading // so boot it asynchronously - context.terminal = new FastTerminal( + context.terminal = createTerminal(context); + context.closeables.add(MessageUtils::systemUninstall); + + // Create the build log appender + ProjectBuildLogAppender projectBuildLogAppender = + new ProjectBuildLogAppender(determineBuildEventListener(context)); + context.closeables.add(projectBuildLogAppender); + } + + protected Terminal createTerminal(C context) { + return new FastTerminal( () -> TerminalBuilder.builder() .name("Maven") .streams( @@ -306,16 +319,12 @@ protected void configureLogging(C context) throws Exception { .dumb(true) .build(), terminal -> doConfigureWithTerminal(context, terminal)); - ProjectBuildLogAppender projectBuildLogAppender = - new ProjectBuildLogAppender(determineBuildEventListener(context)); - context.closeables.add(projectBuildLogAppender); } protected void doConfigureWithTerminal(C context, Terminal terminal) { MessageUtils.systemInstall(terminal); AnsiConsole.setTerminal(terminal); AnsiConsole.systemInstall(); - context.closeables.add(MessageUtils::systemUninstall); MessageUtils.registerShutdownHook(); // safety belt O options = context.invokerRequest.options(); From ec41f5d4a3e9a225a5863cd826eba08289b7004f Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 15 Oct 2024 07:27:57 +0200 Subject: [PATCH 12/14] Enable MDC --- .../main/java/org/apache/maven/slf4j/MavenServiceProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java index c20ddf6e1818..e3adcdb759e5 100644 --- a/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java @@ -22,6 +22,7 @@ import org.slf4j.ILoggerFactory; import org.slf4j.IMarkerFactory; +import org.slf4j.helpers.BasicMDCAdapter; import org.slf4j.helpers.BasicMarkerFactory; import org.slf4j.helpers.NOPMDCAdapter; import org.slf4j.spi.MDCAdapter; @@ -39,7 +40,7 @@ public class MavenServiceProvider implements SLF4JServiceProvider { private MavenLoggerFactory loggerFactory = loadMavenLoggerFactory(); private IMarkerFactory markerFactory = new BasicMarkerFactory(); - private MDCAdapter mdcAdapter = new NOPMDCAdapter(); + private MDCAdapter mdcAdapter = new BasicMDCAdapter(); protected MavenLoggerFactory loadMavenLoggerFactory() { return ServiceLoader.load(MavenLoggerFactory.class).findFirst().orElseGet(MavenLoggerFactory::new); From 2b4cb876c95f7cadad8b88143ba7ab964f71f255 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 15 Oct 2024 09:17:26 +0200 Subject: [PATCH 13/14] Remove import --- .../main/java/org/apache/maven/slf4j/MavenServiceProvider.java | 1 - 1 file changed, 1 deletion(-) diff --git a/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java index e3adcdb759e5..af7fe1ca6521 100644 --- a/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java @@ -24,7 +24,6 @@ import org.slf4j.IMarkerFactory; import org.slf4j.helpers.BasicMDCAdapter; import org.slf4j.helpers.BasicMarkerFactory; -import org.slf4j.helpers.NOPMDCAdapter; import org.slf4j.spi.MDCAdapter; import org.slf4j.spi.SLF4JServiceProvider; From 7c7ea98493f9825991c6fec461a23b81a14e3abb Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Tue, 15 Oct 2024 10:38:12 +0200 Subject: [PATCH 14/14] Add missing err() stream --- .../java/org/apache/maven/api/cli/InvokerRequest.java | 8 ++++++++ .../apache/maven/cling/invoker/BaseInvokerRequest.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java index f43dc66a193b..34418a5fcc2c 100644 --- a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java +++ b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/InvokerRequest.java @@ -149,6 +149,14 @@ default Lookup lookup() { @Nonnull Optional out(); + /** + * Returns the error stream for the Maven execution, if running in embedded mode. + * + * @return an {@link Optional} containing the error stream, or empty if not applicable + */ + @Nonnull + Optional err(); + /** * Returns a list of core extensions, if configured in the .mvn/extensions.xml file. * diff --git a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java index 0f799bd7d5f9..1aa26ca7be1d 100644 --- a/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java +++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/BaseInvokerRequest.java @@ -128,6 +128,11 @@ public Optional out() { return Optional.ofNullable(out); } + @Override + public Optional err() { + return Optional.ofNullable(err); + } + @Override public Optional> coreExtensions() { return Optional.ofNullable(coreExtensions);