Make sure mvnd's plexus-interactivity is not in the maven classloader, fixes #807

* the logging framework is extracted into its own jar and moved in the mvn/lib/ directory
* the other daemon jars are moved into the mvn/lib/mvnd directory and not used by maven at all
This makes maven class loader almost identical to the stock maven classloader, but for the logging framework
This commit is contained in:
Guillaume Nodet
2023-03-13 17:15:04 +01:00
parent 4d24010f55
commit 0e057cb12b
16 changed files with 83 additions and 30 deletions

58
logging/pom.xml Normal file
View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2021 the original author or authors.
Licensed 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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.maven.daemon</groupId>
<artifactId>mvnd</artifactId>
<version>1.0.0-m5-SNAPSHOT</version>
</parent>
<artifactId>mvnd-logging</artifactId>
<packaging>jar</packaging>
<name>Maven Daemon - Logging</name>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
</dependency>
<!-- Logging -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,47 @@
/*
* 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.mvndaemon.mvnd.logging.internal;
import org.apache.maven.cli.logging.Slf4jConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MvndSlf4jConfiguration implements Slf4jConfiguration {
@Override
public void setRootLoggerLevel(Level level) {
ch.qos.logback.classic.Level value;
switch (level) {
case DEBUG:
value = ch.qos.logback.classic.Level.DEBUG;
break;
case INFO:
value = ch.qos.logback.classic.Level.INFO;
break;
default:
value = ch.qos.logback.classic.Level.ERROR;
break;
}
((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(value);
}
@Override
public void activate() {}
}

View File

@@ -0,0 +1,69 @@
/*
* 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.mvndaemon.mvnd.logging.internal;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.ThrowableProxyUtil;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.CoreConstants;
import static org.apache.maven.shared.utils.logging.MessageUtils.level;
/**
* This appender acts like the slf4j simple logger.
* It's used
*/
public class SimpleAppender extends AppenderBase<ILoggingEvent> {
@Override
protected void append(ILoggingEvent eventObject) {
StringBuilder buf = new StringBuilder();
buf.append('[');
buf.append(renderLevel(eventObject.getLevel()));
buf.append(']');
buf.append(' ');
buf.append(eventObject.getFormattedMessage());
buf.append(CoreConstants.LINE_SEPARATOR);
IThrowableProxy tp = eventObject.getThrowableProxy();
if (tp != null) {
buf.append(CoreConstants.LINE_SEPARATOR);
buf.append(ThrowableProxyUtil.asString(tp));
}
System.out.print(buf.toString());
}
private String renderLevel(Level level) {
switch (level.toInt()) {
case Level.TRACE_INT:
return level().debug("TRACE");
case Level.DEBUG_INT:
return level().debug("DEBUG");
case Level.INFO_INT:
return level().info("INFO");
case Level.WARN_INT:
return level().warning("WARNING");
case Level.ERROR_INT:
return level().error("ERROR");
default:
throw new IllegalStateException("Level " + level + " is unknown.");
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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.mvndaemon.mvnd.logging.internal;
import org.codehaus.plexus.logging.Logger;
import org.mvndaemon.mvnd.logging.smart.ProjectBuildLogAppender;
/**
* Adapt an SLF4J logger to a Plexus logger, ignoring Plexus logger API parts that are not classical and
* probably not really used.
*
* <p>
* Adapted from
* https://github.com/apache/maven/blob/maven-3.6.3/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java
* The main change is that the MDC property for redirecting the log to the correct maven project is set
* when the logger is instantiated (usually when injected into a mojo).
*
* @author Jason van Zyl
*/
public class Slf4jLogger implements Logger {
private org.slf4j.Logger logger;
private String projectId;
public Slf4jLogger(org.slf4j.Logger logger) {
this.logger = logger;
this.projectId = ProjectBuildLogAppender.getProjectId();
}
public void debug(String message) {
setMdc();
logger.debug(message);
}
public void debug(String message, Throwable throwable) {
setMdc();
logger.debug(message, throwable);
}
public boolean isDebugEnabled() {
return logger.isDebugEnabled();
}
public void info(String message) {
setMdc();
logger.info(message);
}
public void info(String message, Throwable throwable) {
setMdc();
logger.info(message, throwable);
}
public boolean isInfoEnabled() {
return logger.isInfoEnabled();
}
public void warn(String message) {
setMdc();
logger.warn(message);
}
public void warn(String message, Throwable throwable) {
setMdc();
logger.warn(message, throwable);
}
public boolean isWarnEnabled() {
return logger.isWarnEnabled();
}
public void error(String message) {
setMdc();
logger.error(message);
}
public void error(String message, Throwable throwable) {
setMdc();
logger.error(message, throwable);
}
public boolean isErrorEnabled() {
return logger.isErrorEnabled();
}
public void fatalError(String message) {
setMdc();
logger.error(message);
}
public void fatalError(String message, Throwable throwable) {
setMdc();
logger.error(message, throwable);
}
public boolean isFatalErrorEnabled() {
return logger.isErrorEnabled();
}
/**
* <b>Warning</b>: ignored (always return <code>0 == Logger.LEVEL_DEBUG</code>).
*/
public int getThreshold() {
return 0;
}
/**
* <b>Warning</b>: ignored.
*/
public void setThreshold(int threshold) {}
/**
* <b>Warning</b>: ignored (always return <code>null</code>).
*/
public Logger getChildLogger(String name) {
return null;
}
public String getName() {
return logger.getName();
}
private void setMdc() {
if (projectId != null && ProjectBuildLogAppender.getProjectId() == null) {
ProjectBuildLogAppender.setProjectId(projectId);
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* 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.mvndaemon.mvnd.logging.internal;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.logging.LoggerManager;
import org.slf4j.ILoggerFactory;
import org.slf4j.LoggerFactory;
/**
* Use an SLF4J {@link ILoggerFactory} as a backing for a Plexus
* {@link LoggerManager},
* ignoring Plexus logger API parts that are not classical and probably not really used.
*
* <p>
* Adapted from
* https://github.com/apache/maven/blob/maven-3.6.3/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java
* This class has no differences with the above beyond formatting. Its purpose is simply to be able to call the
* Slf4Logger.
*
* @author Jason van Zyl
*/
public class Slf4jLoggerManager implements LoggerManager {
private ILoggerFactory loggerFactory;
public Slf4jLoggerManager() {
loggerFactory = LoggerFactory.getILoggerFactory();
}
public Logger getLoggerForComponent(String role) {
return new Slf4jLogger(loggerFactory.getLogger(role));
}
/**
* The logger name for a component with a non-null hint is <code>role.hint</code>.
* <b>Warning</b>: this does not conform to logger name as class name convention.
* (and what about <code>null</code> and <code>default</code> hint equivalence?)
*/
public Logger getLoggerForComponent(String role, String hint) {
return (null == hint
? getLoggerForComponent(role)
: new Slf4jLogger(loggerFactory.getLogger(role + '.' + hint)));
}
//
// Trying to give loggers back is a bad idea. Ceki said so :-)
// notice to self: what was this method supposed to do?
//
/**
* <b>Warning</b>: ignored.
*/
public void returnComponentLogger(String role) {}
/**
* <b>Warning</b>: ignored.
*/
public void returnComponentLogger(String role, String hint) {}
/**
* <b>Warning</b>: ignored (always return <code>0</code>).
*/
public int getThreshold() {
return 0;
}
/**
* <b>Warning</b>: ignored.
*/
public void setThreshold(int threshold) {}
/**
* <b>Warning</b>: ignored.
*/
public void setThresholds(int threshold) {}
/**
* <b>Warning</b>: ignored (always return <code>0</code>).
*/
public int getActiveLoggerCount() {
return 0;
}
}

View File

@@ -0,0 +1,79 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.mvndaemon.mvnd.logging.smart;
import org.apache.maven.execution.ExecutionEvent;
import org.eclipse.aether.transfer.TransferEvent;
/**
* An abstract build event sink.
*/
public abstract class BuildEventListener {
private static final BuildEventListener DUMMY = new BuildEventListener() {
public void sessionStarted(ExecutionEvent event) {}
public void projectStarted(String projectId) {}
public void projectLogMessage(String projectId, String event) {}
public void projectFinished(String projectId) {}
public void executionFailure(String projectId, boolean halted, String exception) {}
public void mojoStarted(ExecutionEvent event) {}
public void finish(int exitCode) throws Exception {}
public void fail(Throwable t) throws Exception {}
public void log(String msg) {}
public void transfer(String projectId, TransferEvent e) {}
};
/**
* @return a dummy {@link BuildEventListener} that just swallows the messages and does not send them anywhere
*/
public static BuildEventListener dummy() {
return DUMMY;
}
protected BuildEventListener() {}
public abstract void sessionStarted(ExecutionEvent event);
public abstract void projectStarted(String projectId);
public abstract void projectLogMessage(String projectId, String event);
public abstract void projectFinished(String projectId);
public abstract void executionFailure(String projectId, boolean halted, String exception);
public abstract void mojoStarted(ExecutionEvent event);
public abstract void finish(int exitCode) throws Exception;
public abstract void fail(Throwable t) throws Exception;
public abstract void log(String msg);
public abstract void transfer(String projectId, TransferEvent e);
}

View File

@@ -0,0 +1,197 @@
/*
* 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.mvndaemon.mvnd.logging.smart;
import javax.inject.Named;
import javax.inject.Singleton;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.execution.MavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.execution.ProjectExecutionEvent;
import org.apache.maven.execution.ProjectExecutionListener;
import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.lifecycle.internal.ReactorBuildStatus;
import org.eclipse.sisu.Typed;
@Singleton
@Named
@Typed({LoggingExecutionListener.class, ExecutionListener.class, ProjectExecutionListener.class})
public class LoggingExecutionListener implements ExecutionListener, ProjectExecutionListener {
private ExecutionListener delegate;
private BuildEventListener buildEventListener;
public void init(ExecutionListener delegate, BuildEventListener buildEventListener) {
this.delegate = delegate;
this.buildEventListener = buildEventListener;
}
@Override
public void beforeProjectExecution(ProjectExecutionEvent projectExecutionEvent)
throws LifecycleExecutionException {}
@Override
public void beforeProjectLifecycleExecution(ProjectExecutionEvent projectExecutionEvent)
throws LifecycleExecutionException {}
@Override
public void afterProjectExecutionSuccess(ProjectExecutionEvent projectExecutionEvent)
throws LifecycleExecutionException {}
@Override
public void afterProjectExecutionFailure(ProjectExecutionEvent projectExecutionEvent) {
MavenSession session = projectExecutionEvent.getSession();
boolean halted;
// The ReactorBuildStatus is only available if the SmartBuilder is used
ReactorBuildStatus status =
(ReactorBuildStatus) session.getRepositorySession().getData().get(ReactorBuildStatus.class);
if (status != null) {
halted = status.isHalted();
} else {
// assume sensible default
Throwable t = projectExecutionEvent.getCause();
halted = (t instanceof RuntimeException || !(t instanceof Exception))
|| !MavenExecutionRequest.REACTOR_FAIL_NEVER.equals(session.getReactorFailureBehavior())
&& !MavenExecutionRequest.REACTOR_FAIL_AT_END.equals(session.getReactorFailureBehavior());
}
Throwable cause = projectExecutionEvent.getCause();
buildEventListener.executionFailure(
projectExecutionEvent.getProject().getArtifactId(), halted, cause != null ? cause.toString() : null);
}
@Override
public void projectDiscoveryStarted(ExecutionEvent event) {
setMdc(event);
delegate.projectDiscoveryStarted(event);
}
@Override
public void sessionStarted(ExecutionEvent event) {
setMdc(event);
buildEventListener.sessionStarted(event);
delegate.sessionStarted(event);
}
@Override
public void sessionEnded(ExecutionEvent event) {
setMdc(event);
delegate.sessionEnded(event);
}
@Override
public void projectStarted(ExecutionEvent event) {
setMdc(event);
buildEventListener.projectStarted(event.getProject().getArtifactId());
delegate.projectStarted(event);
}
@Override
public void projectSucceeded(ExecutionEvent event) {
setMdc(event);
delegate.projectSucceeded(event);
buildEventListener.projectFinished(event.getProject().getArtifactId());
}
@Override
public void projectFailed(ExecutionEvent event) {
setMdc(event);
delegate.projectFailed(event);
buildEventListener.projectFinished(event.getProject().getArtifactId());
}
@Override
public void projectSkipped(ExecutionEvent event) {
setMdc(event);
buildEventListener.projectStarted(event.getProject().getArtifactId());
delegate.projectSkipped(event);
buildEventListener.projectFinished(event.getProject().getArtifactId());
}
@Override
public void mojoStarted(ExecutionEvent event) {
setMdc(event);
buildEventListener.mojoStarted(event);
delegate.mojoStarted(event);
}
@Override
public void mojoSucceeded(ExecutionEvent event) {
setMdc(event);
delegate.mojoSucceeded(event);
}
@Override
public void mojoFailed(ExecutionEvent event) {
setMdc(event);
delegate.mojoFailed(event);
}
@Override
public void mojoSkipped(ExecutionEvent event) {
setMdc(event);
delegate.mojoSkipped(event);
}
@Override
public void forkStarted(ExecutionEvent event) {
setMdc(event);
delegate.forkStarted(event);
ProjectBuildLogAppender.setForkingProjectId(event.getProject().getArtifactId());
}
@Override
public void forkSucceeded(ExecutionEvent event) {
delegate.forkSucceeded(event);
ProjectBuildLogAppender.setForkingProjectId(null);
}
@Override
public void forkFailed(ExecutionEvent event) {
delegate.forkFailed(event);
ProjectBuildLogAppender.setForkingProjectId(null);
}
@Override
public void forkedProjectStarted(ExecutionEvent event) {
setMdc(event);
delegate.forkedProjectStarted(event);
}
@Override
public void forkedProjectSucceeded(ExecutionEvent event) {
setMdc(event);
delegate.forkedProjectSucceeded(event);
ProjectBuildLogAppender.setProjectId(null);
}
@Override
public void forkedProjectFailed(ExecutionEvent event) {
setMdc(event);
delegate.forkedProjectFailed(event);
ProjectBuildLogAppender.setProjectId(null);
}
private void setMdc(ExecutionEvent event) {
if (event.getProject() != null) {
ProjectBuildLogAppender.setProjectId(event.getProject().getArtifactId());
}
}
}

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.mvndaemon.mvnd.logging.smart;
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,150 @@
/*
* 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.mvndaemon.mvnd.logging.smart;
import java.util.Map;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Context;
import org.apache.maven.shared.utils.logging.LoggerLevelRenderer;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
/**
* Forwards log messages to the client.
*/
public class ProjectBuildLogAppender extends AppenderBase<ILoggingEvent> 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 static final String pattern = "[%level] %msg%n";
private final PatternLayout layout;
private final BuildEventListener buildEventListener;
public ProjectBuildLogAppender(BuildEventListener buildEventListener) {
this.buildEventListener = buildEventListener;
this.name = ProjectBuildLogAppender.class.getName();
this.context = (Context) LoggerFactory.getILoggerFactory();
final PatternLayout l = new PatternLayout();
l.setContext(context);
l.setPattern(pattern);
final Map<String, String> instanceConverterMap = l.getInstanceConverterMap();
final String levelConverterClassName = LevelConverter.class.getName();
instanceConverterMap.put("level", levelConverterClassName);
instanceConverterMap.put("le", levelConverterClassName);
instanceConverterMap.put("p", levelConverterClassName);
this.layout = l;
final Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.addAppender(this);
start();
}
@Override
public void start() {
layout.start();
super.start();
}
@Override
protected void append(ILoggingEvent event) {
String projectId = event.getMDCPropertyMap().get(KEY_PROJECT_ID);
buildEventListener.projectLogMessage(projectId, layout.doLayout(event));
}
public static class LevelConverter extends ClassicConverter {
@Override
public String convert(ILoggingEvent event) {
LoggerLevelRenderer llr = MessageUtils.level();
Level level = event.getLevel();
switch (level.toInt()) {
case Level.ERROR_INT:
return llr.error(level.toString());
case Level.WARN_INT:
return llr.warning(level.toString());
case Level.INFO_INT:
return llr.info(level.toString());
default:
return llr.debug(level.toString());
}
}
}
@Override
public void close() {
stop();
final Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
root.detachAppender(this);
}
@Override
public void stop() {
layout.stop();
super.stop();
}
}