/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jpcsp.log; import org.apache.log4j.Layout; import org.apache.log4j.Level; import org.apache.log4j.helpers.Transform; import org.apache.log4j.spi.LocationInfo; import org.apache.log4j.spi.LoggingEvent; /** * This layout outputs events in a HTML table. * * Appenders using this layout should have their encoding set to UTF-8 or * UTF-16, otherwise events containing non ASCII characters could result in * corrupted log files. * * @author Ceki Gülcü * @modified Florent Castelli */ public class HTMLLayout extends Layout { protected final int BUF_SIZE = 256; protected final int MAX_CAPACITY = 1024; static String TRACE_PREFIX = "<br>    "; // output buffer appended to when format() is invoked private StringBuffer sbuf = new StringBuffer(BUF_SIZE); /** * A string constant used in naming the option for setting the the location * information flag. Current value of this string constant is * <b>LocationInfo</b>. * * <p> * Note that all option keys are case sensitive. * * @deprecated Options are now handled using the JavaBeans paradigm. This * constant is not longer needed and will be removed in the * <em>near</em> term. */ @Deprecated public static final String LOCATION_INFO_OPTION = "LocationInfo"; /** * A string constant used in naming the option for setting the the HTML * document title. Current value of this string constant is <b>Title</b>. */ public static final String TITLE_OPTION = "Title"; // Print no location info by default boolean locationInfo = false; String title = "Log4J Log Messages"; /** * The <b>LocationInfo</b> option takes a boolean value. By default, it is * set to false which means there will be no location information output by * this layout. If the the option is set to true, then the file name and * line number of the statement at the origin of the log statement will be * output. * * <p> * If you are embedding this layout within an * {@link org.apache.log4j.net.SMTPAppender} then make sure to set the * <b>LocationInfo</b> option of that appender as well. */ public void setLocationInfo(boolean flag) { locationInfo = flag; } /** * Returns the current value of the <b>LocationInfo</b> option. */ public boolean getLocationInfo() { return locationInfo; } /** * The <b>Title</b> option takes a String value. This option sets the * document title of the generated HTML document. * * <p> * Defaults to 'Log4J Log Messages'. */ public void setTitle(String title) { this.title = title; } /** * Returns the current value of the <b>Title</b> option. */ public String getTitle() { return title; } /** * Returns the content type output by this layout, i.e "text/html". */ @Override public String getContentType() { return "text/html"; } /** * No options to activate. */ @Override public void activateOptions() { } @Override public String format(LoggingEvent event) { if (sbuf.capacity() > MAX_CAPACITY) { sbuf = new StringBuffer(BUF_SIZE); } else { sbuf.setLength(0); } sbuf.append(Layout.LINE_SEP + "<tr>" + Layout.LINE_SEP); sbuf.append("<td>"); sbuf.append(event.timeStamp - LoggingEvent.getStartTime()); sbuf.append("</td>" + Layout.LINE_SEP); String escapedThread = Transform.escapeTags(event.getThreadName()); sbuf.append("<td title=\"" + escapedThread + " thread\">"); sbuf.append(escapedThread); sbuf.append("</td>" + Layout.LINE_SEP); sbuf.append("<td title=\"Level\" loglevel=\""); sbuf.append(event.getLevel().toInt()); sbuf.append("\">"); if (event.getLevel().equals(Level.DEBUG)) { sbuf.append("<font color=\"#339933\">"); sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel()))); sbuf.append("</font>"); } else if (event.getLevel().isGreaterOrEqual(Level.WARN)) { sbuf.append("<font color=\"#993300\"><strong>"); sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel()))); sbuf.append("</strong></font>"); } else { sbuf.append(Transform.escapeTags(String.valueOf(event.getLevel()))); } sbuf.append("</td>" + Layout.LINE_SEP); String escapedLogger = Transform.escapeTags(event.getLoggerName()); sbuf.append("<td title=\"" + escapedLogger + " category\">"); sbuf.append(escapedLogger); sbuf.append("</td>" + Layout.LINE_SEP); if (locationInfo) { LocationInfo locInfo = event.getLocationInformation(); sbuf.append("<td>"); sbuf.append(Transform.escapeTags(locInfo.getFileName())); sbuf.append(':'); sbuf.append(locInfo.getLineNumber()); sbuf.append("</td>" + Layout.LINE_SEP); } sbuf.append("<td title=\"Message\" class=\"message\">"); sbuf.append(Transform.escapeTags(event.getRenderedMessage())); sbuf.append("</td>" + Layout.LINE_SEP); sbuf.append("</tr>" + Layout.LINE_SEP); if (event.getNDC() != null) { sbuf .append("<tr><td bgcolor=\"#EEEEEE\" style=\"font-size : xx-small;\" colspan=\"6\" title=\"Nested Diagnostic Context\">"); sbuf.append("NDC: " + Transform.escapeTags(event.getNDC())); sbuf.append("</td></tr>" + Layout.LINE_SEP); } String[] s = event.getThrowableStrRep(); if (s != null) { sbuf .append("<tr><td bgcolor=\"#993300\" style=\"color:White; font-size : xx-small;\" colspan=\"6\">"); appendThrowableAsHTML(s, sbuf); sbuf.append("</td></tr>" + Layout.LINE_SEP); } return sbuf.toString(); } void appendThrowableAsHTML(String[] s, StringBuffer sbuf) { if (s != null) { int len = s.length; if (len == 0) return; sbuf.append(Transform.escapeTags(s[0])); sbuf.append(Layout.LINE_SEP); for (int i = 1; i < len; i++) { sbuf.append(TRACE_PREFIX); sbuf.append(Transform.escapeTags(s[i])); sbuf.append(Layout.LINE_SEP); } } } /** * Returns appropriate HTML headers. */ @Override public String getHeader() { StringBuilder sbuf = new StringBuilder(); sbuf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">" + Layout.LINE_SEP); sbuf.append("<html>" + Layout.LINE_SEP); sbuf.append("<head>" + Layout.LINE_SEP); sbuf.append("<title>" + title + "</title>" + Layout.LINE_SEP); sbuf.append("<style type=\"text/css\">" + Layout.LINE_SEP); sbuf.append("<!--" + Layout.LINE_SEP); sbuf.append("body, table {font-family: arial,sans-serif; font-size: x-small;}" + Layout.LINE_SEP); sbuf.append("th {background: #336699; color: #FFFFFF; text-align: left;}" + Layout.LINE_SEP); sbuf.append("td.message {white-space: pre; font-family: monospace;}" + Layout.LINE_SEP); sbuf.append("-->" + Layout.LINE_SEP); sbuf.append("</style>" + Layout.LINE_SEP); sbuf.append("<script type=\"text/javascript\">" + Layout.LINE_SEP); sbuf.append("var isIE = false;" + Layout.LINE_SEP); sbuf.append("if(navigator.userAgent.indexOf('MSIE') >= 0){isIE = true;}" + Layout.LINE_SEP); sbuf.append("function changeLogLevel(level) {" + Layout.LINE_SEP); sbuf.append(" var allElements, e;" + Layout.LINE_SEP); sbuf.append(" allElements = document.getElementsByTagName(\"td\");" + Layout.LINE_SEP); sbuf.append(" for ( var i = 0; i < allElements.length; i++) {" + Layout.LINE_SEP); sbuf.append(" e = allElements[i];" + Layout.LINE_SEP); sbuf.append(" if(e.getAttribute(\"logLevel\") != null)" + Layout.LINE_SEP); sbuf.append(" if(e.getAttribute(\"logLevel\") < level)" + Layout.LINE_SEP); sbuf.append(" e.parentNode.style.display = \"none\";" + Layout.LINE_SEP); sbuf.append(" else" + Layout.LINE_SEP); sbuf.append(" e.parentNode.style.display = isIE ? \"block\" : \"table-row\";" + Layout.LINE_SEP); sbuf.append(" }" + Layout.LINE_SEP); sbuf.append("}" + Layout.LINE_SEP); sbuf.append("function findUnimplemented() {" + Layout.LINE_SEP); sbuf.append(" var allElements, e, recorded;" + Layout.LINE_SEP); sbuf.append(" recorded = new Array();" + Layout.LINE_SEP); sbuf.append(" allElements = document.getElementsByTagName(\"td\");" + Layout.LINE_SEP); sbuf.append(" for ( var i = 0; i < allElements.length; i++) {" + Layout.LINE_SEP); sbuf.append(" e = allElements[i];" + Layout.LINE_SEP); sbuf.append(" if (e.getAttribute(\"title\") == \"Message\") {" + Layout.LINE_SEP); sbuf.append(" var m = isIE ? e.innerHTML.toLowerCase() : e.textContent.toLowerCase();" + Layout.LINE_SEP); sbuf.append(" if ((m.indexOf(\"unimplement\") == -1" + Layout.LINE_SEP); sbuf.append(" && m.indexOf(\"unsupport\") == -1)" + Layout.LINE_SEP); sbuf.append(" || recorded[m] != null)" + Layout.LINE_SEP); sbuf.append(" e.parentNode.style.display = \"none\";" + Layout.LINE_SEP); sbuf.append(" else {" + Layout.LINE_SEP); sbuf.append(" if(m.indexOf(\"unsupported syscall\") != 1)" + Layout.LINE_SEP); sbuf.append(" m = m.substr(0, m.length - 27);" + Layout.LINE_SEP); sbuf.append(" if(recorded[m] != null)" + Layout.LINE_SEP); sbuf.append(" e.parentNode.style.display = \"none\";" + Layout.LINE_SEP); sbuf.append(" else { " + Layout.LINE_SEP); sbuf.append(" recorded[m] = true;" + Layout.LINE_SEP); sbuf.append(" e.parentNode.style.display = isIE ? \"block\" : \"table-row\";" + Layout.LINE_SEP); sbuf.append(" }" + Layout.LINE_SEP); sbuf.append(" }" + Layout.LINE_SEP); sbuf.append(" }" + Layout.LINE_SEP); sbuf.append(" }" + Layout.LINE_SEP); sbuf.append("}" + Layout.LINE_SEP); sbuf.append("</script>" + Layout.LINE_SEP); sbuf.append("</head>" + Layout.LINE_SEP); sbuf.append("<body bgcolor=\"#FFFFFF\" topmargin=\"6\" leftmargin=\"6\">" + Layout.LINE_SEP); for (Level l : new Level[] { Level.FATAL, Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE }) { sbuf.append("<button onclick=\"javascript:changeLogLevel("); sbuf.append(l.toInt()); sbuf.append(")\" type=\"button\">"); sbuf.append(l); sbuf.append("</button>" + Layout.LINE_SEP); } sbuf.append("<button onclick=\"javascript:findUnimplemented()\" type=\"button\">Find unimplemented</button>" + Layout.LINE_SEP); sbuf.append("<hr size=\"1\" noshade>" + Layout.LINE_SEP); sbuf.append("Log session start time " + new java.util.Date() + "<br>" + Layout.LINE_SEP); sbuf.append("<br>" + Layout.LINE_SEP); sbuf.append("<table cellspacing=\"0\" cellpadding=\"4\" border=\"1\" bordercolor=\"#224466\" width=\"100%\">" + Layout.LINE_SEP); sbuf.append("<tr>" + Layout.LINE_SEP); sbuf.append("<th>Time</th>" + Layout.LINE_SEP); sbuf.append("<th>Thread</th>" + Layout.LINE_SEP); sbuf.append("<th>Level</th>" + Layout.LINE_SEP); sbuf.append("<th>Category</th>" + Layout.LINE_SEP); if (locationInfo) { sbuf.append("<th>File:Line</th>" + Layout.LINE_SEP); } sbuf.append("<th>Message</th>" + Layout.LINE_SEP); sbuf.append("</tr>" + Layout.LINE_SEP); return sbuf.toString(); } /** * Returns the appropriate HTML footers. */ @Override public String getFooter() { StringBuilder sbuf = new StringBuilder(); sbuf.append("</table>" + Layout.LINE_SEP); sbuf.append("<br>" + Layout.LINE_SEP); sbuf.append("</body></html>"); return sbuf.toString(); } /** * The HTML layout handles the throwable contained in logging events. Hence, * this method return <code>false</code>. */ @Override public boolean ignoresThrowable() { return false; } }