From 7935062de951ab332cd50fbc05d8ab2070d2b205 Mon Sep 17 00:00:00 2001 From: Daniel Johnson Date: Thu, 21 May 2020 12:13:37 -0700 Subject: [PATCH] 152 - Adds option to redirect program output of exec:exec to the maven logger. --- .../java/org/codehaus/mojo/exec/ExecMojo.java | 112 ++++++++++++++++++ .../org/codehaus/mojo/exec/Invokable.java | 37 ++++++ .../mojo/exec/LineRedirectOutputStream.java | 61 ++++++++++ 3 files changed, 210 insertions(+) create mode 100644 src/main/java/org/codehaus/mojo/exec/Invokable.java create mode 100644 src/main/java/org/codehaus/mojo/exec/LineRedirectOutputStream.java diff --git a/src/main/java/org/codehaus/mojo/exec/ExecMojo.java b/src/main/java/org/codehaus/mojo/exec/ExecMojo.java index 533ed430..439d875a 100644 --- a/src/main/java/org/codehaus/mojo/exec/ExecMojo.java +++ b/src/main/java/org/codehaus/mojo/exec/ExecMojo.java @@ -148,6 +148,82 @@ public class ExecMojo @Parameter( property = "exec.outputFile" ) private File outputFile; + /** + * When enabled, program standard and error output will be redirected to the + * Maven logger as Info and Error level logs, respectively. If not enabled the + * traditional behavior of program output being directed to standard System.out + * and System.err is used.
+ *
+ * NOTE: When enabled, to log the program standard out as Maven Debug level instead of + * Info level use {@code exec.quietLogs=true}.
+ *
+ * This option can be extremely helpful when combined with multithreaded builds + * for two reasons:
+ * + * + * For Example, if using {@code exec:exec} to run a script to echo a count from + * 1 to 100 as: + * + *
+     * for i in {1..100}
+     * do
+     *   echo "${project.artifactId} - $i"
+     * done
+     * 
+ * + * When this script is run multi-threaded on two modules, {@code module1} and + * {@code module2}, you might get output such as: + * + *
+     * [BuilderThread 1] [INFO] --- exec-maven-plugin:1.6.0:exec (test) @ module1 ---
+     * [BuilderThread 2] [INFO] --- exec-maven-plugin:1.6.0:exec (test) @ module2 ---
+     * ...
+     * module2 - 98
+     * modu
+     * module1 - 97
+     * module1 -
+     * le2 - 9899
+     * ...
+     * 
+ * + * With this flag enabled, the output will instead come something similar to: + * + *
+     * ...
+     * [Exec Stream Pumper] [INFO] [BuilderThread 2] module2 - 98
+     * [Exec Stream Pumper] [INFO] [BuilderThread 1] module1 - 97
+     * [Exec Stream Pumper] [INFO] [BuilderThread 1] module1 - 98
+     * [Exec Stream Pumper] [INFO] [BuilderThread 2] module2 - 99
+     * ...
+     * 
+ * + * NOTE 1: To show the thread in the Maven log, configure the Maven + * installations conf/logging/simplelogger.properties option: + * {@code org.slf4j.simpleLogger.showThreadName=true}
+ * + * NOTE 2: This option is ignored when {@code exec.outputFile} is specified. + * + * @since 3.0.0 + * @see java.lang.System#err + * @see java.lang.System#in + */ + @Parameter( property = "exec.useMavenLogger", defaultValue = "false" ) + private boolean useMavenLogger; + + /** + * When combined with {@code exec.useMavenLogger=true}, prints all executed + * program output at debug level instead of the default info level to the Maven + * logger. + * + * @since 3.0.0 + */ + @Parameter( property = "exec.quietLogs", defaultValue = "false" ) + private boolean quietLogs; + /** *

* A list of arguments passed to the {@code executable}, which should be of type <argument> or @@ -340,6 +416,42 @@ else if ( !StringUtils.isEmpty( argsProp ) ) IOUtil.close( outputStream ); } } + else if (useMavenLogger) + { + getLog().debug("Will redirect program output to Maven logger"); + final String parentThreadName = Thread.currentThread().getName(); + final String logSuffix = "[" + parentThreadName + "] "; + Invokable mavenOutRedirect = new Invokable() + { + + @Override + public void accept(String logMessage) + { + if (quietLogs) + { + getLog().debug(logSuffix + logMessage); + } + else + { + getLog().info(logSuffix + logMessage); + } + } + }; + Invokable mavenErrRedirect = new Invokable() + { + + @Override + public void accept(String logMessage) + { + getLog().error(logSuffix + logMessage); + } + }; + + try (OutputStream out = new LineRedirectOutputStream(mavenOutRedirect); + OutputStream err = new LineRedirectOutputStream(mavenErrRedirect)) { + resultCode = executeCommandLine(exec, commandLine, enviro, out, err); + } + } else { resultCode = executeCommandLine( exec, commandLine, enviro, System.out, System.err ); diff --git a/src/main/java/org/codehaus/mojo/exec/Invokable.java b/src/main/java/org/codehaus/mojo/exec/Invokable.java new file mode 100644 index 00000000..466fd4b2 --- /dev/null +++ b/src/main/java/org/codehaus/mojo/exec/Invokable.java @@ -0,0 +1,37 @@ +package org.codehaus.mojo.exec; + +/* + * 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. + */ + +/** + * A simple Java 7 pseudo-class based on the Java 8 {@code Consumer} class. Can and should be + * deleted once this project moves to a minimum execution environment of Java 8+. + * + * @param - The type of object that will be acted upon. + * @since 3.0.0 + */ +interface Invokable { + + /** + * Takes some object and acts upon it. + * + * @param object - The object that will be taken + */ + void accept(T object); +} diff --git a/src/main/java/org/codehaus/mojo/exec/LineRedirectOutputStream.java b/src/main/java/org/codehaus/mojo/exec/LineRedirectOutputStream.java new file mode 100644 index 00000000..4cfc52f3 --- /dev/null +++ b/src/main/java/org/codehaus/mojo/exec/LineRedirectOutputStream.java @@ -0,0 +1,61 @@ +package org.codehaus.mojo.exec; + +/* + * 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 java.io.OutputStream; + +/** + * An output stream that captures one line of output at a time, and then + * redirects that line to some {@link Invokable} to act upon as it pleases. This + * class is not thread safe and expects to have only one active writer consuming + * it at any given time. + * + * @since 3.0.0 + */ +class LineRedirectOutputStream extends OutputStream { + + private StringBuilder currentLine = new StringBuilder(); + private final Invokable linePrinter; + + public LineRedirectOutputStream(Invokable linePrinter) { + this.linePrinter = linePrinter; + } + + @Override + public void write(final int b) { + if ((char) b == '\n') { + printAndReset(); + return; + } + currentLine.append((char) b); + } + + @Override + public void flush() { + if (currentLine.length() > 0) { + printAndReset(); + } + } + + private void printAndReset() { + linePrinter.accept(currentLine.toString()); + currentLine = new StringBuilder(); + } +}