1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import java.io.IOException;
20 import java.io.InterruptedIOException;
21 import java.io.LineNumberReader;
22 import java.io.PrintWriter;
23 import java.io.StringReader;
24 import java.io.StringWriter;
25 import java.lang.management.ManagementFactory;
26 import java.nio.charset.Charset;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.Map;
30
31 import org.apache.logging.log4j.Level;
32 import org.apache.logging.log4j.core.LogEvent;
33 import org.apache.logging.log4j.core.config.plugins.Plugin;
34 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
35 import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
36 import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
37 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
38 import org.apache.logging.log4j.core.util.Charsets;
39 import org.apache.logging.log4j.core.util.Constants;
40 import org.apache.logging.log4j.core.util.Transform;
41
42
43
44
45
46
47
48 @Plugin(name = "HtmlLayout", category = "Core", elementType = "layout", printObject = true)
49 public final class HtmlLayout extends AbstractStringLayout {
50
51 private static final int BUF_SIZE = 256;
52
53 private static final String TRACE_PREFIX = "<br /> ";
54
55 private static final String REGEXP = Constants.LINE_SEPARATOR.equals("\n") ? "\n" : Constants.LINE_SEPARATOR + "|\n";
56
57 private static final String DEFAULT_TITLE = "Log4j Log Messages";
58
59 private static final String DEFAULT_CONTENT_TYPE = "text/html";
60
61 public static final String DEFAULT_FONT_FAMILY = "arial,sans-serif";
62
63 private final long jvmStartTime = ManagementFactory.getRuntimeMXBean().getStartTime();
64
65
66 private final boolean locationInfo;
67
68 private final String title;
69
70 private final String contentType;
71
72
73 public static enum FontSize {
74 SMALLER("smaller"), XXSMALL("xx-small"), XSMALL("x-small"), SMALL("small"), MEDIUM("medium"), LARGE("large"),
75 XLARGE("x-large"), XXLARGE("xx-large"), LARGER("larger");
76
77 private final String size;
78
79 private FontSize(final String size) {
80 this.size = size;
81 }
82
83 public String getFontSize() {
84 return size;
85 }
86
87 public static FontSize getFontSize(final String size) {
88 for (final FontSize fontSize : values()) {
89 if (fontSize.size.equals(size)) {
90 return fontSize;
91 }
92 }
93 return SMALL;
94 }
95
96 public FontSize larger() {
97 return this.ordinal() < XXLARGE.ordinal() ? FontSize.values()[this.ordinal() + 1] : this;
98 }
99 }
100
101 private final String font;
102 private final String fontSize;
103 private final String headerSize;
104
105 private HtmlLayout(final boolean locationInfo, final String title, final String contentType, final Charset charset,
106 final String font, final String fontSize, final String headerSize) {
107 super(charset);
108 this.locationInfo = locationInfo;
109 this.title = title;
110 this.contentType = addCharsetToContentType(contentType);
111 this.font = font;
112 this.fontSize = fontSize;
113 this.headerSize = headerSize;
114 }
115
116 private String addCharsetToContentType(final String contentType) {
117 if (contentType == null) {
118 return DEFAULT_CONTENT_TYPE + "; charset=" + getCharset();
119 }
120 return contentType.contains("charset") ? contentType : contentType + "; charset=" + getCharset();
121 }
122
123
124
125
126
127
128
129 @Override
130 public String toSerializable(final LogEvent event) {
131 final StringBuilder sbuf = new StringBuilder(BUF_SIZE);
132
133 sbuf.append(Constants.LINE_SEPARATOR).append("<tr>").append(Constants.LINE_SEPARATOR);
134
135 sbuf.append("<td>");
136 sbuf.append(event.getTimeMillis() - jvmStartTime);
137 sbuf.append("</td>").append(Constants.LINE_SEPARATOR);
138
139 final String escapedThread = Transform.escapeHtmlTags(event.getThreadName());
140 sbuf.append("<td title=\"").append(escapedThread).append(" thread\">");
141 sbuf.append(escapedThread);
142 sbuf.append("</td>").append(Constants.LINE_SEPARATOR);
143
144 sbuf.append("<td title=\"Level\">");
145 if (event.getLevel().equals(Level.DEBUG)) {
146 sbuf.append("<font color=\"#339933\">");
147 sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel())));
148 sbuf.append("</font>");
149 } else if (event.getLevel().isMoreSpecificThan(Level.WARN)) {
150 sbuf.append("<font color=\"#993300\"><strong>");
151 sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel())));
152 sbuf.append("</strong></font>");
153 } else {
154 sbuf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel())));
155 }
156 sbuf.append("</td>").append(Constants.LINE_SEPARATOR);
157
158 String escapedLogger = Transform.escapeHtmlTags(event.getLoggerName());
159 if (escapedLogger.isEmpty()) {
160 escapedLogger = "root";
161 }
162 sbuf.append("<td title=\"").append(escapedLogger).append(" logger\">");
163 sbuf.append(escapedLogger);
164 sbuf.append("</td>").append(Constants.LINE_SEPARATOR);
165
166 if (locationInfo) {
167 final StackTraceElement element = event.getSource();
168 sbuf.append("<td>");
169 sbuf.append(Transform.escapeHtmlTags(element.getFileName()));
170 sbuf.append(':');
171 sbuf.append(element.getLineNumber());
172 sbuf.append("</td>").append(Constants.LINE_SEPARATOR);
173 }
174
175 sbuf.append("<td title=\"Message\">");
176 sbuf.append(Transform.escapeHtmlTags(event.getMessage().getFormattedMessage()).replaceAll(REGEXP, "<br />"));
177 sbuf.append("</td>").append(Constants.LINE_SEPARATOR);
178 sbuf.append("</tr>").append(Constants.LINE_SEPARATOR);
179
180 if (event.getContextStack() != null && !event.getContextStack().isEmpty()) {
181 sbuf.append("<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : ").append(fontSize);
182 sbuf.append(";\" colspan=\"6\" ");
183 sbuf.append("title=\"Nested Diagnostic Context\">");
184 sbuf.append("NDC: ").append(Transform.escapeHtmlTags(event.getContextStack().toString()));
185 sbuf.append("</td></tr>").append(Constants.LINE_SEPARATOR);
186 }
187
188 if (event.getContextMap() != null && !event.getContextMap().isEmpty()) {
189 sbuf.append("<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : ").append(fontSize);
190 sbuf.append(";\" colspan=\"6\" ");
191 sbuf.append("title=\"Mapped Diagnostic Context\">");
192 sbuf.append("MDC: ").append(Transform.escapeHtmlTags(event.getContextMap().toString()));
193 sbuf.append("</td></tr>").append(Constants.LINE_SEPARATOR);
194 }
195
196 final Throwable throwable = event.getThrown();
197 if (throwable != null) {
198 sbuf.append("<tr><td bgcolor=\"#993300\" style=\"color:White; font-size : ").append(fontSize);
199 sbuf.append(";\" colspan=\"6\">");
200 appendThrowableAsHtml(throwable, sbuf);
201 sbuf.append("</td></tr>").append(Constants.LINE_SEPARATOR);
202 }
203
204 return sbuf.toString();
205 }
206
207
208
209
210
211
212 @Override
213 public Map<String, String> getContentFormat() {
214 return new HashMap<String, String>();
215 }
216
217 @Override
218
219
220
221 public String getContentType() {
222 return contentType;
223 }
224
225 private void appendThrowableAsHtml(final Throwable throwable, final StringBuilder sbuf) {
226 final StringWriter sw = new StringWriter();
227 final PrintWriter pw = new PrintWriter(sw);
228 try {
229 throwable.printStackTrace(pw);
230 } catch (final RuntimeException ex) {
231
232 }
233 pw.flush();
234 final LineNumberReader reader = new LineNumberReader(new StringReader(sw.toString()));
235 final ArrayList<String> lines = new ArrayList<String>();
236 try {
237 String line = reader.readLine();
238 while (line != null) {
239 lines.add(line);
240 line = reader.readLine();
241 }
242 } catch (final IOException ex) {
243 if (ex instanceof InterruptedIOException) {
244 Thread.currentThread().interrupt();
245 }
246 lines.add(ex.toString());
247 }
248 boolean first = true;
249 for (final String line : lines) {
250 if (!first) {
251 sbuf.append(TRACE_PREFIX);
252 } else {
253 first = false;
254 }
255 sbuf.append(Transform.escapeHtmlTags(line));
256 sbuf.append(Constants.LINE_SEPARATOR);
257 }
258 }
259
260
261
262
263
264 @Override
265 public byte[] getHeader() {
266 final StringBuilder sbuf = new StringBuilder();
267 sbuf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" ");
268 sbuf.append("\"http://www.w3.org/TR/html4/loose.dtd\">");
269 sbuf.append(Constants.LINE_SEPARATOR);
270 sbuf.append("<html>").append(Constants.LINE_SEPARATOR);
271 sbuf.append("<head>").append(Constants.LINE_SEPARATOR);
272 sbuf.append("<meta charset=\"").append(getCharset()).append("\"/>").append(Constants.LINE_SEPARATOR);
273 sbuf.append("<title>").append(title).append("</title>").append(Constants.LINE_SEPARATOR);
274 sbuf.append("<style type=\"text/css\">").append(Constants.LINE_SEPARATOR);
275 sbuf.append("<!--").append(Constants.LINE_SEPARATOR);
276 sbuf.append("body, table {font-family:").append(font).append("; font-size: ");
277 sbuf.append(headerSize).append(";}").append(Constants.LINE_SEPARATOR);
278 sbuf.append("th {background: #336699; color: #FFFFFF; text-align: left;}").append(Constants.LINE_SEPARATOR);
279 sbuf.append("-->").append(Constants.LINE_SEPARATOR);
280 sbuf.append("</style>").append(Constants.LINE_SEPARATOR);
281 sbuf.append("</head>").append(Constants.LINE_SEPARATOR);
282 sbuf.append("<body bgcolor=\"#FFFFFF\" topmargin=\"6\" leftmargin=\"6\">").append(Constants.LINE_SEPARATOR);
283 sbuf.append("<hr size=\"1\" noshade>").append(Constants.LINE_SEPARATOR);
284 sbuf.append("Log session start time " + new java.util.Date() + "<br>").append(Constants.LINE_SEPARATOR);
285 sbuf.append("<br>").append(Constants.LINE_SEPARATOR);
286 sbuf.append(
287 "<table cellspacing=\"0\" cellpadding=\"4\" border=\"1\" bordercolor=\"#224466\" width=\"100%\">");
288 sbuf.append(Constants.LINE_SEPARATOR);
289 sbuf.append("<tr>").append(Constants.LINE_SEPARATOR);
290 sbuf.append("<th>Time</th>").append(Constants.LINE_SEPARATOR);
291 sbuf.append("<th>Thread</th>").append(Constants.LINE_SEPARATOR);
292 sbuf.append("<th>Level</th>").append(Constants.LINE_SEPARATOR);
293 sbuf.append("<th>Logger</th>").append(Constants.LINE_SEPARATOR);
294 if (locationInfo) {
295 sbuf.append("<th>File:Line</th>").append(Constants.LINE_SEPARATOR);
296 }
297 sbuf.append("<th>Message</th>").append(Constants.LINE_SEPARATOR);
298 sbuf.append("</tr>").append(Constants.LINE_SEPARATOR);
299 return sbuf.toString().getBytes(getCharset());
300 }
301
302
303
304
305
306 @Override
307 public byte[] getFooter() {
308 final StringBuilder sbuf = new StringBuilder();
309 sbuf.append("</table>").append(Constants.LINE_SEPARATOR);
310 sbuf.append("<br>").append(Constants.LINE_SEPARATOR);
311 sbuf.append("</body></html>");
312 return sbuf.toString().getBytes(getCharset());
313 }
314
315
316
317
318
319
320
321
322
323
324
325 @PluginFactory
326 public static HtmlLayout createLayout(
327 @PluginAttribute(value = "locationInfo", defaultBoolean = false) final boolean locationInfo,
328 @PluginAttribute(value = "title", defaultString = DEFAULT_TITLE) final String title,
329 @PluginAttribute("contentType") String contentType,
330 @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset,
331 @PluginAttribute("fontSize") String fontSize,
332 @PluginAttribute(value = "fontName", defaultString = DEFAULT_FONT_FAMILY) final String font) {
333 final FontSize fs = FontSize.getFontSize(fontSize);
334 fontSize = fs.getFontSize();
335 final String headerSize = fs.larger().getFontSize();
336 if (contentType == null) {
337 contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset;
338 }
339 return new HtmlLayout(locationInfo, title, contentType, charset, font, fontSize, headerSize);
340 }
341
342
343
344
345
346
347 public static HtmlLayout createDefaultLayout() {
348 return newBuilder().build();
349 }
350
351 @PluginBuilderFactory
352 public static Builder newBuilder() {
353 return new Builder();
354 }
355
356 public static class Builder implements org.apache.logging.log4j.core.util.Builder<HtmlLayout> {
357
358 @PluginBuilderAttribute
359 private boolean locationInfo = false;
360
361 @PluginBuilderAttribute
362 private String title = DEFAULT_TITLE;
363
364 @PluginBuilderAttribute
365 private String contentType = null;
366
367 @PluginBuilderAttribute
368 private Charset charset = Charsets.UTF_8;
369
370 @PluginBuilderAttribute
371 private FontSize fontSize = FontSize.SMALL;
372
373 @PluginBuilderAttribute
374 private String fontName = DEFAULT_FONT_FAMILY;
375
376 private Builder() {
377 }
378
379 public Builder withLocationInfo(final boolean locationInfo) {
380 this.locationInfo = locationInfo;
381 return this;
382 }
383
384 public Builder withTitle(final String title) {
385 this.title = title;
386 return this;
387 }
388
389 public Builder withContentType(final String contentType) {
390 this.contentType = contentType;
391 return this;
392 }
393
394 public Builder withCharset(final Charset charset) {
395 this.charset = charset;
396 return this;
397 }
398
399 public Builder withFontSize(final FontSize fontSize) {
400 this.fontSize = fontSize;
401 return this;
402 }
403
404 public Builder withFontName(final String fontName) {
405 this.fontName = fontName;
406 return this;
407 }
408
409 @Override
410 public HtmlLayout build() {
411
412 if (contentType == null) {
413 contentType = DEFAULT_CONTENT_TYPE + "; charset=" + charset;
414 }
415 return new HtmlLayout(locationInfo, title, contentType, charset, fontName, fontSize.getFontSize(),
416 fontSize.larger().getFontSize());
417 }
418 }
419 }