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 }