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

Specify the SyslogAppender connect timeout value as part of the configuration

    Details

    • Type: New Feature
    • Status: Resolved
    • Priority: Minor
    • Resolution: Fixed
    • Affects Version/s: 2.1
    • Fix Version/s: 2.2
    • Component/s: None
    • Labels:
      None
    • Environment:

      All

      Description

      I was testing the use case of taking the logserver down for maintenance and noticed that the SyslogAppender doesn't provide a way to override the default socket timeout. This resulted in my application noticeably hanging on startup when waiting for the socket to timeout.

      As a short term fix I created an extension largely based on the SyslogAppender that includes the connection timeout as a configurable parameter (timeoutMillis). I'd like something like this to be added in a future revision so I don't have to maintain this extension. Here's my version:

      FailFastSyslogAppender.java
      import java.io.ByteArrayOutputStream;
      import java.io.IOException;
      import java.io.OutputStream;
      import java.io.Serializable;
      import java.net.InetAddress;
      import java.net.InetSocketAddress;
      import java.net.Socket;
      import java.net.UnknownHostException;
      import java.nio.charset.Charset;
      
      import javax.net.ssl.SSLSocket;
      import javax.net.ssl.SSLSocketFactory;
      
      import org.apache.logging.log4j.Level;
      import org.apache.logging.log4j.core.Filter;
      import org.apache.logging.log4j.core.Layout;
      import org.apache.logging.log4j.core.appender.ManagerFactory;
      import org.apache.logging.log4j.core.appender.SocketAppender;
      import org.apache.logging.log4j.core.config.Configuration;
      import org.apache.logging.log4j.core.config.plugins.Plugin;
      import org.apache.logging.log4j.core.config.plugins.PluginAliases;
      import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
      import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
      import org.apache.logging.log4j.core.config.plugins.PluginElement;
      import org.apache.logging.log4j.core.config.plugins.PluginFactory;
      import org.apache.logging.log4j.core.layout.LoggerFields;
      import org.apache.logging.log4j.core.layout.Rfc5424Layout;
      import org.apache.logging.log4j.core.layout.SyslogLayout;
      import org.apache.logging.log4j.core.net.AbstractSocketManager;
      import org.apache.logging.log4j.core.net.Advertiser;
      import org.apache.logging.log4j.core.net.Facility;
      import org.apache.logging.log4j.core.net.Protocol;
      import org.apache.logging.log4j.core.net.SslSocketManager;
      import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
      import org.apache.logging.log4j.util.EnglishEnums;
      
      /**
       * The FailFastSyslog Appender.
       * I created this because the Syslog Appender doesn't currently allow you to set a connection timeout value.
       * Other then allowing a timeout it's basically identical to the SSL Syslog Appender.
       */
      @Plugin(name = "FailFastSyslog", category = "Core", elementType = "appender", printObject = true)
      public class FailFastSyslogAppender extends SocketAppender {
      
          private static final long serialVersionUID = 1L;
          protected static final String RFC5424 = "RFC5424";
      
          protected FailFastSyslogAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
                                   final boolean ignoreExceptions, final boolean immediateFlush,
                                   final AbstractSocketManager manager, final Advertiser advertiser) {
              super(name, layout, filter, manager, ignoreExceptions, immediateFlush, advertiser);
      
          }
      
          /**
           * Create a FailFastSyslogAppender.
           * @param host The name of the host to connect to.
           * @param port The port to connect to on the target host.
           * @param protocolStr The Protocol to use.
           * @param sslConfig TODO
           * @param reconnectionDelayMillis The interval in which failed writes should be retried.
           * @param immediateFail True if the write should fail if no socket is immediately available.
           * @param name The name of the Appender.
           * @param immediateFlush "true" if data should be flushed on each write.
           * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged;
           *                         otherwise they are propagated to the caller.
           * @param facility The Facility is used to try to classify the message.
           * @param id The default structured data id to use when formatting according to RFC 5424.
           * @param enterpriseNumber The IANA enterprise number.
           * @param includeMdc Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
           * record. Defaults to "true:.
           * @param mdcId The id to use for the MDC Structured Data Element.
           * @param mdcPrefix The prefix to add to MDC key names.
           * @param eventPrefix The prefix to add to event key names.
           * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false.
           * @param escapeNL String that should be used to replace newlines within the message text.
           * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record.
           * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records.
           * @param excludes A comma separated list of mdc keys that should be excluded from the LogEvent.
           * @param includes A comma separated list of mdc keys that should be included in the FlumeEvent.
           * @param required A comma separated list of mdc keys that must be present in the MDC.
           * @param format If set to "RFC5424" the data will be formatted in accordance with RFC 5424. Otherwise,
           * it will be formatted as a BSD Syslog record.
           * @param filter A Filter to determine if the event should be handled by this Appender.
           * @param config The Configuration.
           * @param charsetName The character set to use when converting the syslog String to a byte array.
           * @param exceptionPattern The converter pattern to use for formatting exceptions.
           * @param loggerFields The logger fields
           * @param advertise Whether to advertise
           * @return A FailFastSyslogAppender.
           */
          @PluginFactory
          public static FailFastSyslogAppender createAppender(
                  // @formatter:off
                  @PluginAttribute("host") final String host,
                  @PluginAttribute(value = "port", defaultInt = 0) final int port,
                  @PluginAttribute("protocol") final String protocolStr,
                  @PluginElement("SSL") final SslConfiguration sslConfig,
                  @PluginAliases("reconnectionDelay") // deprecated
                  @PluginAttribute(value = "reconnectionDelayMillis", defaultInt = 0) final int reconnectionDelayMillis,
                  @PluginAttribute(value = "timeoutMillis", defaultInt = 2000) final int timeoutMillis,
                  @PluginAttribute(value = "immediateFail", defaultBoolean = true) final boolean immediateFail,
                  @PluginAttribute("name") final String name,
                  @PluginAttribute(value = "immediateFlush", defaultBoolean = true) final boolean immediateFlush,
                  @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions,
                  @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility,
                  @PluginAttribute("id") final String id,
                  @PluginAttribute(value = "enterpriseNumber", defaultInt = Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
                  @PluginAttribute(value = "includeMdc", defaultBoolean = true) final boolean includeMdc,
                  @PluginAttribute("mdcId") final String mdcId,
                  @PluginAttribute("mdcPrefix") final String mdcPrefix,
                  @PluginAttribute("eventPrefix") final String eventPrefix,
                  @PluginAttribute(value = "newLine", defaultBoolean = false) final boolean newLine,
                  @PluginAttribute("newLineEscape") final String escapeNL,
                  @PluginAttribute("appName") final String appName,
                  @PluginAttribute("messageId") final String msgId,
                  @PluginAttribute("mdcExcludes") final String excludes,
                  @PluginAttribute("mdcIncludes") final String includes,
                  @PluginAttribute("mdcRequired") final String required,
                  @PluginAttribute("format") final String format,
                  @PluginElement("Filter") final Filter filter,
                  @PluginConfiguration final Configuration config,
                  @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charsetName,
                  @PluginAttribute("exceptionPattern") final String exceptionPattern,
                  @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
                  @PluginAttribute(value = "advertise", defaultBoolean = false) final boolean advertise) {
              // @formatter:on
      
              // TODO: add Protocol to TypeConverters
              final Protocol protocol = EnglishEnums.valueOf(Protocol.class, protocolStr);
              final boolean useTlsMessageFormat = sslConfig != null || protocol == Protocol.SSL;
              final Layout<? extends Serializable> layout = RFC5424.equalsIgnoreCase(format) ?
                  Rfc5424Layout.createLayout(facility, id, enterpriseNumber, includeMdc, mdcId, mdcPrefix, eventPrefix, newLine,
                      escapeNL, appName, msgId, excludes, includes, required, exceptionPattern, useTlsMessageFormat, loggerFields,
                      config) :
                  SyslogLayout.createLayout(facility, newLine, escapeNL, charsetName);
      
              if (name == null) {
                  LOGGER.error("No name provided for FailFastSyslogAppender");
                  return null;
              }
              
              SslSocketManagerFactory factory = new SslSocketManagerFactory();
              AbstractSocketManager manager = factory.createManager("TLS:" + host + ':' + port, new SslFactoryData(sslConfig, host, port, reconnectionDelayMillis, timeoutMillis, immediateFail, layout));
              
              return new FailFastSyslogAppender(name, layout, filter, ignoreExceptions, immediateFlush, manager,
                      advertise ? config.getAdvertiser() : null);
          }
          
          
          private static SSLSocketFactory createSslSocketFactory(final SslConfiguration sslConf) {
              SSLSocketFactory socketFactory;
      
              if (sslConf != null) {
                  socketFactory = sslConf.getSslSocketFactory();
              } else {
                  socketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
              }
      
              return socketFactory;
          }
      
      
          private static class SslSocketManagerFactory implements ManagerFactory<SslSocketManager, SslFactoryData> {
      
              private class TlsSocketManagerFactoryException extends Exception {
      
                  private static final long serialVersionUID = 1L;
              }
      
      
          @Override
          public SslSocketManager createManager(final String name, final SslFactoryData data) {
              InetAddress inetAddress = null;
              OutputStream os = null;
              Socket socket = null;
      
              try {
                  inetAddress = resolveAddress(data.host);
                  socket = createSocket(data);
                  os = socket.getOutputStream();
                  checkDelay(data.delayMillis, os);
              }
              catch (final IOException e) {
                  LOGGER.error("SslSocketManager ({})", name, e);
                  os = new ByteArrayOutputStream();
              }
              catch (final TlsSocketManagerFactoryException e) {
                  LOGGER.catching(Level.DEBUG, e);
                  return null;
              }
              return createManager(name, os, socket, data.sslConfig, inetAddress, data.host, data.port, data.delayMillis, data.immediateFail, data.layout);
          }
      
          private InetAddress resolveAddress(final String hostName) throws TlsSocketManagerFactoryException {
              InetAddress address;
      
              try {
                  address = InetAddress.getByName(hostName);
              } catch (final UnknownHostException ex) {
                  LOGGER.error("Could not find address of {}", hostName, ex);
                  throw new TlsSocketManagerFactoryException();
              }
      
              return address;
          }
      
          private void checkDelay(final int delay, final OutputStream os) throws TlsSocketManagerFactoryException {
              if (delay == 0 && os == null) {
                  throw new TlsSocketManagerFactoryException();
              }
          }
      
          private Socket createSocket(final SslFactoryData data) throws IOException {
              SSLSocketFactory socketFactory;
              SSLSocket socket;
      
              socketFactory = createSslSocketFactory(data.sslConfig);
      //      **********************************************************************************
      //        This is the changed part
              socket = (SSLSocket) socketFactory.createSocket();
              socket.connect(new InetSocketAddress(data.host, data.port), data.timeoutMillis);
      //      **********************************************************************************
              return socket;
          }
      
          private SslSocketManager createManager(final String name, final OutputStream os, final Socket socket,
                  final SslConfiguration sslConfig, final InetAddress inetAddress, final String host, final int port,
                  final int delay, final boolean immediateFail, final Layout<? extends Serializable> layout) {
              return new SslSocketManager(name, os, socket, sslConfig, inetAddress, host, port, delay, immediateFail,
                      layout);
          }
      }
      
          
          private static class SslFactoryData {
              protected SslConfiguration sslConfig;
              private final String host;
              private final int port;
              private final int delayMillis;
              private final int timeoutMillis;
              private final boolean immediateFail;
              private final Layout<? extends Serializable> layout;
      
              public SslFactoryData(final SslConfiguration sslConfig, final String host, final int port, final int delayMillis, 
              		final int timeoutMillis, final boolean immediateFail, final Layout<? extends Serializable> layout) {
                  this.host = host;
                  this.port = port;
                  this.delayMillis = delayMillis;
                  this.timeoutMillis = timeoutMillis;
                  this.immediateFail = immediateFail;
                  this.layout = layout;
                  this.sslConfig = sslConfig;
              }
          }
      }
      

      Thanks - Sam

        Attachments

          Issue Links

            Activity

              People

              • Assignee:
                garydgregory Gary Gregory
                Reporter:
                Sam Beroz Sam Beroz
              • Votes:
                0 Vote for this issue
                Watchers:
                2 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: