blob: 24652d4c187aac3655cc50d9bacbf78ad1ed45ef [file] [log] [blame]
package org.apache.maven.cli.event;
/*
* 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 static org.apache.maven.cli.CLIReportingUtils.formatDuration;
import static org.apache.maven.cli.CLIReportingUtils.formatTimestamp;
import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
import java.util.List;
import java.util.Objects;
import org.apache.maven.execution.AbstractExecutionListener;
import org.apache.maven.execution.BuildFailure;
import org.apache.maven.execution.BuildSuccess;
import org.apache.maven.execution.BuildSummary;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenExecutionResult;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.logwrapper.LogLevelRecorder;
import org.apache.maven.logwrapper.MavenSlf4jWrapperFactory;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.plugin.descriptor.MojoDescriptor;
import org.apache.maven.project.MavenProject;
import org.apache.maven.shared.utils.logging.MessageBuilder;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Logs execution events to logger, eventually user-supplied.
*
* @author Benjamin Bentmann
*/
public class ExecutionEventLogger extends AbstractExecutionListener
{
private final Logger logger;
private static final int LINE_LENGTH = 72;
private static final int MAX_PADDED_BUILD_TIME_DURATION_LENGTH = 9;
private static final int MAX_PROJECT_NAME_LENGTH = 52;
private int totalProjects;
private volatile int currentVisitedProjectCount;
public ExecutionEventLogger()
{
logger = LoggerFactory.getLogger( ExecutionEventLogger.class );
}
// TODO should we deprecate?
public ExecutionEventLogger( Logger logger )
{
this.logger = Objects.requireNonNull( logger, "logger cannot be null" );
}
private static String chars( char c, int count )
{
StringBuilder buffer = new StringBuilder( count );
for ( int i = count; i > 0; i-- )
{
buffer.append( c );
}
return buffer.toString();
}
private void infoLine( char c )
{
infoMain( chars( c, LINE_LENGTH ) );
}
private void infoMain( String msg )
{
logger.info( buffer().strong( msg ).toString() );
}
@Override
public void projectDiscoveryStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( "Scanning for projects..." );
}
}
@Override
public void sessionStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() && event.getSession().getProjects().size() > 1 )
{
infoLine( '-' );
infoMain( "Reactor Build Order:" );
logger.info( "" );
final List<MavenProject> projects = event.getSession().getProjects();
for ( MavenProject project : projects )
{
int len = LINE_LENGTH - project.getName().length() - project.getPackaging().length() - 2;
logger.info( "{}{}[{}]",
project.getName(), chars( ' ', ( len > 0 ) ? len : 1 ), project.getPackaging() );
}
totalProjects = projects.size();
}
}
@Override
public void sessionEnded( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
if ( event.getSession().getProjects().size() > 1 )
{
logReactorSummary( event.getSession() );
}
ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
if ( iLoggerFactory instanceof MavenSlf4jWrapperFactory )
{
MavenSlf4jWrapperFactory loggerFactory = (MavenSlf4jWrapperFactory) iLoggerFactory;
loggerFactory.getLogLevelRecorder()
.filter( LogLevelRecorder::metThreshold )
.ifPresent( recorder ->
event.getSession().getResult().addException( new Exception(
"Build failed due to log statements with a higher severity than allowed. "
+ "Fix the logged issues or remove flag --fail-on-severity (-fos)." ) )
);
}
logResult( event.getSession() );
logStats( event.getSession() );
infoLine( '-' );
}
}
private boolean isSingleVersionedReactor( MavenSession session )
{
boolean result = true;
MavenProject topProject = session.getTopLevelProject();
List<MavenProject> sortedProjects = session.getProjectDependencyGraph().getSortedProjects();
for ( MavenProject mavenProject : sortedProjects )
{
if ( !topProject.getVersion().equals( mavenProject.getVersion() ) )
{
result = false;
break;
}
}
return result;
}
private void logReactorSummary( MavenSession session )
{
boolean isSingleVersion = isSingleVersionedReactor( session );
infoLine( '-' );
StringBuilder summary = new StringBuilder( "Reactor Summary" );
if ( isSingleVersion )
{
summary.append( " for " );
summary.append( session.getTopLevelProject().getName() );
summary.append( " " );
summary.append( session.getTopLevelProject().getVersion() );
}
summary.append( ":" );
infoMain( summary.toString() );
logger.info( "" );
MavenExecutionResult result = session.getResult();
List<MavenProject> projects = session.getProjects();
for ( MavenProject project : projects )
{
StringBuilder buffer = new StringBuilder( 128 );
buffer.append( project.getName() );
buffer.append( ' ' );
if ( !isSingleVersion )
{
buffer.append( project.getVersion() );
buffer.append( ' ' );
}
if ( buffer.length() <= MAX_PROJECT_NAME_LENGTH )
{
while ( buffer.length() < MAX_PROJECT_NAME_LENGTH )
{
buffer.append( '.' );
}
buffer.append( ' ' );
}
BuildSummary buildSummary = result.getBuildSummary( project );
if ( buildSummary == null )
{
buffer.append( buffer().warning( "SKIPPED" ) );
}
else if ( buildSummary instanceof BuildSuccess )
{
buffer.append( buffer().success( "SUCCESS" ) );
buffer.append( " [" );
String buildTimeDuration = formatDuration( buildSummary.getTime() );
int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
if ( padSize > 0 )
{
buffer.append( chars( ' ', padSize ) );
}
buffer.append( buildTimeDuration );
buffer.append( ']' );
}
else if ( buildSummary instanceof BuildFailure )
{
buffer.append( buffer().failure( "FAILURE" ) );
buffer.append( " [" );
String buildTimeDuration = formatDuration( buildSummary.getTime() );
int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
if ( padSize > 0 )
{
buffer.append( chars( ' ', padSize ) );
}
buffer.append( buildTimeDuration );
buffer.append( ']' );
}
logger.info( buffer.toString() );
}
}
private void logResult( MavenSession session )
{
infoLine( '-' );
MessageBuilder buffer = buffer();
if ( session.getResult().hasExceptions() )
{
buffer.failure( "BUILD FAILURE" );
}
else
{
buffer.success( "BUILD SUCCESS" );
}
logger.info( buffer.toString() );
}
private void logStats( MavenSession session )
{
infoLine( '-' );
long finish = System.currentTimeMillis();
long time = finish - session.getRequest().getStartTime().getTime();
String wallClock = session.getRequest().getDegreeOfConcurrency() > 1 ? " (Wall Clock)" : "";
logger.info( "Total time: {}{}", formatDuration( time ), wallClock );
logger.info( "Finished at: {}", formatTimestamp( finish ) );
}
@Override
public void projectSkipped( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( "" );
infoLine( '-' );
infoMain( "Skipping " + event.getProject().getName() );
logger.info( "This project has been banned from the build due to previous failures." );
infoLine( '-' );
}
}
@Override
public void projectStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
MavenProject project = event.getProject();
logger.info( "" );
// -------< groupId:artifactId >-------
String projectKey = project.getGroupId() + ':' + project.getArtifactId();
final String preHeader = "--< ";
final String postHeader = " >--";
final int headerLen = preHeader.length() + projectKey.length() + postHeader.length();
String prefix = chars( '-', Math.max( 0, ( LINE_LENGTH - headerLen ) / 2 ) ) + preHeader;
String suffix = postHeader + chars( '-',
Math.max( 0, LINE_LENGTH - headerLen - prefix.length() + preHeader.length() ) );
logger.info( buffer().strong( prefix ).project( projectKey ).strong( suffix ).toString() );
// Building Project Name Version [i/n]
String building = "Building " + event.getProject().getName() + " " + event.getProject().getVersion();
if ( totalProjects <= 1 )
{
infoMain( building );
}
else
{
// display progress [i/n]
int number;
synchronized ( this )
{
number = ++currentVisitedProjectCount;
}
String progress = " [" + number + '/' + totalProjects + ']';
int pad = LINE_LENGTH - building.length() - progress.length();
infoMain( building + ( ( pad > 0 ) ? chars( ' ', pad ) : "" ) + progress );
}
// ----------[ packaging ]----------
prefix = chars( '-', Math.max( 0, ( LINE_LENGTH - project.getPackaging().length() - 4 ) / 2 ) );
suffix = chars( '-', Math.max( 0, LINE_LENGTH - project.getPackaging().length() - 4 - prefix.length() ) );
infoMain( prefix + "[ " + project.getPackaging() + " ]" + suffix );
}
}
@Override
public void mojoSkipped( ExecutionEvent event )
{
if ( logger.isWarnEnabled() )
{
logger.warn( "Goal '{}' requires online mode for execution but Maven is currently offline, skipping",
event.getMojoExecution().getGoal() );
}
}
/**
* <pre>--- mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId ---</pre>
*/
@Override
public void mojoStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( "" );
MessageBuilder buffer = buffer().strong( "--- " );
append( buffer, event.getMojoExecution() );
append( buffer, event.getProject() );
buffer.strong( " ---" );
logger.info( buffer.toString() );
}
}
// CHECKSTYLE_OFF: LineLength
/**
* <pre>&gt;&gt;&gt; mojo-artifactId:version:goal (mojo-executionId) &gt; :forked-goal @ project-artifactId &gt;&gt;&gt;</pre>
* <pre>&gt;&gt;&gt; mojo-artifactId:version:goal (mojo-executionId) &gt; [lifecycle]phase @ project-artifactId &gt;&gt;&gt;</pre>
*/
// CHECKSTYLE_ON: LineLength
@Override
public void forkStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( "" );
MessageBuilder buffer = buffer().strong( ">>> " );
append( buffer, event.getMojoExecution() );
buffer.strong( " > " );
appendForkInfo( buffer, event.getMojoExecution().getMojoDescriptor() );
append( buffer, event.getProject() );
buffer.strong( " >>>" );
logger.info( buffer.toString() );
}
}
// CHECKSTYLE_OFF: LineLength
/**
* <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) &lt; :forked-goal @ project-artifactId &lt;&lt;&lt;</pre>
* <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) &lt; [lifecycle]phase @ project-artifactId &lt;&lt;&lt;</pre>
*/
// CHECKSTYLE_ON: LineLength
@Override
public void forkSucceeded( ExecutionEvent event )
{
if ( logger.isInfoEnabled() )
{
logger.info( "" );
MessageBuilder buffer = buffer().strong( "<<< " );
append( buffer, event.getMojoExecution() );
buffer.strong( " < " );
appendForkInfo( buffer, event.getMojoExecution().getMojoDescriptor() );
append( buffer, event.getProject() );
buffer.strong( " <<<" );
logger.info( buffer.toString() );
logger.info( "" );
}
}
private void append( MessageBuilder buffer, MojoExecution me )
{
buffer.mojo( me.getArtifactId() + ':' + me.getVersion() + ':' + me.getGoal() );
if ( me.getExecutionId() != null )
{
buffer.a( ' ' ).strong( '(' + me.getExecutionId() + ')' );
}
}
private void appendForkInfo( MessageBuilder buffer, MojoDescriptor md )
{
StringBuilder buff = new StringBuilder();
if ( StringUtils.isNotEmpty( md.getExecutePhase() ) )
{
// forked phase
if ( StringUtils.isNotEmpty( md.getExecuteLifecycle() ) )
{
buff.append( '[' );
buff.append( md.getExecuteLifecycle() );
buff.append( ']' );
}
buff.append( md.getExecutePhase() );
}
else
{
// forked goal
buff.append( ':' );
buff.append( md.getExecuteGoal() );
}
buffer.strong( buff.toString() );
}
private void append( MessageBuilder buffer, MavenProject project )
{
buffer.a( " @ " ).project( project.getArtifactId() );
}
@Override
public void forkedProjectStarted( ExecutionEvent event )
{
if ( logger.isInfoEnabled() && event.getMojoExecution().getForkedExecutions().size() > 1 )
{
logger.info( "" );
infoLine( '>' );
infoMain( "Forking " + event.getProject().getName() + " " + event.getProject().getVersion() );
infoLine( '>' );
}
}
}