View Javadoc
1   package org.apache.maven.cli.event;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.apache.maven.cli.CLIReportingUtils.formatDuration;
23  import static org.apache.maven.cli.CLIReportingUtils.formatTimestamp;
24  import static org.apache.maven.shared.utils.logging.MessageUtils.buffer;
25  
26  import java.util.List;
27  
28  import org.apache.commons.lang3.Validate;
29  import org.apache.maven.execution.AbstractExecutionListener;
30  import org.apache.maven.execution.BuildFailure;
31  import org.apache.maven.execution.BuildSuccess;
32  import org.apache.maven.execution.BuildSummary;
33  import org.apache.maven.execution.ExecutionEvent;
34  import org.apache.maven.execution.MavenExecutionResult;
35  import org.apache.maven.execution.MavenSession;
36  import org.apache.maven.plugin.MojoExecution;
37  import org.apache.maven.plugin.descriptor.MojoDescriptor;
38  import org.apache.maven.project.MavenProject;
39  import org.apache.maven.shared.utils.logging.MessageBuilder;
40  import org.codehaus.plexus.util.StringUtils;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * Logs execution events to logger, eventually user-supplied.
46   *
47   * @author Benjamin Bentmann
48   */
49  public class ExecutionEventLogger
50      extends AbstractExecutionListener
51  {
52      private final Logger logger;
53  
54      private static final int LINE_LENGTH = 72;
55      private static final int MAX_PADDED_BUILD_TIME_DURATION_LENGTH = 9;
56      private static final int MAX_PROJECT_NAME_LENGTH = 52;
57  
58      private int totalProjects;
59      private volatile int currentVisitedProjectCount;
60  
61      public ExecutionEventLogger()
62      {
63          logger = LoggerFactory.getLogger( ExecutionEventLogger.class );
64      }
65  
66      // TODO should we deprecate?
67      public ExecutionEventLogger( Logger logger )
68      {
69          this.logger = Validate.notNull( logger, "logger cannot be null" );
70      }
71  
72      private static String chars( char c, int count )
73      {
74          StringBuilder buffer = new StringBuilder( count );
75  
76          for ( int i = count; i > 0; i-- )
77          {
78              buffer.append( c );
79          }
80  
81          return buffer.toString();
82      }
83  
84      private void infoLine( char c )
85      {
86          infoMain( chars( c, LINE_LENGTH ) );
87      }
88  
89      private void infoMain( String msg )
90      {
91          logger.info( buffer().strong( msg ).toString() );
92      }
93  
94      @Override
95      public void projectDiscoveryStarted( ExecutionEvent event )
96      {
97          if ( logger.isInfoEnabled() )
98          {
99              logger.info( "Scanning for projects..." );
100         }
101     }
102 
103     @Override
104     public void sessionStarted( ExecutionEvent event )
105     {
106         if ( logger.isInfoEnabled() && event.getSession().getProjects().size() > 1 )
107         {
108             infoLine( '-' );
109 
110             infoMain( "Reactor Build Order:" );
111 
112             logger.info( "" );
113 
114             final List<MavenProject> projects = event.getSession().getProjects();
115             for ( MavenProject project : projects )
116             {
117                 int len = LINE_LENGTH - project.getName().length() - project.getPackaging().length() - 2;
118                 logger.info( project.getName() + chars( ' ', ( len > 0 ) ? len : 1 ) + '[' + project.getPackaging()
119                     + ']' );
120             }
121 
122             totalProjects = projects.size();
123         }
124     }
125 
126     @Override
127     public void sessionEnded( ExecutionEvent event )
128     {
129         if ( logger.isInfoEnabled() )
130         {
131             if ( event.getSession().getProjects().size() > 1 )
132             {
133                 logReactorSummary( event.getSession() );
134             }
135 
136             logResult( event.getSession() );
137 
138             logStats( event.getSession() );
139 
140             infoLine( '-' );
141         }
142     }
143 
144     private void logReactorSummary( MavenSession session )
145     {
146         infoLine( '-' );
147 
148         infoMain( "Reactor Summary:" );
149 
150         logger.info( "" );
151 
152         MavenExecutionResult result = session.getResult();
153 
154         List<MavenProject> projects = session.getProjects();
155         MavenProject lastProject = projects.get( projects.size() - 1 );
156         MavenProject topProject = session.getTopLevelProject();
157 
158         for ( MavenProject project : projects )
159         {
160             StringBuilder buffer = new StringBuilder( 128 );
161 
162             buffer.append( project.getName() );
163             buffer.append( ' ' );
164 
165             if ( topProject.equals( project ) || lastProject.equals( project )
166                 || !topProject.getVersion().equals( project.getVersion() ) )
167             {
168                 buffer.append( project.getVersion() );
169                 buffer.append( ' ' );
170             }
171 
172             if ( buffer.length() <= MAX_PROJECT_NAME_LENGTH )
173             {
174                 while ( buffer.length() < MAX_PROJECT_NAME_LENGTH )
175                 {
176                     buffer.append( '.' );
177                 }
178                 buffer.append( ' ' );
179             }
180 
181             BuildSummary buildSummary = result.getBuildSummary( project );
182 
183             if ( buildSummary == null )
184             {
185                 buffer.append( buffer().warning( "SKIPPED" ) );
186             }
187             else if ( buildSummary instanceof BuildSuccess )
188             {
189                 buffer.append( buffer().success( "SUCCESS" ) );
190                 buffer.append( " [" );
191                 String buildTimeDuration = formatDuration( buildSummary.getTime() );
192                 int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
193                 if ( padSize > 0 )
194                 {
195                     buffer.append( chars( ' ', padSize ) );
196                 }
197                 buffer.append( buildTimeDuration );
198                 buffer.append( ']' );
199             }
200             else if ( buildSummary instanceof BuildFailure )
201             {
202                 buffer.append( buffer().failure( "FAILURE" ) );
203                 buffer.append( " [" );
204                 String buildTimeDuration = formatDuration( buildSummary.getTime() );
205                 int padSize = MAX_PADDED_BUILD_TIME_DURATION_LENGTH - buildTimeDuration.length();
206                 if ( padSize > 0 )
207                 {
208                     buffer.append( chars( ' ', padSize ) );
209                 }
210                 buffer.append( buildTimeDuration );
211                 buffer.append( ']' );
212             }
213 
214             logger.info( buffer.toString() );
215         }
216     }
217 
218     private void logResult( MavenSession session )
219     {
220         infoLine( '-' );
221         MessageBuilder buffer = buffer();
222 
223         if ( session.getResult().hasExceptions() )
224         {
225             buffer.failure( "BUILD FAILURE" );
226         }
227         else
228         {
229             buffer.success( "BUILD SUCCESS" );
230         }
231         logger.info( buffer.toString() );
232     }
233 
234     private void logStats( MavenSession session )
235     {
236         infoLine( '-' );
237 
238         long finish = System.currentTimeMillis();
239 
240         long time = finish - session.getRequest().getStartTime().getTime();
241 
242         String wallClock = session.getRequest().getDegreeOfConcurrency() > 1 ? " (Wall Clock)" : "";
243 
244         logger.info( "Total time: " + formatDuration( time ) + wallClock );
245 
246         logger.info( "Finished at: " + formatTimestamp( finish ) );
247     }
248 
249     @Override
250     public void projectSkipped( ExecutionEvent event )
251     {
252         if ( logger.isInfoEnabled() )
253         {
254             logger.info( "" );
255             infoLine( '-' );
256 
257             infoMain( "Skipping " + event.getProject().getName() );
258             logger.info( "This project has been banned from the build due to previous failures." );
259 
260             infoLine( '-' );
261         }
262     }
263 
264     @Override
265     public void projectStarted( ExecutionEvent event )
266     {
267         if ( logger.isInfoEnabled() )
268         {
269             MavenProject project = event.getProject();
270 
271             logger.info( "" );
272 
273             // -------< groupId:artifactId >-------
274             String projectKey = project.getGroupId() + ':' + project.getArtifactId();
275             
276             final String preHeader  = "--< ";
277             final String postHeader = " >--";
278 
279             final int headerLen = preHeader.length() + projectKey.length() + postHeader.length();
280 
281             String prefix = chars( '-', Math.max( 0, ( LINE_LENGTH - headerLen ) / 2 ) ) + preHeader;
282 
283             String suffix = postHeader
284                 + chars( '-', Math.max( 0, LINE_LENGTH - headerLen - prefix.length() + preHeader.length() ) );
285 
286             logger.info( buffer().strong( prefix ).project( projectKey ).strong( suffix ).toString() );
287 
288             // Building Project Name Version    [i/n]
289             String building = "Building " + event.getProject().getName() + " " + event.getProject().getVersion();
290 
291             if ( totalProjects <= 1 )
292             {
293                 infoMain( building );
294             }
295             else
296             {
297                 // display progress [i/n]
298                 int number;
299                 synchronized ( this )
300                 {
301                     number = ++currentVisitedProjectCount;
302                 }
303                 String progress = " [" + number + '/' + totalProjects + ']';
304 
305                 int pad = LINE_LENGTH - building.length() - progress.length();
306 
307                 infoMain( building + ( ( pad > 0 ) ? chars( ' ', pad ) : "" ) + progress );
308             }
309 
310             // ----------[ packaging ]----------
311             prefix = chars( '-', Math.max( 0, ( LINE_LENGTH - project.getPackaging().length() - 4 ) / 2 ) );
312             suffix = chars( '-', Math.max( 0, LINE_LENGTH - project.getPackaging().length() - 4 - prefix.length() ) );
313             infoMain( prefix + "[ " + project.getPackaging() + " ]" + suffix );
314         }
315     }
316 
317     @Override
318     public void mojoSkipped( ExecutionEvent event )
319     {
320         if ( logger.isWarnEnabled() )
321         {
322             logger.warn( "Goal " + event.getMojoExecution().getGoal()
323                 + " requires online mode for execution but Maven is currently offline, skipping" );
324         }
325     }
326 
327     /**
328      * <pre>--- mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId ---</pre>
329      */
330     @Override
331     public void mojoStarted( ExecutionEvent event )
332     {
333         if ( logger.isInfoEnabled() )
334         {
335             logger.info( "" );
336 
337             MessageBuilder buffer = buffer().strong( "--- " );
338             append( buffer, event.getMojoExecution() );
339             append( buffer, event.getProject() );
340             buffer.strong( " ---" );
341 
342             logger.info( buffer.toString() );
343         }
344     }
345 
346     // CHECKSTYLE_OFF: LineLength
347     /**
348      * <pre>&gt;&gt;&gt; mojo-artifactId:version:goal (mojo-executionId) &gt; :forked-goal @ project-artifactId &gt;&gt;&gt;</pre>
349      * <pre>&gt;&gt;&gt; mojo-artifactId:version:goal (mojo-executionId) &gt; [lifecycle]phase @ project-artifactId &gt;&gt;&gt;</pre>
350      */
351     // CHECKSTYLE_ON: LineLength
352     @Override
353     public void forkStarted( ExecutionEvent event )
354     {
355         if ( logger.isInfoEnabled() )
356         {
357             logger.info( "" );
358 
359             MessageBuilder buffer = buffer().strong( ">>> " );
360             append( buffer, event.getMojoExecution() );
361             buffer.strong( " > " );
362             appendForkInfo( buffer, event.getMojoExecution().getMojoDescriptor() );
363             append( buffer, event.getProject() );
364             buffer.strong( " >>>" );
365 
366             logger.info( buffer.toString() );
367         }
368     }
369 
370     // CHECKSTYLE_OFF: LineLength
371     /**
372      * <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) &lt; :forked-goal @ project-artifactId &lt;&lt;&lt;</pre>
373      * <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) &lt; [lifecycle]phase @ project-artifactId &lt;&lt;&lt;</pre>
374      */
375     // CHECKSTYLE_ON: LineLength
376     @Override
377     public void forkSucceeded( ExecutionEvent event )
378     {
379         if ( logger.isInfoEnabled() )
380         {
381             logger.info( "" );
382 
383             MessageBuilder buffer = buffer().strong( "<<< " );
384             append( buffer, event.getMojoExecution() );
385             buffer.strong( " < " );
386             appendForkInfo( buffer, event.getMojoExecution().getMojoDescriptor() );
387             append( buffer, event.getProject() );
388             buffer.strong( " <<<" );
389 
390             logger.info( buffer.toString() );
391 
392             logger.info( "" );
393         }
394     }
395 
396     private void append( MessageBuilder buffer, MojoExecution me )
397     {
398         buffer.mojo( me.getArtifactId() + ':' + me.getVersion() + ':' + me.getGoal() );
399         if ( me.getExecutionId() != null )
400         {
401             buffer.a( ' ' ).strong( '(' + me.getExecutionId() + ')' );
402         }
403     }
404 
405     private void appendForkInfo( MessageBuilder buffer, MojoDescriptor md )
406     {
407         StringBuilder buff = new StringBuilder();
408         if ( StringUtils.isNotEmpty( md.getExecutePhase() ) )
409         {
410             // forked phase
411             if ( StringUtils.isNotEmpty( md.getExecuteLifecycle() ) )
412             {
413                 buff.append( '[' );
414                 buff.append( md.getExecuteLifecycle() );
415                 buff.append( ']' );
416             }
417             buff.append( md.getExecutePhase() );
418         }
419         else
420         {
421             // forked goal
422             buff.append( ':' );
423             buff.append( md.getExecuteGoal() );
424         }
425         buffer.strong( buff.toString() );
426     }
427 
428     private void append( MessageBuilder buffer, MavenProject project )
429     {
430         buffer.a( " @ " ).project( project.getArtifactId() );
431     }
432 
433     @Override
434     public void forkedProjectStarted( ExecutionEvent event )
435     {
436         if ( logger.isInfoEnabled() && event.getMojoExecution().getForkedExecutions().size() > 1 )
437         {
438             logger.info( "" );
439             infoLine( '>' );
440 
441             infoMain( "Forking " + event.getProject().getName() + " " + event.getProject().getVersion() );
442 
443             infoLine( '>' );
444         }
445     }
446 }