001package net.sf.logdistiller;
002
003/*
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017import java.io.*;
018import java.util.*;
019
020import org.apache.commons.io.IOUtils;
021import org.apache.commons.lang.ArrayUtils;
022
023/**
024 * The class executing a log distillation, using a log distiller, and processing every log events. A log distillation
025 * can be run either with LogDistiller's {@link net.sf.logdistiller.gui GUI} or with {@link net.sf.logdistiller.ant Ant
026 * tasks}.
027 *
028 * @see LogDistiller
029 */
030public class LogDistillation
031    implements Serializable
032{
033    private static final long serialVersionUID = -9098523382541148543L;
034
035    public final static String LINE_SEPARATOR = System.getProperty( "line.separator" );
036
037    private final LogDistiller ld;
038
039    private final Group[] groups;
040
041    private final Category[] categories;
042
043    private final String content;
044
045    private final File destinationDirectory;
046
047    private final String version = LogDistiller.getVersion();
048
049    private transient Map<String, Group> mapGroups;
050
051    private transient Map<String, Category> mapCategories;
052
053    private long beginTime;
054
055    private long endTime;
056
057    private int eventCount = -1;
058
059    private long eventBytes = 0;
060
061    private int skippedEventCount = 0;
062
063    private long skippedEventBytes = 0;
064
065    private transient LogType.Description logtypeDescription;
066
067    public LogDistillation( LogDistiller ld )
068    {
069        this.ld = ld;
070
071        // init categories
072        LogDistiller.Category[] categoryDefinitions = ld.getCategories();
073        int length = categoryDefinitions.length;
074        categories = new Category[length];
075        mapCategories = new HashMap<String, Category>();
076        for ( int i = 0; i < length; i++ )
077        {
078            categories[i] = new Category( categoryDefinitions[i] );
079            mapCategories.put( categoryDefinitions[i].getId(), categories[i] );
080        }
081
082        // init groups
083        LogDistiller.Group[] groupDefinitions = ld.getGroups();
084        length = groupDefinitions.length;
085        groups = new Group[length];
086        for ( int i = 0; i < length; i++ )
087        {
088            LogDistiller.Group groupDefinition = groupDefinitions[i];
089            LogDistiller.Category categoryDefinition = groupDefinition.getCategory();
090            Category category =
091                ( categoryDefinition == null ) ? null : (Category) mapCategories.get( categoryDefinition.getId() );
092            groups[i] = new Group( groupDefinition, category );
093        }
094
095        // misc.
096        content = ld.getOutput().getContent();
097        destinationDirectory = new File( ld.getOutput().getDirectory() );
098        updateRef();
099    }
100
101    /**
102     * Create a logtype description with parameters defined in LogDistiller configuration.
103     * @return a new logtype description
104     */
105    private LogType.Description newLogTypeDescription()
106    {
107        LogDistiller.LogType lt = ld.getLogType();
108
109        LogType logtype = LogTypes.getLogType( lt.getId() );
110        LogType.Description logtypeDescription = logtype.newDescription( ld.getLogType().getParams() );
111        logtypeDescription.setExtensions( ld.getLogType().getExtensions() );
112        ld.checkLogtypeAttributes( logtypeDescription );
113
114        return logtypeDescription;
115    }
116
117    /**
118     * update internal maps, skip value and references from categories and groups
119     */
120    private void updateRef()
121    {
122        String skip = ld.getOutput().getSkip();
123
124        // process categories
125        mapCategories = new HashMap<String, Category>();
126        Category skipCategory = null;
127        int len = categories.length;
128        for ( int i = 0; i < len; i++ )
129        {
130            Category category = categories[i];
131            mapCategories.put( category.getDefinition().getId(), category );
132            category.updateRef( this );
133
134            if ( category.getDefinition().getId().equals( skip ) )
135            {
136                skipCategory = category;
137            }
138        }
139
140        // process groups
141        mapGroups = new HashMap<String, Group>();
142        len = groups.length;
143        for ( int i = 0; i < len; i++ )
144        {
145            Group group = groups[i];
146            mapGroups.put( group.getDefinition().getId(), group );
147            group.updateRef( this );
148
149            if ( group.getDefinition().getId().equals( skip ) )
150            {
151                group.setSkip( true );
152            }
153        }
154
155        if ( skipCategory != null )
156        {
157            skipCategory.setSkip( true );
158        }
159
160        logtypeDescription = newLogTypeDescription();
161    }
162
163    private void readObject( ObjectInputStream in )
164        throws IOException, ClassNotFoundException
165    {
166        in.defaultReadObject();
167        if ( ld.getOutput() != null )
168        {
169            ld.updateRef(); // workaround: a problem with Group deserialization happens
170            updateRef();
171        }
172    }
173
174    /**
175     * begin the distillation
176     *
177     * @throws IOException
178     */
179    public void begin()
180        throws IOException
181    {
182        destinationDirectory.mkdirs();
183        int count = groups.length;
184        for ( int i = 0; i < count; i++ )
185        {
186            groups[i].begin( destinationDirectory );
187        }
188        eventCount = 0;
189        beginTime = System.currentTimeMillis();
190    }
191
192    /**
193     * Process a log event
194     *
195     * @param le LogEvent the log event
196     * @throws IOException if an error occurs when (eventually) saving the log event
197     */
198    public void processLogEvent( LogEvent le )
199        throws IOException
200    {
201        if ( eventCount < 0 )
202        {
203            throw new IllegalStateException( "LogDistiller not initialized: need to call begin() before processing" );
204        }
205        eventCount++;
206        int eventSize = le.getRawLog().length() + LINE_SEPARATOR.length();
207        eventBytes += eventSize;
208        int count = groups.length;
209        for ( int i = 0; i < count; i++ )
210        {
211            Group group = groups[i];
212            if ( group.accept( le ) )
213            {
214                if ( group.isSkip() )
215                {
216                    // logevent matched a skipped group => update counters
217                    eventCount--;
218                    eventBytes -= eventSize;
219                    skippedEventCount++;
220                    skippedEventBytes += eventSize;
221                    break;
222                }
223                if ( !group.getDefinition().continueProcessing() )
224                {
225                    break;
226                }
227            }
228        }
229    }
230
231    /**
232     * end the distillation
233     *
234     * @throws IOException if an error occurs when closing the files where log events were (eventually) saved
235     */
236    public void end()
237        throws IOException
238    {
239        endTime = System.currentTimeMillis();
240        int count = groups.length;
241        for ( int i = 0; i < count; i++ )
242        {
243            Group group = groups[i];
244            group.end();
245        }
246        saveTo( newDestinationFile( "result.ser" ) );
247    }
248
249    /**
250     * Saves the distillation to a file, in Java serialized format.
251     *
252     * @param file File destination file.
253     */
254    public void saveTo( File file )
255        throws IOException
256    {
257        OutputStream out = null;
258        try
259        {
260            out = new FileOutputStream( file );
261            saveTo( out );
262        }
263        finally
264        {
265            IOUtils.closeQuietly( out );
266        }
267    }
268
269    public void saveTo( OutputStream out )
270        throws IOException
271    {
272        ObjectOutputStream obj = new ObjectOutputStream( out );
273        obj.writeObject( this );
274    }
275
276    public List<Category> listCategoriesToReport()
277    {
278        List<Category> reportCategories = new ArrayList<Category>();
279        int count = categories.length;
280        for ( int i = 0; i < count; i++ )
281        {
282            Category category = categories[i];
283            if ( ( !category.isSkip() ) && ( category.sumEventCount() > 0 ) )
284            {
285                reportCategories.add( category );
286            }
287        }
288        return reportCategories;
289    }
290
291    public List<Group> listGroupsToReport()
292    {
293        List<Group> reportGroups = new ArrayList<Group>();
294        int count = groups.length;
295        for ( int i = 0; i < count; i++ )
296        {
297            Group group = groups[i];
298            if ( ( !group.isSkip() ) && ( group.getEventCount() > 0 ) && ( group.getCategory() == null ) )
299            {
300                reportGroups.add( group );
301            }
302        }
303        return reportGroups;
304    }
305
306    public LogDistiller getDefinition()
307    {
308        return ld;
309    }
310
311    public Group[] getGroups()
312    {
313        return (Group[]) ArrayUtils.clone( groups );
314    }
315
316    public Group getGroup( String id )
317    {
318        return mapGroups.get( id );
319    }
320
321    public Category[] getCategories()
322    {
323        return (Category[]) ArrayUtils.clone( categories );
324    }
325
326    public Category getCategory( String id )
327    {
328        return mapCategories.get( id );
329    }
330
331    public String getContent()
332    {
333        return content;
334    }
335
336    public int getEventCount()
337    {
338        return eventCount;
339    }
340
341    public long getEventBytes()
342    {
343        return eventBytes;
344    }
345
346    public long getBeginTime()
347    {
348        return beginTime;
349    }
350
351    public long getEndTime()
352    {
353        return endTime;
354    }
355
356    public int getSkippedEventCount()
357    {
358        return skippedEventCount;
359    }
360
361    public long getSkippedEventBytes()
362    {
363        return skippedEventBytes;
364    }
365
366    public File newDestinationFile( String filename )
367    {
368        return new File( destinationDirectory, filename );
369    }
370
371    public LogType.Description getLogTypeDescription()
372    {
373        return logtypeDescription;
374    }
375
376    /**
377     * What is the version of <b>LogDistiller</b> that was used to create this instance?
378     *
379     * @return String the version
380     */
381    public String getVersion()
382    {
383        return ( version == null ) ? "unknown" : version;
384    }
385
386    public class Group
387        implements Serializable
388    {
389        private static final long serialVersionUID = 5788707827350407084L;
390
391        private final LogDistiller.Group definition;
392
393        private final Category category;
394
395        private transient LogDistillation logdistillation;
396
397        private boolean skip = false;
398
399        /**
400         * maximum number of events saved, -1 = no limit
401         *
402         * @since 0.7
403         */
404        private final int maxSaveCount;
405
406        /**
407         * maximum size sum of events saved in bytes, -1 = no limit
408         *
409         * @since 0.7
410         */
411        private final long maxSaveBytes;
412
413        private final Plugin[] plugins;
414
415        private File logFile;
416
417        private transient PrintStream logStream;
418
419        private int eventCount = 0;
420
421        private int savedEventCount = 0;
422
423        private long bytes = 0;
424
425        public Group( LogDistiller.Group definition, Category category )
426        {
427            this.definition = definition;
428            this.category = category;
429            maxSaveCount = Integer.parseInt( definition.getParam( "maxSave.count", "-1" ) );
430            maxSaveBytes = Long.parseLong( definition.getParam( "maxSave.size", "-1" ) ) * 1024;
431            LogDistiller.Plugin[] pluginDefs = definition.getPlugins();
432            plugins = new Plugin[pluginDefs.length];
433            for ( int i = 0; i < pluginDefs.length; i++ )
434            {
435                plugins[i] = Plugins.newInstance( pluginDefs[i] );
436            }
437        }
438
439        private void updateRef( LogDistillation logdistillation )
440        {
441            this.logdistillation = logdistillation;
442            if ( category != null )
443            {
444                category.addGroup( this );
445            }
446            int len = plugins.length;
447            for ( int i = 0; i < len; i++ )
448            {
449                plugins[i].updateRef( this );
450            }
451        }
452
453        public LogDistillation getLogdistillation()
454        {
455            return logdistillation;
456        }
457
458        public Category getCategory()
459        {
460            return category;
461        }
462
463        public void setSkip( boolean skip )
464        {
465            this.skip = skip;
466        }
467
468        public boolean isSkip()
469        {
470            return skip;
471        }
472
473        /**
474         * Check if given LogEvent meet one of the conditions defined.
475         *
476         * @param le LogEvent the LogEvent to check
477         * @throws IOException
478         * @return boolean <code>true</code> if the LogEvent meets a condition
479         */
480        public boolean accept( LogEvent le )
481            throws IOException
482        {
483            Condition condition = definition.accept( le );
484            if ( condition != null )
485            {
486                addLogEvent( le, condition );
487                return true;
488            }
489            return false;
490        }
491
492        /**
493         * Add a LogEvent to the current Group.
494         *
495         * @param le LogEvent
496         * @param condition the condition that matched the LogEvent
497         * @throws IOException
498         */
499        protected void addLogEvent( LogEvent le, Condition condition )
500            throws IOException
501        {
502            eventCount++;
503            bytes += le.getRawLog().length() + LINE_SEPARATOR.length();
504            if ( definition.getSave() && ( ( maxSaveCount < 0 ) || ( eventCount <= maxSaveCount ) )
505                && ( ( maxSaveBytes < 0 ) || ( bytes <= maxSaveBytes ) ) )
506            {
507                if ( logStream == null )
508                {
509                    logStream = new PrintStream( new FileOutputStream( logFile ) );
510                }
511                logStream.println( le.getRawLog() );
512                savedEventCount++;
513            }
514            int len = plugins.length;
515            for ( int i = 0; i < len; i++ )
516            {
517                plugins[i].addLogEvent( le, condition );
518            }
519        }
520
521        void begin( File destinationDirectory )
522            throws IOException
523        {
524            logFile = newDestinationFile( definition.getId() + ".log" );
525            int len = plugins.length;
526            for ( int i = 0; i < len; i++ )
527            {
528                plugins[i].begin( destinationDirectory );
529            }
530        }
531
532        void end()
533            throws IOException
534        {
535            if ( logStream != null )
536            {
537                logStream.close();
538            }
539            int len = plugins.length;
540            for ( int i = 0; i < len; i++ )
541            {
542                plugins[i].end();
543            }
544        }
545
546        public LogDistiller.Group getDefinition()
547        {
548            return definition;
549        }
550
551        public int getEventCount()
552        {
553            return eventCount;
554        }
555
556        public int getSavedEventCount()
557        {
558            return savedEventCount;
559        }
560
561        public long getBytes()
562        {
563            return bytes;
564        }
565
566        public File getLogFile()
567        {
568            return logFile;
569        }
570
571        public int getMaxSaveCount()
572        {
573            return maxSaveCount;
574        }
575
576        public long getMaxSaveBytes()
577        {
578            return maxSaveBytes;
579        }
580
581        public Plugin[] getPlugins()
582        {
583            return (Plugin[]) ArrayUtils.clone( plugins );
584        }
585    }
586
587    public static class Category
588        implements Serializable
589    {
590        private static final long serialVersionUID = 7098883103191641753L;
591
592        private final LogDistiller.Category definition;
593
594        private transient LogDistillation logdistillation;
595
596        private transient List<Group> groups;
597
598        private boolean skip = false;
599
600        public Category( LogDistiller.Category definition )
601        {
602            this.definition = definition;
603        }
604
605        private void updateRef( LogDistillation logdistillation )
606        {
607            this.logdistillation = logdistillation;
608            groups = new ArrayList<Group>();
609        }
610
611        public LogDistillation getLogdistillation()
612        {
613            return logdistillation;
614        }
615
616        public LogDistiller.Category getDefinition()
617        {
618            return definition;
619        }
620
621        private void addGroup( Group group )
622        {
623            groups.add( group );
624        }
625
626        public List<Group> getGroups()
627        {
628            return groups;
629        }
630
631        public void setSkip( boolean skip )
632        {
633            this.skip = skip;
634            for ( Group group : groups )
635            {
636                group.setSkip( skip );
637            }
638        }
639
640        public boolean isSkip()
641        {
642            return skip;
643        }
644
645        public int sumEventCount()
646        {
647            int sum = 0;
648            for ( Group group : groups )
649            {
650                sum += group.getEventCount();
651            }
652            return sum;
653        }
654
655        public long sumBytes()
656        {
657            long sum = 0;
658            for ( Group group : groups )
659            {
660                sum += group.getBytes();
661            }
662            return sum;
663        }
664
665        public List<Group> listGroupsToReport()
666        {
667            List<Group> reportGroups = new ArrayList<Group>();
668            for ( Group group : groups )
669            {
670                if ( group.getEventCount() > 0 )
671                {
672                    reportGroups.add( group );
673                }
674            }
675            return reportGroups;
676        }
677    }
678
679    public static abstract class Plugin
680        implements Serializable
681    {
682        private static final long serialVersionUID = -722318023109374109L;
683
684        protected final LogDistiller.Plugin definition;
685
686        protected transient Group group;
687
688        public Plugin( LogDistiller.Plugin definition )
689        {
690            this.definition = definition;
691        }
692
693        private void updateRef( Group group )
694        {
695            this.group = group;
696        }
697
698        public Group getGroup()
699        {
700            return group;
701        }
702
703        public LogDistiller.Plugin getDefinition()
704        {
705            return definition;
706        }
707
708        public String getId()
709        {
710            return getDefinition().getType();
711        }
712
713        /**
714         * Begin the log distillation for this plugin instance.
715         *
716         * @param destinationDirectory the directory where reports will be stored.
717         * @throws IOException
718         */
719        abstract public void begin( File destinationDirectory )
720            throws IOException;
721
722        /**
723         * Add a log event to this plugin instance.
724         *
725         * @param le the log event.
726         * @throws IOException
727         */
728        abstract public void addLogEvent( LogEvent le )
729            throws IOException;
730
731        /**
732         * Add a log event to this plugin instance, with information on which condition was matched.
733         *
734         * @param le the log event.
735         * @param condition the condition which was matched
736         * @since 1.1
737         */
738        public void addLogEvent( LogEvent le, Condition condition )
739            throws IOException
740        {
741            addLogEvent( le ); // default implementation does ignore condition
742        }
743
744        /**
745         * End the log distillation for this plugin instance.
746         *
747         * @throws IOException
748         */
749        abstract public void end()
750            throws IOException;
751
752        /**
753         * Append a summary of this plugin instance results to the group report.
754         *
755         * @param report the plugin report formatter
756         */
757        abstract public void appendGroupReport( ReportFormat.PluginReport report );
758
759        /**
760         * Append a summary of this plugin instance results to the global report.
761         *
762         * @param report the plugin report formatter
763         */
764        abstract public void appendGlobalReport( ReportFormat.PluginReport report );
765    }
766}