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

Move logging infrastructure to support multi threaded output from mvnd to maven.
Refactor a bit the terminal/log creation done by Cling.
This commit is contained in:
Guillaume Nodet 2024-10-15 15:30:39 +02:00 committed by GitHub
parent d742fd624c
commit 740100b50c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 1789 additions and 1150 deletions

View File

@ -99,7 +99,7 @@ under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-provider</artifactId> <artifactId>maven-logging</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jline</groupId> <groupId>org.jline</groupId>

View File

@ -166,6 +166,14 @@ public interface Options {
@Nonnull @Nonnull
Optional<String> logFile(); Optional<String> logFile();
/**
* Returns whether raw streams should be logged.
*
* @return a boolean indicating whether raw streams should be logged
*/
@Nonnull
Optional<Boolean> rawStreams();
/** /**
* Returns the color setting for console output. * Returns the color setting for console output.
* *

View File

@ -175,7 +175,7 @@ under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-wrapper</artifactId> <artifactId>maven-logging</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>

View File

@ -181,6 +181,14 @@ public abstract class CommonsCliOptions implements Options {
return Optional.empty(); return Optional.empty();
} }
@Override
public Optional<Boolean> rawStreams() {
if (commandLine.hasOption(CLIManager.RAW_STREAMS)) {
return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@Override @Override
public Optional<String> color() { public Optional<String> color() {
if (commandLine.hasOption(CLIManager.COLOR)) { if (commandLine.hasOption(CLIManager.COLOR)) {
@ -262,6 +270,7 @@ public abstract class CommonsCliOptions implements Options {
public static final String ALTERNATE_INSTALLATION_TOOLCHAINS = "it"; public static final String ALTERNATE_INSTALLATION_TOOLCHAINS = "it";
public static final String LOG_FILE = "l"; 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 COLOR = "color";
public static final String HELP = "h"; public static final String HELP = "h";
@ -348,6 +357,10 @@ public abstract class CommonsCliOptions implements Options {
.hasArg() .hasArg()
.desc("Log file where all build output will go (disables output color)") .desc("Log file where all build output will go (disables output color)")
.build()); .build());
options.addOption(Option.builder()
.longOpt(RAW_STREAMS)
.desc("Do not decorate standard output and error streams")
.build());
options.addOption(Option.builder(SHOW_VERSION) options.addOption(Option.builder(SHOW_VERSION)
.longOpt("show-version") .longOpt("show-version")
.desc("Display version information WITHOUT stopping build") .desc("Display version information WITHOUT stopping build")

View File

@ -122,6 +122,11 @@ public abstract class LayeredOptions<O extends Options> implements Options {
return returnFirstPresentOrEmpty(Options::logFile); return returnFirstPresentOrEmpty(Options::logFile);
} }
@Override
public Optional<Boolean> rawStreams() {
return returnFirstPresentOrEmpty(Options::rawStreams);
}
@Override @Override
public Optional<String> color() { public Optional<String> color() {
return returnFirstPresentOrEmpty(Options::color); return returnFirstPresentOrEmpty(Options::color);

View File

@ -20,13 +20,14 @@ package org.apache.maven.cling.invoker;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.function.Function; import java.util.function.Function;
@ -38,6 +39,7 @@ import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Logger; import org.apache.maven.api.cli.Logger;
import org.apache.maven.api.cli.Options; import org.apache.maven.api.cli.Options;
import org.apache.maven.api.services.Lookup; 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.api.services.MessageBuilder;
import org.apache.maven.artifact.InvalidRepositoryException; import org.apache.maven.artifact.InvalidRepositoryException;
import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.repository.ArtifactRepository;
@ -53,9 +55,13 @@ import org.apache.maven.cli.transfer.QuietMavenTransferListener;
import org.apache.maven.cli.transfer.SimplexTransferListener; import org.apache.maven.cli.transfer.SimplexTransferListener;
import org.apache.maven.cli.transfer.Slf4jMavenTransferListener; import org.apache.maven.cli.transfer.Slf4jMavenTransferListener;
import org.apache.maven.execution.MavenExecutionRequest; import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.jline.FastTerminal;
import org.apache.maven.jline.MessageUtils; import org.apache.maven.jline.MessageUtils;
import org.apache.maven.logwrapper.LogLevelRecorder; import org.apache.maven.logging.BuildEventListener;
import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory; 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.Mirror;
import org.apache.maven.settings.Profile; import org.apache.maven.settings.Profile;
import org.apache.maven.settings.Proxy; import org.apache.maven.settings.Proxy;
@ -68,9 +74,14 @@ import org.apache.maven.settings.building.SettingsBuilder;
import org.apache.maven.settings.building.SettingsBuildingRequest; import org.apache.maven.settings.building.SettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingResult; import org.apache.maven.settings.building.SettingsBuildingResult;
import org.apache.maven.settings.building.SettingsProblem; import org.apache.maven.settings.building.SettingsProblem;
import org.apache.maven.slf4j.MavenSimpleLogger;
import org.eclipse.aether.transfer.TransferListener; 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.ILoggerFactory;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.slf4j.spi.LocationAwareLogger;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static org.apache.maven.cling.invoker.Utils.toFile; import static org.apache.maven.cling.invoker.Utils.toFile;
@ -111,9 +122,6 @@ public abstract class LookupInvoker<
public final Function<String, Path> cwdResolver; public final Function<String, Path> cwdResolver;
public final Function<String, Path> installationResolver; public final Function<String, Path> installationResolver;
public final Function<String, Path> userResolver; public final Function<String, Path> userResolver;
public final InputStream stdIn;
public final PrintWriter stdOut;
public final PrintWriter stdErr;
protected LookupInvokerContext(LookupInvoker<O, R, C> invoker, R invokerRequest) { protected LookupInvokerContext(LookupInvoker<O, R, C> invoker, R invokerRequest) {
this.invoker = invoker; this.invoker = invoker;
@ -127,9 +135,6 @@ public abstract class LookupInvoker<
.toAbsolutePath(); .toAbsolutePath();
this.userResolver = s -> this.userResolver = s ->
invokerRequest.userHomeDirectory().resolve(s).normalize().toAbsolutePath(); 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(); this.logger = invokerRequest.parserRequest().logger();
} }
@ -137,6 +142,8 @@ public abstract class LookupInvoker<
public ILoggerFactory loggerFactory; public ILoggerFactory loggerFactory;
public Slf4jConfiguration slf4jConfiguration; public Slf4jConfiguration slf4jConfiguration;
public Slf4jConfiguration.Level loggerLevel; public Slf4jConfiguration.Level loggerLevel;
public Terminal terminal;
public BuildEventListener buildEventListener;
public ClassLoader currentThreadContextClassLoader; public ClassLoader currentThreadContextClassLoader;
public ContainerCapsule containerCapsule; public ContainerCapsule containerCapsule;
public Lookup lookup; public Lookup lookup;
@ -149,10 +156,29 @@ public abstract class LookupInvoker<
public Path userSettingsPath; public Path userSettingsPath;
public Settings effectiveSettings; public Settings effectiveSettings;
public final List<AutoCloseable> closeables = new ArrayList<>();
@Override @Override
public void close() throws InvokerException { public void close() throws InvokerException {
if (containerCapsule != null) { List<Exception> causes = null;
containerCapsule.close(); List<AutoCloseable> cs = new ArrayList<>(closeables);
Collections.reverse(cs);
for (AutoCloseable c : cs) {
if (c != null) {
try {
c.close();
} catch (Exception e) {
if (causes == null) {
causes = new ArrayList<>();
}
causes.add(e);
}
}
}
if (causes != null) {
InvokerException exception = new InvokerException("Unable to close context");
causes.forEach(exception::addSuppressed);
throw exception;
} }
} }
} }
@ -272,20 +298,77 @@ public abstract class LookupInvoker<
// else fall back to default log level specified in conf // else fall back to default log level specified in conf
// see https://issues.apache.org/jira/browse/MNG-2570 // see https://issues.apache.org/jira/browse/MNG-2570
// LOG STREAMS // JLine is quite slow to start due to the native library unpacking and loading
if (mavenOptions.logFile().isPresent()) { // so boot it asynchronously
Path logFile = context.cwdResolver.apply(mavenOptions.logFile().get()); context.terminal = createTerminal(context);
// redirect stdout and stderr to file context.closeables.add(MessageUtils::systemUninstall);
try {
PrintStream ps = new PrintStream(Files.newOutputStream(logFile)); // Create the build log appender
System.setOut(ps); ProjectBuildLogAppender projectBuildLogAppender =
System.setErr(ps); new ProjectBuildLogAppender(determineBuildEventListener(context));
} catch (IOException e) { context.closeables.add(projectBuildLogAppender);
throw new InvokerException("Cannot set up log " + e.getMessage(), e); }
}
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 {
PrintWriter printWriter = new PrintWriter(Files.newBufferedWriter(logFile));
bel = new SimpleBuildEventListener(printWriter::println);
} catch (IOException e) {
throw new MavenException("Unable to redirect logging to " + logFile, e);
}
} else {
// 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 { protected void activateLogging(C context) throws Exception {
R invokerRequest = context.invokerRequest; R invokerRequest = context.invokerRequest;
Options mavenOptions = invokerRequest.options(); Options mavenOptions = invokerRequest.options();
@ -298,13 +381,19 @@ public abstract class LookupInvoker<
if (mavenOptions.failOnSeverity().isPresent()) { if (mavenOptions.failOnSeverity().isPresent()) {
String logLevelThreshold = mavenOptions.failOnSeverity().get(); String logLevelThreshold = mavenOptions.failOnSeverity().get();
if (context.loggerFactory instanceof LogLevelRecorder recorder) {
if (context.loggerFactory instanceof MavenSlf4jWrapperFactory) { LogLevelRecorder.Level level =
LogLevelRecorder logLevelRecorder = new LogLevelRecorder(logLevelThreshold); switch (logLevelThreshold.toLowerCase(Locale.ENGLISH)) {
((MavenSlf4jWrapperFactory) context.loggerFactory).setLogLevelRecorder(logLevelRecorder); 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 + "."); context.logger.info("Enabled to break the build on log level " + logLevelThreshold + ".");
} else { } 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 '" + "', but found '"
+ context.loggerFactory.getClass().getName() + "' instead. " + context.loggerFactory.getClass().getName() + "' instead. "
+ "The --fail-on-severity flag will not take effect."); + "The --fail-on-severity flag will not take effect.");
@ -315,14 +404,14 @@ public abstract class LookupInvoker<
protected void helpOrVersionAndMayExit(C context) throws Exception { protected void helpOrVersionAndMayExit(C context) throws Exception {
R invokerRequest = context.invokerRequest; R invokerRequest = context.invokerRequest;
if (invokerRequest.options().help().isPresent()) { 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); throw new ExitException(0);
} }
if (invokerRequest.options().showVersionAndExit().isPresent()) { if (invokerRequest.options().showVersionAndExit().isPresent()) {
if (invokerRequest.options().quiet().orElse(false)) { if (invokerRequest.options().quiet().orElse(false)) {
context.stdOut.println(CLIReportingUtils.showVersionMinimal()); context.terminal.writer().println(CLIReportingUtils.showVersionMinimal());
} else { } else {
context.stdOut.println(CLIReportingUtils.showVersion()); context.terminal.writer().println(CLIReportingUtils.showVersion());
} }
throw new ExitException(0); throw new ExitException(0);
} }
@ -331,12 +420,13 @@ public abstract class LookupInvoker<
protected void preCommands(C context) throws Exception { protected void preCommands(C context) throws Exception {
Options mavenOptions = context.invokerRequest.options(); Options mavenOptions = context.invokerRequest.options();
if (mavenOptions.verbose().orElse(false) || mavenOptions.showVersion().orElse(false)) { 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 { protected void container(C context) throws Exception {
context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(context); context.containerCapsule = createContainerCapsuleFactory().createContainerCapsule(context);
context.closeables.add(context.containerCapsule);
context.lookup = context.containerCapsule.getLookup(); context.lookup = context.containerCapsule.getLookup();
context.settingsBuilder = context.lookup.lookup(SettingsBuilder.class); context.settingsBuilder = context.lookup.lookup(SettingsBuilder.class);
@ -704,7 +794,7 @@ public abstract class LookupInvoker<
} else if (context.interactive && !logFile) { } else if (context.interactive && !logFile) {
return new SimplexTransferListener(new ConsoleMavenTransferListener( return new SimplexTransferListener(new ConsoleMavenTransferListener(
context.invokerRequest.messageBuilderFactory(), context.invokerRequest.messageBuilderFactory(),
context.stdOut, context.terminal.writer(),
context.invokerRequest.options().verbose().orElse(false))); context.invokerRequest.options().verbose().orElse(false)));
} else { } else {
return new Slf4jMavenTransferListener(); return new Slf4jMavenTransferListener();

View File

@ -56,6 +56,8 @@ import org.apache.maven.execution.ProfileActivation;
import org.apache.maven.execution.ProjectActivation; import org.apache.maven.execution.ProjectActivation;
import org.apache.maven.jline.MessageUtils; import org.apache.maven.jline.MessageUtils;
import org.apache.maven.lifecycle.LifecycleExecutionException; 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.model.building.ModelProcessor;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.building.SettingsBuildingRequest; import org.apache.maven.settings.building.SettingsBuildingRequest;
@ -65,6 +67,7 @@ import org.apache.maven.toolchain.building.ToolchainsBuilder;
import org.apache.maven.toolchain.building.ToolchainsBuildingResult; import org.apache.maven.toolchain.building.ToolchainsBuildingResult;
import org.codehaus.plexus.PlexusContainer; import org.codehaus.plexus.PlexusContainer;
import org.eclipse.aether.DefaultRepositoryCache; import org.eclipse.aether.DefaultRepositoryCache;
import org.eclipse.aether.transfer.TransferListener;
import static java.util.Comparator.comparing; import static java.util.Comparator.comparing;
import static org.apache.maven.cling.invoker.Utils.toProperties; import static org.apache.maven.cling.invoker.Utils.toProperties;
@ -359,12 +362,17 @@ public abstract class DefaultMavenInvoker<
} }
protected ExecutionListener determineExecutionListener(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) { if (context.eventSpyDispatcher != null) {
return context.eventSpyDispatcher.chainListener(executionListener); listener = context.eventSpyDispatcher.chainListener(listener);
} else {
return executionListener;
} }
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) { protected String determineMakeBehavior(C context) {

View File

@ -39,8 +39,11 @@ public abstract class MavenInvokerTestSupport<O extends MavenOptions, R extends
protected void invoke(Path cwd, Collection<String> goals) throws Exception { protected void invoke(Path cwd, Collection<String> goals) throws Exception {
// works only in recent Maven4 // works only in recent Maven4
Assumptions.assumeTrue(Files.isRegularFile( Assumptions.assumeTrue(
Paths.get(System.getProperty("maven.home")).resolve("conf").resolve("maven.properties"))); 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")); Files.createDirectory(cwd.resolve(".mvn"));

View File

@ -93,7 +93,7 @@ under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-provider</artifactId> <artifactId>maven-logging</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven.resolver</groupId> <groupId>org.apache.maven.resolver</groupId>

View File

@ -93,9 +93,6 @@ public class MultiThreadedBuilder implements Builder {
ExecutorService executor = Executors.newFixedThreadPool(nThreads, new BuildThreadFactory()); ExecutorService executor = Executors.newFixedThreadPool(nThreads, new BuildThreadFactory());
CompletionService<ProjectSegment> service = new ExecutorCompletionService<>(executor); CompletionService<ProjectSegment> service = new ExecutorCompletionService<>(executor);
// Currently disabled
ThreadOutputMuxer muxer = null; // new ThreadOutputMuxer( analyzer.getProjectBuilds(), System.out );
for (TaskSegment taskSegment : taskSegments) { for (TaskSegment taskSegment : taskSegments) {
ProjectBuildList segmentProjectBuilds = projectBuilds.getByTaskSegment(taskSegment); ProjectBuildList segmentProjectBuilds = projectBuilds.getByTaskSegment(taskSegment);
Map<MavenProject, ProjectSegment> projectBuildMap = projectBuilds.selectSegment(taskSegment); Map<MavenProject, ProjectSegment> projectBuildMap = projectBuilds.selectSegment(taskSegment);
@ -103,7 +100,7 @@ public class MultiThreadedBuilder implements Builder {
ConcurrencyDependencyGraph analyzer = ConcurrencyDependencyGraph analyzer =
new ConcurrencyDependencyGraph(segmentProjectBuilds, session.getProjectDependencyGraph()); new ConcurrencyDependencyGraph(segmentProjectBuilds, session.getProjectDependencyGraph());
multiThreadedProjectTaskSegmentBuild( multiThreadedProjectTaskSegmentBuild(
analyzer, reactorContext, session, service, taskSegment, projectBuildMap, muxer); analyzer, reactorContext, session, service, taskSegment, projectBuildMap);
if (reactorContext.getReactorBuildStatus().isHalted()) { if (reactorContext.getReactorBuildStatus().isHalted()) {
break; break;
} }
@ -123,8 +120,7 @@ public class MultiThreadedBuilder implements Builder {
MavenSession rootSession, MavenSession rootSession,
CompletionService<ProjectSegment> service, CompletionService<ProjectSegment> service,
TaskSegment taskSegment, TaskSegment taskSegment,
Map<MavenProject, ProjectSegment> projectBuildList, Map<MavenProject, ProjectSegment> projectBuildList) {
ThreadOutputMuxer muxer) {
// gather artifactIds which are not unique so that the respective thread names can be extended with the groupId // gather artifactIds which are not unique so that the respective thread names can be extended with the groupId
Set<String> duplicateArtifactIds = projectBuildList.keySet().stream() Set<String> duplicateArtifactIds = projectBuildList.keySet().stream()
.map(MavenProject::getArtifactId) .map(MavenProject::getArtifactId)
@ -139,8 +135,8 @@ public class MultiThreadedBuilder implements Builder {
for (MavenProject mavenProject : analyzer.getRootSchedulableBuilds()) { for (MavenProject mavenProject : analyzer.getRootSchedulableBuilds()) {
ProjectSegment projectSegment = projectBuildList.get(mavenProject); ProjectSegment projectSegment = projectBuildList.get(mavenProject);
logger.debug("Scheduling: {}", projectSegment.getProject()); logger.debug("Scheduling: {}", projectSegment.getProject());
Callable<ProjectSegment> cb = createBuildCallable( Callable<ProjectSegment> cb =
rootSession, projectSegment, reactorContext, taskSegment, muxer, duplicateArtifactIds); createBuildCallable(rootSession, projectSegment, reactorContext, taskSegment, duplicateArtifactIds);
service.submit(cb); service.submit(cb);
} }
@ -160,12 +156,7 @@ public class MultiThreadedBuilder implements Builder {
ProjectSegment scheduledDependent = projectBuildList.get(mavenProject); ProjectSegment scheduledDependent = projectBuildList.get(mavenProject);
logger.debug("Scheduling: {}", scheduledDependent); logger.debug("Scheduling: {}", scheduledDependent);
Callable<ProjectSegment> cb = createBuildCallable( Callable<ProjectSegment> cb = createBuildCallable(
rootSession, rootSession, scheduledDependent, reactorContext, taskSegment, duplicateArtifactIds);
scheduledDependent,
reactorContext,
taskSegment,
muxer,
duplicateArtifactIds);
service.submit(cb); service.submit(cb);
} }
} }
@ -185,7 +176,6 @@ public class MultiThreadedBuilder implements Builder {
final ProjectSegment projectBuild, final ProjectSegment projectBuild,
final ReactorContext reactorContext, final ReactorContext reactorContext,
final TaskSegment taskSegment, final TaskSegment taskSegment,
final ThreadOutputMuxer muxer,
final Set<String> duplicateArtifactIds) { final Set<String> duplicateArtifactIds) {
return () -> { return () -> {
final Thread currentThread = Thread.currentThread(); final Thread currentThread = Thread.currentThread();
@ -198,10 +188,8 @@ public class MultiThreadedBuilder implements Builder {
currentThread.setName("mvn-builder-" + threadNameSuffix); currentThread.setName("mvn-builder-" + threadNameSuffix);
try { try {
// muxer.associateThreadWithProjectSegment( projectBuild );
lifecycleModuleBuilder.buildProject( lifecycleModuleBuilder.buildProject(
projectBuild.getSession(), rootSession, reactorContext, project, taskSegment); projectBuild.getSession(), rootSession, reactorContext, project, taskSegment);
// muxer.setThisModuleComplete( projectBuild );
return projectBuild; return projectBuild;
} finally { } finally {

View File

@ -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;
/**
* <strong>NOTE:</strong> 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<ProjectSegment> projects;
private final ThreadLocal<ProjectSegment> projectBuildThreadLocal = new ThreadLocal<>();
private final Map<ProjectSegment, ByteArrayOutputStream> streams = new HashMap<>();
private final Map<ProjectSegment, PrintStream> printStreams = new HashMap<>();
private final ByteArrayOutputStream defaultOutputStreamForUnknownData = new ByteArrayOutputStream();
private final PrintStream defaultPrintStream = new PrintStream(defaultOutputStreamForUnknownData);
private final Set<ProjectSegment> 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();
}
}
}
}

View File

@ -164,7 +164,6 @@ public class BuildPlanExecutor {
final MavenSession session; final MavenSession session;
final ReactorContext reactorContext; final ReactorContext reactorContext;
final PhasingExecutor executor; final PhasingExecutor executor;
final ConcurrentLogOutput appender;
final Map<Object, Clock> clocks = new ConcurrentHashMap<>(); final Map<Object, Clock> clocks = new ConcurrentHashMap<>();
final ReadWriteLock lock = new ReentrantReadWriteLock(); final ReadWriteLock lock = new ReentrantReadWriteLock();
final int threads; final int threads;
@ -179,7 +178,6 @@ public class BuildPlanExecutor {
// Propagate the parallel flag to the root session // Propagate the parallel flag to the root session
session.setParallel(threads > 1); session.setParallel(threads > 1);
this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory())); this.executor = new PhasingExecutor(Executors.newFixedThreadPool(threads, new BuildThreadFactory()));
this.appender = new ConcurrentLogOutput();
// build initial plan // build initial plan
this.plan = buildInitialPlan(taskSegments); this.plan = buildInitialPlan(taskSegments);
@ -190,7 +188,6 @@ public class BuildPlanExecutor {
this.reactorContext = null; this.reactorContext = null;
this.threads = 1; this.threads = 1;
this.executor = null; this.executor = null;
this.appender = null;
this.plan = null; this.plan = null;
} }
@ -312,7 +309,6 @@ public class BuildPlanExecutor {
@Override @Override
public void close() { public void close() {
this.appender.close();
this.executor.close(); this.executor.close();
} }
@ -331,7 +327,7 @@ public class BuildPlanExecutor {
.forEach(step -> { .forEach(step -> {
boolean nextIsPlanning = step.successors.stream().anyMatch(st -> PLAN.equals(st.name)); boolean nextIsPlanning = step.successors.stream().anyMatch(st -> PLAN.equals(st.name));
executor.execute(() -> { executor.execute(() -> {
try (AutoCloseable ctx = appender.build(step.project)) { try {
executeStep(step); executeStep(step);
if (nextIsPlanning) { if (nextIsPlanning) {
lock.writeLock().lock(); lock.writeLock().lock();

View File

@ -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<ProjectExecutionContext> 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<String> 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);
}
}
}

View File

@ -16,17 +16,33 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.maven.logwrapper; package org.apache.maven.logging;
import java.util.Optional; import org.apache.maven.execution.ExecutionEvent;
import org.eclipse.aether.transfer.TransferEvent;
import org.slf4j.ILoggerFactory;
/** /**
* Wrapper for creating loggers which can have a log level threshold. * An abstract build event sink.
*/ */
public interface MavenSlf4jWrapperFactory extends ILoggerFactory { public interface BuildEventListener {
void setLogLevelRecorder(LogLevelRecorder logLevelRecorder);
Optional<LogLevelRecorder> getLogLevelRecorder(); 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);
void transfer(String projectId, TransferEvent e);
} }

View File

@ -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());
}
}
}

View File

@ -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<String> consumer;
public LoggingOutputStream(Consumer<String> consumer) {
this(new EolBaos(), consumer);
}
LoggingOutputStream(EolBaos out, Consumer<String> 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();
}
}
}

View File

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

View File

@ -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<String> PROJECT_ID = new InheritableThreadLocal<>();
private static final ThreadLocal<String> 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);
}
}

View File

@ -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<String> output;
public SimpleBuildEventListener(Consumer<String> 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) {}
}

View File

@ -44,8 +44,7 @@ public class PluginResolutionException extends Exception {
+ System.lineSeparator() + "\t" + System.lineSeparator() + "\t"
+ exceptions.stream() + exceptions.stream()
.map(Throwable::getMessage) .map(Throwable::getMessage)
.collect(Collectors.joining(System.lineSeparator() + "\t")) .collect(Collectors.joining(System.lineSeparator() + "\t")),
+ System.lineSeparator(),
cause); cause);
this.plugin = plugin; this.plugin = plugin;
} }

View File

@ -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<String> stringList = Arrays.asList(
"Thinkin", "of", "a", "master", "plan", "Cuz", "aint", "nuthin", "but", "sweat", "inside", "my",
"hand");
Iterator<String> lyrics = stringList.iterator();
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletionService<ProjectSegment> service = new ExecutorCompletionService<>(executor);
List<Future<ProjectSegment>> futures = new ArrayList<>();
for (ProjectSegment projectBuild : projectBuildList) {
final Future<ProjectSegment> buildFuture =
service.submit(new Outputter(threadOutputMuxer, projectBuild, lyrics.next()));
futures.add(buildFuture);
}
for (Future<ProjectSegment> 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<ProjectSegment> {
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);
}
}

View File

@ -74,7 +74,7 @@ under the License.
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-wrapper</artifactId> <artifactId>maven-logging</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.maven.resolver</groupId> <groupId>org.apache.maven.resolver</groupId>

View File

@ -38,6 +38,7 @@ import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Properties; import java.util.Properties;
@ -99,8 +100,7 @@ import org.apache.maven.extension.internal.CoreExtensionEntry;
import org.apache.maven.jline.JLineMessageBuilderFactory; import org.apache.maven.jline.JLineMessageBuilderFactory;
import org.apache.maven.jline.MessageUtils; import org.apache.maven.jline.MessageUtils;
import org.apache.maven.lifecycle.LifecycleExecutionException; import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.logwrapper.LogLevelRecorder; import org.apache.maven.logging.api.LogLevelRecorder;
import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory;
import org.apache.maven.model.building.ModelProcessor; import org.apache.maven.model.building.ModelProcessor;
import org.apache.maven.model.root.RootLocator; import org.apache.maven.model.root.RootLocator;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
@ -541,15 +541,22 @@ public class MavenCli {
if (commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) { if (commandLine.hasOption(CLIManager.FAIL_ON_SEVERITY)) {
String logLevelThreshold = commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY); String logLevelThreshold = commandLine.getOptionValue(CLIManager.FAIL_ON_SEVERITY);
if (slf4jLoggerFactory instanceof MavenSlf4jWrapperFactory) { if (slf4jLoggerFactory instanceof LogLevelRecorder recorder) {
LogLevelRecorder logLevelRecorder = new LogLevelRecorder(logLevelThreshold); LogLevelRecorder.Level level =
((MavenSlf4jWrapperFactory) slf4jLoggerFactory).setLogLevelRecorder(logLevelRecorder); 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); slf4jLogger.info("Enabled to break the build on log level {}.", logLevelThreshold);
} else { } else {
slf4jLogger.warn( slf4jLogger.warn(
"Expected LoggerFactory to be of type '{}', but found '{}' instead. " "Expected LoggerFactory to be of type '{}', but found '{}' instead. "
+ "The --fail-on-severity flag will not take effect.", + "The --fail-on-severity flag will not take effect.",
MavenSlf4jWrapperFactory.class.getName(), LogLevelRecorder.class.getName(),
slf4jLoggerFactory.getClass().getName()); slf4jLoggerFactory.getClass().getName());
} }
} }

View File

@ -32,8 +32,6 @@ import org.apache.maven.execution.BuildSummary;
import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenExecutionResult; import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.execution.MavenSession; 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.MojoExecution;
import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProject;
@ -79,13 +77,7 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
} }
private static String chars(char c, int count) { private static String chars(char c, int count) {
StringBuilder buffer = new StringBuilder(count); return String.valueOf(c).repeat(Math.max(0, count));
for (int i = count; i > 0; i--) {
buffer.append(c);
}
return buffer.toString();
} }
private void infoLine(char c) { private void infoLine(char c) {
@ -137,9 +129,8 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
} }
final List<MavenProject> allProjects = event.getSession().getAllProjects(); final List<MavenProject> allProjects = event.getSession().getAllProjects();
final int projectsSkipped = allProjects.size() - projects.size();
currentVisitedProjectCount = projectsSkipped; currentVisitedProjectCount = allProjects.size() - projects.size();
totalProjects = allProjects.size(); totalProjects = allProjects.size();
} }
} }
@ -154,16 +145,13 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory(); ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
if (iLoggerFactory instanceof MavenSlf4jWrapperFactory) { if (iLoggerFactory instanceof org.apache.maven.logging.api.LogLevelRecorder recorder
MavenSlf4jWrapperFactory loggerFactory = (MavenSlf4jWrapperFactory) iLoggerFactory; && recorder.hasReachedMaxLevel()) {
loggerFactory event.getSession()
.getLogLevelRecorder() .getResult()
.filter(LogLevelRecorder::metThreshold) .addException(
.ifPresent(recorder -> event.getSession() new Exception("Build failed due to log statements with a higher severity than allowed. "
.getResult() + "Fix the logged issues or remove flag --fail-on-severity (-fos)."));
.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()); logResult(event.getSession());
@ -297,7 +285,7 @@ public class ExecutionEventLogger extends AbstractExecutionListener {
infoLine('-'); infoLine('-');
String name = event.getProject().getName(); String name = event.getProject().getName();
infoMain("Skipping " + name); 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('-'); infoLine('-');
} }

View File

@ -18,6 +18,7 @@
*/ */
package org.apache.maven.cli.logging; package org.apache.maven.cli.logging;
import org.apache.maven.logging.ProjectBuildLogAppender;
import org.codehaus.plexus.logging.Logger; import org.codehaus.plexus.logging.Logger;
/** /**
@ -29,16 +30,20 @@ import org.codehaus.plexus.logging.Logger;
public class Slf4jLogger implements Logger { public class Slf4jLogger implements Logger {
private org.slf4j.Logger logger; private org.slf4j.Logger logger;
private String projectId;
public Slf4jLogger(org.slf4j.Logger logger) { public Slf4jLogger(org.slf4j.Logger logger) {
this.logger = logger; this.logger = logger;
this.projectId = ProjectBuildLogAppender.getProjectId();
} }
public void debug(String message) { public void debug(String message) {
setMdc();
logger.debug(message); logger.debug(message);
} }
public void debug(String message, Throwable throwable) { public void debug(String message, Throwable throwable) {
setMdc();
logger.debug(message, throwable); logger.debug(message, throwable);
} }
@ -47,10 +52,12 @@ public class Slf4jLogger implements Logger {
} }
public void info(String message) { public void info(String message) {
setMdc();
logger.info(message); logger.info(message);
} }
public void info(String message, Throwable throwable) { public void info(String message, Throwable throwable) {
setMdc();
logger.info(message, throwable); logger.info(message, throwable);
} }
@ -59,10 +66,12 @@ public class Slf4jLogger implements Logger {
} }
public void warn(String message) { public void warn(String message) {
setMdc();
logger.warn(message); logger.warn(message);
} }
public void warn(String message, Throwable throwable) { public void warn(String message, Throwable throwable) {
setMdc();
logger.warn(message, throwable); logger.warn(message, throwable);
} }
@ -71,10 +80,12 @@ public class Slf4jLogger implements Logger {
} }
public void error(String message) { public void error(String message) {
setMdc();
logger.error(message); logger.error(message);
} }
public void error(String message, Throwable throwable) { public void error(String message, Throwable throwable) {
setMdc();
logger.error(message, throwable); logger.error(message, throwable);
} }
@ -83,10 +94,12 @@ public class Slf4jLogger implements Logger {
} }
public void fatalError(String message) { public void fatalError(String message) {
setMdc();
logger.error(message); logger.error(message);
} }
public void fatalError(String message, Throwable throwable) { public void fatalError(String message, Throwable throwable) {
setMdc();
logger.error(message, throwable); logger.error(message, throwable);
} }
@ -116,4 +129,10 @@ public class Slf4jLogger implements Logger {
public String getName() { public String getName() {
return logger.getName(); return logger.getName();
} }
private void setMdc() {
if (projectId != null && ProjectBuildLogAppender.getProjectId() == null) {
ProjectBuildLogAppender.setProjectId(projectId);
}
}
} }

View File

@ -19,14 +19,16 @@
package org.apache.maven.cli.logging.impl; package org.apache.maven.cli.logging.impl;
import org.apache.maven.cli.logging.BaseSlf4jConfiguration; 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. * Configuration for slf4j-simple.
* *
* @since 3.1.0 * @since 3.1.0
*/ */
public class Slf4jSimpleConfiguration extends BaseSlf4jConfiguration { public class MavenSimpleConfiguration extends BaseSlf4jConfiguration {
@Override @Override
public void setRootLoggerLevel(Level level) { public void setRootLoggerLevel(Level level) {
String value; String value;
@ -48,7 +50,9 @@ public class Slf4jSimpleConfiguration extends BaseSlf4jConfiguration {
@Override @Override
public void activate() { public void activate() {
// property for root logger level or System.out redirection need to be taken into account ILoggerFactory lf = LoggerFactory.getILoggerFactory();
MavenSlf4jSimpleFriend.init(); if (lf instanceof MavenLoggerFactory mlf) {
mlf.reconfigure();
}
} }
} }

View File

@ -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();
}
}
}

View File

@ -17,7 +17,7 @@
# key = Slf4j effective logger factory implementation # key = Slf4j effective logger factory implementation
# value = corresponding o.a.m.cli.logging.Slf4jConfiguration class # value = corresponding o.a.m.cli.logging.Slf4jConfiguration class
org.slf4j.impl.SimpleLoggerFactory=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.Slf4jSimpleConfiguration org.apache.maven.slf4j.MavenLoggerFactory=org.apache.maven.cli.logging.impl.MavenSimpleConfiguration
org.apache.logging.slf4j.Log4jLoggerFactory=org.apache.maven.cli.logging.impl.Log4j2Configuration org.apache.logging.slf4j.Log4jLoggerFactory=org.apache.maven.cli.logging.impl.Log4j2Configuration
ch.qos.logback.classic.LoggerContext=org.apache.maven.cli.logging.impl.LogbackConfiguration ch.qos.logback.classic.LoggerContext=org.apache.maven.cli.logging.impl.LogbackConfiguration

View File

@ -98,4 +98,8 @@ public class MessageUtils {
public static MessageBuilder builder() { public static MessageBuilder builder() {
return messageBuilderFactory.builder(); return messageBuilderFactory.builder();
} }
public static Terminal getTerminal() {
return terminal;
}
} }

View File

@ -19,19 +19,25 @@ under the License.
--> -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven</artifactId> <artifactId>maven</artifactId>
<version>4.0.0-beta-5-SNAPSHOT</version> <version>4.0.0-beta-5-SNAPSHOT</version>
</parent> </parent>
<artifactId>maven-slf4j-wrapper</artifactId> <artifactId>maven-logging</artifactId>
<name>Maven Logging</name>
<name>Maven SLF4J Wrapper</name> <description>Provides the Maven Logging infrastructure</description>
<description>This modules provides an ILoggerFactory interface which avoids a cyclic dependency between maven-embedder and maven-slf4j-provider.</description>
<dependencies> <dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-jline</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
@ -48,4 +54,14 @@ under the License.
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.sisu</groupId>
<artifactId>sisu-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>

View File

@ -16,17 +16,22 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.slf4j; package org.apache.maven.logging.api;
/** public interface LogLevelRecorder {
* Utility for Maven to access Slf4j internals through package access.
* Use with precaution, since this is not normally intended for production use. enum Level {
*/ DEBUG,
public class MavenSlf4jFriend { INFO,
/** WARN,
* Reset Slf4j internal state. ERROR
*/
public static void reset() {
LoggerFactory.reset();
} }
boolean hasReachedMaxLevel();
Level getMaxLevelReached();
Level getMaxLevelAllowed();
void setMaxLevelAllowed(Level level);
} }

View File

@ -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<String, Level> 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<Level> 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();
}
}

View File

@ -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;
/**
* <p>
* 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:
*
*
* <ul>
* <li><code>org.slf4j.simpleLogger.logFile</code> - The output target which can
* be the <em>path</em> to a file, or the special values "System.out" and
* "System.err". Default is "System.err".</li>
*
* <li><code>org.slf4j.simpleLogger.cacheOutputStream</code> - 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
* <code>System.out/err</code> 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
* <code>System.out/err</code>.</li>
*
* <li><code>org.slf4j.simpleLogger.defaultLogLevel</code> - 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".</li>
*
* <li><code>org.slf4j.simpleLogger.log.<em>a.b.c</em></code> - 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
* <code>org.slf4j.simpleLogger.defaultLogLevel</code> will be used.</li>
*
* <li><code>org.slf4j.simpleLogger.showDateTime</code> - Set to
* <code>true</code> if you want the current date and time to be included in
* output messages. Default is <code>false</code></li>
*
* <li><code>org.slf4j.simpleLogger.dateTimeFormat</code> - The date and time
* format to be used in the output messages. The pattern describing the date and
* time format is defined by <a href=
* "http://docs.oracle.com/javase/1.5.0/docs/api/java/text/SimpleDateFormat.html">
* <code>SimpleDateFormat</code></a>. If the format is not specified or is
* invalid, the number of milliseconds since start up will be output.</li>
*
* <li><code>org.slf4j.simpleLogger.showThreadName</code> -Set to
* <code>true</code> if you want to output the current thread name. Defaults to
* <code>true</code>.</li>
*
* <li>(since version 1.7.33 and 2.0.0-alpha6) <code>org.slf4j.simpleLogger.showThreadId</code> -
* If you would like to output the current thread id, then set to
* <code>true</code>. Defaults to <code>false</code>.</li>
*
* <li><code>org.slf4j.simpleLogger.showLogName</code> - Set to
* <code>true</code> if you want the Logger instance name to be included in
* output messages. Defaults to <code>true</code>.</li>
*
* <li><code>org.slf4j.simpleLogger.showShortLogName</code> - Set to
* <code>true</code> if you want the last component of the name to be included
* in output messages. Defaults to <code>false</code>.</li>
*
* <li><code>org.slf4j.simpleLogger.levelInBrackets</code> - Should the level
* string be output in brackets? Defaults to <code>false</code>.</li>
*
* <li><code>org.slf4j.simpleLogger.warnLevelString</code> - The string value
* output for the warn level. Defaults to <code>WARN</code>.</li>
*
* </ul>
*
* <p>
* In addition to looking for system properties with the names specified above,
* this implementation also checks for a class loader resource named
* <code>"simplelogger.properties"</code>, and includes any matching definitions
* from this resource (if it exists).
*
*
* <p>
* 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.
*
* <p>
* Sample output follows.
*
*
* <pre>
* 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.
* </pre>
*
* <p>
* This implementation is heavily inspired by
* <a href="http://commons.apache.org/logging/">Apache Commons Logging</a>'s
* SimpleLog.
*
*
* @author Ceki G&uuml;lc&uuml;
* @author Scott Sanders
* @author Rod Waldhoff
* @author Robert Burrell Donkin
* @author C&eacute;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 <code>SimpleLogger</code> 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<Marker> markers = null;
if (marker != null) {
markers = new ArrayList<>();
markers.add(marker);
}
innerHandleNormalizedLoggingCall(level, markers, messagePattern, arguments, throwable);
}
private void innerHandleNormalizedLoggingCall(
Level level, List<Marker> 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;
}
}

View File

@ -18,7 +18,6 @@
*/ */
package org.apache.maven.slf4j; package org.apache.maven.slf4j;
import org.apache.maven.logwrapper.LogLevelRecorder;
import org.slf4j.event.Level; import org.slf4j.event.Level;
/** /**
@ -26,9 +25,9 @@ import org.slf4j.event.Level;
* Currently only support WARN and ERROR states, since it's been used for the --fail-on-severity flag. * Currently only support WARN and ERROR states, since it's been used for the --fail-on-severity flag.
*/ */
public class MavenFailOnSeverityLogger extends MavenSimpleLogger { 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); super(name);
this.logLevelRecorder = logLevelRecorder; this.logLevelRecorder = logLevelRecorder;
} }

View File

@ -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<String, Logger> 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);
}
});
}
}

View File

@ -18,10 +18,12 @@
*/ */
package org.apache.maven.slf4j; package org.apache.maven.slf4j;
import java.util.ServiceLoader;
import org.slf4j.ILoggerFactory; import org.slf4j.ILoggerFactory;
import org.slf4j.IMarkerFactory; import org.slf4j.IMarkerFactory;
import org.slf4j.helpers.BasicMDCAdapter;
import org.slf4j.helpers.BasicMarkerFactory; import org.slf4j.helpers.BasicMarkerFactory;
import org.slf4j.helpers.NOPMDCAdapter;
import org.slf4j.spi.MDCAdapter; import org.slf4j.spi.MDCAdapter;
import org.slf4j.spi.SLF4JServiceProvider; import org.slf4j.spi.SLF4JServiceProvider;
@ -35,9 +37,13 @@ public class MavenServiceProvider implements SLF4JServiceProvider {
@SuppressWarnings({"checkstyle:StaticVariableName", "checkstyle:VisibilityModifier"}) @SuppressWarnings({"checkstyle:StaticVariableName", "checkstyle:VisibilityModifier"})
public static String REQUESTED_API_VERSION = "2.0.99"; // !final 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 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() { public ILoggerFactory getLoggerFactory() {
return loggerFactory; return loggerFactory;

View File

@ -22,7 +22,6 @@ import java.io.PrintStream;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.maven.api.services.MessageBuilder; import org.apache.maven.api.services.MessageBuilder;
import org.slf4j.simple.ExtSimpleLogger;
import static org.apache.maven.jline.MessageUtils.builder; import static org.apache.maven.jline.MessageUtils.builder;
@ -32,7 +31,7 @@ import static org.apache.maven.jline.MessageUtils.builder;
* *
* @since 3.5.0 * @since 3.5.0
*/ */
public class MavenSimpleLogger extends ExtSimpleLogger { public class MavenSimpleLogger extends MavenBaseLogger {
private String traceRenderedLevel; private String traceRenderedLevel;
private String debugRenderedLevel; private String debugRenderedLevel;
@ -74,18 +73,24 @@ public class MavenSimpleLogger extends ExtSimpleLogger {
} }
} }
@Override protected void write(StringBuilder buf, Throwable t) {
protected void doWrite(StringBuilder buf, Throwable t) {
Consumer<String> sink = logSink; Consumer<String> sink = logSink;
if (sink != null) { if (sink != null) {
sink.accept(buf.toString()); sink.accept(buf.toString());
if (t != null) {
writeThrowable(t, sink);
}
} else { } else {
super.doWrite(buf, t); super.write(buf, t);
} }
} }
@Override @Override
protected void writeThrowable(Throwable t, PrintStream stream) { protected void writeThrowable(Throwable t, PrintStream stream) {
writeThrowable(t, stream::println);
}
protected void writeThrowable(Throwable t, Consumer<String> stream) {
if (t == null) { if (t == null) {
return; return;
} }
@ -93,12 +98,12 @@ public class MavenSimpleLogger extends ExtSimpleLogger {
if (t.getMessage() != null) { if (t.getMessage() != null) {
builder.a(": ").failure(t.getMessage()); builder.a(": ").failure(t.getMessage());
} }
stream.println(builder); stream.accept(builder.toString());
printStackTrace(t, stream, ""); printStackTrace(t, stream, "");
} }
private void printStackTrace(Throwable t, PrintStream stream, String prefix) { protected void printStackTrace(Throwable t, Consumer<String> stream, String prefix) {
MessageBuilder builder = builder(); MessageBuilder builder = builder();
for (StackTraceElement e : t.getStackTrace()) { for (StackTraceElement e : t.getStackTrace()) {
builder.a(prefix); builder.a(prefix);
@ -111,7 +116,7 @@ public class MavenSimpleLogger extends ExtSimpleLogger {
builder.a("("); builder.a("(");
builder.strong(getLocation(e)); builder.strong(getLocation(e));
builder.a(")"); builder.a(")");
stream.println(builder); stream.accept(builder.toString());
builder.setLength(0); builder.setLength(0);
} }
for (Throwable se : t.getSuppressed()) { for (Throwable se : t.getSuppressed()) {
@ -123,13 +128,13 @@ public class MavenSimpleLogger extends ExtSimpleLogger {
} }
} }
private void writeThrowable(Throwable t, PrintStream stream, String caption, String prefix) { protected void writeThrowable(Throwable t, Consumer<String> stream, String caption, String prefix) {
MessageBuilder builder = MessageBuilder builder =
builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName()); builder().a(prefix).strong(caption).a(": ").a(t.getClass().getName());
if (t.getMessage() != null) { if (t.getMessage() != null) {
builder.a(": ").failure(t.getMessage()); builder.a(": ").failure(t.getMessage());
} }
stream.println(builder); stream.accept(builder.toString());
printStackTrace(t, stream, prefix); printStackTrace(t, stream, prefix);
} }
@ -147,4 +152,17 @@ public class MavenSimpleLogger extends ExtSimpleLogger {
return e.getFileName(); 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;
}
} }

View File

@ -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&uuml;lc&uuml;
*
*/
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();
}
}
}

View File

@ -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&uuml;lc&uuml;
* @author Scott Sanders
* @author Rod Waldhoff
* @author Robert Burrell Donkin
* @author C&eacute;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);
}
}
}
}

View File

@ -16,7 +16,7 @@
* specific language governing permissions and limitations * specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package org.apache.maven.logwrapper; package org.apache.maven.slf4j;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.event.Level; import org.slf4j.event.Level;
@ -29,7 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class LogLevelRecorderTest { class LogLevelRecorderTest {
@Test @Test
void createsLogLevelRecorder() { void createsLogLevelRecorder() {
LogLevelRecorder logLevelRecorder = new LogLevelRecorder("WARN"); DefaultLogLevelRecorder logLevelRecorder = new DefaultLogLevelRecorder("WARN");
logLevelRecorder.record(Level.ERROR); logLevelRecorder.record(Level.ERROR);
assertTrue(logLevelRecorder.metThreshold()); assertTrue(logLevelRecorder.metThreshold());
@ -37,12 +37,12 @@ class LogLevelRecorderTest {
@Test @Test
void failsOnLowerThanWarn() { void failsOnLowerThanWarn() {
assertThrows(IllegalArgumentException.class, () -> new LogLevelRecorder("INFO")); assertThrows(IllegalArgumentException.class, () -> new DefaultLogLevelRecorder("INFO"));
} }
@Test @Test
void createsLogLevelRecorderWithWarning() { void createsLogLevelRecorderWithWarning() {
LogLevelRecorder logLevelRecorder = new LogLevelRecorder("WARNING"); DefaultLogLevelRecorder logLevelRecorder = new DefaultLogLevelRecorder("WARNING");
logLevelRecorder.record(Level.ERROR); logLevelRecorder.record(Level.ERROR);
assertTrue(logLevelRecorder.metThreshold()); assertTrue(logLevelRecorder.metThreshold());
@ -50,7 +50,7 @@ class LogLevelRecorderTest {
@Test @Test
void failsOnUnknownLogLevel() { void failsOnUnknownLogLevel() {
Throwable thrown = assertThrows(IllegalArgumentException.class, () -> new LogLevelRecorder("SEVERE")); Throwable thrown = assertThrows(IllegalArgumentException.class, () -> new DefaultLogLevelRecorder("SEVERE"));
String message = thrown.getMessage(); String message = thrown.getMessage();
assertThat(message, containsString("SEVERE is not a valid log severity threshold")); assertThat(message, containsString("SEVERE is not a valid log severity threshold"));
assertThat(message, containsString("WARN")); assertThat(message, containsString("WARN"));

View File

@ -18,7 +18,6 @@
*/ */
package org.apache.maven.slf4j; package org.apache.maven.slf4j;
import org.apache.maven.logwrapper.LogLevelRecorder;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -28,7 +27,6 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
class MavenLoggerFactoryTest { class MavenLoggerFactoryTest {
@ -58,31 +56,18 @@ class MavenLoggerFactoryTest {
@Test @Test
void reportsWhenFailOnSeverityThresholdHasBeenHit() { void reportsWhenFailOnSeverityThresholdHasBeenHit() {
MavenLoggerFactory mavenLoggerFactory = new MavenLoggerFactory(); MavenLoggerFactory mavenLoggerFactory = new MavenLoggerFactory();
mavenLoggerFactory.setLogLevelRecorder(new LogLevelRecorder("ERROR")); mavenLoggerFactory.logLevelRecorder = new DefaultLogLevelRecorder("ERROR");
assertTrue(mavenLoggerFactory.getLogLevelRecorder().isPresent());
LogLevelRecorder logLevelRecorder =
mavenLoggerFactory.getLogLevelRecorder().get();
MavenFailOnSeverityLogger logger = (MavenFailOnSeverityLogger) mavenLoggerFactory.getLogger("Test"); MavenFailOnSeverityLogger logger = (MavenFailOnSeverityLogger) mavenLoggerFactory.getLogger("Test");
assertFalse(logLevelRecorder.metThreshold()); assertFalse(mavenLoggerFactory.logLevelRecorder.metThreshold());
logger.warn("This should not hit the fail threshold"); logger.warn("This should not hit the fail threshold");
assertFalse(logLevelRecorder.metThreshold()); assertFalse(mavenLoggerFactory.logLevelRecorder.metThreshold());
logger.error("This should hit the fail threshold"); logger.error("This should hit the fail threshold");
assertTrue(logLevelRecorder.metThreshold()); assertTrue(mavenLoggerFactory.logLevelRecorder.metThreshold());
logger.warn("This should not reset the fail threshold"); logger.warn("This should not reset the fail threshold");
assertTrue(logLevelRecorder.metThreshold()); assertTrue(mavenLoggerFactory.logLevelRecorder.metThreshold());
}
@Test
void failOnSeverityThresholdCanOnlyBeSetOnce() {
MavenLoggerFactory mavenLoggerFactory = new MavenLoggerFactory();
mavenLoggerFactory.setLogLevelRecorder(new LogLevelRecorder("WARN"));
assertThrows(
IllegalStateException.class,
() -> mavenLoggerFactory.setLogLevelRecorder(new LogLevelRecorder("ERROR")));
} }
} }

View File

@ -49,7 +49,7 @@ class MavenSimpleLoggerTest {
} }
@Test @Test
void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo testInfo) throws Exception { void includesCauseAndSuppressedExceptionsWhenWritingThrowables(TestInfo testInfo) {
Exception causeOfSuppressed = new NoSuchElementException("cause of suppressed"); Exception causeOfSuppressed = new NoSuchElementException("cause of suppressed");
Exception suppressed = new IllegalStateException("suppressed", causeOfSuppressed); Exception suppressed = new IllegalStateException("suppressed", causeOfSuppressed);
suppressed.addSuppressed(new IllegalArgumentException( suppressed.addSuppressed(new IllegalArgumentException(
@ -62,7 +62,7 @@ class MavenSimpleLoggerTest {
new MavenSimpleLogger("logger").writeThrowable(throwable, new PrintStream(output)); new MavenSimpleLogger("logger").writeThrowable(throwable, new PrintStream(output));
String actual = output.toString(UTF_8.name()); String actual = output.toString(UTF_8);
List<String> actualLines = Arrays.asList(actual.split(System.lineSeparator())); List<String> actualLines = Arrays.asList(actual.split(System.lineSeparator()));
Class<?> testClass = testInfo.getTestClass().get(); Class<?> testClass = testInfo.getTestClass().get();

View File

@ -1,113 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven</groupId>
<artifactId>maven</artifactId>
<version>4.0.0-beta-5-SNAPSHOT</version>
</parent>
<artifactId>maven-slf4j-provider</artifactId>
<name>Maven SLF4J Simple Provider</name>
<description>Maven SLF4J provider based on SLF4J's simple provider, extended to support Maven styled colors
for levels and stacktrace rendering.</description>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-wrapper</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-api-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-jline</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<configuration>
<artifactItems>
<artifactItem>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>${slf4jVersion}</version>
<type>jar</type>
<classifier>sources</classifier>
<overWrite>false</overWrite>
<outputDirectory>${project.build.directory}/generated-sources/slf4j-simple</outputDirectory>
<includes>org/slf4j/simple/*.java</includes>
</artifactItem>
</artifactItems>
</configuration>
<executions>
<execution>
<id>unzip-slf4j-simple</id>
<goals>
<goal>unpack</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-slf4j-simple</id>
<goals>
<goal>add-source</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<sources>
<source>${project.build.directory}/generated-sources/slf4j-simple</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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<LogLevelRecorder> getLogLevelRecorder() {
return Optional.ofNullable(logLevelRecorder);
}
protected Logger createLogger(String name) {
if (logLevelRecorder == null) {
return new MavenSimpleLogger(name);
} else {
return new MavenFailOnSeverityLogger(name, logLevelRecorder);
}
}
}

View File

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

View File

@ -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<String, Level> 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;
}
}

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/DECORATION/1.8.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.8.0 http://maven.apache.org/xsd/decoration-1.8.0.xsd">
<edit>${project.scm.url}</edit>
<body>
<menu name="Overview">
<item name="Introduction" href="index.html"/>
<item name="Javadocs" href="apidocs/index.html"/>
<item name="Source Xref" href="xref/index.html"/>
<!--item name="FAQ" href="faq.html"/-->
</menu>
<menu ref="parent"/>
<menu ref="reports"/>
</body>
</project>

18
pom.xml
View File

@ -109,14 +109,13 @@ under the License.
<module>maven-di</module> <module>maven-di</module>
<module>maven-xml-impl</module> <module>maven-xml-impl</module>
<module>maven-jline</module> <module>maven-jline</module>
<module>maven-logging</module>
<module>maven-core</module> <module>maven-core</module>
<module>maven-settings</module> <module>maven-settings</module>
<module>maven-settings-builder</module> <module>maven-settings-builder</module>
<module>maven-artifact</module> <module>maven-artifact</module>
<module>maven-resolver-provider</module> <module>maven-resolver-provider</module>
<module>maven-repository-metadata</module> <module>maven-repository-metadata</module>
<module>maven-slf4j-provider</module>
<module>maven-slf4j-wrapper</module>
<module>maven-embedder</module> <module>maven-embedder</module>
<module>maven-cli</module> <module>maven-cli</module>
<module>maven-compat</module> <module>maven-compat</module>
@ -215,6 +214,11 @@ under the License.
<artifactId>maven-jline</artifactId> <artifactId>maven-jline</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-logging</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId> <artifactId>maven-core</artifactId>
@ -340,11 +344,6 @@ under the License.
<artifactId>maven-toolchain-builder</artifactId> <artifactId>maven-toolchain-builder</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-wrapper</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-xml-impl</artifactId> <artifactId>maven-xml-impl</artifactId>
@ -355,11 +354,6 @@ under the License.
<artifactId>maven-compat</artifactId> <artifactId>maven-compat</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-slf4j-provider</artifactId>
<version>${project.version}</version>
</dependency>
<!--bootstrap-end-comment--> <!--bootstrap-end-comment-->
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>