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 }