[LOGBack-dev] svn commit: r559 - in logback/trunk: . logback-classic logback-classic/src/main/java/ch/qos/logback/classic logback-classic/src/main/java/ch/qos/logback/classic/helpers logback-classic/src/main/java/ch/qos/logback/classic/html logback-classic/src/main/java/ch/qos/logback/classic/net logback-classic/src/main/java/ch/qos/logback/classic/pattern logback-classic/src/main/java/ch/qos/logback/classic/spi logback-classic/src/test/java/ch/qos/logback/classic/html

noreply.seb at qos.ch noreply.seb at qos.ch
Tue Sep 12 09:35:04 CEST 2006


Author: seb
Date: Tue Sep 12 09:35:04 2006
New Revision: 559

Added:
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/Transform.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/html/
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/html/HTMLLayout.java
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/html/
   logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/html/HTMLLayoutTest.java
Modified:
   logback/trunk/logback-classic/pom.xml
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/ThrowableHandlingConverter.java
   logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java
   logback/trunk/pom.xml

Log:
- added HTMLLayout, work in progress
- defaultConverterMap in PatternLayout now is public, to allow sharing of the map with HTMLLayout
- added a Dom4j dependency for Classic tests, in pom.xml
- a few re-format of classes, replacing tabs by spaces


Modified: logback/trunk/logback-classic/pom.xml
==============================================================================
--- logback/trunk/logback-classic/pom.xml	(original)
+++ logback/trunk/logback-classic/pom.xml	Tue Sep 12 09:35:04 2006
@@ -42,7 +42,13 @@
 			<artifactId>mail</artifactId>
 			<scope>compile</scope>
 		</dependency>
-
+		
+		<dependency>
+			<groupId>dom4j</groupId>
+			<artifactId>dom4j</artifactId>
+			<scope>test</scope>
+		</dependency>
+		
 		<dependency>
 			<groupId>ch.qos.logback</groupId>
 			<artifactId>logback-core</artifactId>

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/PatternLayout.java	Tue Sep 12 09:35:04 2006
@@ -38,7 +38,7 @@
 
   // FIXME fix exception handling
 
-  static final Map<String, String> defaultConverterMap = new HashMap<String, String>();
+  public static final Map<String, String> defaultConverterMap = new HashMap<String, String>();
 
   static {
 

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/Transform.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/Transform.java	Tue Sep 12 09:35:04 2006
@@ -0,0 +1,98 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+package ch.qos.logback.classic.helpers;
+
+/**
+ * Utility class for transforming strings.
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @author Michael A. McAngus
+ */
+public class Transform {
+  private static final String CDATA_START = "<![CDATA[";
+  private static final String CDATA_END = "]]>";
+  private static final String CDATA_PSEUDO_END = "]]&gt;";
+  private static final String CDATA_EMBEDED_END = CDATA_END + CDATA_PSEUDO_END
+      + CDATA_START;
+  private static final int CDATA_END_LEN = CDATA_END.length();
+
+  /**
+   * This method takes a string which may contain HTML tags (ie, &lt;b&gt;,
+   * &lt;table&gt;, etc) and replaces any '<' and '>' characters with
+   * respective predefined entity references.
+   * 
+   * @param input
+   *          The text to be converted.
+   */
+  public static String escapeTags(final String input) {
+    // Check if the string is null or zero length -- if so, return
+    // what was sent in.
+    if ((input == null) || (input.length() == 0)
+        || (input.indexOf("<") == -1 && input.indexOf(">") == -1)) {
+      return input;
+    }
+
+    StringBuffer buf = new StringBuffer(input);
+    for (int i = 0; i < buf.length(); i++) {
+      char ch = buf.charAt(i);
+      if (ch == '<') {
+        buf.replace(i, i + 1, "&lt;");
+      } else if (ch == '>') {
+        buf.replace(i, i + 1, "&gt;");
+      }
+    }
+    return buf.toString();
+  }
+
+  // public static void appendEscapingCDATA(StringBuffer buf, String str) {
+  //            
+  // }
+
+  /**
+   * Ensures that embeded CDEnd strings (]]>) are handled properly within
+   * message, NDC and throwable tag text.
+   * 
+   * @param output
+   *          Writer. The initial CDSutart (<![CDATA[) and final CDEnd (]]>) of
+   *          the CDATA section are the responsibility of the calling method.
+   * 
+   * @param str
+   *          The String that is inserted into an existing CDATA Section.
+   */
+  public static void appendEscapingCDATA(StringBuffer output, String str) {
+    if (str == null) {
+      return;
+    }
+
+    int end = str.indexOf(CDATA_END);
+
+    if (end < 0) {
+      output.append(str);
+
+      return;
+    }
+
+    int start = 0;
+
+    while (end > -1) {
+      output.append(str.substring(start, end));
+      output.append(CDATA_EMBEDED_END);
+      start = end + CDATA_END_LEN;
+
+      if (start < str.length()) {
+        end = str.indexOf(CDATA_END, start);
+      } else {
+        return;
+      }
+    }
+
+    output.append(str.substring(start));
+  }
+}

Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/html/HTMLLayout.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/html/HTMLLayout.java	Tue Sep 12 09:35:04 2006
@@ -0,0 +1,424 @@
+/**
+ * Logback: the reliable, generic, fast and flexible logging framework.
+ * 
+ * Copyright (C) 1999-2006, QOS.ch
+ * 
+ * This library is free software, you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the Free
+ * Software Foundation.
+ */
+
+package ch.qos.logback.classic.html;
+
+import ch.qos.logback.classic.ClassicLayout;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.helpers.Transform;
+import ch.qos.logback.classic.pattern.NopThrowableInformationConverter;
+import ch.qos.logback.classic.pattern.ThrowableHandlingConverter;
+import ch.qos.logback.classic.pattern.ThrowableInformationConverter;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableInformation;
+import ch.qos.logback.core.LayoutBase;
+import ch.qos.logback.core.pattern.Converter;
+import ch.qos.logback.core.pattern.DynamicConverter;
+import ch.qos.logback.core.pattern.parser.Node;
+import ch.qos.logback.core.pattern.parser.Parser;
+import ch.qos.logback.core.pattern.parser.ScanException;
+
+/**
+ * 
+ * HTMLLayout outputs events in an HTML table. The content of the table columns
+ * are specified using a conversion pattern. See
+ * {@link ch.qos.logback.classic.PatternLayout} for documentation on the
+ * available patterns.
+ * 
+ * @author Ceki G&uuml;lc&uuml;
+ * @author S&eacute;bastien Pennec
+ */
+public class HTMLLayout extends LayoutBase implements ClassicLayout {
+
+  /**
+   * Default pattern string for log output. Currently set to the string <b>"%m"
+   * </b> which just prints the application supplied message.
+   */
+  static final String DEFAULT_CONVERSION_PATTERN = "%date%thread%level%logger%mdc%msg";
+
+  static final String TRACE_PREFIX = "<br />&nbsp;&nbsp;&nbsp;&nbsp;";
+  protected final int BUF_SIZE = 256;
+  protected final int MAX_CAPACITY = 1024;
+  
+  private String pattern;
+  
+  private Converter head;
+  
+  //private String timezone;
+  private String title = "Logback Log Messages";
+  private boolean locationInfo;
+
+  private boolean internalCSS = false;
+  private String url2ExternalCSS = "http://logging.apache.org/log4j/docs/css/eventTable-1.0.css";
+
+  // Does our PatternConverter chain handle throwable on its own?
+  private boolean chainHandlesThrowable;
+
+  // counter keeping track of the rows output
+  private long counter = 0;
+
+  /**
+   * Constructs a PatternLayout using the DEFAULT_LAYOUT_PATTERN.
+   * 
+   * The default pattern just produces the application supplied message.
+   */
+  public HTMLLayout() {
+    pattern = DEFAULT_CONVERSION_PATTERN;
+  }
+
+  /**
+   * Set the <b>ConversionPattern </b> option. This is the string which controls
+   * formatting and consists of a mix of literal content and conversion
+   * specifiers.
+   */
+  public void setPattern(String conversionPattern) {
+    pattern = conversionPattern;
+  }
+
+  /**
+   * Returns the value of the <b>ConversionPattern </b> option.
+   */
+  public String getPattern() {
+    return pattern;
+  }
+
+  /**
+   * Parses the pattern and creates the Converter linked list.
+   */
+  public void start() {
+    try {
+      Parser p = new Parser(pattern);
+      if (getContext() != null) {
+        p.setStatusManager(getContext().getStatusManager());
+      }
+      Node t = p.parse();
+      this.head = p.compile(t, PatternLayout.defaultConverterMap);
+      postCompileProcessing(head);
+      DynamicConverter.startConverters(this.head);
+    }  catch (ScanException ex) {
+      addError("Incorrect pattern found", ex);
+    }
+    
+    started = true;
+  }
+  
+  private void postCompileProcessing(Converter c) {
+    while (c != null) {
+      if (c instanceof ThrowableHandlingConverter) {
+        chainHandlesThrowable = true;
+      }
+      c = c.getNext();
+    }
+    chainHandlesThrowable = false;
+  }
+
+  /**
+   * The <b>Title </b> option takes a String value. This option sets the
+   * document title of the generated HTML document.
+   * 
+   * <p>
+   * Defaults to 'Logback 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 value of the internalCSS option. See {@link #setInternalCSS}
+   * method for details about the meaning of this option.
+   * 
+   * @return boolean Value of internalCSS option
+   */
+  public boolean isInternalCSS() {
+    return internalCSS;
+  }
+
+  /**
+   * Set the value of the internalCSS option. If set to true, the generated HTML
+   * ouput will include an internal cascading style sheet. Otherwise, the
+   * generated HTML output will include a reference to an external CSS.
+   * <p>
+   * By default, <code>internalCSS</code> value is set to false, that is, by
+   * default, only a link to an external CSS file will be generated.
+   * 
+   * @see #setURL2ExternalCSS
+   * 
+   * @param internalCSS
+   */
+  public void setInternalCSS(boolean internalCSS) {
+    this.internalCSS = internalCSS;
+  }
+
+  /**
+   * Return the URL to the external CSS file. See {@link #setURL2ExternalCSS}
+   * method for details about the meaning of this option.
+   * 
+   * @return URL to the external CSS file.
+   */
+  public String getURL2ExternalCSS() {
+    return url2ExternalCSS;
+  }
+
+  /**
+   * Set the URL for the external CSS file. By default, the external CSS file is
+   * set to "http://logging.apache.org/log4j/docs/css/eventTable-1.0.css".
+   */
+  public void setURL2ExternalCSS(String url2ExternalCSS) {
+    this.url2ExternalCSS = url2ExternalCSS;
+  }
+
+  /**
+   * Returns the content type output by this layout, i.e "text/html".
+   */
+  public String getContentType() {
+    return "text/html";
+  }
+
+  void appendThrowableAsHTML(final String[] s, final StringBuffer sbuf) {
+    if (s != null) {
+      int len = s.length;
+      if (len == 0) {
+        return;
+      }
+      sbuf.append(Transform.escapeTags(s[0]));
+      sbuf.append(LINE_SEP);
+      for (int i = 1; i < len; i++) {
+        sbuf.append(TRACE_PREFIX);
+        sbuf.append(Transform.escapeTags(s[i]));
+        sbuf.append(LINE_SEP);
+      }
+    }
+  }
+
+  /**
+   * Returns appropriate HTML headers.
+   */
+  public String getHeader() {
+    StringBuffer sbuf = new StringBuffer();
+    sbuf.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"");
+    sbuf.append(" \"http://www.w3.org/TR/html4/loose.dtd\">");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<html>");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<head>");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<title>");
+    sbuf.append(title);
+    sbuf.append("</title>");
+    sbuf.append(LINE_SEP);
+    if (internalCSS) {
+      getInternalCSS(sbuf);
+    } else {
+      sbuf.append("<LINK REL=StyleSheet HREF=\"");
+      sbuf.append(url2ExternalCSS);
+      sbuf.append("\" TITLE=\"Basic\">");
+    }
+    sbuf.append(LINE_SEP);
+    sbuf.append("</head>");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<body>");
+    sbuf.append(LINE_SEP);
+
+    sbuf.append("<hr size=\"1\" noshade>");
+    sbuf.append(LINE_SEP);
+
+    sbuf.append("Log session start time ");
+    sbuf.append(new java.util.Date());
+    sbuf.append("<br>");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<br>");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<table cellspacing=\"0\">");
+    sbuf.append(LINE_SEP);
+
+    sbuf.append("<tr class=\"header\">");
+    sbuf.append(LINE_SEP);
+    
+    Converter c = head;
+    String name;
+    while (c != null) {
+      name = computeConverterName(c);
+      if (name == null) {
+        c = c.getNext();
+        continue;
+      }
+      sbuf.append("<td class=\"");
+      sbuf.append(computeConverterName(c));
+      sbuf.append("\">");
+      sbuf.append("<td>");
+      sbuf.append(computeConverterName(c));
+      sbuf.append("</td>");
+      sbuf.append(LINE_SEP);  
+      c = c.getNext();
+    }
+    sbuf.append("</tr>");
+    sbuf.append(LINE_SEP);
+
+    return sbuf.toString();
+  }
+
+  /**
+   * Returns the appropriate HTML footers.
+   */
+  public String getFooter() {
+    StringBuffer sbuf = new StringBuffer();
+    sbuf.append("</table>");
+    sbuf.append(LINE_SEP);
+    sbuf.append("<br>");
+    sbuf.append(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>.
+   */
+  public boolean ignoresThrowable() {
+    return false;
+  }
+
+  public String doLayout(Object event) {
+    return doLayout((LoggingEvent) event);
+  }
+
+  public String doLayout(LoggingEvent event) {
+
+    boolean odd = true;
+    if (((counter++) & 1) == 0) {
+      odd = false;
+    }
+
+    String level = event.getLevel().toString().toLowerCase();
+
+    StringBuffer buf = new StringBuffer();
+    buf.append(LINE_SEP);
+    buf.append("<tr class=\"");
+    buf.append(level);
+    if (odd) {
+      buf.append(" odd\">");
+    } else {
+      buf.append(" even\">");
+    }
+    buf.append(LINE_SEP);
+
+    Converter c = head;
+    while (c != null) {
+      if (c instanceof ThrowableHandlingConverter) {
+        ThrowableHandlingConverter converter = (ThrowableHandlingConverter)c;
+        if (converter.onNewLine(event)) {
+          buf.append("</tr>");
+          buf.append("<tr>");
+          appendEventToBuffer(buf, c, event);
+          if (c.getNext() != null) {
+            //here we assume that when we exist the while loop,
+            //a </tr> tag is added.
+            buf.append("</tr>");
+            buf.append("<tr>");
+          }
+        }
+      } else {
+        appendEventToBuffer(buf, c, event);
+      }
+      c = c.getNext();
+    }
+    buf.append("</tr>");
+    buf.append(LINE_SEP);
+
+    // if the pattern chain handles throwables then no need to do it again here.
+    if (!chainHandlesThrowable) {
+      ThrowableInformation ti = event.getThrowableInformation();
+      if (ti != null) {
+        String[] s = ti.getThrowableStrRep();
+        if (s != null) {
+          buf.append("<tr><td class=\"Exception\" colspan=\"6\">");
+          appendThrowableAsHTML(s, buf);
+          buf.append("</td></tr>");
+          buf.append(LINE_SEP);
+        }
+      }
+    }
+    return buf.toString();
+  }
+  
+  private void appendEventToBuffer(StringBuffer buf, Converter c, LoggingEvent event) {
+    buf.append("<td class=\"");
+    buf.append(computeConverterName(c));
+    buf.append("\">");
+    buf.append(c.convert(event));
+    buf.append("</td>");
+    buf.append(LINE_SEP);    
+  }
+
+  /**
+   * Generate an internal CSS file.
+   * 
+   * @param buf The StringBuffer where the CSS file will be placed.
+   */
+  void getInternalCSS(StringBuffer buf) {
+
+    buf.append("<STYLE  type=\"text/css\">");
+    buf.append(LINE_SEP);
+    buf.append("table { margin-left: 2em; margin-right: 2em; border-left: 2px solid #AAA; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TR.even { background: #FFFFFF; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TR.odd { background: #DADADA; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TR.warn TD.level, TR.error TD.level, TR.fatal TD.level {font-weight: bold; color: #FF4040 }");
+    buf.append(LINE_SEP);
+
+    buf.append("TD { padding-right: 1ex; padding-left: 1ex; border-right: 2px solid #AAA; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TD.Time, TD.Date { text-align: right; font-family: courier, monospace; font-size: smaller; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TD.Thread { text-align: left; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TD.Level { text-align: right; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TD.Logger { text-align: left; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TR.header { background: #9090FF; color: #FFF; font-weight: bold; font-size: larger; }");
+    buf.append(LINE_SEP);
+
+    buf.append("TD.Exception { background: #C0C0F0; font-family: courier, monospace;}");
+    buf.append(LINE_SEP);
+
+    buf.append("</STYLE>");
+    buf.append(LINE_SEP);
+
+  }
+  
+  private String computeConverterName(Converter c) {    
+    String className = c.getClass().getSimpleName();
+    int index = className.indexOf("Converter");
+    if (index == -1) {
+      return className;
+    } else {
+      return className.substring(0, index);
+    }
+  }
+
+}

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java	Tue Sep 12 09:35:04 2006
@@ -50,342 +50,341 @@
  * @since 1.0
  */
 public class SMTPAppender extends AppenderBase {
-	private Layout layout;
+  private Layout layout;
 
-	private String to;
-	private String from;
-	private String subject;
-	private String smtpHost;
-	private int bufferSize = 512;
-	private boolean locationInfo = false;
-
-	protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
-	protected Message msg;
-
-	protected TriggeringPolicy evaluator;
-
-	/**
-	 * The default constructor will instantiate the appender with a
-	 * {@link TriggeringEventEvaluator} that will trigger on events with level
-	 * ERROR or higher.
-	 */
-	public SMTPAppender() {
-		this(new DefaultEvaluator());
-	}
-
-	/**
-	 * Use <code>evaluator</code> passed as parameter as the {@link
-	 * TriggeringEventEvaluator} for this SMTPAppender.
-	 */
-	public SMTPAppender(TriggeringPolicy evaluator) {
-		this.evaluator = evaluator;
-	}
-
-	/**
-	 * Start the appender
-	 */
-	public void start() {
-		Properties props = new Properties(System.getProperties());
-		if (smtpHost != null) {
-			props.put("mail.smtp.host", smtpHost);
-		}
-
-		Session session = Session.getInstance(props, null);
-		// session.setDebug(true);
-		msg = new MimeMessage(session);
-
-		try {
-			if (from != null) {
-				msg.setFrom(getAddress(from));
-			} else {
-				msg.setFrom();
-			}
-
-			msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
-			if (subject != null) {
-				msg.setSubject(subject);
-			}
-
-			started = true;
-
-		} catch (MessagingException e) {
-			addError("Could not activate SMTPAppender options.", e);
-		}
-	}
-
-	/**
-	 * Perform SMTPAppender specific appending actions, mainly adding the event to
-	 * a cyclic buffer and checking if the event triggers an e-mail to be sent.
-	 */
-	protected void append(Object eventObject) {
-		LoggingEvent event = (LoggingEvent) eventObject;
-
-		if (!checkEntryConditions()) {
-			return;
-		}
-
-		event.getThreadName();
-		// event.getNDC();
-		// if (locationInfo) {
-		// event.getLocationInformation();
-		// }
-		cb.add(event);
-		//addInfo("Added event to the cyclic buffer: " + event.getMessage());
-
-		if (evaluator.isTriggeringEvent(null, event)) {
-			sendBuffer();
-		}
-	}
-
-	/**
-	 * This method determines if there is a sense in attempting to append.
-	 * 
-	 * <p>
-	 * It checks whether there is a set output target and also if there is a set
-	 * layout. If these checks fail, then the boolean value <code>false</code>
-	 * is returned.
-	 */
-	protected boolean checkEntryConditions() {
-		if (this.msg == null) {
-			addError("Message object not configured.");
-			return false;
-		}
-
-		if (this.evaluator == null) {
-			addError("No TriggeringPolicy is set for appender [" + name + "].");
-			return false;
-		}
-
-		if (this.layout == null) {
-			addError("No layout set for appender named [" + name + "].");
-			return false;
-		}
-		return true;
-	}
-
-	synchronized public void stop() {
-		this.started = false;
-	}
-
-	InternetAddress getAddress(String addressStr) {
-		try {
-			return new InternetAddress(addressStr);
-		} catch (AddressException e) {
-			addError("Could not parse address [" + addressStr + "].", e);
-			return null;
-		}
-	}
-
-	InternetAddress[] parseAddress(String addressStr) {
-		try {
-			return InternetAddress.parse(addressStr, true);
-		} catch (AddressException e) {
-			addError("Could not parse address [" + addressStr + "].", e);
-			return null;
-		}
-	}
-
-	/**
-	 * Returns value of the <b>To</b> option.
-	 */
-	public String getTo() {
-		return to;
-	}
-	
-	/**
-	 * Send the contents of the cyclic buffer as an e-mail message.
-	 */
-	protected void sendBuffer() {
-
-		// Note: this code already owns the monitor for this
-		// appender. This frees us from needing to synchronize on 'cb'.
-		try {
-			MimeBodyPart part = new MimeBodyPart();
-
-			StringBuffer sbuf = new StringBuffer();
-			String t = layout.getHeader();
-			if (t != null)
-				sbuf.append(t);
-			int len = cb.length();
-			for (int i = 0; i < len; i++) {
-				//sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
-				LoggingEvent event = cb.get();
-				sbuf.append(layout.doLayout(event));
-				// if (layout.ignoresThrowable()) {
-				// String[] s = event.getThrowableStrRep();
-				// if (s != null) {
-				// for (int j = 0; j < s.length; j++) {
-				// sbuf.append(s[j]);
-				// }
-				// }
-				// }
-			}
-			t = layout.getFooter();
-			if (t != null)
-				sbuf.append(t);
-			part.setContent(sbuf.toString(), "text/plain");
-
-			Multipart mp = new MimeMultipart();
-			mp.addBodyPart(part);
-			msg.setContent(mp);
-
-			msg.setSentDate(new Date());
-			Transport.send(msg);
-		} catch (Exception e) {
-			addError("Error occured while sending e-mail notification.", e);
-		}
-	}
-
-	/**
-	 * Returns value of the <b>EvaluatorClass</b> option.
-	 */
-	public String getEvaluatorClass() {
-		return evaluator == null ? null : evaluator.getClass().getName();
-	}
-
-	/**
-	 * Returns value of the <b>From</b> option.
-	 */
-	public String getFrom() {
-		return from;
-	}
-
-	/**
-	 * Returns value of the <b>Subject</b> option.
-	 */
-	public String getSubject() {
-		return subject;
-	}
-
-	/**
-	 * The <b>From</b> option takes a string value which should be a e-mail
-	 * address of the sender.
-	 */
-	public void setFrom(String from) {
-		this.from = from;
-	}
-
-	/**
-	 * The <b>Subject</b> option takes a string value which should be a the
-	 * subject of the e-mail message.
-	 */
-	public void setSubject(String subject) {
-		this.subject = subject;
-	}
-
-	/**
-	 * The <b>BufferSize</b> option takes a positive integer representing the
-	 * maximum number of logging events to collect in a cyclic buffer. When the
-	 * <code>BufferSize</code> is reached, oldest events are deleted as new
-	 * events are added to the buffer. By default the size of the cyclic buffer is
-	 * 512 events.
-	 */
-	public void setBufferSize(int bufferSize) {
-		this.bufferSize = bufferSize;
-		cb.resize(bufferSize);
-	}
-
-	/**
-	 * The <b>SMTPHost</b> option takes a string value which should be a the host
-	 * name of the SMTP server that will send the e-mail message.
-	 */
-	public void setSMTPHost(String smtpHost) {
-		this.smtpHost = smtpHost;
-	}
-
-	/**
-	 * Returns value of the <b>SMTPHost</b> option.
-	 */
-	public String getSMTPHost() {
-		return smtpHost;
-	}
-
-	/**
-	 * The <b>To</b> option takes a string value which should be a comma
-	 * separated list of e-mail address of the recipients.
-	 */
-	public void setTo(String to) {
-		this.to = to;
-	}
-
-	/**
-	 * Returns value of the <b>BufferSize</b> option.
-	 */
-	public int getBufferSize() {
-		return bufferSize;
-	}
-
-	/**
-	 * The <b>EvaluatorClass</b> option takes a string value representing the
-	 * name of the class implementing the {@link TriggeringEventEvaluator}
-	 * interface. A corresponding object will be instantiated and assigned as the
-	 * triggering event evaluator for the SMTPAppender.
-	 */
-	public void setEvaluatorClass(String value) {
-		try {
-			evaluator = (TriggeringPolicy) OptionHelper.instantiateByClassName(value,
-					TriggeringPolicy.class);
-		} catch (Exception ex) {
-			addError("Evaluator class instanciation failed");
-		}
-	}
-
-	/**
-	 * The <b>LocationInfo</b> option takes a boolean value. By default, it is
-	 * set to false which means there will be no effort to extract the location
-	 * information related to the event. As a result, the layout that formats the
-	 * events as they are sent out in an e-mail is likely to place the wrong
-	 * location information (if present in the format).
-	 * 
-	 * <p>
-	 * Location information extraction is comparatively very slow and should be
-	 * avoided unless performance is not a concern.
-	 */
-	public void setLocationInfo(boolean locationInfo) {
-		this.locationInfo = locationInfo;
-	}
-
-	/**
-	 * Returns value of the <b>LocationInfo</b> option.
-	 */
-	public boolean getLocationInfo() {
-		return locationInfo;
-	}
-
-	public Layout getLayout() {
-		return layout;
-	}
-
-	public void setLayout(Layout layout) {
-		this.layout = layout;
-	}
+  private String to;
+  private String from;
+  private String subject;
+  private String smtpHost;
+  private int bufferSize = 512;
+  private boolean locationInfo = false;
+
+  protected CyclicBuffer cb = new CyclicBuffer(bufferSize);
+  protected Message msg;
+
+  protected TriggeringPolicy evaluator;
+
+  /**
+   * The default constructor will instantiate the appender with a
+   * {@link TriggeringEventEvaluator} that will trigger on events with level
+   * ERROR or higher.
+   */
+  public SMTPAppender() {
+    this(new DefaultEvaluator());
+  }
+
+  /**
+   * Use <code>evaluator</code> passed as parameter as the {@link
+   * TriggeringEventEvaluator} for this SMTPAppender.
+   */
+  public SMTPAppender(TriggeringPolicy evaluator) {
+    this.evaluator = evaluator;
+  }
+
+  /**
+   * Start the appender
+   */
+  public void start() {
+    Properties props = new Properties(System.getProperties());
+    if (smtpHost != null) {
+      props.put("mail.smtp.host", smtpHost);
+    }
+
+    Session session = Session.getInstance(props, null);
+    // session.setDebug(true);
+    msg = new MimeMessage(session);
+
+    try {
+      if (from != null) {
+        msg.setFrom(getAddress(from));
+      } else {
+        msg.setFrom();
+      }
+
+      msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
+      if (subject != null) {
+        msg.setSubject(subject);
+      }
+
+      started = true;
+
+    } catch (MessagingException e) {
+      addError("Could not activate SMTPAppender options.", e);
+    }
+  }
+
+  /**
+   * Perform SMTPAppender specific appending actions, mainly adding the event to
+   * a cyclic buffer and checking if the event triggers an e-mail to be sent.
+   */
+  protected void append(Object eventObject) {
+    LoggingEvent event = (LoggingEvent) eventObject;
+
+    if (!checkEntryConditions()) {
+      return;
+    }
+
+    event.getThreadName();
+    // if (locationInfo) {
+    // event.getLocationInformation();
+    // }
+    cb.add(event);
+    // addInfo("Added event to the cyclic buffer: " + event.getMessage());
+
+    if (evaluator.isTriggeringEvent(null, event)) {
+      sendBuffer();
+    }
+  }
+
+  /**
+   * This method determines if there is a sense in attempting to append.
+   * 
+   * <p>
+   * It checks whether there is a set output target and also if there is a set
+   * layout. If these checks fail, then the boolean value <code>false</code>
+   * is returned.
+   */
+  protected boolean checkEntryConditions() {
+    if (this.msg == null) {
+      addError("Message object not configured.");
+      return false;
+    }
+
+    if (this.evaluator == null) {
+      addError("No TriggeringPolicy is set for appender [" + name + "].");
+      return false;
+    }
+
+    if (this.layout == null) {
+      addError("No layout set for appender named [" + name + "].");
+      return false;
+    }
+    return true;
+  }
+
+  synchronized public void stop() {
+    this.started = false;
+  }
+
+  InternetAddress getAddress(String addressStr) {
+    try {
+      return new InternetAddress(addressStr);
+    } catch (AddressException e) {
+      addError("Could not parse address [" + addressStr + "].", e);
+      return null;
+    }
+  }
+
+  InternetAddress[] parseAddress(String addressStr) {
+    try {
+      return InternetAddress.parse(addressStr, true);
+    } catch (AddressException e) {
+      addError("Could not parse address [" + addressStr + "].", e);
+      return null;
+    }
+  }
+
+  /**
+   * Returns value of the <b>To</b> option.
+   */
+  public String getTo() {
+    return to;
+  }
+
+  /**
+   * Send the contents of the cyclic buffer as an e-mail message.
+   */
+  protected void sendBuffer() {
+
+    // Note: this code already owns the monitor for this
+    // appender. This frees us from needing to synchronize on 'cb'.
+    try {
+      MimeBodyPart part = new MimeBodyPart();
+
+      StringBuffer sbuf = new StringBuffer();
+      String t = layout.getHeader();
+      if (t != null)
+        sbuf.append(t);
+      int len = cb.length();
+      for (int i = 0; i < len; i++) {
+        // sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
+        LoggingEvent event = cb.get();
+        sbuf.append(layout.doLayout(event));
+        // if (layout.ignoresThrowable()) {
+        // String[] s = event.getThrowableStrRep();
+        // if (s != null) {
+        // for (int j = 0; j < s.length; j++) {
+        // sbuf.append(s[j]);
+        // }
+        // }
+        // }
+      }
+      t = layout.getFooter();
+      if (t != null)
+        sbuf.append(t);
+      part.setContent(sbuf.toString(), "text/plain");
+
+      Multipart mp = new MimeMultipart();
+      mp.addBodyPart(part);
+      msg.setContent(mp);
+
+      msg.setSentDate(new Date());
+      Transport.send(msg);
+    } catch (Exception e) {
+      addError("Error occured while sending e-mail notification.", e);
+    }
+  }
+
+  /**
+   * Returns value of the <b>EvaluatorClass</b> option.
+   */
+  public String getEvaluatorClass() {
+    return evaluator == null ? null : evaluator.getClass().getName();
+  }
+
+  /**
+   * Returns value of the <b>From</b> option.
+   */
+  public String getFrom() {
+    return from;
+  }
+
+  /**
+   * Returns value of the <b>Subject</b> option.
+   */
+  public String getSubject() {
+    return subject;
+  }
+
+  /**
+   * The <b>From</b> option takes a string value which should be a e-mail
+   * address of the sender.
+   */
+  public void setFrom(String from) {
+    this.from = from;
+  }
+
+  /**
+   * The <b>Subject</b> option takes a string value which should be a the
+   * subject of the e-mail message.
+   */
+  public void setSubject(String subject) {
+    this.subject = subject;
+  }
+
+  /**
+   * The <b>BufferSize</b> option takes a positive integer representing the
+   * maximum number of logging events to collect in a cyclic buffer. When the
+   * <code>BufferSize</code> is reached, oldest events are deleted as new
+   * events are added to the buffer. By default the size of the cyclic buffer is
+   * 512 events.
+   */
+  public void setBufferSize(int bufferSize) {
+    this.bufferSize = bufferSize;
+    cb.resize(bufferSize);
+  }
+
+  /**
+   * The <b>SMTPHost</b> option takes a string value which should be a the host
+   * name of the SMTP server that will send the e-mail message.
+   */
+  public void setSMTPHost(String smtpHost) {
+    this.smtpHost = smtpHost;
+  }
+
+  /**
+   * Returns value of the <b>SMTPHost</b> option.
+   */
+  public String getSMTPHost() {
+    return smtpHost;
+  }
+
+  /**
+   * The <b>To</b> option takes a string value which should be a comma
+   * separated list of e-mail address of the recipients.
+   */
+  public void setTo(String to) {
+    this.to = to;
+  }
+
+  /**
+   * Returns value of the <b>BufferSize</b> option.
+   */
+  public int getBufferSize() {
+    return bufferSize;
+  }
+
+  /**
+   * The <b>EvaluatorClass</b> option takes a string value representing the
+   * name of the class implementing the {@link TriggeringEventEvaluator}
+   * interface. A corresponding object will be instantiated and assigned as the
+   * triggering event evaluator for the SMTPAppender.
+   */
+  public void setEvaluatorClass(String value) {
+    try {
+      evaluator = (TriggeringPolicy) OptionHelper.instantiateByClassName(value,
+          TriggeringPolicy.class);
+    } catch (Exception ex) {
+      addError("Evaluator class instanciation failed");
+    }
+  }
+
+  /**
+   * The <b>LocationInfo</b> option takes a boolean value. By default, it is
+   * set to false which means there will be no effort to extract the location
+   * information related to the event. As a result, the layout that formats the
+   * events as they are sent out in an e-mail is likely to place the wrong
+   * location information (if present in the format).
+   * 
+   * <p>
+   * Location information extraction is comparatively very slow and should be
+   * avoided unless performance is not a concern.
+   */
+  public void setLocationInfo(boolean locationInfo) {
+    this.locationInfo = locationInfo;
+  }
+
+  /**
+   * Returns value of the <b>LocationInfo</b> option.
+   */
+  public boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  public Layout getLayout() {
+    return layout;
+  }
+
+  public void setLayout(Layout layout) {
+    this.layout = layout;
+  }
 }
 
 class DefaultEvaluator implements TriggeringPolicy {
 
-	private boolean started;
+  private boolean started;
 
-	/**
-	 * Is this <code>event</code> the e-mail triggering event?
-	 * 
-	 * <p>
-	 * This method returns <code>true</code>, if the event level has ERROR
-	 * level or higher. Otherwise it returns <code>false</code>.
-	 */
-	public boolean isTriggeringEvent(File file, Object eventObject) {
-		LoggingEvent event = (LoggingEvent) eventObject;
-		return event.getLevel().isGreaterOrEqual(Level.ERROR);
-	}
-
-	public boolean isStarted() {
-		return started == true;
-	}
-
-	public void start() {
-		started = true;
-	}
-
-	public void stop() {
-		started = false;
-	}
+  /**
+   * Is this <code>event</code> the e-mail triggering event?
+   * 
+   * <p>
+   * This method returns <code>true</code>, if the event level has ERROR
+   * level or higher. Otherwise it returns <code>false</code>.
+   */
+  public boolean isTriggeringEvent(File file, Object eventObject) {
+    LoggingEvent event = (LoggingEvent) eventObject;
+    return event.getLevel().isGreaterOrEqual(Level.ERROR);
+  }
+
+  public boolean isStarted() {
+    return started == true;
+  }
+
+  public void start() {
+    started = true;
+  }
+
+  public void stop() {
+    started = false;
+  }
 }

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/ThrowableHandlingConverter.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/ThrowableHandlingConverter.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/pattern/ThrowableHandlingConverter.java	Tue Sep 12 09:35:04 2006
@@ -1,6 +1,7 @@
 package ch.qos.logback.classic.pattern;
 
 import ch.qos.logback.classic.pattern.ClassicConverter;
+import ch.qos.logback.classic.spi.LoggingEvent;
 
 
 /**
@@ -12,4 +13,9 @@
   boolean handlesThrowable() {
     return true;
   }
+  
+  // tentatively...
+  public boolean onNewLine(LoggingEvent le) {
+    return le.getThrowableInformation() != null;
+  }
 }

Modified: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java
==============================================================================
--- logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java	(original)
+++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/spi/LoggingEvent.java	Tue Sep 12 09:35:04 2006
@@ -41,247 +41,247 @@
  */
 public class LoggingEvent implements Serializable {
 
-	/**
-	 * 
-	 */
-	private static final long serialVersionUID = 3022264832697160750L;
-
-	/**
-	 * 
-	 */
-	private static long startTime = System.currentTimeMillis();
-
-	/**
-	 * Fully qualified name of the calling Logger class. This field does not
-	 * survive serialization.
-	 * 
-	 * <p>
-	 * Note that the getCallerInformation() method relies on this fact.
-	 */
-	transient String fqnOfLoggerClass;
-
-	/**
-	 * The name of thread in which this logging event was generated.
-	 */
-	private String threadName;
-
-	/**
-	 * Level of logging event.
-	 * 
-	 * <p>
-	 * This field should not be accessed directly. You shoud use the {@link
-	 * #getLevel} method instead.
-	 * </p>
-	 * 
-	 */
-	private transient Level level;
-
-	private String message;
-	private String formattedMessage;
-
-	private Object[] argumentArray;
-
-	private ThrowableInformation throwableInfo;
-
-	private CallerData[] callerDataArray;
-	private LoggerRemoteView loggerRemoteView;
-
-	private Marker marker;
-	
-	private Map<String, String> mdcPropertyMap;
-
-	/**
-	 * The number of milliseconds elapsed from 1/1/1970 until logging event was
-	 * created.
-	 */
-	private long timeStamp;
-
-	public LoggingEvent() {
-	}
-
-	public LoggingEvent(String fqcn, Logger logger, Level level, String message,
-			Throwable throwable, Object[] argArray) {
-		this.fqnOfLoggerClass = fqcn;
-		this.loggerRemoteView = logger.getLoggerRemoteView();
-		this.level = level;
-		this.message = message;
-
-		if (throwable != null) {
-			this.throwableInfo = new ThrowableInformation(throwable);
-		}
-
-		if (argArray != null) {
-			formattedMessage = MessageFormatter.arrayFormat(message, argArray);
-		} else {
-			formattedMessage = message;
-		}
-		timeStamp = System.currentTimeMillis();
-
-		mdcPropertyMap = MDC.getPropertyMap();
-	}
-
-	public void setArgumentArray(Object[] argArray) {
-		if (this.argumentArray != null) {
-			throw new IllegalStateException("argArray has been already set");
-		}
-		this.argumentArray = argArray;
-	}
-
-	public Object[] getArgumentArray() {
-		return this.argumentArray;
-	}
-
-	public Level getLevel() {
-		return level;
-	}
-
-	public String getThreadName() {
-		if (threadName == null) {
-			threadName = (Thread.currentThread()).getName();
-		}
-		return threadName;
-	}
-
-	/**
-	 * @param threadName
-	 *          The threadName to set.
-	 * @throws IllegalStateException
-	 *           If threadName has been already set.
-	 */
-	public void setThreadName(String threadName) throws IllegalStateException {
-		if (this.threadName != null) {
-			throw new IllegalStateException("threadName has been already set");
-		}
-		this.threadName = threadName;
-	}
-
-	/**
-	 * Returns the throwable information contained within this event. May be
-	 * <code>null</code> if there is no such information.
-	 */
-	public ThrowableInformation getThrowableInformation() {
-		return throwableInfo;
-	}
-
-	/**
-	 * Set this event's throwable information.
-	 */
-	public void setThrowableInformation(ThrowableInformation ti) {
-		if (throwableInfo != null) {
-			throw new IllegalStateException(
-					"ThrowableInformation has been already set.");
-		} else {
-			throwableInfo = ti;
-		}
-	}
-
-	/**
-	 * This method should be called prior to serializing an event. It should also
-	 * be called when using asynchronous logging.
-	 */
-	public void prepareForDeferredProcessing() {
-		this.getThreadName();
-	}
-
-	public LoggerRemoteView getLoggerRemoteView() {
-		return loggerRemoteView;
-	}
-
-	public void setLoggerRemoteView(LoggerRemoteView loggerRemoteView) {
-		this.loggerRemoteView = loggerRemoteView;
-	}
-
-	public String getMessage() {
-		return message;
-	}
-
-	public void setMessage(String message) {
-		if (this.message != null) {
-			throw new IllegalStateException(
-					"The message for this event has been set already.");
-		}
-		this.message = message;
-	}
-
-	public long getTimeStamp() {
-		return timeStamp;
-	}
-
-	public void setTimeStamp(long timeStamp) {
-		this.timeStamp = timeStamp;
-	}
-
-	public void setLevel(Level level) {
-		if (this.level != null) {
-			throw new IllegalStateException(
-					"The level has been already set for this event.");
-		}
-		this.level = level;
-	}
-
-	/**
-	 * The time at which this class was loaded into memory, expressed in
-	 * millisecond elapsed since the epoch (1.1.1970).
-	 * 
-	 * @return The time as measured when this class was loaded into memory.
-	 */
-	public static final long getStartTime() {
-		return startTime;
-	}
-
-	/**
-	 * Get the caller information for this logging event. If caller information is
-	 * null at the time of its invocation, this method extracts location
-	 * information. The collected information is cached for future use.
-	 * 
-	 * <p>
-	 * Note that after serialization it is impossible to correctly extract caller
-	 * information.
-	 * </p>
-	 */
-	public CallerData[] getCallerData() {
-		// we rely on the fact that fqnOfLoggerClass does not survive
-		// serialization
-		if (callerDataArray == null && fqnOfLoggerClass != null) {
-			callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass);
-		}
-		return callerDataArray;
-	}
-
-	public void setCallerInformation(CallerData[] callerDataArray) {
-		this.callerDataArray = callerDataArray;
-	}
-
-	public Marker getMarker() {
-		return marker;
-	}
-
-	public void setMarker(Marker marker) {
-		if (this.marker != null) {
-			throw new IllegalStateException(
-					"The marker has been already set for this event.");
-		}
-		this.marker = marker;
-	}
-
-	public String getFormattedMessage() {
-		return formattedMessage;
-	}
-	
-	public Map<String, String> getMDCPropertyMap() {
-		return mdcPropertyMap;
-	}
-
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.defaultWriteObject();
-		out.writeInt(level.levelInt);
-	}
-
-	private void readObject(ObjectInputStream in) throws IOException,
-			ClassNotFoundException {
-		in.defaultReadObject();
-		int levelInt = in.readInt();
-		level = Level.toLevel(levelInt);
-	}
+  /**
+   * 
+   */
+  private static final long serialVersionUID = 3022264832697160750L;
+
+  /**
+   * 
+   */
+  private static long startTime = System.currentTimeMillis();
+
+  /**
+   * Fully qualified name of the calling Logger class. This field does not
+   * survive serialization.
+   * 
+   * <p>
+   * Note that the getCallerInformation() method relies on this fact.
+   */
+  transient String fqnOfLoggerClass;
+
+  /**
+   * The name of thread in which this logging event was generated.
+   */
+  private String threadName;
+
+  /**
+   * Level of logging event.
+   * 
+   * <p>
+   * This field should not be accessed directly. You shoud use the {@link
+   * #getLevel} method instead.
+   * </p>
+   * 
+   */
+  private transient Level level;
+
+  private String message;
+  private String formattedMessage;
+
+  private Object[] argumentArray;
+
+  private ThrowableInformation throwableInfo;
+
+  private CallerData[] callerDataArray;
+  private LoggerRemoteView loggerRemoteView;
+
+  private Marker marker;
+
+  private Map<String, String> mdcPropertyMap;
+
+  /**
+   * The number of milliseconds elapsed from 1/1/1970 until logging event was
+   * created.
+   */
+  private long timeStamp;
+
+  public LoggingEvent() {
+  }
+
+  public LoggingEvent(String fqcn, Logger logger, Level level, String message,
+      Throwable throwable, Object[] argArray) {
+    this.fqnOfLoggerClass = fqcn;
+    this.loggerRemoteView = logger.getLoggerRemoteView();
+    this.level = level;
+    this.message = message;
+
+    if (throwable != null) {
+      this.throwableInfo = new ThrowableInformation(throwable);
+    }
+
+    if (argArray != null) {
+      formattedMessage = MessageFormatter.arrayFormat(message, argArray);
+    } else {
+      formattedMessage = message;
+    }
+    timeStamp = System.currentTimeMillis();
+
+    mdcPropertyMap = MDC.getPropertyMap();
+  }
+
+  public void setArgumentArray(Object[] argArray) {
+    if (this.argumentArray != null) {
+      throw new IllegalStateException("argArray has been already set");
+    }
+    this.argumentArray = argArray;
+  }
+
+  public Object[] getArgumentArray() {
+    return this.argumentArray;
+  }
+
+  public Level getLevel() {
+    return level;
+  }
+
+  public String getThreadName() {
+    if (threadName == null) {
+      threadName = (Thread.currentThread()).getName();
+    }
+    return threadName;
+  }
+
+  /**
+   * @param threadName
+   *          The threadName to set.
+   * @throws IllegalStateException
+   *           If threadName has been already set.
+   */
+  public void setThreadName(String threadName) throws IllegalStateException {
+    if (this.threadName != null) {
+      throw new IllegalStateException("threadName has been already set");
+    }
+    this.threadName = threadName;
+  }
+
+  /**
+   * Returns the throwable information contained within this event. May be
+   * <code>null</code> if there is no such information.
+   */
+  public ThrowableInformation getThrowableInformation() {
+    return throwableInfo;
+  }
+
+  /**
+   * Set this event's throwable information.
+   */
+  public void setThrowableInformation(ThrowableInformation ti) {
+    if (throwableInfo != null) {
+      throw new IllegalStateException(
+          "ThrowableInformation has been already set.");
+    } else {
+      throwableInfo = ti;
+    }
+  }
+
+  /**
+   * This method should be called prior to serializing an event. It should also
+   * be called when using asynchronous logging.
+   */
+  public void prepareForDeferredProcessing() {
+    this.getThreadName();
+  }
+
+  public LoggerRemoteView getLoggerRemoteView() {
+    return loggerRemoteView;
+  }
+
+  public void setLoggerRemoteView(LoggerRemoteView loggerRemoteView) {
+    this.loggerRemoteView = loggerRemoteView;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
+  public void setMessage(String message) {
+    if (this.message != null) {
+      throw new IllegalStateException(
+          "The message for this event has been set already.");
+    }
+    this.message = message;
+  }
+
+  public long getTimeStamp() {
+    return timeStamp;
+  }
+
+  public void setTimeStamp(long timeStamp) {
+    this.timeStamp = timeStamp;
+  }
+
+  public void setLevel(Level level) {
+    if (this.level != null) {
+      throw new IllegalStateException(
+          "The level has been already set for this event.");
+    }
+    this.level = level;
+  }
+
+  /**
+   * The time at which this class was loaded into memory, expressed in
+   * millisecond elapsed since the epoch (1.1.1970).
+   * 
+   * @return The time as measured when this class was loaded into memory.
+   */
+  public static final long getStartTime() {
+    return startTime;
+  }
+
+  /**
+   * Get the caller information for this logging event. If caller information is
+   * null at the time of its invocation, this method extracts location
+   * information. The collected information is cached for future use.
+   * 
+   * <p>
+   * Note that after serialization it is impossible to correctly extract caller
+   * information.
+   * </p>
+   */
+  public CallerData[] getCallerData() {
+    // we rely on the fact that fqnOfLoggerClass does not survive
+    // serialization
+    if (callerDataArray == null && fqnOfLoggerClass != null) {
+      callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass);
+    }
+    return callerDataArray;
+  }
+
+  public void setCallerInformation(CallerData[] callerDataArray) {
+    this.callerDataArray = callerDataArray;
+  }
+
+  public Marker getMarker() {
+    return marker;
+  }
+
+  public void setMarker(Marker marker) {
+    if (this.marker != null) {
+      throw new IllegalStateException(
+          "The marker has been already set for this event.");
+    }
+    this.marker = marker;
+  }
+
+  public String getFormattedMessage() {
+    return formattedMessage;
+  }
+
+  public Map<String, String> getMDCPropertyMap() {
+    return mdcPropertyMap;
+  }
+
+  private void writeObject(ObjectOutputStream out) throws IOException {
+    out.defaultWriteObject();
+    out.writeInt(level.levelInt);
+  }
+
+  private void readObject(ObjectInputStream in) throws IOException,
+      ClassNotFoundException {
+    in.defaultReadObject();
+    int levelInt = in.readInt();
+    level = Level.toLevel(levelInt);
+  }
 
 }

Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/html/HTMLLayoutTest.java
==============================================================================
--- (empty file)
+++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/html/HTMLLayoutTest.java	Tue Sep 12 09:35:04 2006
@@ -0,0 +1,158 @@
+package ch.qos.logback.classic.html;
+
+import junit.framework.TestCase;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.ThrowableInformation;
+import ch.qos.logback.core.appender.ListAppender;
+
+public class HTMLLayoutTest extends TestCase {
+
+  LoggerContext lc;
+  Logger logger;
+  HTMLLayout layout;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    lc = new LoggerContext();
+    lc.setName("default");
+
+    ListAppender appender = new ListAppender();
+    appender.setContext(lc);
+    layout = new HTMLLayout();
+    layout.setContext(lc);
+    layout.setPattern("%level %thread %msg");
+    layout.start();
+    appender.setLayout(layout);
+    logger = lc.getLogger(LoggerContext.ROOT_NAME);
+    logger.addAppender(appender);
+    appender.start();
+  }
+
+  @Override
+  public void tearDown() throws Exception {
+    super.tearDown();
+    lc = null;
+    layout = null;
+  }
+
+  public void testHeader() throws Exception {
+    String header = layout.getHeader();
+    // System.out.println(header);
+    assertTrue(header.indexOf("Level") == 422);
+    assertTrue(header.indexOf("Literal") == 456);
+    assertTrue(header.indexOf("Thread") == 494);
+    assertTrue(header.lastIndexOf("Literal") == 543);
+    assertTrue(header.indexOf("Message") == 139);
+  }
+
+  public void testAppendThrowable() throws Exception {
+    StringBuffer buf = new StringBuffer();
+    String[] strArray = { "test1", "test2" };
+    layout.appendThrowableAsHTML(strArray, buf);
+    // System.out.println(buf.toString());
+    String[] result = buf.toString().split(HTMLLayout.LINE_SEP);
+    assertEquals("test1", result[0]);
+    assertEquals(HTMLLayout.TRACE_PREFIX + "test2", result[1]);
+  }
+
+  public void testDoLayout() throws Exception {
+    LoggingEvent le = createLoggingEvent();
+    String result = layout.doLayout(le);
+    Document doc = parseOutput(result);
+    Element trElement = doc.getRootElement();
+    assertEquals(5, trElement.elements().size());
+    {
+      Element tdElement = (Element) trElement.elements().get(0);
+      assertEquals("DEBUG", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(1);
+      assertEquals(" ", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(2);
+      assertEquals("main", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(3);
+      assertEquals(" ", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(4);
+      assertEquals("test message", tdElement.getText());
+    }
+    // System.out.println(result);
+  }
+
+  public void testDoLayoutWithException() throws Exception {
+    layout.setPattern("%level %thread %msg %ex");
+    LoggingEvent le = createLoggingEvent();
+    le.setThrowableInformation(new ThrowableInformation(new Exception(
+        "test Exception")));
+    String result = layout.doLayout(le);
+
+    //System.out.println(result);
+
+    Document doc = parseOutput(result);
+    Element trElement = doc.getRootElement();
+    assertEquals(6, trElement.elements().size());
+    {
+      Element tdElement = (Element) trElement.elements().get(0);
+      assertEquals("DEBUG", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(1);
+      assertEquals(" ", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(2);
+      assertEquals("main", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(3);
+      assertEquals(" ", tdElement.getText());
+    }
+    {
+      Element tdElement = (Element) trElement.elements().get(4);
+      assertEquals("test message", tdElement.getText());
+    }
+//    {
+//      Element trElement2 = (Element) trElement.elements().get(5);
+//      Element tdElement = (Element) trElement2.elements().get(0);
+//      assertTrue(tdElement.getText().contains(
+//          "java.lang.Exception: test Exception"));
+//    }
+  }
+  
+  public void testLog() {
+    for (int i = 0; i < 2000; i++) {
+      logger.debug("test message");
+    }
+  }
+
+  private LoggingEvent createLoggingEvent() {
+    LoggingEvent le = new LoggingEvent(this.getClass().getName(), logger,
+        Level.DEBUG, "test message", null, null);
+    return le;
+  }
+
+  Document parseOutput(String output) {
+    try {
+      Document document = DocumentHelper.parseText(output);
+      return document;
+    } catch (Exception e) {
+      System.err.println(e);
+      fail();
+    }
+    return null;
+  }
+}

Modified: logback/trunk/pom.xml
==============================================================================
--- logback/trunk/pom.xml	(original)
+++ logback/trunk/pom.xml	Tue Sep 12 09:35:04 2006
@@ -64,7 +64,11 @@
 			  <artifactId>mail</artifactId>
 			  <version>1.4</version>
 		  </dependency>
-
+			<dependency>
+			  <groupId>dom4j</groupId>
+  			  <artifactId>dom4j</artifactId>
+ 			   <version>1.6.1</version>
+			</dependency>
 			
 			<!-- Access Module Dependencies -->
 			<dependency>



More information about the logback-dev mailing list