001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 * 
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 * 
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.log4j;
019
020import org.apache.log4j.spi.LoggingEvent;
021import org.apache.log4j.spi.LocationInfo;
022import org.apache.log4j.helpers.Transform;
023
024/**
025 * This layout outputs events in a HTML table.
026 *
027 * Appenders using this layout should have their encoding
028 * set to UTF-8 or UTF-16, otherwise events containing
029 * non ASCII characters could result in corrupted
030 * log files.
031 *
032 *  @author Ceki Gülcü
033 */
034public class HTMLLayout extends Layout {
035
036  protected final int BUF_SIZE = 256;
037  protected final int MAX_CAPACITY = 1024;
038
039  static String TRACE_PREFIX = "<br>&nbsp;&nbsp;&nbsp;&nbsp;";
040
041  // output buffer appended to when format() is invoked
042  private StringBuffer sbuf = new StringBuffer(BUF_SIZE);
043
044  /**
045     A string constant used in naming the option for setting the the
046     location information flag.  Current value of this string
047     constant is <b>LocationInfo</b>.
048
049     <p>Note that all option keys are case sensitive.
050
051     @deprecated Options are now handled using the JavaBeans paradigm.
052     This constant is not longer needed and will be removed in the
053     <em>near</em> term.
054
055  */
056  public static final String LOCATION_INFO_OPTION = "LocationInfo";
057
058  /**
059     A string constant used in naming the option for setting the the
060     HTML document title.  Current value of this string
061     constant is <b>Title</b>.
062  */
063  public static final String TITLE_OPTION = "Title";
064
065  // Print no location info by default
066  boolean locationInfo = false;
067
068  String title = "Log4J Log Messages";
069
070  /**
071     The <b>LocationInfo</b> option takes a boolean value. By
072     default, it is set to false which means there will be no location
073     information output by this layout. If the the option is set to
074     true, then the file name and line number of the statement
075     at the origin of the log statement will be output.
076
077     <p>If you are embedding this layout within an {@link
078     org.apache.log4j.net.SMTPAppender} then make sure to set the
079     <b>LocationInfo</b> option of that appender as well.
080   */
081  public
082  void setLocationInfo(boolean flag) {
083    locationInfo = flag;
084  }
085
086  /**
087     Returns the current value of the <b>LocationInfo</b> option.
088   */
089  public
090  boolean getLocationInfo() {
091    return locationInfo;
092  }
093
094  /**
095    The <b>Title</b> option takes a String value. This option sets the
096    document title of the generated HTML document.
097
098    <p>Defaults to 'Log4J Log Messages'.
099  */
100  public
101  void setTitle(String title) {
102    this.title = title;
103  }
104
105  /**
106     Returns the current value of the <b>Title</b> option.
107  */
108  public
109  String getTitle() {
110    return title;
111  }
112
113 /**
114     Returns the content type output by this layout, i.e "text/html".
115  */
116  public
117  String getContentType() {
118    return "text/html";
119  }
120
121  /**
122     No options to activate.
123  */
124  public
125  void activateOptions() {
126  }
127
128  public
129  String format(LoggingEvent event) {
130
131    if(sbuf.capacity() > MAX_CAPACITY) {
132      sbuf = new StringBuffer(BUF_SIZE);
133    } else {
134      sbuf.setLength(0);
135    }
136
137    sbuf.append(Layout.LINE_SEP + "<tr>" + Layout.LINE_SEP);
138
139    sbuf.append("<td>");
140    sbuf.append(event.timeStamp - LoggingEvent.getStartTime());
141    sbuf.append("</td>" + Layout.LINE_SEP);
142
143    String escapedThread = Transform.escapeTags(event.getThreadName());
144    sbuf.append("<td title=\"" + escapedThread + " thread\">");
145    sbuf.append(escapedThread);
146    sbuf.append("</td>" + Layout.LINE_SEP);
147
148    sbuf.append("<td title=\"Level\">");
149    if (event.getLevel().equals(Level.DEBUG)) {
150      sbuf.append("<font color=\"#339933\">");
151      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
152      sbuf.append("</font>");
153    }
154    else if(event.getLevel().isGreaterOrEqual(Level.WARN)) {
155      sbuf.append("<font color=\"#993300\"><strong>");
156      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
157      sbuf.append("</strong></font>");
158    } else {
159      sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel())));
160    }
161    sbuf.append("</td>" + Layout.LINE_SEP);
162
163    String escapedLogger = Transform.escapeTags(event.getLoggerName());
164    sbuf.append("<td title=\"" + escapedLogger + " category\">");
165    sbuf.append(escapedLogger);
166    sbuf.append("</td>" + Layout.LINE_SEP);
167
168    if(locationInfo) {
169      LocationInfo locInfo = event.getLocationInformation();
170      sbuf.append("<td>");
171      sbuf.append(Transform.escapeTags(locInfo.getFileName()));
172      sbuf.append(':');
173      sbuf.append(locInfo.getLineNumber());
174      sbuf.append("</td>" + Layout.LINE_SEP);
175    }
176
177    sbuf.append("<td title=\"Message\">");
178    sbuf.append(Transform.escapeTags(event.getRenderedMessage()));
179    sbuf.append("</td>" + Layout.LINE_SEP);
180    sbuf.append("</tr>" + Layout.LINE_SEP);
181
182    if (event.getNDC() != null) {
183      sbuf.append("<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : xx-small;\" colspan=\"6\" title=\"Nested Diagnostic Context\">");
184      sbuf.append("NDC: " + Transform.escapeTags(event.getNDC()));
185      sbuf.append("</td></tr>" + Layout.LINE_SEP);
186    }
187
188    String[] s = event.getThrowableStrRep();
189    if(s != null) {
190      sbuf.append("<tr><td bgcolor=\"#993300\" style=\"color:White; font-size : xx-small;\" colspan=\"6\">");
191      appendThrowableAsHTML(s, sbuf);
192      sbuf.append("</td></tr>" + Layout.LINE_SEP);
193    }
194
195    return sbuf.toString();
196  }
197
198  void appendThrowableAsHTML(String[] s, StringBuffer sbuf) {
199    if(s != null) {
200      int len = s.length;
201      if(len == 0)
202        return;
203      sbuf.append(Transform.escapeTags(s[0]));
204      sbuf.append(Layout.LINE_SEP);
205      for(int i = 1; i < len; i++) {
206        sbuf.append(TRACE_PREFIX);
207        sbuf.append(Transform.escapeTags(s[i]));
208        sbuf.append(Layout.LINE_SEP);
209      }
210    }
211  }
212
213  /**
214     Returns appropriate HTML headers.
215  */
216  public
217  String getHeader() {
218    StringBuffer sbuf = new StringBuffer();
219    sbuf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"  + Layout.LINE_SEP);
220    sbuf.append("<html>" + Layout.LINE_SEP);
221    sbuf.append("<head>" + Layout.LINE_SEP);
222    sbuf.append("<title>" + title + "</title>" + Layout.LINE_SEP);
223    sbuf.append("<style type=\"text/css\">"  + Layout.LINE_SEP);
224    sbuf.append("<!--"  + Layout.LINE_SEP);
225    sbuf.append("body, table {font-family: arial,sans-serif; font-size: x-small;}" + Layout.LINE_SEP);
226    sbuf.append("th {background: #336699; color: #FFFFFF; text-align: left;}" + Layout.LINE_SEP);
227    sbuf.append("-->" + Layout.LINE_SEP);
228    sbuf.append("</style>" + Layout.LINE_SEP);
229    sbuf.append("</head>" + Layout.LINE_SEP);
230    sbuf.append("<body bgcolor=\"#FFFFFF\" topmargin=\"6\" leftmargin=\"6\">" + Layout.LINE_SEP);
231    sbuf.append("<hr size=\"1\" noshade>" + Layout.LINE_SEP);
232    sbuf.append("Log session start time " + new java.util.Date() + "<br>" + Layout.LINE_SEP);
233    sbuf.append("<br>" + Layout.LINE_SEP);
234    sbuf.append("<table cellspacing=\"0\" cellpadding=\"4\" border=\"1\" bordercolor=\"#224466\" width=\"100%\">" + Layout.LINE_SEP);
235    sbuf.append("<tr>" + Layout.LINE_SEP);
236    sbuf.append("<th>Time</th>" + Layout.LINE_SEP);
237    sbuf.append("<th>Thread</th>" + Layout.LINE_SEP);
238    sbuf.append("<th>Level</th>" + Layout.LINE_SEP);
239    sbuf.append("<th>Category</th>" + Layout.LINE_SEP);
240    if(locationInfo) {
241      sbuf.append("<th>File:Line</th>" + Layout.LINE_SEP);
242    }
243    sbuf.append("<th>Message</th>" + Layout.LINE_SEP);
244    sbuf.append("</tr>" + Layout.LINE_SEP);
245    return sbuf.toString();
246  }
247
248  /**
249     Returns the appropriate HTML footers.
250  */
251  public
252  String getFooter() {
253    StringBuffer sbuf = new StringBuffer();
254    sbuf.append("</table>" + Layout.LINE_SEP);
255    sbuf.append("<br>" + Layout.LINE_SEP);
256    sbuf.append("</body></html>");
257    return sbuf.toString();
258  }
259
260  /**
261     The HTML layout handles the throwable contained in logging
262     events. Hence, this method return <code>false</code>.  */
263  public
264  boolean ignoresThrowable() {
265    return false;
266  }
267}