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