Sorry, the reason I haven't submitted it into my contrib directory is
because it's not very well documented yet and needs some more clean-up...
It does a substition akin to the way PatternLayout does, with the symbols
and meanings as defined in doPatternSubst().
An example:
Set the subject to be "MyApp error: %m - [%x] %s" and for the SMTPAppender
fires on errors.
Now if in your code you have the line
logger.error("Couldn't read customer data", new SMTPException("Unknown
field"));
At runtime, as it composes the email to send to you, if pulls apart the
LogMessage and sets the email's subject to be "MyApp error: Couldn't read
customer data - [java.sql.SMTPException] Unknown field".
Make sense now?
-Jim Moore
-----Original Message-----
From: Gray Jones [mailto:[EMAIL PROTECTED]]
Sent: Thursday, May 31, 2001 10:18 AM
To: LOG4J Users Mailing List
Subject: Re: dynamic subject with smtp appender
Thanks Jim for the code.
I've been looking at it and am a little confused. In the doPatternSubst()
method, it looks like it is switching based upon a character. Is this a
special 'cookie' type character that you are adding to your log message?
Can you give a little insight as to what is happening here?
Any backgound info would be appreciated.
Thanks,
Gray Jones
----- Original Message -----
From: "Jim Moore" <[EMAIL PROTECTED]>
To: "'LOG4J Users Mailing List'" <[EMAIL PROTECTED]>
Sent: Wednesday, May 30, 2001 11:16 AM
Subject: RE: dynamic subject with smtp appender
> You can't with what's in the disribution. Here's one I wrote, though.
Take
> a look at doPatternSubst.
>
> -Jim Moore
>
> ===
>
> import org.apache.log4j.*;
> import org.apache.log4j.helpers.*;
> import org.apache.log4j.spi.*;
>
> import java.util.*;
>
> import javax.mail.*;
> import javax.mail.internet.*;
>
>
> /**
> * Send an e-mail when a specific logging event occurs, typically on
> 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.
> */
> public class SMTPAppender extends AppenderSkeleton {
> private String to;
> private String from;
> private String subject;
> private String smtpHost;
> private int bufferSize = 512;
>
> private CyclicBuffer _cb;
> private boolean locationInfo = false;
>
> protected TriggeringEventEvaluator evaluator;
> private Session session;
>
>
> /**
> * The default constructor will instantiate the appender with a
> * {@link TriggeringEventEvaluator} that will tirgger on events with
> * priority ERROR or higher.
> */
> public SMTPAppender() {
> this(new DefaultEvaluator());
> }
>
>
> /**
> * Use <code>evaluator</code> passed as parameter as the {@link
> * TriggeringEventEvaluator} for this SMTPAppender.
> *
> * @param evaluator
> */
> public SMTPAppender(TriggeringEventEvaluator evaluator) {
> this.evaluator = evaluator;
> setCyclicBuffer(new CyclicBuffer(bufferSize));
> }
>
>
> public void activeOptions() {
> Properties props = System.getProperties();
>
> if (smtpHost != null) {
> props.put("mail.smtp.host", smtpHost);
> }
>
> session = Session.getDefaultInstance(System.getProperties(), null);
> // session.setDebug(true);
>
> super.activateOptions();
> }
>
>
> /**
> * 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.
> *
> * @param event
> */
> public void append(LoggingEvent event) {
> if (!checkEntryConditions()) {
> return;
> }
>
> event.getThreadName();
> event.getNDC();
> if (locationInfo) {
> event.getLocationInformation();
> }
> getCyclicBuffer().add(event);
> if (evaluator.isTriggeringEvent(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.
> *
> * @return
> */
> protected boolean checkEntryConditions() {
> if (this.evaluator == null) {
> errorHandler.error("No TriggeringEventEvaluator is set for appender
> ["+
> name+"].");
> return false;
> }
>
>
> if (this.layout == null) {
> errorHandler.error("No layout set for appender named ["+name+"].");
> return false;
> }
> return true;
> }
>
>
> public synchronized void close() {
> this.closed = true;
> }
>
>
> protected InternetAddress getAddress(String addressStr) {
> try {
> return new InternetAddress(addressStr);
> }
> catch (AddressException e) {
> errorHandler.error("Could not parse address ["+addressStr+"].", e,
> ErrorCode.ADDRESS_PARSE_FAILURE);
> return null;
> }
> }
>
>
> protected InternetAddress[] parseAddress(String addressStr) {
> try {
> return InternetAddress.parse(addressStr, true);
> }
> catch (AddressException e) {
> errorHandler.error("Could not parse address ["+addressStr+"].", e,
> ErrorCode.ADDRESS_PARSE_FAILURE);
> return null;
> }
> }
>
>
> /**
> * Returns value of the <b>To</b> option.
> *
> * @return
> */
> public String getTo() {
> return to;
> }
>
>
> /**
> * The <code>SMTPAppender</code> requires a {@link Layout layout}.
> *
> * @return
> */
> 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 {
> Message msg = createMessage();
>
> try {
> Transport.send(msg);
> }
> catch (MessagingException exp) {
> LogLog.error("Error occured while sending e-mail notification.",
> exp);
> }
> }
> catch (MessagingException exp) {
> // the only thing that could've thrown this is createMessage()
> LogLog.error("Could not create Message.", exp);
> }
> }
>
>
> protected void setMessageFrom(Message msg) throws MessagingException {
> if (from != null) {
> msg.setFrom(getAddress(from));
> }
> else {
> msg.setFrom();
> }
> }
>
>
> protected void setMessageTo(Message msg) throws MessagingException {
> msg.setRecipients(Message.RecipientType.TO, parseAddress(to));
> }
>
>
> protected void setMessageSubject(Message msg) throws MessagingException
{
> if (subject != null) {
> CyclicBuffer cb = getCyclicBuffer();
> int length = cb.length();
>
> String theSubject;
> if (length > 0) {
> theSubject = doPatternSubst(subject, cb.get(length-1));
> }
> else {
> theSubject = subject;
> }
>
> msg.setSubject(theSubject);
> }
> else {
> LogLog.warn("There is no subject set for emails");
> }
> }
>
>
> public String doPatternSubst(String source, LoggingEvent evt) {
> String str = source;
> StringBuffer sBuf = new StringBuffer();
>
> while (true) {
> int index = str.indexOf('%');
>
> if (index < 0 || index == str.length()) {
> break;
> }
>
> char c = str.charAt(index+1);
> switch (c) {
> case 'm':
> str = appendEventMessage(str, sBuf, index, evt);
> break;
> case 's':
> str = appendExceptionMessage(str, sBuf, index, evt);
> break;
> case 'x':
> str = appendExceptionClassName(str, sBuf, index, evt);
> break;
> default:
> // nothing
> }
> }
>
> sBuf.append(str);
>
> return sBuf.toString();
> }
>
>
> /**
> * Appends <tt>str</tt> to <tt>sBuf</tt> through the <tt>index</tt>
> * and then appends the Throwable class name that's in <tt>evt</tt>.
> *
> * @param str the source string
> *
> * @param sBuf the StringBuffer to append to
> *
> * @param index the index to where the substitution parameter was
found
> *
> * @param evt the LoggingEvent to get the Throwable from
> *
> * @return the portion of <tt>str</tt> after the substitution (never
null)
> */
> private static String appendExceptionClassName(String str, StringBuffer
> sBuf,
> int index, LoggingEvent
> evt) {
> sBuf.append(str.substring(0, index));
>
> ThrowableInformation info = evt.getThrowableInformation();
>
> if (info != null) {
> Throwable throwable = info.getThrowable();
>
> if (throwable != null) {
> sBuf.append(throwable.getClass().getName());
> }
> }
>
> if (index+2 > str.length()) {
> return "";
> }
>
> return str.substring(index+2);
> }
>
>
> /**
> * Appends <tt>str</tt> to <tt>sBuf</tt> through the <tt>index</tt>
> * and then appends the Throwable message that's in <tt>evt</tt>.
> *
> * @param str the source string
> *
> * @param sBuf the StringBuffer to append to
> *
> * @param index the index to where the substitution parameter was
found
> *
> * @param evt the LoggingEvent to get the Throwable from
> *
> * @return the portion of <tt>str</tt> after the substitution (never
null)
> */
> private static String appendExceptionMessage(String str, StringBuffer
> sBuf,
> int index, LoggingEvent
evt)
> {
> sBuf.append(str.substring(0, index));
>
> ThrowableInformation info = evt.getThrowableInformation();
>
> if (info != null) {
> Throwable throwable = info.getThrowable();
>
> if (throwable != null) {
> String msg = throwable.getMessage();
> sBuf.append(msg == null ? "" : msg);
> }
> }
>
> if (index+2 > str.length()) {
> return "";
> }
>
> return str.substring(index+2);
> }
>
>
> /**
> * Appends <tt>str</tt> to <tt>sBuf</tt> through the <tt>index</tt>
> * and then appends the message that's in <tt>evt</tt>.
> *
> * @param str the source string
> *
> * @param sBuf the StringBuffer to append to
> *
> * @param index the index to where the substitution parameter was
found
> *
> * @param evt the LoggingEvent to get the message from
> *
> * @return the portion of <tt>str</tt> after the substitution (never
null)
> */
> private static String appendEventMessage(String str, StringBuffer sBuf,
> int index, LoggingEvent evt) {
> sBuf.append(str.substring(0, index));
>
> Object msg = evt.getMessage();
> sBuf.append(msg == null ? "" : msg.toString());
>
> if (index+2 > str.length()) {
> return "";
> }
>
> return str.substring(index+2);
> }
>
>
> protected void addHeader(StringBuffer sbuf) {
> String t = layout.getHeader();
>
> if (t != null)
> sbuf.append(t);
> }
>
>
> protected void addFooter(StringBuffer sbuf) {
> String t = layout.getFooter();
>
> if (t != null)
> sbuf.append(t);
> }
>
>
> protected void addLogEvents(StringBuffer sbuf) {
> CyclicBuffer cb = getCyclicBuffer();
>
> int len = cb.length();
> for (int i = 0; i < len; i++) {
> file://sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
> LoggingEvent event = cb.get();
> sbuf.append(layout.format(event));
>
> if (layout.ignoresThrowable()) {
> String[] s = event.getThrowableStrRep();
>
> if (s != null) {
> for (int j = 0; j < s.length; j++) {
> sbuf.append(s[j]);
> }
> }
> }
> }
> }
>
>
> /**
> */
> protected Message createMessage() throws MessagingException {
> // Note: this code already owns the monitor for this
> // appender. This frees us from needing to synchronize on 'cb'.
>
> MimeMessage msg = new MimeMessage(session);
>
> setMessageFrom(msg);
> setMessageTo(msg);
> setMessageSubject(msg);
>
> StringBuffer sbuf = new StringBuffer();
>
> addHeader(sbuf);
> addLogEvents(sbuf);
> addFooter(sbuf);
>
> MimeBodyPart part = new MimeBodyPart();
> part.setContent(sbuf.toString(), layout.getContentType());
>
> Multipart mp = new MimeMultipart();
> mp.addBodyPart(part);
>
> msg.setContent(mp);
> msg.setSentDate(new Date());
>
> return msg;
> }
>
>
> /**
> * Sets the CyclicBuffer this will use for keeping track
> * of {@link LoggingEvent}s.
> *
> * @param cb the CyclicBuffer to store the events in
> */
> protected void setCyclicBuffer(CyclicBuffer cb) {
> _cb = cb;
> }
>
>
> /**
> * Returns the CyclicBuffer the {@link LoggingEvent}s are stored in.
> */
> protected CyclicBuffer getCyclicBuffer() {
> return _cb;
> }
>
>
> /**
> * Returns value of the <b>EvaluatorClass</b> option.
> *
> * @return
> */
> public String getEvaluatorClass() {
> return evaluator == null ? null : evaluator.getClass().getName();
> }
>
>
> /**
> * Returns value of the <b>From</b> option.
> *
> * @return
> */
> public String getFrom() {
> return from;
> }
>
> /**
> * Returns value of the <b>Subject</b> option.
> *
> * @return
> */
> public String getSubject() {
> return subject;
> }
>
>
> /**
> * The <b>From</b> option takes a string value which should be a
> * e-mail address of the sender.
> *
> * @param from
> */
> 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.
> *
> * @param subject
> */
> 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.
> *
> * @param bufferSize
> */
> public void setBufferSize(int bufferSize) {
> this.bufferSize = bufferSize;
> getCyclicBuffer().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.
> *
> * @param smtpHost
> */
> 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
> * repsenting 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) {
> evaluator = (TriggeringEventEvaluator)
> OptionConverter.instantiateByClassName(value,
> TriggeringEventEvaluator.class,
> evaluator);
> }
>
>
> /**
> * 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.
> *
> * @param locationInfo
> */
> public void setLocationInfo(boolean locationInfo) {
> this.locationInfo = locationInfo;
> }
>
>
> /**
> * Returns value of the <b>LocationInfo</b> option.
> *
> * @return
> */
> public boolean getLocationInfo() {
> return locationInfo;
> }
>
>
> protected static class DefaultEvaluator implements
> TriggeringEventEvaluator {
> /**
> * Is this <code>event</code> the e-mail triggering event?
> *
> * <p>This method returns <code>true</code>, if the event priority
> * has ERROR priority or higher. Otherwisem it returns
> * <code>false</code>.
> *
> * @param event
> * @return
> */
> public boolean isTriggeringEvent(LoggingEvent event) {
> return event.priority.isGreaterOrEqual(Priority.ERROR);
> }
> }
>
> }
>
>
>
> -----Original Message-----
> From: Gray Jones [mailto:[EMAIL PROTECTED]]
> Sent: Wednesday, May 30, 2001 11:05 AM
> To: LOG4J Users Mailing List
> Subject: dynamic subject with smtp appender
>
>
> Hello,
>
> Does anybody know if you can somehow dynamically create the subject field
of
> an smtp log? For instance we are using an smtp appender to send emails of
> exceptions to the development group. Our exception class has an id and
> description field which would be nice to include in the subject field of
the
> email message so we can sort, etc.
>
> Thanks,
>
> Gray Jones
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]