| 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.io.File; |
| 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.apache.maven.shared.utils.logging.MessageUtils; |
| 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 MAX_LOG_PREFIX_SIZE = 8; // "[ERROR] " |
| private static final int PROJECT_STATUS_SUFFIX_SIZE = 20; // "SUCCESS [ 0.000 s]" |
| private static final int MIN_TERMINAL_WIDTH = 60; |
| private static final int DEFAULT_TERMINAL_WIDTH = 80; |
| private static final int MAX_TERMINAL_WIDTH = 130; |
| private static final int MAX_PADDED_BUILD_TIME_DURATION_LENGTH = 9; |
| |
| private final int terminalWidth; |
| private final int lineLength; |
| private final int maxProjectNameLength; |
| private int totalProjects; |
| private volatile int currentVisitedProjectCount; |
| |
| public ExecutionEventLogger() |
| { |
| this( LoggerFactory.getLogger( ExecutionEventLogger.class ) ); |
| } |
| |
| public ExecutionEventLogger( int terminalWidth ) |
| { |
| this( LoggerFactory.getLogger( ExecutionEventLogger.class ), terminalWidth ); |
| } |
| |
| public ExecutionEventLogger( Logger logger ) |
| { |
| this( logger, MessageUtils.getTerminalWidth() ); |
| } |
| |
| public ExecutionEventLogger( Logger logger, int terminalWidth ) |
| { |
| this.logger = Objects.requireNonNull( logger, "logger cannot be null" ); |
| this.terminalWidth = Math.min( MAX_TERMINAL_WIDTH, |
| Math.max( terminalWidth < 0 ? DEFAULT_TERMINAL_WIDTH : terminalWidth, |
| MIN_TERMINAL_WIDTH ) ); |
| this.lineLength = this.terminalWidth - MAX_LOG_PREFIX_SIZE; |
| this.maxProjectNameLength = this.lineLength - PROJECT_STATUS_SUFFIX_SIZE; |
| } |
| |
| 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, lineLength ) ); |
| } |
| |
| 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 = lineLength - 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() <= maxProjectNameLength ) |
| { |
| while ( buffer.length() < maxProjectNameLength ) |
| { |
| 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, ( lineLength - headerLen ) / 2 ) ) + preHeader; |
| |
| String suffix = postHeader + chars( '-', |
| Math.max( 0, lineLength - 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 = lineLength - building.length() - progress.length(); |
| |
| infoMain( building + ( ( pad > 0 ) ? chars( ' ', pad ) : "" ) + progress ); |
| } |
| |
| // path to pom.xml |
| File currentPom = project.getFile(); |
| if ( currentPom != null ) |
| { |
| MavenSession session = event.getSession(); |
| File rootBasedir = session.getTopLevelProject().getBasedir(); |
| logger.info( " from " + rootBasedir.toPath().relativize( currentPom.toPath() ) ); |
| } |
| |
| // ----------[ packaging ]---------- |
| prefix = chars( '-', Math.max( 0, ( lineLength - project.getPackaging().length() - 4 ) / 2 ) ); |
| suffix = chars( '-', Math.max( 0, lineLength - 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>>>> mojo-artifactId:version:goal (mojo-executionId) > :forked-goal @ project-artifactId >>></pre> |
| * <pre>>>> mojo-artifactId:version:goal (mojo-executionId) > [lifecycle]phase @ project-artifactId >>></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><<< mojo-artifactId:version:goal (mojo-executionId) < :forked-goal @ project-artifactId <<<</pre> |
| * <pre><<< mojo-artifactId:version:goal (mojo-executionId) < [lifecycle]phase @ project-artifactId <<<</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( '>' ); |
| } |
| } |
| } |