
Author: seb Date: Thu Aug 24 16:24:39 2006 New Revision: 484 Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/CyclicBuffer.java logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java Modified: logback/trunk/logback-classic/examples/ (props changed) logback/trunk/logback-classic/examples/classes/ (props changed) logback/trunk/logback-site/ (props changed) Log: - SMTPAppender: on going work. - A working version of this appender is commited. Reliable tests will follow. - Minor ignore-list modifications. Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/CyclicBuffer.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/helpers/CyclicBuffer.java Thu Aug 24 16:24:39 2006 @@ -0,0 +1,153 @@ +/** + * 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; + +import ch.qos.logback.classic.spi.LoggingEvent; + + +/** + + CyclicBuffer is used by other appenders to hold {@link LoggingEvent + LoggingEvents} for immediate or differed display. + + <p>This buffer gives read access to any element in the buffer not + just the first or last element. + + @author Ceki Gülcü + @since 0.9.0 + + */ +public class CyclicBuffer { + + LoggingEvent[] ea; + int first; + int last; + int numElems; + int maxSize; + + /** + Instantiate a new CyclicBuffer of at most <code>maxSize</code> events. + + The <code>maxSize</code> argument must a positive integer. + + @param maxSize The maximum number of elements in the buffer. + */ + public CyclicBuffer(int maxSize) throws IllegalArgumentException { + if(maxSize < 1) { + throw new IllegalArgumentException("The maxSize argument ("+maxSize+ + ") is not a positive integer."); + } + this.maxSize = maxSize; + ea = new LoggingEvent[maxSize]; + first = 0; + last = 0; + numElems = 0; + } + + /** + Add an <code>event</code> as the last event in the buffer. + + */ + public + void add(LoggingEvent event) { + ea[last] = event; + if(++last == maxSize) + last = 0; + + if(numElems < maxSize) + numElems++; + else if(++first == maxSize) + first = 0; + } + + + /** + Get the <i>i</i>th oldest event currently in the buffer. If + <em>i</em> is outside the range 0 to the number of elements + currently in the buffer, then <code>null</code> is returned. + + + */ + public + LoggingEvent get(int i) { + if(i < 0 || i >= numElems) + return null; + + return ea[(first + i) % maxSize]; + } + + public + int getMaxSize() { + return maxSize; + } + + /** + Get the oldest (first) element in the buffer. The oldest element + is removed from the buffer. + */ + public + LoggingEvent get() { + LoggingEvent r = null; + if(numElems > 0) { + numElems--; + r = ea[first]; + ea[first] = null; + if(++first == maxSize) + first = 0; + } + return r; + } + + /** + Get the number of elements in the buffer. This number is + guaranteed to be in the range 0 to <code>maxSize</code> + (inclusive). + */ + public + int length() { + return numElems; + } + + /** + Resize the cyclic buffer to <code>newSize</code>. + + @throws IllegalArgumentException if <code>newSize</code> is negative. + */ + public + void resize(int newSize) { + if(newSize < 0) { + throw new IllegalArgumentException("Negative array size ["+newSize+ + "] not allowed."); + } + if(newSize == numElems) + return; // nothing to do + + LoggingEvent[] temp = new LoggingEvent[newSize]; + + int loopLen = newSize < numElems ? newSize : numElems; + + for(int i = 0; i < loopLen; i++) { + temp[i] = ea[first]; + ea[first] = null; + if(++first == numElems) + first = 0; + } + ea = temp; + first = 0; + numElems = loopLen; + maxSize = newSize; + if (loopLen == newSize) { + last = 0; + } else { + last = loopLen; + } + } +} Added: logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/main/java/ch/qos/logback/classic/net/SMTPAppender.java Thu Aug 24 16:24:39 2006 @@ -0,0 +1,399 @@ +/** + * 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.net; + +import java.io.File; +import java.util.Date; +import java.util.Properties; + +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.AddressException; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.helpers.CyclicBuffer; +import ch.qos.logback.classic.spi.LoggingEvent; +import ch.qos.logback.core.AppenderBase; +import ch.qos.logback.core.Layout; +import ch.qos.logback.core.rolling.TriggeringPolicy; +import ch.qos.logback.core.util.OptionHelper; + +/** + * Send an e-mail when a specific logging event occurs, typically on errors or + * fatal errors. + * + * <p> + * The number of logging events delivered in this e-mail depend on the value of + * <b>BufferSize</b> option. The <code>SMTPAppender</code> keeps only the + * last <code>BufferSize</code> logging events in its cyclic buffer. This + * keeps memory requirements at a reasonable level while still delivering useful + * application context. + * + * @author Ceki Gülcü + * @author Sébastien Pennec + * + * @since 1.0 + */ +public class SMTPAppender extends AppenderBase { + 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; + } + + /** + * The <code>SMTPAppender</code> requires a {@link org.apache.log4j.Layout + * layout}. + */ + public boolean requiresLayout() { + return true; + } + + /** + * 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; + + /** + * 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; + } +} Added: logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java ============================================================================== --- (empty file) +++ logback/trunk/logback-classic/src/test/java/ch/qos/logback/classic/net/SMTPAppenderTest.java Thu Aug 24 16:24:39 2006 @@ -0,0 +1,48 @@ +package ch.qos.logback.classic.net; + +import org.slf4j.LoggerFactory; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.PatternLayout; +import ch.qos.logback.core.Layout; +import ch.qos.logback.core.util.StatusPrinter; + +public class SMTPAppenderTest { + + public static void main(String[] args) { + + Logger logger = (Logger) LoggerFactory.getLogger(SocketAppenderTest.class); + LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); + SMTPAppender appender = new SMTPAppender(); + appender.setContext(lc); + appender.setName("smtp"); + appender.setFrom("user@host.dom"); + appender.setLayout(buildLayout(lc)); + appender.setSMTPHost("mail.qos.ch"); + appender.setSubject("logging report"); + appender.setTo("sebastien@qos.ch"); + + appender.start(); + + logger.addAppender(appender); + + for (int i = 0; i <= 10; i++) { + logger.debug("** Hello world. n=" + i); + } + logger.error("Triggering request"); + + StatusPrinter.print(lc.getStatusManager()); + } + + private static Layout buildLayout(LoggerContext lc) { + PatternLayout layout = new PatternLayout(); + layout.setContext(lc); + layout.setHeader("Some header\n"); + layout.setPattern("%-4relative [%thread] %-5level %class - %msg%n"); + layout.setFooter("Some footer"); + layout.start(); + return layout; + } + +}