Uploaded image for project: 'Log4j 2'
  1. Log4j 2
  2. LOG4J2-1039

SmtpAppender needs the ability to filter out logging events that contain a specific Marker

    XMLWordPrintableJSON

Details

    • Patch

    Description

      I have a use case where some of my logged events have been marked with a custom Marker (e.g. "PRIVATE"). When the SMTP Appender sends out an e-mail with the triggering event and its cyclic buffer of collected log events, I do not want the log events that have been set with a custom Marker to be included in the e-mail.

      What follows is the source code for a custom SMTP appender plugin that I created to support the above use case:

      org.apache.logging.log4j.core.appender.FilteredSmtpAppender.java
      package org.apache.logging.log4j.core.appender;
      
      import org.apache.logging.log4j.core.Filter;
      import org.apache.logging.log4j.core.Layout;
      import org.apache.logging.log4j.core.LogEvent;
      import org.apache.logging.log4j.core.appender.AbstractAppender;
      import org.apache.logging.log4j.core.config.plugins.Plugin;
      import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
      import org.apache.logging.log4j.core.config.plugins.PluginElement;
      import org.apache.logging.log4j.core.config.plugins.PluginFactory;
      import org.apache.logging.log4j.core.filter.CompositeFilter;
      import org.apache.logging.log4j.core.filter.MarkerFilter;
      import org.apache.logging.log4j.core.filter.ThresholdFilter;
      import org.apache.logging.log4j.core.layout.HtmlLayout;
      import org.apache.logging.log4j.core.net.SmtpManager;
      import org.apache.logging.log4j.core.util.Booleans;
      
      import java.io.Serializable;
      import java.util.List;
      
      /**
       * Send an e-mail when a specific logging event occurs, typically on errors or
       * fatal errors.  Unlike the vanilla SmtpAppender, this variation allows for
       * using a MarkerFilter to filter out certain logging events from appearing in
       * the e-mail.
       * <p/>
       * &lt;Filters&gt;
       *   &lt;MarkerFilter marker="PRIVATE" onMatch="DENY" onMismatch="NEUTRAL"/&gt;
       *   &lt;ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/&gt;
       * &lt;/Filters&gt;
       * <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.
       * <p/>
       * By default, an email message will be formatted as HTML. This can be modified
       * by setting a layout for the appender.
       * <p/>
       * By default, an email message will be sent when an ERROR or higher severity
       * message is appended. This can be modified by setting a filter for the
       * appender.
       */
      @Plugin(name = "FilteredSMTP", category = "Core", elementType = "appender", printObject = true)
      public class FilteredSmtpAppender extends AbstractAppender {
      
        private static final long serialVersionUID = 1L;
        private static final int DEFAULT_BUFFER_SIZE = 512;
      
        /**
         * The SMTP Manager
         */
        private final SmtpManager manager;
      
        /**
         * Constructor.
         *
         * @param name             The Appender name.
         * @param filter           The Filter to associate with the Appender.
         * @param layout           The layout to use to format the event.
         * @param manager          The SmtpManager to use to format and send the e-mail.
         * @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be propagated.
         */
        protected FilteredSmtpAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final SmtpManager manager, final boolean ignoreExceptions) {
          super(name, filter, layout, ignoreExceptions);
          this.manager = manager;
        }
      
        /**
         * Create a FilteredSmtpAppender.
         *
         * @param name          The name of the Appender.
         * @param to            The comma-separated list of recipient email addresses.
         * @param cc            The comma-separated list of CC email addresses.
         * @param bcc           The comma-separated list of BCC email addresses.
         * @param from          The email address of the sender.
         * @param replyTo       The comma-separated list of reply-to email addresses.
         * @param subject       The subject of the email message.
         * @param smtpProtocol  The SMTP transport protocol (such as "smtps", defaults to "smtp").
         * @param smtpHost      The SMTP hostname to send to.
         * @param smtpPortStr   The SMTP port to send to.
         * @param smtpUsername  The username required to authenticate against the SMTP server.
         * @param smtpPassword  The password required to authenticate against the SMTP server.
         * @param smtpDebug     Enable mail session debuging on STDOUT.
         * @param bufferSizeStr How many log events should be buffered for inclusion in the
         *                      message?
         * @param layout        The layout to use (defaults to HtmlLayout).
         * @param filter        The Filter or null (defaults to ThresholdFilter, level of
         *                      ERROR).
         * @param ignore        If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
         *                      they are propagated to the caller.
         *
         * @return The FilteredSmtpAppender.
         */
        @PluginFactory
        public static FilteredSmtpAppender createAppender(
          @PluginAttribute("name") final String name,
          @PluginAttribute("to") final String to,
          @PluginAttribute("cc") final String cc,
          @PluginAttribute("bcc") final String bcc,
          @PluginAttribute("from") final String from,
          @PluginAttribute("replyTo") final String replyTo,
          @PluginAttribute("subject") final String subject,
          @PluginAttribute("smtpProtocol") final String smtpProtocol,
          @PluginAttribute("smtpHost") final String smtpHost,
          @PluginAttribute("smtpPort") final String smtpPortStr,
          @PluginAttribute("smtpUsername") final String smtpUsername,
          @PluginAttribute("smtpPassword") final String smtpPassword,
          @PluginAttribute("smtpDebug") final String smtpDebug,
          @PluginAttribute("bufferSize") final String bufferSizeStr,
          @PluginElement("Layout") Layout<? extends Serializable> layout,
          @PluginElement("Filter") Filter filter,
          @PluginAttribute("ignoreExceptions") final String ignore) {
          if (name == null) {
            LOGGER.error("No name provided for FilteredSmtpAppender");
            return null;
          }
      
          final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
          final int smtpPort = AbstractAppender.parseInt(smtpPortStr, 0);
          final boolean isSmtpDebug = Boolean.parseBoolean(smtpDebug);
          final int bufferSize = bufferSizeStr == null ? DEFAULT_BUFFER_SIZE : Integer.parseInt(bufferSizeStr);
      
          if (layout == null) {
            layout = HtmlLayout.createDefaultLayout();
          }
          if (filter == null) {
            filter = ThresholdFilter.createFilter(null, null, null);
          }
      
          final SmtpManager manager = SmtpManager.getSMTPManager(to, cc, bcc, from, replyTo, subject, smtpProtocol,
            smtpHost, smtpPort, smtpUsername, smtpPassword, isSmtpDebug, filter.toString(), bufferSize);
          if (manager == null) {
            return null;
          }
      
          return new FilteredSmtpAppender(name, filter, layout, manager, ignoreExceptions);
        }
      
        /**
         * Capture all events in CyclicBuffer.  Ignore events that are denied by a
         * MarkerFilter.
         *
         * @param event The Log event.
         *
         * @return true if the event should be filtered.
         */
        @Override
        public boolean isFiltered(final LogEvent event) {
          boolean filtered = false;
      
          final Filter filter = this.getFilter();
          if (filter instanceof CompositeFilter) {
            final List<Filter> filters = ((CompositeFilter)filter).getFilters();
            for (final Filter aFilter : filters) {
              final Filter.Result filterResult = (aFilter != null) ? aFilter.filter(event) : Filter.Result.NEUTRAL;
              if (!Filter.Result.NEUTRAL.equals(filterResult)) {
                filtered = (Filter.Result.DENY.equals(filterResult));
                if (filtered) {
                  final boolean isMarkerFilter = aFilter instanceof MarkerFilter;
                  // Ignore events that are denied by a MarkerFilter.
                  if (!isMarkerFilter) {
                    manager.add(event);
                  }
                }
                break;
              }
            }
          }
          else {
            filtered = super.isFiltered(event);
            if (filtered) {
              final boolean isMarkerFilter = filter instanceof MarkerFilter;
              if (!isMarkerFilter) {
                manager.add(event);
              }
            }
          }
      
          return filtered;
        }
      
        /**
         * Perform FilterableSmtpAppender 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 The Log event.
         */
        @Override
        public void append(final LogEvent event) {
          manager.sendEvents(getLayout(), event);
        }
      }

      Such a use case could be configured as follows for the custom Filtered SMTP Appender plugin. Here it is configured to accept only logging events of WARN level or greater:

      <?xml version="1.0" encoding="UTF-8"?>
      ...
          <Appender type="FilteredSMTP" name="filteredSmtp"
                    bufferSize="5"
                    smtpHost="${smtpHost}" smtpPort="${smtpPort}" smtpProtocol="${smtpProtocol}"
                    smtpUsername="${smtpUsername}" smtpPassword="${smtpPassword}"
                    subject="${emailSubject}"
                    from="${emailFrom}"
                    to="${emailTo}"
                    replyTo="${emailReplyTo}"
                    cc="${emailCc}"
                    bcc="${emailBcc}"
            >
            <Layout type="PatternLayout">
              <Pattern>%m</Pattern>
            </Layout>
            <Filters>
              <MarkerFilter marker="PRIVATE" onMatch="DENY" onMismatch="NEUTRAL"/>
              <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
            </Filters>
          </Appender>
      ...

      Attachments

        1. SmtpAppender.patch
          2 kB
          Tony DeFusco
        2. FilteredSmtpAppender.java
          8 kB
          Tony DeFusco

        Issue Links

          Activity

            People

              rgrabowski Ron Grabowski
              tonedef71 Tony DeFusco
              Votes:
              0 Vote for this issue
              Watchers:
              4 Start watching this issue

              Dates

                Created:
                Updated: