Skip to content

Commit

Permalink
152 - Adds option to redirect program output of exec:exec to the mave…
Browse files Browse the repository at this point in the history
…n logger.
  • Loading branch information
hankolerd committed May 26, 2020
1 parent dbe2c73 commit 7935062
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 0 deletions.
112 changes: 112 additions & 0 deletions src/main/java/org/codehaus/mojo/exec/ExecMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <i>Info</i> and <i>Error</i> level logs, respectively. If not enabled the
* traditional behavior of program output being directed to standard System.out
* and System.err is used.<br>
* <br>
* NOTE: When enabled, to log the program standard out as Maven <i>Debug</i> level instead of
* <i>Info</i> level use {@code exec.quietLogs=true}. <br>
* <br>
* This option can be extremely helpful when combined with multithreaded builds
* for two reasons:<br>
* <ul>
* <li>Program output is suffixed with the owning thread name, making it easier
* to trace execution of a specific projects build thread.</li>
* <li>Program output will not get jumbled with other maven log messages.</li>
* </ul>
*
* For Example, if using {@code exec:exec} to run a script to echo a count from
* 1 to 100 as:
*
* <pre>
* for i in {1..100}
* do
* echo "${project.artifactId} - $i"
* done
* </pre>
*
* When this script is run multi-threaded on two modules, {@code module1} and
* {@code module2}, you might get output such as:
*
* <pre>
* [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
* ...
* </pre>
*
* With this flag enabled, the output will instead come something similar to:
*
* <pre>
* ...
* [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
* ...
* </pre>
*
* NOTE 1: To show the thread in the Maven log, configure the Maven
* installations <i>conf/logging/simplelogger.properties</i> option:
* {@code org.slf4j.simpleLogger.showThreadName=true}<br>
*
* 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;

/**
* <p>
* A list of arguments passed to the {@code executable}, which should be of type <code>&lt;argument&gt;</code> or
Expand Down Expand Up @@ -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<String> mavenOutRedirect = new Invokable<String>()
{

@Override
public void accept(String logMessage)
{
if (quietLogs)
{
getLog().debug(logSuffix + logMessage);
}
else
{
getLog().info(logSuffix + logMessage);
}
}
};
Invokable<String> mavenErrRedirect = new Invokable<String>()
{

@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 );
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/org/codehaus/mojo/exec/Invokable.java
Original file line number Diff line number Diff line change
@@ -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 <T> - The type of object that will be acted upon.
* @since 3.0.0
*/
interface Invokable<T> {

/**
* Takes some object and acts upon it.
*
* @param object - The object that will be taken
*/
void accept(T object);
}
61 changes: 61 additions & 0 deletions src/main/java/org/codehaus/mojo/exec/LineRedirectOutputStream.java
Original file line number Diff line number Diff line change
@@ -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<String> linePrinter;

public LineRedirectOutputStream(Invokable<String> 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();
}
}

0 comments on commit 7935062

Please sign in to comment.