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