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/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/CommonsCliOptions.java b/maven-cli/src/main/java/org/apache/maven/cling/invoker/CommonsCliOptions.java index c3adcb5ee10f..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 @@ -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,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") 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..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 @@ -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; @@ -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; @@ -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; @@ -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; @@ -111,9 +122,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; @@ -127,9 +135,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(); } @@ -137,6 +142,8 @@ 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; public Lookup lookup; @@ -149,10 +156,29 @@ 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; + List 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; } } } @@ -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 { @@ -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."); @@ -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); } @@ -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); @@ -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(); 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..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 @@ -56,6 +56,8 @@ import org.apache.maven.execution.ProjectActivation; 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; @@ -65,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; @@ -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); } + listener = new LoggingExecutionListener(listener, determineBuildEventListener(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) { 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/lifecycle/internal/concurrent/BuildPlanExecutor.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/BuildPlanExecutor.java index d40e2e5ac47e..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 @@ -164,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; @@ -179,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); @@ -190,7 +188,6 @@ class BuildContext implements AutoCloseable { this.reactorContext = null; this.threads = 1; this.executor = null; - this.appender = null; this.plan = null; } @@ -312,7 +309,6 @@ void execute() { @Override public void close() { - this.appender.close(); this.executor.close(); } @@ -331,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/lifecycle/internal/concurrent/ConcurrentLogOutput.java b/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/ConcurrentLogOutput.java deleted file mode 100644 index 5c5342ba8ef8..000000000000 --- a/maven-core/src/main/java/org/apache/maven/lifecycle/internal/concurrent/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.lifecycle.internal.concurrent; - -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -import org.apache.maven.project.MavenProject; -import org.apache.maven.slf4j.MavenSimpleLogger; - -/** - * 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 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); - } - } -} diff --git a/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java b/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java similarity index 53% rename from maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java rename to maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java index afa8bb11f565..39573d061cd0 100644 --- a/maven-slf4j-wrapper/src/main/java/org/apache/maven/logwrapper/MavenSlf4jWrapperFactory.java +++ b/maven-core/src/main/java/org/apache/maven/logging/BuildEventListener.java @@ -16,17 +16,33 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.maven.logwrapper; +package org.apache.maven.logging; -import java.util.Optional; - -import org.slf4j.ILoggerFactory; +import org.apache.maven.execution.ExecutionEvent; +import org.eclipse.aether.transfer.TransferEvent; /** - * Wrapper for creating loggers which can have a log level threshold. + * An abstract build event sink. */ -public interface MavenSlf4jWrapperFactory extends ILoggerFactory { - void setLogLevelRecorder(LogLevelRecorder logLevelRecorder); +public interface BuildEventListener { + + void sessionStarted(ExecutionEvent event); + + void projectStarted(String projectId); + + void projectLogMessage(String projectId, String event); + + void projectFinished(String projectId); + + void executionFailure(String projectId, boolean halted, String exception); + + void mojoStarted(ExecutionEvent event); + + void finish(int exitCode) throws Exception; + + void fail(Throwable t) throws Exception; + + void log(String msg); - Optional getLogLevelRecorder(); + void transfer(String projectId, TransferEvent e); } 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..040a481455e2 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/LoggingExecutionListener.java @@ -0,0 +1,190 @@ +/* + * 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.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; + +public class LoggingExecutionListener implements ExecutionListener, ProjectExecutionListener { + + private final ExecutionListener delegate; + private final BuildEventListener buildEventListener; + + public LoggingExecutionListener(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/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); + } +} 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-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..87f7baa1fd59 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/logging/SimpleBuildEventListener.java @@ -0,0 +1,67 @@ +/* + * 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.function.Consumer; + +import org.apache.maven.execution.ExecutionEvent; +import org.eclipse.aether.transfer.TransferEvent; + +public class SimpleBuildEventListener implements BuildEventListener { + + final Consumer output; + + public SimpleBuildEventListener(Consumer output) { + this.output = output; + } + + @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) { + output.accept(msg); + } + + @Override + public void transfer(String projectId, TransferEvent e) {} +} 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-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-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); + } + } } 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/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-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-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.java b/maven-logging/src/main/java/org/apache/maven/logging/api/LogLevelRecorder.java similarity index 71% rename from maven-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.java rename to maven-logging/src/main/java/org/apache/maven/logging/api/LogLevelRecorder.java index b729879b0e3b..25ccc266e229 100644 --- a/maven-embedder/src/main/java/org/slf4j/MavenSlf4jFriend.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.slf4j; +package org.apache.maven.logging.api; -/** - * 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(); +public interface LogLevelRecorder { + + enum Level { + DEBUG, + INFO, + WARN, + ERROR } + + boolean hasReachedMaxLevel(); + + Level getMaxLevelReached(); + + Level getMaxLevelAllowed(); + + 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-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenBaseLogger.java new file mode 100644 index 000000000000..273f13ebc72c --- /dev/null +++ b/maven-logging/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: + * + * + *

    + *
  • org.slf4j.simpleLogger.logFile - The output target which can + * be the path to a file, or the special values "System.out" and + * "System.err". Default is "System.err".
  • + * + *
  • org.slf4j.simpleLogger.cacheOutputStream - If the output + * target is set to "System.out" or "System.err" (see preceding entry), by + * default, logs will be output to the latest value referenced by + * System.out/err variables. By setting this parameter to true, the + * output stream will be cached, i.e. assigned once at initialization time and + * re-used independently of the current value referenced by + * System.out/err.
  • + * + *
  • org.slf4j.simpleLogger.defaultLogLevel - Default log level + * for all instances of SimpleLogger. Must be one of ("trace", "debug", "info", + * "warn", "error" or "off"). If not specified, defaults to "info".
  • + * + *
  • org.slf4j.simpleLogger.log.a.b.c - Logging detail + * level for a SimpleLogger instance named "a.b.c". Right-side value must be one + * of "trace", "debug", "info", "warn", "error" or "off". When a SimpleLogger + * named "a.b.c" is initialized, its level is assigned from this property. If + * unspecified, the level of nearest parent logger will be used, and if none is + * set, then the value specified by + * org.slf4j.simpleLogger.defaultLogLevel will be used.
  • + * + *
  • org.slf4j.simpleLogger.showDateTime - Set to + * true if you want the current date and time to be included in + * output messages. Default is false
  • + * + *
  • org.slf4j.simpleLogger.dateTimeFormat - The date and time + * format to be used in the output messages. The pattern describing the date and + * time format is defined by + * SimpleDateFormat. If the format is not specified or is + * invalid, the number of milliseconds since start up will be output.
  • + * + *
  • org.slf4j.simpleLogger.showThreadName -Set to + * true if you want to output the current thread name. Defaults to + * true.
  • + * + *
  • (since version 1.7.33 and 2.0.0-alpha6) org.slf4j.simpleLogger.showThreadId - + * If you would like to output the current thread id, then set to + * true. Defaults to false.
  • + * + *
  • org.slf4j.simpleLogger.showLogName - Set to + * true if you want the Logger instance name to be included in + * output messages. Defaults to true.
  • + * + *
  • org.slf4j.simpleLogger.showShortLogName - Set to + * true if you want the last component of the name to be included + * in output messages. Defaults to false.
  • + * + *
  • org.slf4j.simpleLogger.levelInBrackets - Should the level + * string be output in brackets? Defaults to false.
  • + * + *
  • org.slf4j.simpleLogger.warnLevelString - The string value + * output for the warn level. Defaults to WARN.
  • + * + *
+ * + *

+ * 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/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-logging/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java new file mode 100644 index 000000000000..9ea6c18bc412 --- /dev/null +++ b/maven-logging/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java @@ -0,0 +1,82 @@ +/* + * 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.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +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 org.apache.maven.logging.api.LogLevelRecorder, ILoggerFactory { + DefaultLogLevelRecorder logLevelRecorder = null; + final ConcurrentMap loggerMap = new ConcurrentHashMap<>(); + + public MavenLoggerFactory() { + MavenSimpleLogger.lazyInit(); + } + + @Override + public boolean hasReachedMaxLevel() { + return logLevelRecorder != null && logLevelRecorder.metThreshold(); + } + + @Override + public Level getMaxLevelReached() { + return null; + } + + @Override + 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. + */ + @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-logging/src/main/java/org/apache/maven/slf4j/MavenServiceProvider.java similarity index 84% 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 index 11e2f9371506..af7fe1ca6521 100644 --- 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 @@ -18,10 +18,12 @@ */ package org.apache.maven.slf4j; +import java.util.ServiceLoader; + 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; import org.slf4j.spi.SLF4JServiceProvider; @@ -35,9 +37,13 @@ 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(); + private MDCAdapter mdcAdapter = new BasicMDCAdapter(); + + 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-logging/src/main/java/org/apache/maven/slf4j/MavenSimpleLogger.java similarity index 80% 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 index 749d60de883a..14b81aff4923 100644 --- 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 @@ -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,18 +73,24 @@ 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()); + if (t != null) { + writeThrowable(t, sink); + } } else { - super.doWrite(buf, t); + super.write(buf, t); } } @Override protected void writeThrowable(Throwable t, PrintStream stream) { + writeThrowable(t, stream::println); + } + + protected void writeThrowable(Throwable t, Consumer stream) { if (t == null) { return; } @@ -93,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, ""); } - private 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); @@ -111,7 +116,7 @@ private 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()) { @@ -123,13 +128,13 @@ 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, 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); } @@ -147,4 +152,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-logging/src/main/java/org/apache/maven/slf4j/OutputChoice.java b/maven-logging/src/main/java/org/apache/maven/slf4j/OutputChoice.java new file mode 100644 index 000000000000..fa491b596dca --- /dev/null +++ b/maven-logging/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-logging/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java b/maven-logging/src/main/java/org/apache/maven/slf4j/SimpleLoggerConfiguration.java new file mode 100644 index 000000000000..02ca0f464e2a --- /dev/null +++ b/maven-logging/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/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-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java b/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java deleted file mode 100644 index ea0b98272bcc..000000000000 --- a/maven-slf4j-provider/src/main/java/org/apache/maven/slf4j/MavenLoggerFactory.java +++ /dev/null @@ -1,57 +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.slf4j; - -import java.util.Optional; - -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 { - private LogLevelRecorder logLevelRecorder = null; - - public MavenLoggerFactory() {} - - @Override - public void setLogLevelRecorder(LogLevelRecorder logLevelRecorder) { - if (this.logLevelRecorder != null) { - throw new IllegalStateException("LogLevelRecorder has already been set."); - } - this.logLevelRecorder = logLevelRecorder; - reset(); - } - - @Override - public Optional getLogLevelRecorder() { - return Optional.ofNullable(logLevelRecorder); - } - - protected Logger createLogger(String name) { - if (logLevelRecorder == null) { - return new MavenSimpleLogger(name); - } else { - return new MavenFailOnSeverityLogger(name, logLevelRecorder); - } - } -} 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); - } -} 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