diff --git a/client/src/main/java/org/jboss/fuse/mvnd/client/TerminalOutput.java b/client/src/main/java/org/jboss/fuse/mvnd/client/TerminalOutput.java index 03c6b7ed..0b89c550 100644 --- a/client/src/main/java/org/jboss/fuse/mvnd/client/TerminalOutput.java +++ b/client/src/main/java/org/jboss/fuse/mvnd/client/TerminalOutput.java @@ -176,8 +176,12 @@ public class TerminalOutput implements ClientOutput { } case LOG: { if (entry.projectId != null) { - Project prj = projects.computeIfAbsent(entry.projectId, p -> new Project()); - prj.log.add(entry.message); + Project prj = projects.get(entry.projectId); + if (prj != null) { + prj.log.add(entry.message); + } else { + log.accept(entry.message); + } } else { log.accept(entry.message); } @@ -213,7 +217,7 @@ public class TerminalOutput implements ClientOutput { linesPerProject = Math.max(0, linesPerProject - 1); break; case CTRL_L: - display.reset(); + display.update(List.of(), 0); break; case CTRL_M: displayDone = !displayDone; @@ -267,24 +271,26 @@ public class TerminalOutput implements ClientOutput { return; } final List lines = new ArrayList<>(rows); - final int dispLines = rows - 1; + int dispLines = rows - 1; // for the "Building..." line + dispLines--; // there's a bug which sometimes make the cursor goes one line below, so keep one more line empty at the end if (projects.size() <= dispLines) { lines.add(new AttributedString("Building...")); int remLogLines = dispLines - projects.size(); for (Project prj : projects.values()) { - lines.add(AttributedString.fromAnsi(prj.status)); + lines.add(AttributedString.fromAnsi(prj.status != null ? prj.status : "")); // get the last lines of the project log, taking multi-line logs into account - List logs = lastN(prj.log, linesPerProject).stream() + int nb = Math.min(remLogLines, linesPerProject); + List logs = lastN(prj.log, nb).stream() .flatMap(s -> AttributedString.fromAnsi(s).columnSplitLength(Integer.MAX_VALUE).stream()) .map(s -> concat(" ", s)) - .collect(lastN(Math.min(remLogLines, linesPerProject))); + .collect(lastN(nb)); lines.addAll(logs); remLogLines -= logs.size(); } } else { lines.add(new AttributedString("Building... (" + (projects.size() - dispLines) + " more)")); lines.addAll(projects.values().stream() - .map(prj -> AttributedString.fromAnsi(prj.status)) + .map(prj -> AttributedString.fromAnsi(prj.status != null ? prj.status : "")) .collect(lastN(dispLines))); } List trimmed = lines.stream() @@ -299,9 +305,11 @@ public class TerminalOutput implements ClientOutput { private static Collector> lastN(int n) { return Collector., List> of(ArrayDeque::new, (acc, t) -> { - if (acc.size() == n) - acc.pollFirst(); - acc.add(t); + if (n > 0) { + if (acc.size() == n) + acc.pollFirst(); + acc.add(t); + } }, (acc1, acc2) -> { while (acc2.size() < n && !acc1.isEmpty()) { acc2.addFirst(acc1.pollLast()); diff --git a/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java b/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java index 419ea46d..07c240a5 100644 --- a/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java +++ b/common/src/main/java/org/jboss/fuse/mvnd/common/Message.java @@ -139,7 +139,8 @@ public abstract class Message { @Override public String toString() { return "BuildEvent{" + - "type=" + type + + "projectId='" + projectId + '\'' + + ", type=" + type + ", display='" + display + '\'' + '}'; } @@ -165,7 +166,8 @@ public abstract class Message { @Override public String toString() { return "BuildMessage{" + - "message='" + message + '\'' + + "projectId='" + projectId + '\'' + + ", message='" + message + '\'' + '}'; } } diff --git a/daemon/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java b/daemon/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java new file mode 100644 index 00000000..41966e12 --- /dev/null +++ b/daemon/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java @@ -0,0 +1,166 @@ +/* + * Copyright 2019 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. + */ +package org.apache.maven.cli.logging; + +/* + * 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. + */ +import org.codehaus.plexus.logging.Logger; +import org.jboss.fuse.mvnd.logging.smart.ProjectBuildLogAppender; +import org.slf4j.MDC; + +/** + * Adapt an SLF4J logger to a Plexus logger, ignoring Plexus logger API parts that are not classical and + * probably not really used. + * + *

+ * Adapted from https://github.com/apache/maven/blob/maven-3.6.3/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLogger.java + * + * @author Jason van Zyl + */ +public class Slf4jLogger + implements Logger { + + private static final ThreadLocal PROJECT_ID = new ThreadLocal<>(); + + private org.slf4j.Logger logger; + private String projectId; + + public Slf4jLogger(org.slf4j.Logger logger) { + this.logger = logger; + this.projectId = PROJECT_ID.get(); + } + + public static void setCurrentProject(String projectId) { + PROJECT_ID.set(projectId); + } + + 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(); + } + + /** + * Warning: ignored (always return 0 == Logger.LEVEL_DEBUG). + */ + public int getThreshold() { + return 0; + } + + /** + * Warning: ignored. + */ + public void setThreshold(int threshold) { + } + + /** + * Warning: ignored (always return null). + */ + public Logger getChildLogger(String name) { + return null; + } + + public String getName() { + return logger.getName(); + } + + private void setMdc() { + if (projectId != null) { + MDC.put(ProjectBuildLogAppender.KEY_PROJECT_ID, projectId); + } + } + +} diff --git a/daemon/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java b/daemon/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java new file mode 100644 index 00000000..c7ca9936 --- /dev/null +++ b/daemon/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java @@ -0,0 +1,117 @@ +/* + * Copyright 2019 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. + */ +package org.apache.maven.cli.logging; + +/* + * 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. + */ +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. + * + *

+ * Adapted from https://github.com/apache/maven/blob/maven-3.6.3/maven-embedder/src/main/java/org/apache/maven/cli/logging/Slf4jLoggerManager.java + * + * @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 role.hint. + * Warning: this does not conform to logger name as class name convention. + * (and what about null and default 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? + // + /** + * Warning: ignored. + */ + public void returnComponentLogger(String role) { + } + + /** + * Warning: ignored. + */ + public void returnComponentLogger(String role, String hint) { + } + + /** + * Warning: ignored (always return 0). + */ + public int getThreshold() { + return 0; + } + + /** + * Warning: ignored. + */ + public void setThreshold(int threshold) { + } + + /** + * Warning: ignored. + */ + public void setThresholds(int threshold) { + } + + /** + * Warning: ignored (always return 0). + */ + public int getActiveLoggerCount() { + return 0; + } + +} diff --git a/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java b/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java index 63d0d44a..d78826b4 100644 --- a/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java +++ b/daemon/src/main/java/org/jboss/fuse/mvnd/daemon/Server.java @@ -439,11 +439,8 @@ public class Server implements AutoCloseable, Runnable { int getClassOrder(Message m) { if (m instanceof BuildRequest) { return 0; - } else if (m instanceof BuildEvent) { - BuildEvent be = (BuildEvent) m; - return be.getType() == Type.BuildStopped ? 98 : 1; - } else if (m instanceof BuildMessage) { - return 2; + } else if (m instanceof BuildEvent || m instanceof BuildMessage) { + return 1; } else if (m instanceof BuildException) { return 97; } else if (m == STOP) { diff --git a/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/AbstractLoggingSpy.java b/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/AbstractLoggingSpy.java index 6d3277ef..97b6ae4e 100644 --- a/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/AbstractLoggingSpy.java +++ b/daemon/src/main/java/org/jboss/fuse/mvnd/logging/smart/AbstractLoggingSpy.java @@ -17,6 +17,7 @@ package org.jboss.fuse.mvnd.logging.smart; import java.util.List; import java.util.Map; +import org.apache.maven.cli.logging.Slf4jLogger; import org.apache.maven.eventspy.AbstractEventSpy; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.plugin.MojoExecution; @@ -115,19 +116,24 @@ public abstract class AbstractLoggingSpy extends AbstractEventSpy { } protected void onStopProject(String projectId, String display) { + MDC.put(KEY_PROJECT_ID, projectId); update(); MDC.remove(KEY_PROJECT_ID); } protected void onStartMojo(String projectId, String display) { + Slf4jLogger.setCurrentProject(projectId); + MDC.put(KEY_PROJECT_ID, projectId); update(); } protected void onStopMojo(String projectId, String display) { + MDC.put(KEY_PROJECT_ID, projectId); update(); } protected void onProjectLog(String projectId, String message) { + MDC.put(KEY_PROJECT_ID, projectId); update(); }