Thanks all for your help. Here's what I came up with. I created a new
class called DynamicSMTPAppender that is basically a copy of the
SMTPAppender class (I couldn't subclass it because the variables aren't
protected?).
The only difference is that I rewrote the sendBuffer() method. Basically I
pulled the method apart and created helper methods that act as hooks that
can be overridden by subclasses. The default behaivor is the same as what
was occuring before.
Anybody see any major flaws with this approach? I don't really understand
what is happening with the event buffer. For the way we are using it
currently, sending an email for each exception, it works okay. I suppose it
could break if somebody was trying to store multiple logs and email them
inside of one email message.
/**
Send the contents of the cyclic buffer as an e-mail message.
*/
protected
void sendBuffer() {
LoggingEvent event = null;
try
event = cb.get();
msg.setSentDate(new Date());
// potentially dynamic aspects of the message
msg.setRecipients(Message.RecipientType.TO,
parseAddress(getMessageTo(event)));
msg.setFrom(getAddress(getMessageFrom(event)));
msg.setSubject(getMessageSubject(event));
Multipart mp = new MimeMultipart();
mp.addBodyPart(getMessageContent(event));
msg.setContent(mp);
Transport.send(msg);
} catch(Exception e) {
LogLog.error("Error occured while sending e-mail notification.", e);
}
}
protected MimeBodyPart getMessageContent(LoggingEvent event) throws
Exception {
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++) {
file://sbuf.append(MimeUtility.encodeText(layout.format(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]);
}
}
}
}
t = layout.getFooter();
if(t != null) {
sbuf.append(t);
}
part.setContent(sbuf.toString(), layout.getContentType());
return part;
}
protected String getMessageSubject(LoggingEvent event) throws Exception {
return subject;
}
protected String getMessageTo(LoggingEvent event) throws Exception {
return to;
}
protected String getMessageFrom(LoggingEvent event) throws Exception {
return from;
}
----- Original Message -----
From: "Ceki G�lc�" <[EMAIL PROTECTED]>
To: "LOG4J Users Mailing List" <[EMAIL PROTECTED]>
Sent: Thursday, May 31, 2001 11:44 AM
Subject: Re: dynamic subject with smtp appender
>
> Gray,
>
> Jim is using the "subject" property of the SMTPAppender as a pattern. If
the subject property contains %m then he sets subject of the e-mail message
to the message of the last event. Similarly, %s causes the e-mail subject to
be set to the exception contained in the last event, %x sets the subject to
the class name of the exception.
>
> Jim, have you considered using the PatternLayout to set the subject? It
seems to me it would give you more power with less code... Ceki
>
> At 10:17 31.05.2001 -0400, you wrote:
> >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]
>
> --
> Ceki G�lc�
>
>
> ---------------------------------------------------------------------
> 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]