How do I update the "to" addresses on an SMTPAppender in Logback?
Asked Answered
C

3

5

I am creating an admin page which has a couple of logback properties that I want to set on the fly, one of them being the admin emails that I send system alerts to. The api for the SMTPAppender has the methods to add to the list of "to" addresses, or get them as a list, but I didn't find anything to clear, remove any or update them. How should I do this?

I see two options currently:

  1. One option is to remove the appender and create a new one with the new properties (yuck).
  2. Figure out how to configure this directly through Joran (maybe yuck?).

I'm moving forward with (2), but please post if there's a better way.

Cavort answered 24/7, 2012 at 18:2 Comment(0)
S
4

Maybe you're done with this, but I just needed to find a way to set dynamic "to" addresses, and this topic lead me to the way (thanks to @gresdiplitude idea on system properties and MDC values), so I'm sharing my solution.

I am using SMTPAppender to send small execution reports, but those need to be sent to different mailboxes. The solution looks yuck-less, or at least I got very pleased with the result.

My logback.xml:

<!-- this is the trick: a converter to use on the "to" field pattern -->
<conversionRule conversionWord="smtpTo" converterClass="com.example.logback.MyConverter" />
<appender name="SMTP" class="ch.qos.logback.classic.net.SMTPAppender">
    <!-- a filter to select just the log entries that will be sent by mail -->
    <filter class="com.example.logback.MyFilter" />
    <!-- an evaluator, mine is like CounterBasedEvaluator from the manual:
        http://logback.qos.ch/xref/chapters/appenders/mail/CounterBasedEvaluator.html
    -->
    <evaluator class="com.example.logback.MyEvaluator">
        <limit>25</limit>
    </evaluator>
    <!-- a discriminator to create a cyclic buffer for each mailing group I need -->
    <discriminator class="com.example.logback.MyDiscriminator" />
    <!-- just matching buffer size to evaluator limit -->
    <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTrackerImpl">
        <bufferSize>25</bufferSize>
    </cyclicBufferTracker>
    <smtpHost>${smtp.host}</smtpHost>
    <smtpPort>${smtp.port}</smtpPort>
    <SSL>${smtp.ssl}</SSL>
    <username>${smtp.username}</username>
    <password>${smtp.password}</password>
    <from>${smtp.from}</from>

    <!-- here you use the converter: in this case will get data
        from marker containing the destination addresses
    -->
    <to>%smtpTo</to>
    <subject>my subject</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>%date: %message%n%xThrowable{full}</pattern>
    </layout>
</appender>

MyFilter.java:

public FilterReply decide(ILoggingEvent event) {
    return event.getMarker() != null
            && event.getMarker().contains("REPORT") ? FilterReply.ACCEPT
            : FilterReply.DENY;
}

MyDiscriminator.java:

public String getDiscriminatingValue(ILoggingEvent e) {
    Marker marker = e.getMarker();
    if (marker == null || !(marker instanceof MyMarker)) {
        return null;
    }
    return ((MyMarker) marker).getDiscriminatingValue();
}

MyConverter.java:

public class MyConverter extends ClassicConverter {

    @Override
    public String convert(ILoggingEvent event) {
        Marker marker = event.getMarker();
        if (marker == null || !(marker instanceof MyMarker)) {
            return null;
        }
        return ((MyMarker) marker).getSmtpTo();
    }
}

MyMarker.java:

public interface MyMarker extends Marker {
    // a list of destination addresses, like "[email protected], [email protected]"
    String getSmtpTo();
    // an "id" to tell the buffers apart, could be "smtpTo" itself
    // but in my case it would mix different reports that goes to the same addresses
    String getDiscriminatingValue();
}

I just created an implementation for MyMarker, and used several instances of it on every logging statement that should be reported:

// suggestion: make the marker immutable, then you can store and reuse them instead of recreating them every time
Marker marker1 = new MyMarkerImpl(
    "REPORT", // marker name
    "[email protected], [email protected]", // smtpTo
    "alertGroup"); // discriminatingValue
logger.warn(marker1, "SNAFU");
Marker marker2 = new MyMarkerImpl(
    "REPORT", "[email protected], [email protected]", "phbGroup");
logger.info(marker2, "Everything is fine");
// here we have same smtpTo as above but different discriminatingValues, so this will be sent in another email/report
Marker marker3 = new MyMarkerImpl(
    "REPORT", "[email protected], [email protected]", "bugFixingGroup");
logger.error(marker3, "Why things gone bad", exception);

Hope it can be useful.

Smack answered 8/11, 2012 at 13:2 Comment(2)
This is great. The implementation of this feature had been pushed off, thinking that a better way of doing it would eventually be implemented, or a work around posted. For now, I think this is the best approach. However, I have an enum that I use with all the markers "alive" in the application stored in them. I'll make the markers mutable, but only have one instance.Cavort
@Cavort That's a simple approach, however I dislike mutable enums of any kind. It could also lead you to race condition issues if your application is multithreaded, a web application firing SMTP logging at user request, or a clustered environment. My way is usually creating immutable markers wrapping detached simple markers, and storing them for as long as they're needed. Good to know this might be useful to you =)Smack
S
4

Two more options I can think of:

1/ Set updated address as a system property, and configure the SMTPAppender to evaluate smtpTo address using the system property like so

<to>%property{smtpTo}</to> 

Or 2/ Set the updated address somewhere database/system properties. Put the smtpTo address into the MDC for every thread, and configure the SMTPAppender to get it from MDC like so

<to>%X{smtpTo}</to>
Soilasoilage answered 30/7, 2012 at 7:21 Comment(0)
S
4

Maybe you're done with this, but I just needed to find a way to set dynamic "to" addresses, and this topic lead me to the way (thanks to @gresdiplitude idea on system properties and MDC values), so I'm sharing my solution.

I am using SMTPAppender to send small execution reports, but those need to be sent to different mailboxes. The solution looks yuck-less, or at least I got very pleased with the result.

My logback.xml:

<!-- this is the trick: a converter to use on the "to" field pattern -->
<conversionRule conversionWord="smtpTo" converterClass="com.example.logback.MyConverter" />
<appender name="SMTP" class="ch.qos.logback.classic.net.SMTPAppender">
    <!-- a filter to select just the log entries that will be sent by mail -->
    <filter class="com.example.logback.MyFilter" />
    <!-- an evaluator, mine is like CounterBasedEvaluator from the manual:
        http://logback.qos.ch/xref/chapters/appenders/mail/CounterBasedEvaluator.html
    -->
    <evaluator class="com.example.logback.MyEvaluator">
        <limit>25</limit>
    </evaluator>
    <!-- a discriminator to create a cyclic buffer for each mailing group I need -->
    <discriminator class="com.example.logback.MyDiscriminator" />
    <!-- just matching buffer size to evaluator limit -->
    <cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTrackerImpl">
        <bufferSize>25</bufferSize>
    </cyclicBufferTracker>
    <smtpHost>${smtp.host}</smtpHost>
    <smtpPort>${smtp.port}</smtpPort>
    <SSL>${smtp.ssl}</SSL>
    <username>${smtp.username}</username>
    <password>${smtp.password}</password>
    <from>${smtp.from}</from>

    <!-- here you use the converter: in this case will get data
        from marker containing the destination addresses
    -->
    <to>%smtpTo</to>
    <subject>my subject</subject>
    <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>%date: %message%n%xThrowable{full}</pattern>
    </layout>
</appender>

MyFilter.java:

public FilterReply decide(ILoggingEvent event) {
    return event.getMarker() != null
            && event.getMarker().contains("REPORT") ? FilterReply.ACCEPT
            : FilterReply.DENY;
}

MyDiscriminator.java:

public String getDiscriminatingValue(ILoggingEvent e) {
    Marker marker = e.getMarker();
    if (marker == null || !(marker instanceof MyMarker)) {
        return null;
    }
    return ((MyMarker) marker).getDiscriminatingValue();
}

MyConverter.java:

public class MyConverter extends ClassicConverter {

    @Override
    public String convert(ILoggingEvent event) {
        Marker marker = event.getMarker();
        if (marker == null || !(marker instanceof MyMarker)) {
            return null;
        }
        return ((MyMarker) marker).getSmtpTo();
    }
}

MyMarker.java:

public interface MyMarker extends Marker {
    // a list of destination addresses, like "[email protected], [email protected]"
    String getSmtpTo();
    // an "id" to tell the buffers apart, could be "smtpTo" itself
    // but in my case it would mix different reports that goes to the same addresses
    String getDiscriminatingValue();
}

I just created an implementation for MyMarker, and used several instances of it on every logging statement that should be reported:

// suggestion: make the marker immutable, then you can store and reuse them instead of recreating them every time
Marker marker1 = new MyMarkerImpl(
    "REPORT", // marker name
    "[email protected], [email protected]", // smtpTo
    "alertGroup"); // discriminatingValue
logger.warn(marker1, "SNAFU");
Marker marker2 = new MyMarkerImpl(
    "REPORT", "[email protected], [email protected]", "phbGroup");
logger.info(marker2, "Everything is fine");
// here we have same smtpTo as above but different discriminatingValues, so this will be sent in another email/report
Marker marker3 = new MyMarkerImpl(
    "REPORT", "[email protected], [email protected]", "bugFixingGroup");
logger.error(marker3, "Why things gone bad", exception);

Hope it can be useful.

Smack answered 8/11, 2012 at 13:2 Comment(2)
This is great. The implementation of this feature had been pushed off, thinking that a better way of doing it would eventually be implemented, or a work around posted. For now, I think this is the best approach. However, I have an enum that I use with all the markers "alive" in the application stored in them. I'll make the markers mutable, but only have one instance.Cavort
@Cavort That's a simple approach, however I dislike mutable enums of any kind. It could also lead you to race condition issues if your application is multithreaded, a web application firing SMTP logging at user request, or a clustered environment. My way is usually creating immutable markers wrapping detached simple markers, and storing them for as long as they're needed. Good to know this might be useful to you =)Smack
R
4

I do it like this:

smtpappender.getToList().clear();
Rostellum answered 5/3, 2013 at 22:1 Comment(1)
Thank you, I was trying to clear the getToAsListOfString and that wasn't working.Asshur

© 2022 - 2024 — McMap. All rights reserved.