001    package net.sf.logdistiller.logtypes;
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    
017    import java.io.*;
018    import java.text.SimpleDateFormat;
019    import java.text.DateFormat;
020    import java.text.ParseException;
021    import java.util.Date;
022    
023    import org.apache.commons.lang.StringUtils;
024    
025    import net.sf.logdistiller.LogEvent;
026    import net.sf.logdistiller.LogType;
027    import net.sf.logdistiller.util.StringCutter;
028    
029    /**
030     * Log event for log4j XMLLayout (<code>log4j-XML</code>). By default, the classification rules generated by the GUI for
031     * this type of logs will sort events based on the following attributes: <code>level</code>, then <code>logger</code>.
032     */
033    public class Log4jXmlLogEvent
034        extends LogEvent
035        implements Comparable
036    {
037        public final static String ID = "log4j-XML";
038    
039        public final String logger;
040    
041        public final String timestamp;
042    
043        public final String datetime;
044    
045        public final long timestampValue;
046    
047        public final String level;
048    
049        public final String thread;
050    
051        public final String message;
052    
053        public final String ndc;
054    
055        public final String throwable;
056    
057        public final String locationInfoClass;
058    
059        public final String locationInfoMethod;
060    
061        public final String locationInfoFile;
062    
063        public final String locationInfoLine;
064    
065        public final static LogType LOGTYPE = new LogType.Basic( ID );
066    
067        private final static DateFormat DATE_FORMAT = new SimpleDateFormat( "dd/MM/yyyy HH:mm:ss" );
068    
069        private final static String LOGEVENT_START = "<log4j:event logger=\"";
070    
071        private final static String[] ATTRIBUTE_NAMES =
072            { "logSource", "datetime", "timestamp", "level", "logger", "thread", "message", "NDC", "throwable",
073                "locationInfo.class", "locationInfo.method", "locationInfo.file", "locationInfo.line" };
074    
075        public final static LogType.Description DESCRIPTION = new Description( (LogType.Basic) LOGTYPE, ATTRIBUTE_NAMES );
076    
077        public Log4jXmlLogEvent( Factory factory, String rawLog )
078            throws ParseException
079        {
080            super( factory, rawLog );
081            StringCutter cutter = new StringCutter( rawLog );
082            cutter.parseTo( LOGEVENT_START );
083            logger = cutter.parseTo( "\" timestamp=\"" );
084            timestamp = cutter.parseTo( "\" level=\"" );
085            timestampValue = Long.parseLong( timestamp );
086            datetime = DATE_FORMAT.format( new Date( timestampValue ) );
087            level = cutter.parseTo( "\" thread=\"" );
088            thread = cutter.parseTo( "\">" );
089            cutter.parseTo( "<log4j:message><![CDATA[" );
090            message = unescapeCDATA( cutter.parseTo( "]]></log4j:message>" ) );
091    
092            String remaining = cutter.getRemaining();
093            if ( remaining.indexOf( "<log4j:NDC>" ) < 0 )
094            {
095                ndc = "";
096            }
097            else
098            {
099                cutter.parseTo( "<log4j:NDC><![CDATA[" );
100                ndc = unescapeCDATA( cutter.parseTo( "]]></log4j:NDC>" ) );
101                remaining = cutter.getRemaining();
102            }
103            if ( remaining.indexOf( "<log4j:throwable>" ) < 0 )
104            {
105                throwable = "";
106            }
107            else
108            {
109                cutter.parseTo( "<log4j:throwable><![CDATA[" );
110                throwable = unescapeCDATA( cutter.parseTo( "]]></log4j:throwable>" ) );
111                remaining = cutter.getRemaining();
112            }
113            if ( remaining.indexOf( "<log4j:locationInfo" ) < 0 )
114            {
115                locationInfoClass = "";
116                locationInfoMethod = "";
117                locationInfoFile = "";
118                locationInfoLine = "";
119            }
120            else
121            {
122                cutter.parseTo( "<log4j:locationInfo class=\"" );
123                locationInfoClass = cutter.parseTo( "\" method=\"" );
124                locationInfoMethod = cutter.parseTo( "\" file=\"" );
125                locationInfoFile = cutter.parseTo( "\" line=\"" );
126                locationInfoLine = cutter.parseTo( "\"/>" );
127            }
128            setAttributes( new String[] { factory.getLogSource(), datetime, timestamp, level, logger, thread, message, ndc,
129                throwable, locationInfoClass, locationInfoMethod, locationInfoFile, locationInfoLine } );
130        }
131    
132        public static String unescapeCDATA( String cdata )
133        {
134            return StringUtils.replace( cdata, "]]>]]&gt;<![CDATA[", "]]>" );
135        }
136    
137        public int compareTo( Object o )
138        {
139            long diff = timestampValue - ( (Log4jXmlLogEvent) o ).timestampValue;
140            return ( diff < 0 ) ? -1 : ( diff > 0 ) ? 1 : 0;
141        }
142    
143        private static class Description
144            extends LogType.Description
145        {
146            public Description( LogType.Basic logtype, String[] attributeNames )
147            {
148                super( logtype, attributeNames );
149                logtype.setDescription( this );
150            }
151    
152            public LogEvent.Factory newFactory( Reader reader, String logSource )
153                throws IOException
154            {
155                return new Factory( this, reader, logSource );
156            }
157    
158            public String getDefaultSpecificGroups()
159            {
160                return "  <group id=\"warn\">\n"
161                    + "    <description>WARN events</description>\n"
162                    + "    <condition>\n"
163                    + "      <match attribute=\"level\" type=\"equals\">WARN</match>\n"
164                    + "    </condition>\n"
165                    + "    <report publisher=\"file\"/>\n"
166                    + "    <plugin type=\"sampling\">\n"
167                    + "      <param name=\"attributes\">logger</param>\n"
168                    + "    </plugin>\n"
169                    + "  </group>\n"
170                    + "\n"
171                    + "  <group id=\"error\">\n"
172                    + "    <description>ERROR events</description>\n"
173                    + "    <condition>\n"
174                    + "      <match attribute=\"level\" type=\"equals\">ERROR</match>\n"
175                    + "    </condition>\n"
176                    + "    <report publisher=\"file\"/>\n"
177                    + "    <plugin type=\"sampling\">\n"
178                    + "      <param name=\"attributes\">logger</param>\n"
179                    + "    </plugin>\n"
180                    + "  </group>";
181            }
182    
183            public String getDefaultSamplingAttributes()
184            {
185                return "level,logger";
186            }
187        }
188    
189        private static class Factory
190            extends LogEvent.Factory
191        {
192            private final LineNumberReader reader;
193    
194            private String curLine;
195    
196            public Factory( Description description, Reader reader, String logSource )
197                throws FileNotFoundException
198            {
199                super( description, logSource );
200                this.reader = new LineNumberReader( reader );
201            }
202    
203            protected boolean detectLogEventStart( String line )
204            {
205                return line.startsWith( LOGEVENT_START );
206            }
207    
208            protected LogEvent readNextEvent()
209                throws IOException, ParseException
210            {
211                if ( curLine == null )
212                {
213                    curLine = reader.readLine();
214                    if ( curLine == null )
215                    {
216                        // EOF
217                        return null;
218                    }
219                }
220                if ( !detectLogEventStart( curLine ) )
221                {
222                    throw new ParseException(
223                                              "bad log format, beginnig of line " + reader.getLineNumber() + ": " + curLine,
224                                              0 );
225                }
226                StringBuffer buffer = new StringBuffer( curLine );
227                while ( ( ( curLine = reader.readLine() ) != null ) && ( !detectLogEventStart( curLine ) ) )
228                {
229                    buffer.append( "\r\n" ); // fixed newline, as log4j
230                    buffer.append( curLine );
231                }
232                return new Log4jXmlLogEvent( this, buffer.toString() );
233            }
234        }
235    }