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 org.apache.log4j.EnhancedPatternLayout; 21 import org.apache.log4j.Layout; 22 import org.apache.log4j.Logger; 23 import org.apache.log4j.spi.LocationInfo; 24 import org.apache.log4j.spi.LoggingEvent; 25 26 import java.util.Hashtable; 27 import java.util.Set; 28 29 30 /** 31 * This layout is used for formatting HTML text for use inside 32 * the Chainsaw Event Detail Panel, and the tooltip used 33 * when mouse-over on a particular log event row. 34 * <p> 35 * It relies an an internal PatternLayout to accomplish this, but ensures HTML characters 36 * from any LoggingEvent are escaped first. 37 * 38 * @author Paul Smith <psmith@apache.org> 39 */ 40 public class EventDetailLayout extends Layout { 41 private final EnhancedPatternLayout patternLayout = new EnhancedPatternLayout(); 42 43 public EventDetailLayout() { 44 } 45 46 public void setConversionPattern(String conversionPattern) { 47 patternLayout.setConversionPattern(conversionPattern); 48 patternLayout.activateOptions(); 49 } 50 51 public String getConversionPattern() { 52 return patternLayout.getConversionPattern(); 53 } 54 55 /* (non-Javadoc) 56 * @see org.apache.log4j.Layout#getFooter() 57 */ 58 public String getFooter() { 59 return ""; 60 } 61 62 /* (non-Javadoc) 63 * @see org.apache.log4j.Layout#getHeader() 64 */ 65 public String getHeader() { 66 return ""; 67 } 68 69 // /* (non-Javadoc) 70 // * @see org.apache.log4j.Layout#format(java.io.Writer, org.apache.log4j.spi.LoggingEvent) 71 // */ 72 // public void format(Writer output, LoggingEvent event) 73 // throws IOException { 74 // boolean pastFirst = false; 75 // output.write("<html><body><table cellspacing=0 cellpadding=0>"); 76 // 77 // List columnNames = ChainsawColumns.getColumnsNames(); 78 // 79 // Vector v = ChainsawAppenderHandler.convert(event); 80 // 81 // /** 82 // * we need to add the ID property from the event 83 // */ 84 // v.add(event.getProperty(ChainsawConstants.LOG4J_ID_KEY)); 85 // 86 // // ListIterator iter = displayFilter.getDetailColumns().listIterator(); 87 // Iterator iter = columnNames.iterator(); 88 // String column = null; 89 // int index = -1; 90 // 91 // while (iter.hasNext()) { 92 // column = (String) iter.next(); 93 // index = columnNames.indexOf(column); 94 // 95 // if (index > -1) { 96 // if (pastFirst) { 97 // output.write("</td></tr>"); 98 // } 99 // 100 // output.write("<tr><td valign=\"top\"><b>"); 101 // output.write(column); 102 // output.write(": </b></td><td>"); 103 // 104 // 105 // if (index<v.size()) { 106 // Object o = v.get(index); 107 // 108 // if (o != null) { 109 // output.write(escape(o.toString())); 110 // } else { 111 // output.write("{null}"); 112 // } 113 // 114 // }else { 115 //// output.write("Invalid column " + column + " (index=" + index + ")"); 116 // } 117 // 118 // pastFirst = true; 119 // } 120 // } 121 // 122 // output.write("</table></body></html>"); 123 // } 124 125 /** 126 * Escape <, > & and " as their entities. It is very 127 * dumb about & handling. 128 * 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 StringBuilder buf = new StringBuilder(); 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 * 177 * @param event 178 * @return new LoggingEvent 179 */ 180 private static LoggingEvent copyForHTML(LoggingEvent event) { 181 Logger logger = Logger.getLogger(event.getLoggerName()); 182 String threadName = event.getThreadName(); 183 Object msg = escape(event.getMessage().toString()); 184 String ndc = event.getNDC(); 185 // Hashtable mdc = formatMDC(event); 186 LocationInfo li = null; 187 if (event.locationInformationExists()) { 188 li = formatLocationInfo(event); 189 } 190 Hashtable<String, String> properties = formatProperties(event); 191 LoggingEvent copy = new LoggingEvent(null, 192 logger, event.getTimeStamp(), 193 event.getLevel(), 194 msg, 195 threadName, 196 event.getThrowableInformation(), 197 ndc, 198 li, 199 properties); 200 201 return copy; 202 } 203 204 // /** 205 // * @param event 206 // * @return 207 // */ 208 // private static Hashtable formatMDC(LoggingEvent event) { 209 // Set keySet = event.getMDCKeySet(); 210 // Hashtable hashTable = new Hashtable(); 211 // 212 // for (Iterator iter = keySet.iterator(); iter.hasNext();) { 213 // Object key = (Object) iter.next(); 214 // Object value = event.getMDC(key.toString()); 215 // hashTable.put(escape(key.toString()), escape(value.toString())); 216 // } 217 // 218 // return hashTable; 219 // } 220 221 /** 222 * @param event 223 * @return 224 */ 225 private static LocationInfo formatLocationInfo(LoggingEvent event) { 226 LocationInfo info = event.getLocationInformation(); 227 LocationInfo newInfo = 228 new LocationInfo( 229 escape(info.getFileName()), escape(info.getClassName()), 230 escape(info.getMethodName()), escape(info.getLineNumber())); 231 232 return newInfo; 233 } 234 235 /** 236 * @param event 237 * @return 238 */ 239 private static Hashtable<String, String> formatProperties(LoggingEvent event) { 240 Set keySet = event.getPropertyKeySet(); 241 Hashtable<String, String> hashTable = new Hashtable<>(); 242 243 for (Object key : keySet) { 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 }