1

Bafflingly, there's no SMTP implementation out of the box. How is it not a basic feature?

Tried adding a custom handler type MailHandler, but I get stuck setting the properties. It seems a good chunk of them (such as the mail server's hostname) are defined by a Properties object in the MailHandler's constructor, but I don't know how to set that up in the custom handler's properties, as it relies on setters instead.

Also tried registering a log4j SMTPAppender. Although this works surprisingly well, and the documentation claims it's legal, Wildfly threatens to bite:

16:49:21,090 WARN  [org.jboss.as.logging] (management-handler-thread - 3) WFLYLOG0099: Usage of a log4j appender (org.apache.log4j.net.SMTPAppender) found in a custom-handler. Support for using appenders as custom handlers has been deprecated and will be removed in a future release.

Am I naive in thinking I will be able to pull this off without coding my own generic mail handler, or deferring the mail stuff to syslog?

Yd Ahhrk
  • 113

3 Answers3

3

You'd have to develop your own MailHandler or SmtpHandler. WildFly does not currently provide one because there is not a good way to configure one. Also as noted in the comments of the question, it's not really the ideal way to be notified of errors.

The log4j SmtpAppender will work until WildFly 27 where log4j support was removed.

The JUL MailHandler won't work as WildFly needs to be able to use setters to set properties.

0

Am I naive in thinking I will be able to pull this off without coding my own generic mail handler, or deferring the mail stuff to syslog?

If you are using at least JavaMail 1.5.0, JakartaMail, or AngusMail, the MailHandler itself will connect to local address name on port 25 as user.name system property with a computed from and to address when no properties have been specified. If there is a mail transfer agent running that accepts the connection you can then configure rules on the MTA to forward and authenticate. Securing that MTA is the tricky part of this approach.

Bafflingly, there's no SMTP implementation out of the box. How is it not a basic feature?

I created an Angus Mail issue #110 WildFly support for MailHandler to provide out of the box support in Angus Mail 2.0.3 or newer. This change adds new methods to configure the mail properties via strings. Steps for setup are as follows:

  1. Determine the module name of the MailHandler. Start by verify the version Angus Mail in WildFly. The jar is located in wildfly-29.0.1.Final/modules/system/layers/base/org/eclipse/angus/mail/main. If that version is angus-mail-2.0.3.jar or newer the module name is org.eclipse.angus.mail and you can continue to step 2 using this name. If the version is older then a new module must be created. Download latest jakarta.mail-api, jakarta.activation-api, angus-activation, and angus-mail jars. Start WildFly and connect the jbosscli. Using the 4 jars that were downloaded create a module named org.eclipse.angus.logging-mailhandler which will be used in the following steps. Here is an example:
module add --name=org.eclipse.angus.logging-mailhandler --resource-delimiter=; --resources=~/jaf-api/api/target/jakarta.activation-api-X.Y.Z.jar;~/mail/api/target/jakarta.mail-api-X.Y.Z.jar;~/angus-activation/activation-registry/target/angus-activation-X.Y.Z.jar;~/angus-mail/providers/angus-mail/target/angus-mail-X.Y.Z.jar --dependencies=jakarta.activation.api,jakarta.mail.api,org.eclipse.angus.activation,org.eclipse.angus.mail,org.jboss.modules,java.logging,org.jboss.logging
  1. Create spam and push filters to avoid flooding. Angus Mail ships with DurationFilter that can be installed to control the max rate emails can be generated. Assuming default MailHandler capacity of 1000 and at most one email per hour then use the following in the jbosscli (edit the module name if needed):
/subsystem=logging/filter=SPAM_FILTER:add(module=org.eclipse.angus.logging-mailhandler, class=org.eclipse.angus.mail.util.logging.DurationFilter, properties={records=1000,durationMillis=3600000})

/subsystem=logging/filter=PUSH_FILTER:add(module=org.eclipse.angus.logging-mailhandler, class=org.eclipse.angus.mail.util.logging.DurationFilter, properties={records=1,durationMillis=3600000})

  1. Create MailHandler as CustomHandler. In the jbosscli (edit the module name if needed):
/subsystem=logging/custom-handler="MAIL":add(class="org.eclipse.angus.mail.util.logging.MailHandler", module="org.eclipse.angus.logging-mailhandler", level="WARNING", formatter="%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n", encoding="UTF-8", filter-spec="SPAM_FILTER", properties={"pushLevel"=>"ALL","pushFilter"=>"PUSH_FILTER", "mailEntries"=>"mail.smtp.port:25#!mail.host:localhost#!mail.from:wildfly@localhost#!mail.to:support@localhost#!mail.smtp.connectiontimeout:15000#!mail.smtp.timeout:45000#!verify:local"})

Consult the AngusMail API for additional mail entries. This configuration will buffer records when PUSH_FILTER rate is exceeded and will drop records when the SPAM_FILTER rate is exceeded.

  1. Optionally, wrap the MailHandler in an AsyncHandler. In the jbosscli (edit the module name if needed):
/subsystem=logging/async-handler="ASYNC_MAIL":add(level="WARNING", queue-length="10000", overflow-action="DISCARD", subhandlers=[MAIL])

Overflow action can be BLOCK if the version of WildFly has the fix for WFCORE-6596: AsyncHandler must not park worker thread. By default the AsyncHandler will auto-flush which can bypass the push filter cool down duration which will result in more smaller emails.

  1. Attach the MailHandler to the root logger. In the jbosscli (edit the module name if needed):
/subsystem=logging/root-logger=ROOT:add-handler(name="ASYNC_MAIL")

Additional features or bugs can be filed in the angus-mail issue tracker.

The other option is MailHandler supports getting and setting the mail properties. You can extend the MailHandler and add the needed property methods for WildFly.

public class WildFlyMailHandler extends MailHandler {
     public WildFlyMailHandler() {
     }
 public void setHost(String host) {
     setMailValue("mail.host", host);
 }

 public void setTo(String to) {
     setMailValue("mail.to", to);
 }

 private void setMailValue(String key, String value) {
     Properties p = getMailProperties();
     p.setProperty(key, value);
     setMailProperties(p);
 }

}

You then install that jar as a module in WildFly.

jmehrens
  • 101
0

I solved this by creating a ServletContextListener, which adds a handler to the root logger:

public class ErrorLogListener implements jakarta.servlet.ServletContextListener {
private static final Logger logger = Logger.getLogger(ErrorLogListener.class.getName());

@Override
public void contextInitialized(ServletContextEvent sce) {
    LogManager.getLogManager().getLogger("").addHandler(new LogHandler());
}

static class LogHandler extends java.util.logging.Handler {

    public static Session getMailSession() throws NamingException {
        InitialContext initialContext = new InitialContext();
        return (Session) initialContext.lookup("java:jboss/mail/Default");
    }

    @Override
    public void publish(LogRecord record) {
        if (record.getLevel() == Level.SEVERE) {
            sendErrorEmail(record.getMessage());
        }
    }

    private void sendErrorEmail(String msg) {
        try {
            Message message = new MimeMessage(getMailSession());
            message.setRecipients(...);
            message.setFrom(...);
            message.setSubject(...);
            message.setText(msg);
            Transport.send(message);
        } catch (MessagingException | UnsupportedEncodingException | NamingException e) {
            logger.log(Level.WARNING, "Unable so send error mail", e);
        }
    }

    @Override
    public void flush() {}

    @Override
    public void close() throws SecurityException {}
}

}

In web.xml add the listener:

<listener>
    <listener-class>my.package.ErrorLogListener</listener-class>
</listener>

The configuration of the mailserver I have put in standalone.xml, for example

<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
    ...
    <outbound-socket-binding name="mail-smtp">
        <remote-destination host="mySmtpHost" port="25"/>
    </outbound-socket-binding>
</socket-binding-group>

Or if you are using Jboss CLI:

/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=mail-smtp:write-attribute(name=host, value=mySmtpHost)
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=mail-smtp:write-attribute(name=port, value=25)
m.hrnr
  • 1