Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MNG-8309] Improve log infrastructure (first step toward multi-threading log view support) #1792

Merged
merged 14 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apache-maven/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ under the License.
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-provider</artifactId>
<artifactId>maven-logging</artifactId>
</dependency>
<dependency>
<groupId>org.jline</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,14 @@ public interface Options {
@Nonnull
Optional<String> logFile();

/**
* Returns whether raw streams should be logged.
*
* @return a boolean indicating whether raw streams should be logged
*/
@Nonnull
Optional<Boolean> rawStreams();

/**
* Returns the color setting for console output.
*
Expand Down
2 changes: 1 addition & 1 deletion maven-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ under the License.
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-wrapper</artifactId>
<artifactId>maven-logging</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
Expand Down
11 changes: 2 additions & 9 deletions maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<R> invoker = createInvoker()) {
return invoker.invoke(parseArguments(args));
} catch (ParserException e) {
Expand All @@ -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();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,14 @@ public Optional<String> logFile() {
return Optional.empty();
}

@Override
public Optional<Boolean> rawStreams() {
if (commandLine.hasOption(CLIManager.RAW_STREAMS)) {
return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}

@Override
public Optional<String> color() {
if (commandLine.hasOption(CLIManager.COLOR)) {
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -348,6 +357,10 @@ 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)
.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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ public Optional<String> logFile() {
return returnFirstPresentOrEmpty(Options::logFile);
}

@Override
public Optional<Boolean> rawStreams() {
return returnFirstPresentOrEmpty(Options::rawStreams);
}

@Override
public Optional<String> color() {
return returnFirstPresentOrEmpty(Options::color);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@

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.Collections;
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;
Expand All @@ -38,6 +39,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;
Expand All @@ -53,9 +55,13 @@
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.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;
Expand All @@ -68,9 +74,14 @@
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.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;

import static java.util.Objects.requireNonNull;
import static org.apache.maven.cling.invoker.Utils.toFile;
Expand Down Expand Up @@ -111,9 +122,6 @@ public static class LookupInvokerContext<
public final Function<String, Path> cwdResolver;
public final Function<String, Path> installationResolver;
public final Function<String, Path> userResolver;
public final InputStream stdIn;
public final PrintWriter stdOut;
public final PrintWriter stdErr;

protected LookupInvokerContext(LookupInvoker<O, R, C> invoker, R invokerRequest) {
this.invoker = invoker;
Expand All @@ -127,16 +135,15 @@ protected LookupInvokerContext(LookupInvoker<O, R, C> 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();
}

public Logger logger;
public ILoggerFactory loggerFactory;
public Slf4jConfiguration slf4jConfiguration;
public Slf4jConfiguration.Level loggerLevel;
public Terminal terminal;
public BuildEventListener buildEventListener;
public ClassLoader currentThreadContextClassLoader;
public ContainerCapsule containerCapsule;
public Lookup lookup;
Expand All @@ -149,10 +156,29 @@ protected LookupInvokerContext(LookupInvoker<O, R, C> invoker, R invokerRequest)
public Path userSettingsPath;
public Settings effectiveSettings;

public final List<AutoCloseable> closeables = new ArrayList<>();

@Override
public void close() throws InvokerException {
if (containerCapsule != null) {
containerCapsule.close();
List<Exception> causes = null;
List<AutoCloseable> cs = new ArrayList<>(closeables);
Collections.reverse(cs);
for (AutoCloseable c : cs) {
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;
}
}
}
Expand Down Expand Up @@ -272,18 +298,75 @@ 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
if (mavenOptions.logFile().isPresent()) {
Path logFile = context.cwdResolver.apply(mavenOptions.logFile().get());
// redirect stdout and stderr to file
// JLine is quite slow to start due to the native library unpacking and loading
// so boot it asynchronously
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(
context.invokerRequest.in().orElse(null),
context.invokerRequest.out().orElse(null))
.dumb(true)
.build(),
terminal -> doConfigureWithTerminal(context, terminal));
}

protected void doConfigureWithTerminal(C context, Terminal terminal) {
MessageUtils.systemInstall(terminal);
AnsiConsole.setTerminal(terminal);
AnsiConsole.systemInstall();
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());
// no need to set them back, this is already handled by MessageUtils.systemUninstall() above
}
}

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());
try {
PrintStream ps = new PrintStream(Files.newOutputStream(logFile));
System.setOut(ps);
System.setErr(ps);
PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(logFile));
bel = new SimpleBuildEventListener(printWriter::println);
} catch (IOException e) {
throw new InvokerException("Cannot set up log " + e.getMessage(), e);
throw new MavenException("Unable to redirect logging to " + logFile, e);
}
} else {
// 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;
}

protected void activateLogging(C context) throws Exception {
Expand All @@ -298,13 +381,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.");
Expand All @@ -315,14 +404,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);
}
Expand All @@ -331,12 +420,13 @@ 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());
}
}

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);

Expand Down Expand Up @@ -704,7 +794,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();
Expand Down
Loading