Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolves #152 - Adds option to redirect program output of exec:exec to the maven logger. #153

Merged
merged 1 commit into from
May 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 + "] ";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more of a prefix instead of a suffix… but it always says [main] for me and as such is redundant and unnecessary in the output (makes it unnecessarily wider)…

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Jenkins, they are [pool-1-thread-1 for channel], making the output definitely too wide to read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mirabilos Good point about prefix instead of suffix, my bad.
As for always getting [main], were you running Maven multi-threaded on a reactor that is bound to have 2 projects running in parallel with this plugin? This option adds no value for single threaded builds, but can become an aid in tracing program output in large reactors with lots of threads. For example 1000+ projects running with 50+ threads.

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