javalog4j2smtpappender

How can i set TO address at runtime with log4j2 SMTPAppender


I'm converting a simple java healthcheck application we use from using log4j1 to log4j2-17.2. It currently uses a custom SMTPAppender which extends the built in one to set subject and To address at runtime from values in the logged event.

In log4j2 can't do that as the SMTPAppender plugin class is final. Have got it working using the built in one for subject using a ThreadContext lookup, but that doesn't work for TO address and struggling to find anyway to set the To address that works.

This is what I have so far

For subject the SMTPAppender config is:

<SMTP   name="ErrorMail"
                subject="$${ctx:subject:-Int HealthCheck Error}" 
                to="user@company.com" 
                from="support@company.com"
                smtpHost="ldnsmtp" 
                smtpPort="25" 
                bufferSize="2000" 
                ignoreExceptions="true">
                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
            <EmailLoggerLayout />               
          </SMTP>

With this code setting the subject then logging the event. This works and the subject appears in the email OK

            if (healthCheckAlert.getSubject() != null) {
                ThreadContext.put("subject", subjectPrefix + healthCheckAlert.getSubject());
            }
            warningSender.log(level, message);

For the To address I'm trying this to use a ThreadContext variable in same way:

         <SMTP  name="WarningMail" 
                subject="$${ctx:subject:-Int HealthCheck Warning}"
                to="$${ctx:mailto}" 
                from="support@company.com"
                smtpHost="ldnsmtp" 
                smtpPort="25" 
                bufferSize="2000" 
                ignoreExceptions="true">
                <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> 
                <EmailLoggerLayout />               
         </SMTP>

With this code to set it:

        if (healthCheckAlert.getMailTo() != null) {
            ThreadContext.put("mailto", healthCheckAlert.getMailTo());
        }

But get the error below, which indicates it hasn't been expanded

2023-01-05 12:40:25,951 main ERROR SmtpManager SMTP:${ctx:mailto}:::support@company.com::${ctx:subject:-Int HealthCheck Warning}:smtp:ldnsmtp:25:::INFO Could not set SmtpAppender message options: javax.mail.internet.AddressException: Local address contains illegal character in string ``${ctx:mailto}'' javax.mail.internet.AddressException: Local address contains illegal character in string ``${ctx:mailto}''
    at javax.mail.internet.InternetAddress.checkAddress(InternetAddress.java:1216)
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:1091)
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:633)
    at org.apache.logging.log4j.core.net.MimeMessageBuilder.parseAddresses(MimeMessageBuilder.java:99)
    at org.apache.logging.log4j.core.net.MimeMessageBuilder.setRecipients(MimeMessageBuilder.java:66)
    at org.apache.logging.log4j.core.net.SmtpManager.createMimeMessage(SmtpManager.java:71)
    at org.apache.logging.log4j.core.net.SmtpManager.connect(SmtpManager.java:340)
    at org.apache.logging.log4j.core.net.SmtpManager.sendEvents(SmtpManager.java:172)
    at org.apache.logging.log4j.core.appender.SmtpAppender.append(SmtpAppender.java:353)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:675)
    at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:633)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:616)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:552)
    at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
    at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
    at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1994)
    at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1852)
    at org.apache.logging.log4j.spi.AbstractLogger.log(AbstractLogger.java:1642)
    at com.company.utils.healthcheck.logging.EmailLogger.log(EmailLogger.java:42)
    at com.company.utils.healthcheck.EmailLoggerTest.testWarnAlert(EmailLoggerTest.java:42)

Have done a lot of searching to find a way to do this to no avail. Can anyone let me know if this can be done with the ThreadContext lookup somehow, or is that just not possible in log4j2 for the TO address? If so any clues on some other way to do this?.

My current backstop idea is to make our own copy of the SMTPAppender class using the log4j2 source code and modify that, but would rather not.

UPDATE: The stacked trace when trying Piotr's idea to use a routing appender is:

2023-01-17 11:46:23,815 main ERROR SmtpManager SMTP:${ctx:mailto}:::LondonIntegrationRtB@standardbank.com::${ctx:subject:-Int HealthCheck Warning}:smtp:ldnsmtp:25:::INFO Could not set SmtpAppender message options: javax.mail.internet.AddressException: Local address contains illegal character in string ``${ctx:mailto}'' javax.mail.internet.AddressException: Local address contains illegal character in string ``${ctx:mailto}''
    at javax.mail.internet.InternetAddress.checkAddress(InternetAddress.java:1216)
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:1091)
    at javax.mail.internet.InternetAddress.parse(InternetAddress.java:633)
    at org.apache.logging.log4j.core.net.MimeMessageBuilder.parseAddresses(MimeMessageBuilder.java:99)
    at org.apache.logging.log4j.core.net.MimeMessageBuilder.setRecipients(MimeMessageBuilder.java:66)
    at org.apache.logging.log4j.core.net.SmtpManager.createMimeMessage(SmtpManager.java:71)
    at org.apache.logging.log4j.core.net.SmtpManager.connect(SmtpManager.java:340)
    at org.apache.logging.log4j.core.net.SmtpManager.sendEvents(SmtpManager.java:172)
    at org.apache.logging.log4j.core.appender.SmtpAppender.append(SmtpAppender.java:353)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.appender.routing.RoutingAppender.append(RoutingAppender.java:243)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:675)
    at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:633)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:616)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:552)
    at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
    at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
    at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1994)
    at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1852)
    at org.apache.logging.log4j.spi.AbstractLogger.log(AbstractLogger.java:1642)
    at com.standardbank.utils.healthcheck.logging.EmailLogger.log(EmailLogger.java:42)
    at com.standardbank.utils.healthcheck.EmailLoggerTest.testWarnAlert(EmailLoggerTest.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

LogEvent message class = class org.apache.logging.log4j.core.impl.MutableLogEventMessage parameters = [Ljava.lang.Object;@408d971b2023-01-17 11:46:23,849 main ERROR SmtpManager SMTP:${ctx:mailto}:::LondonIntegrationRtB@standardbank.com::${ctx:subject:-Int HealthCheck Warning}:smtp:ldnsmtp:25:::INFO Caught exception while sending e-mail notification.: java.lang.NullPointerException java.lang.NullPointerException
    at org.apache.logging.log4j.core.net.SmtpManager.sendMultipartMessage(SmtpManager.java:287)
    at org.apache.logging.log4j.core.net.SmtpManager.sendEvents(SmtpManager.java:189)
    at org.apache.logging.log4j.core.appender.SmtpAppender.append(SmtpAppender.java:353)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.appender.routing.RoutingAppender.append(RoutingAppender.java:243)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:675)
    at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:633)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:616)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:552)
    at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
    at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
    at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1994)
    at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1852)
    at org.apache.logging.log4j.spi.AbstractLogger.log(AbstractLogger.java:1642)
    at com.standardbank.utils.healthcheck.logging.EmailLogger.log(EmailLogger.java:42)
    at com.standardbank.utils.healthcheck.EmailLoggerTest.testWarnAlert(EmailLoggerTest.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

2023-01-17 11:46:23,852 main ERROR An exception occurred processing Appender WarningMail org.apache.logging.log4j.LoggingException: Error occurred while sending email
    at org.apache.logging.log4j.core.net.SmtpManager.sendEvents(SmtpManager.java:192)
    at org.apache.logging.log4j.core.appender.SmtpAppender.append(SmtpAppender.java:353)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.appender.routing.RoutingAppender.append(RoutingAppender.java:243)
    at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:161)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:134)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:125)
    at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:89)
    at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:675)
    at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:633)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:616)
    at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:552)
    at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:82)
    at org.apache.logging.log4j.core.Logger.log(Logger.java:161)
    at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2205)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2159)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2142)
    at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:1994)
    at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1852)
    at org.apache.logging.log4j.spi.AbstractLogger.log(AbstractLogger.java:1642)
    at com.standardbank.utils.healthcheck.logging.EmailLogger.log(EmailLogger.java:42)
    at com.standardbank.utils.healthcheck.EmailLoggerTest.testWarnAlert(EmailLoggerTest.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:76)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:678)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: java.lang.NullPointerException
    at org.apache.logging.log4j.core.net.SmtpManager.sendMultipartMessage(SmtpManager.java:287)
    at org.apache.logging.log4j.core.net.SmtpManager.sendEvents(SmtpManager.java:189)
    ... 46 more


Solution

  • The SMTPAppender only evaluates the subject of an e-mail as a pattern. The remaining fields are immutable.

    What you can do is to use RoutingAppender to create a different SMTP appender per e-mail recipient. Something like this should work (I didn't test it):

    <Routing name="Routing">
      <Routes pattern="$${ctx:mailto}">
        <Route>
             <SMTP  name="WarningMail-${ctx:mailto}" 
                    subject="$${ctx:subject:-Int HealthCheck Warning}"
                    to="${ctx:mailto:-root@example.com}" 
                    from="support@company.com"
                    smtpHost="ldnsmtp" 
                    smtpPort="25" 
                    bufferSize="2000" 
                    ignoreExceptions="true">
                    <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> 
                    <EmailLoggerLayout />               
             </SMTP>
        </Route>
      </Routes>
      <IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
    </Routing>