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, "]]>]]><![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 }