1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.log4j.chainsaw.layout; 19 20 import java.util.Hashtable; 21 import java.util.Iterator; 22 import java.util.Set; 23 24 import org.apache.log4j.EnhancedPatternLayout; 25 import org.apache.log4j.Layout; 26 import org.apache.log4j.Logger; 27 import org.apache.log4j.spi.LocationInfo; 28 import org.apache.log4j.spi.LoggingEvent; 29 30 31 /** 32 * This layout is used for formatting HTML text for use inside 33 * the Chainsaw Event Detail Panel, and the tooltip used 34 * when mouse-over on a particular log event row. 35 * 36 * It relies an an internal PatternLayout to accomplish this, but ensures HTML characters 37 * from any LoggingEvent are escaped first. 38 * 39 * @author Paul Smith <psmith@apache.org> 40 */ 41 public class EventDetailLayout extends Layout { 42 private EnhancedPatternLayout patternLayout = new EnhancedPatternLayout(); 43 44 public EventDetailLayout() { 45 } 46 47 public void setConversionPattern(String conversionPattern) { 48 patternLayout.setConversionPattern(conversionPattern); 49 patternLayout.activateOptions(); 50 } 51 52 public String getConversionPattern() { 53 return patternLayout.getConversionPattern(); 54 } 55 56 /* (non-Javadoc) 57 * @see org.apache.log4j.Layout#getFooter() 58 */ 59 public String getFooter() { 60 return ""; 61 } 62 63 /* (non-Javadoc) 64 * @see org.apache.log4j.Layout#getHeader() 65 */ 66 public String getHeader() { 67 return ""; 68 } 69 70 // /* (non-Javadoc) 71 // * @see org.apache.log4j.Layout#format(java.io.Writer, org.apache.log4j.spi.LoggingEvent) 72 // */ 73 // public void format(Writer output, LoggingEvent event) 74 // throws IOException { 75 // boolean pastFirst = false; 76 // output.write("<html><body><table cellspacing=0 cellpadding=0>"); 77 // 78 // List columnNames = ChainsawColumns.getColumnsNames(); 79 // 80 // Vector v = ChainsawAppenderHandler.convert(event); 81 // 82 // /** 83 // * we need to add the ID property from the event 84 // */ 85 // v.add(event.getProperty(ChainsawConstants.LOG4J_ID_KEY)); 86 // 87 // // ListIterator iter = displayFilter.getDetailColumns().listIterator(); 88 // Iterator iter = columnNames.iterator(); 89 // String column = null; 90 // int index = -1; 91 // 92 // while (iter.hasNext()) { 93 // column = (String) iter.next(); 94 // index = columnNames.indexOf(column); 95 // 96 // if (index > -1) { 97 // if (pastFirst) { 98 // output.write("</td></tr>"); 99 // } 100 // 101 // output.write("<tr><td valign=\"top\"><b>"); 102 // output.write(column); 103 // output.write(": </b></td><td>"); 104 // 105 // 106 // if (index<v.size()) { 107 // Object o = v.get(index); 108 // 109 // if (o != null) { 110 // output.write(escape(o.toString())); 111 // } else { 112 // output.write("{null}"); 113 // } 114 // 115 // }else { 116 //// output.write("Invalid column " + column + " (index=" + index + ")"); 117 // } 118 // 119 // pastFirst = true; 120 // } 121 // } 122 // 123 // output.write("</table></body></html>"); 124 // } 125 126 /** 127 * Escape <, > & and " as their entities. It is very 128 * dumb about & handling. 129 * @param aStr the String to escape. 130 * @return the escaped String 131 */ 132 private static String escape(String string) { 133 if (string == null) { 134 return ""; 135 } 136 137 final StringBuffer buf = new StringBuffer(); 138 139 for (int i = 0; i < string.length(); i++) { 140 char c = string.charAt(i); 141 142 switch (c) { 143 case '<': 144 buf.append("<"); 145 146 break; 147 148 case '>': 149 buf.append(">"); 150 151 break; 152 153 case '\"': 154 buf.append("""); 155 156 break; 157 158 case '&': 159 buf.append("&"); 160 161 break; 162 163 default: 164 buf.append(c); 165 166 break; 167 } 168 } 169 170 return buf.toString(); 171 } 172 173 /** 174 * Takes a source event and copies it into a new LoggingEvent object 175 * and ensuring all the internal elements of the event are HTML safe 176 * @param event 177 * @return new LoggingEvent 178 */ 179 private static LoggingEvent copyForHTML(LoggingEvent event) { 180 Logger logger = Logger.getLogger(event.getLoggerName()); 181 String threadName = event.getThreadName(); 182 Object msg = escape(event.getMessage().toString()); 183 String ndc = event.getNDC(); 184 // Hashtable mdc = formatMDC(event); 185 LocationInfo li = null; 186 if (event.locationInformationExists()) { 187 li = formatLocationInfo(event); 188 } 189 Hashtable properties = formatProperties(event); 190 LoggingEvent copy = new LoggingEvent(null, 191 logger, event.getTimeStamp(), 192 event.getLevel(), 193 msg, 194 threadName, 195 event.getThrowableInformation(), 196 ndc, 197 li, 198 properties); 199 200 return copy; 201 } 202 203 // /** 204 // * @param event 205 // * @return 206 // */ 207 // private static Hashtable formatMDC(LoggingEvent event) { 208 // Set keySet = event.getMDCKeySet(); 209 // Hashtable hashTable = new Hashtable(); 210 // 211 // for (Iterator iter = keySet.iterator(); iter.hasNext();) { 212 // Object key = (Object) iter.next(); 213 // Object value = event.getMDC(key.toString()); 214 // hashTable.put(escape(key.toString()), escape(value.toString())); 215 // } 216 // 217 // return hashTable; 218 // } 219 220 /** 221 * @param event 222 * @return 223 */ 224 private static LocationInfo formatLocationInfo(LoggingEvent event) { 225 LocationInfo info = event.getLocationInformation(); 226 LocationInfo newInfo = 227 new LocationInfo( 228 escape(info.getFileName()), escape(info.getClassName()), 229 escape(info.getMethodName()), escape(info.getLineNumber())); 230 231 return newInfo; 232 } 233 234 /** 235 * @param event 236 * @return 237 */ 238 private static Hashtable formatProperties(LoggingEvent event) { 239 Set keySet = event.getPropertyKeySet(); 240 Hashtable hashTable = new Hashtable(); 241 242 for (Iterator iter = keySet.iterator(); iter.hasNext();) { 243 Object key = iter.next(); 244 Object value = event.getProperty(key.toString()); 245 hashTable.put(escape(key.toString()), escape(value.toString())); 246 } 247 248 return hashTable; 249 } 250 251 /* (non-Javadoc) 252 * @see org.apache.log4j.Layout#ignoresThrowable() 253 */ 254 public boolean ignoresThrowable() { 255 return false; 256 } 257 258 /* (non-Javadoc) 259 * @see org.apache.log4j.spi.OptionHandler#activateOptions() 260 */ 261 public void activateOptions() { 262 } 263 264 /* (non-Javadoc) 265 * @see org.apache.log4j.Layout#format(java.io.Writer, org.apache.log4j.spi.LoggingEvent) 266 */ 267 public String format(final LoggingEvent event) { 268 LoggingEvent newEvent = copyForHTML(event); 269 /** 270 * Layouts are not thread-safe, but are normally 271 * protected by the fact that their Appender is thread-safe. 272 * 273 * But here in Chainsaw there is no such guarantees. 274 */ 275 synchronized(patternLayout) { 276 return patternLayout.format(newEvent); 277 } 278 } 279 }