001package org.apache.maven.cli.event;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Date;
023
024import org.apache.maven.execution.AbstractExecutionListener;
025import org.apache.maven.execution.BuildFailure;
026import org.apache.maven.execution.BuildSuccess;
027import org.apache.maven.execution.BuildSummary;
028import org.apache.maven.execution.ExecutionEvent;
029import org.apache.maven.execution.MavenExecutionResult;
030import org.apache.maven.execution.MavenSession;
031import org.apache.maven.plugin.MojoExecution;
032import org.apache.maven.project.MavenProject;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * Logs execution events to logger, eventually user-supplied.
038 *
039 * @author Benjamin Bentmann
040 */
041public class ExecutionEventLogger
042    extends AbstractExecutionListener
043{
044    private final Logger logger;
045
046    private static final int LINE_LENGTH = 72;
047
048    public ExecutionEventLogger()
049    {
050        logger = LoggerFactory.getLogger( ExecutionEventLogger.class );
051    }
052
053    // TODO should we deprecate?
054    public ExecutionEventLogger( Logger logger )
055    {
056        if ( logger == null )
057        {
058            throw new IllegalArgumentException( "logger missing" );
059        }
060
061        this.logger = logger;
062    }
063
064    private static String chars( char c, int count )
065    {
066        StringBuilder buffer = new StringBuilder( count );
067
068        for ( int i = count; i > 0; i-- )
069        {
070            buffer.append( c );
071        }
072
073        return buffer.toString();
074    }
075
076    private static String getFormattedTime( long time )
077    {
078        // NOTE: DateFormat is not suitable to format timespans of 24h+
079
080        long h = time / ( 60 * 60 * 1000 );
081        long m = ( time - h * 60 * 60 * 1000 ) / ( 60 * 1000 );
082        long s = ( time - h * 60 * 60 * 1000 - m * 60 * 1000 ) / 1000;
083        long ms = time % 1000;
084
085        String format;
086        if ( h > 0 )
087        {
088            format = "%1$d:%2$02d:%3$02d.%4$03ds";
089        }
090        else if ( m > 0 )
091        {
092            format = "%2$d:%3$02d.%4$03ds";
093        }
094        else
095        {
096            format = "%3$d.%4$03ds";
097        }
098
099        return String.format( format, h, m, s, ms );
100    }
101
102    @Override
103    public void projectDiscoveryStarted( ExecutionEvent event )
104    {
105        if ( logger.isInfoEnabled() )
106        {
107            logger.info( "Scanning for projects..." );
108        }
109    }
110
111    @Override
112    public void sessionStarted( ExecutionEvent event )
113    {
114        if ( logger.isInfoEnabled() && event.getSession().getProjects().size() > 1 )
115        {
116            logger.info( chars( '-', LINE_LENGTH ) );
117
118            logger.info( "Reactor Build Order:" );
119
120            logger.info( "" );
121
122            for ( MavenProject project : event.getSession().getProjects() )
123            {
124                logger.info( project.getName() );
125            }
126        }
127    }
128
129    @Override
130    public void sessionEnded( ExecutionEvent event )
131    {
132        if ( logger.isInfoEnabled() )
133        {
134            if ( event.getSession().getProjects().size() > 1 )
135            {
136                logReactorSummary( event.getSession() );
137            }
138
139            logResult( event.getSession() );
140
141            logStats( event.getSession() );
142
143            logger.info( chars( '-', LINE_LENGTH ) );
144        }
145    }
146
147    private void logReactorSummary( MavenSession session )
148    {
149        logger.info( chars( '-', LINE_LENGTH ) );
150
151        logger.info( "Reactor Summary:" );
152
153        logger.info( "" );
154
155        MavenExecutionResult result = session.getResult();
156
157        for ( MavenProject project : session.getProjects() )
158        {
159            StringBuilder buffer = new StringBuilder( 128 );
160
161            buffer.append( project.getName() );
162
163            buffer.append( ' ' );
164            while ( buffer.length() < LINE_LENGTH - 21 )
165            {
166                buffer.append( '.' );
167            }
168            buffer.append( ' ' );
169
170            BuildSummary buildSummary = result.getBuildSummary( project );
171
172            if ( buildSummary == null )
173            {
174                buffer.append( "SKIPPED" );
175            }
176            else if ( buildSummary instanceof BuildSuccess )
177            {
178                buffer.append( "SUCCESS [" );
179                buffer.append( getFormattedTime( buildSummary.getTime() ) );
180                buffer.append( "]" );
181            }
182            else if ( buildSummary instanceof BuildFailure )
183            {
184                buffer.append( "FAILURE [" );
185                buffer.append( getFormattedTime( buildSummary.getTime() ) );
186                buffer.append( "]" );
187            }
188
189            logger.info( buffer.toString() );
190        }
191    }
192
193    private void logResult( MavenSession session )
194    {
195        logger.info( chars( '-', LINE_LENGTH ) );
196
197        if ( session.getResult().hasExceptions() )
198        {
199            logger.info( "BUILD FAILURE" );
200        }
201        else
202        {
203            logger.info( "BUILD SUCCESS" );
204        }
205    }
206
207    private void logStats( MavenSession session )
208    {
209        logger.info( chars( '-', LINE_LENGTH ) );
210
211        Date finish = new Date();
212
213        long time = finish.getTime() - session.getRequest().getStartTime().getTime();
214
215        String wallClock = session.getRequest().isThreadConfigurationPresent() ? " (Wall Clock)" : "";
216
217        logger.info( "Total time: " + getFormattedTime( time ) + wallClock );
218
219        logger.info( "Finished at: " + finish );
220
221        System.gc();
222
223        Runtime r = Runtime.getRuntime();
224
225        long MB = 1024 * 1024;
226
227        logger.info( "Final Memory: " + ( r.totalMemory() - r.freeMemory() ) / MB + "M/" + r.totalMemory() / MB + "M" );
228    }
229
230    @Override
231    public void projectSkipped( ExecutionEvent event )
232    {
233        if ( logger.isInfoEnabled() )
234        {
235            logger.info( chars( ' ', LINE_LENGTH ) );
236            logger.info( chars( '-', LINE_LENGTH ) );
237
238            logger.info( "Skipping " + event.getProject().getName() );
239            logger.info( "This project has been banned from the build due to previous failures." );
240
241            logger.info( chars( '-', LINE_LENGTH ) );
242        }
243    }
244
245    @Override
246    public void projectStarted( ExecutionEvent event )
247    {
248        if ( logger.isInfoEnabled() )
249        {
250            logger.info( chars( ' ', LINE_LENGTH ) );
251            logger.info( chars( '-', LINE_LENGTH ) );
252
253            logger.info( "Building " + event.getProject().getName() + " " + event.getProject().getVersion() );
254
255            logger.info( chars( '-', LINE_LENGTH ) );
256        }
257    }
258
259    @Override
260    public void mojoSkipped( ExecutionEvent event )
261    {
262        if ( logger.isWarnEnabled() )
263        {
264            logger.warn( "Goal " + event.getMojoExecution().getGoal()
265                + " requires online mode for execution but Maven is currently offline, skipping" );
266        }
267    }
268
269    /**
270     * <pre>--- mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId ---</pre>
271     */
272    @Override
273    public void mojoStarted( ExecutionEvent event )
274    {
275        if ( logger.isInfoEnabled() )
276        {
277            StringBuilder buffer = new StringBuilder( 128 );
278
279            buffer.append( "--- " );
280            append( buffer, event.getMojoExecution() );
281            append( buffer, event.getProject() );
282            buffer.append( " ---" );
283
284            logger.info( "" );
285            logger.info( buffer.toString() );
286        }
287    }
288
289    /**
290     * <pre>>>> mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId >>></pre>
291     */
292    @Override
293    public void forkStarted( ExecutionEvent event )
294    {
295        if ( logger.isInfoEnabled() )
296        {
297            StringBuilder buffer = new StringBuilder( 128 );
298
299            buffer.append( ">>> " );
300            append( buffer, event.getMojoExecution() );
301            append( buffer, event.getProject() );
302            buffer.append( " >>>" );
303
304            logger.info( "" );
305            logger.info( buffer.toString() );
306        }
307    }
308
309    /**
310     * <pre>&lt;&lt;&lt; mojo-artifactId:version:goal (mojo-executionId) @ project-artifactId &lt;&lt;&lt;</pre>
311     */
312    @Override
313    public void forkSucceeded( ExecutionEvent event )
314    {
315        if ( logger.isInfoEnabled() )
316        {
317            StringBuilder buffer = new StringBuilder( 128 );
318
319            buffer.append( "<<< " );
320            append( buffer, event.getMojoExecution() );
321            append( buffer, event.getProject() );
322            buffer.append( " <<<" );
323
324            logger.info( "" );
325            logger.info( buffer.toString() );
326        }
327    }
328
329    private void append( StringBuilder buffer, MojoExecution me )
330    {
331        buffer.append( me.getArtifactId() ).append( ':' ).append( me.getVersion() );
332        buffer.append( ':' ).append( me.getGoal() );
333        if ( me.getExecutionId() != null )
334        {
335            buffer.append( " (" ).append( me.getExecutionId() ).append( ')' );
336        }
337    }
338
339    private void append( StringBuilder buffer, MavenProject project )
340    {
341        buffer.append( " @ " ).append( project.getArtifactId() );
342    }
343
344    @Override
345    public void forkedProjectStarted( ExecutionEvent event )
346    {
347        if ( logger.isInfoEnabled() && event.getMojoExecution().getForkedExecutions().size() > 1 )
348        {
349            logger.info( chars( ' ', LINE_LENGTH ) );
350            logger.info( chars( '>', LINE_LENGTH ) );
351
352            logger.info( "Forking " + event.getProject().getName() + " " + event.getProject().getVersion() );
353
354            logger.info( chars( '>', LINE_LENGTH ) );
355        }
356    }
357
358}