Sam Beroz created LOG4J2-895:
--------------------------------
Summary: Specify the SyslogAppender timeout value as part of the
configuration
Key: LOG4J2-895
URL: https://issues.apache.org/jira/browse/LOG4J2-895
Project: Log4j 2
Issue Type: New Feature
Affects Versions: 2.1
Environment: All
Reporter: Sam Beroz
Priority: Minor
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:
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
--
This message was sent by Atlassian JIRA
(v6.3.4#6332)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]