org.jboss.logmanager.handlers.SyslogHandler Maven / Gradle / Ivy
/*
* Copyright 2018 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.logmanager.handlers;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.DateFormatSymbols;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.Calendar;
import java.util.Collection;
import java.util.Locale;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.regex.Pattern;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import org.jboss.logmanager.ExtHandler;
import org.jboss.logmanager.ExtLogRecord;
import org.wildfly.common.os.Process;
/**
* A syslog handler for logging to syslogd.
*
* This handler can write to syslog servers that accept the RFC3164
* and RFC5424 formats. Writes can be done via TCP, SSL over TCP or
* UDP protocols. You can also override the {@link #setOutputStream(java.io.OutputStream) output stream} if a custom
* protocol is needed.
*
*
*
*
*
* Configuration Properties:
*
*
*
*
* Property
* Description
* Type
* Default
*
*
* serverHostname
* The address of the syslog server
* {@link java.lang.String String}
* localhost
*
*
* port
* The port of the syslog server
* int
* 514
*
*
* facility
* The facility used to calculate the priority of the log message
* {@link Facility Facility}
* {@link Facility#USER_LEVEL USER_LEVEL}
*
*
* appName
* The name of the application that is logging
* {@link java.lang.String String}
* java
*
*
* hostname
* The name of the host the messages are being sent from. See {@link #setHostname(String)} for more
* details
* {@link java.lang.String String}
* {@code null}
*
*
* syslogType
* The type of the syslog used to format the message
* {@link SyslogType SyslogType}
* {@link SyslogType#RFC5424 RFC5424}
*
*
* protocol
* The protocol to send the message over
* {@link Protocol Protocol}
* {@link Protocol#UDP UDP}
*
*
* delimiter
* The delimiter to use at the end of the message if {@link #setUseMessageDelimiter(boolean) useDelimiter}
* is set to {@code true}
* {@link java.lang.String String}
* For {@link Protocol#UDP UDP} {@code null} - For {@link Protocol#TCP TCP} or {@link Protocol#SSL_TCP
* SSL_TCP} {@code \n}
*
*
* useDelimiter
* Whether or not the message should be appended with a {@link #setMessageDelimiter(String)
* delimiter}
* {@code boolean}
* For {@link Protocol#UDP UDP} {@code false} - For {@link Protocol#TCP TCP} or {@link Protocol#SSL_TCP
* SSL_TCP} {@code true}
*
*
* useCountingFraming
* Prefixes the size of the message, mainly used for {@link Protocol#TCP TCP} or {@link Protocol#SSL_TCP
* SSL_TCP}, connections to the message being sent to the syslog server. See http://tools.ietf.org/html/rfc6587
* for more details on framing types.
* {@code boolean}
* {@code false}
*
*
* truncate
* Whether or not a message, including the header, should truncate the message if the length in bytes is
* greater than the {@link #setMaxLength(int) maximum length}. If set to {@code false} messages will be split and sent
* with the same header values.
* {@code boolean}
* {@code true}
*
*
* maxLength
* The maximum length a log message, including the header, is allowed to be.
* {@code int}
* For {@link SyslogType#RFC3164 RFC3164} 1024 (1k) - For {@link SyslogType#RFC5424 RFC5424} 2048
* (2k)
*
*
*
*
*
*
* @author James R. Perkins
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class SyslogHandler extends ExtHandler {
/**
* The type of socket the syslog should write to
*/
public static enum Protocol {
/**
* Transmission Control Protocol
*/
TCP,
/**
* User Datagram Protocol
*/
UDP,
/**
* Transport Layer Security over TCP
*/
SSL_TCP,
}
/**
* Severity as defined by RFC-5424 (http://tools.ietf.org/html/rfc5424)
* and RFC-3164 (http://tools.ietf.org/html/rfc3164).
*/
public static enum Severity {
EMERGENCY(0, "Emergency: system is unusable"),
ALERT(1, "Alert: action must be taken immediately"),
CRITICAL(2, "Critical: critical conditions"),
ERROR(3, "Error: error conditions"),
WARNING(4, "Warning: warning conditions"),
NOTICE(5, "Notice: normal but significant condition"),
INFORMATIONAL(6, "Informational: informational messages"),
DEBUG(7, "Debug: debug-level messages");
final int code;
final String desc;
private Severity(final int code, final String desc) {
this.code = code;
this.desc = desc;
}
@Override
public String toString() {
return String.format("%s[%d,%s]", name(), code, desc);
}
/**
* Maps a {@link Level level} to a {@link Severity severity}. By default returns {@link
* Severity#INFORMATIONAL}.
*
* @param level the level to map
*
* @return the severity
*/
// TODO (jrp) allow for a custom mapping
public static Severity fromLevel(final Level level) {
if (level == null) {
throw new IllegalArgumentException("Level cannot be null");
}
final int levelValue = level.intValue();
if (levelValue >= org.jboss.logmanager.Level.FATAL.intValue()) {
return Severity.EMERGENCY;
} else if (levelValue >= org.jboss.logmanager.Level.SEVERE.intValue() || levelValue >= org.jboss.logmanager.Level.ERROR.intValue()) {
return Severity.ERROR;
} else if (levelValue >= org.jboss.logmanager.Level.WARN.intValue() || levelValue >= Level.WARNING.intValue()) {
return Severity.WARNING;
} else if (levelValue >= org.jboss.logmanager.Level.INFO.intValue()) {
return Severity.INFORMATIONAL;
} else if (levelValue >= org.jboss.logmanager.Level.TRACE.intValue() || levelValue >= Level.FINEST.intValue()) {
// DEBUG for all TRACE, DEBUG, FINE, FINER, FINEST
return Severity.DEBUG;
}
return Severity.INFORMATIONAL;
}
}
/**
* Facility as defined by RFC-5424 (http://tools.ietf.org/html/rfc5424)
* and RFC-3164 (http://tools.ietf.org/html/rfc3164).
*/
public static enum Facility {
KERNEL(0, "kernel messages"),
USER_LEVEL(1, "user-level messages"),
MAIL_SYSTEM(2, "mail system"),
SYSTEM_DAEMONS(3, "system daemons"),
SECURITY(4, "security/authorization messages"),
SYSLOGD(5, "messages generated internally by syslogd"),
LINE_PRINTER(6, "line printer subsystem"),
NETWORK_NEWS(7, "network news subsystem"),
UUCP(8, "UUCP subsystem"),
CLOCK_DAEMON(9, "clock daemon"),
SECURITY2(10, "security/authorization messages"),
FTP_DAEMON(11, "FTP daemon"),
NTP(12, "NTP subsystem"),
LOG_AUDIT(13, "log audit"),
LOG_ALERT(14, "log alert"),
CLOCK_DAEMON2(15, "clock daemon (note 2)"),
LOCAL_USE_0(16, "local use 0 (local0)"),
LOCAL_USE_1(17, "local use 1 (local1)"),
LOCAL_USE_2(18, "local use 2 (local2)"),
LOCAL_USE_3(19, "local use 3 (local3)"),
LOCAL_USE_4(20, "local use 4 (local4)"),
LOCAL_USE_5(21, "local use 5 (local5)"),
LOCAL_USE_6(22, "local use 6 (local6)"),
LOCAL_USE_7(23, "local use 7 (local7)");
final int code;
final String desc;
final int octal;
private Facility(final int code, final String desc) {
this.code = code;
this.desc = desc;
octal = code * 8;
}
@Override
public String toString() {
return String.format("%s[%d,%s]", name(), code, desc);
}
}
/**
* The syslog type used for formatting the message.
*/
public static enum SyslogType {
/**
* Formats the message according the the RFC-5424 specification (http://tools.ietf.org/html/rfc5424#section-6
*/
RFC5424,
/**
* Formats the message according the the RFC-3164 specification (http://tools.ietf.org/html/rfc3164#section-4.1
*/
RFC3164,
}
public static final InetAddress DEFAULT_ADDRESS;
public static final int DEFAULT_PORT = 514;
public static final int DEFAULT_SECURE_PORT = 6514;
public static final String DEFAULT_ENCODING = "UTF-8";
public static final Facility DEFAULT_FACILITY = Facility.USER_LEVEL;
public static final String NILVALUE_SP = "- ";
private static final Pattern PRINTABLE_ASCII_PATTERN = Pattern.compile("[\\P{Print} ]");
static {
try {
DEFAULT_ADDRESS = InetAddress.getByName("localhost");
} catch (UnknownHostException e) {
throw new IllegalStateException("Could not create address to localhost");
}
}
private final Object outputLock = new Object();
private InetAddress serverAddress;
private int port;
private String appName;
private String hostname;
private Facility facility;
private SyslogType syslogType;
private final String pid;
private OutputStream out;
private Protocol protocol;
private boolean useCountingFraming;
private boolean initializeConnection;
private boolean outputStreamSet;
private String delimiter;
private boolean useDelimiter;
private boolean truncate;
private int maxLen;
private boolean blockOnReconnect;
private ClientSocketFactory clientSocketFactory;
/**
* The default class constructor.
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler() throws IOException {
this(DEFAULT_ADDRESS, DEFAULT_PORT);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverHostname}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverHostname the server to send the messages to
* @param port the port the syslogd is listening on
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final String serverHostname, final int port) throws IOException {
this(serverHostname, port, DEFAULT_FACILITY, null);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverAddress the server to send the messages to
* @param port the port the syslogd is listening on
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final InetAddress serverAddress, final int port) throws IOException {
this(serverAddress, port, DEFAULT_FACILITY, null);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverHostname the server to send the messages to
* @param port the port the syslogd is listening on
* @param facility the facility to use when calculating priority
* @param hostname the name of the host the messages are being sent from see {@link #setHostname(String)} for
* details on the hostname
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final String serverHostname, final int port, final Facility facility, final String hostname) throws IOException {
this(serverHostname, port, facility, null, hostname);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverAddress the server to send the messages to
* @param port the port the syslogd is listening on
* @param facility the facility to use when calculating priority
* @param hostname the name of the host the messages are being sent from see {@link #setHostname(String)} for
* details on the hostname
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final InetAddress serverAddress, final int port, final Facility facility, final String hostname) throws IOException {
this(serverAddress, port, facility, null, hostname);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverHostname the server to send the messages to
* @param port the port the syslogd is listening on
* @param facility the facility to use when calculating priority
* @param syslogType the type of the syslog used to format the message
* @param hostname the name of the host the messages are being sent from see {@link #setHostname(String)} for
* details on the hostname
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final String serverHostname, final int port, final Facility facility, final SyslogType syslogType, final String hostname) throws IOException {
this(InetAddress.getByName(serverHostname), port, facility, syslogType, hostname);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverAddress the server to send the messages to
* @param port the port the syslogd is listening on
* @param facility the facility to use when calculating priority
* @param syslogType the type of the syslog used to format the message
* @param hostname the name of the host the messages are being sent from see {@link #setHostname(String)} for
* details on the hostname
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final InetAddress serverAddress, final int port, final Facility facility, final SyslogType syslogType, final String hostname) throws IOException {
this(serverAddress, port, facility, syslogType, null, hostname);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverHostname the server to send the messages to
* @param port the port the syslogd is listening on
* @param facility the facility to use when calculating priority
* @param syslogType the type of the syslog used to format the message
* @param protocol the socket type used to the connect to the syslog server
* @param hostname the name of the host the messages are being sent from see {@link #setHostname(String)} for
* details on the hostname
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final String serverHostname, final int port, final Facility facility, final SyslogType syslogType, final Protocol protocol, final String hostname) throws IOException {
this(InetAddress.getByName(serverHostname), port, facility, syslogType, protocol, hostname);
}
/**
* Creates a new syslog handler that sends the messages to the server represented by the {@code serverAddress}
* parameter on the port represented by the {@code port} parameter.
*
* @param serverAddress the server to send the messages to
* @param port the port the syslogd is listening on
* @param facility the facility to use when calculating priority
* @param syslogType the type of the syslog used to format the message
* @param protocol the socket type used to the connect to the syslog server
* @param hostname the name of the host the messages are being sent from see {@link #setHostname(String)} for
* details on the hostname
*
* @throws IOException if an error occurs creating the UDP socket
*/
public SyslogHandler(final InetAddress serverAddress, final int port, final Facility facility, final SyslogType syslogType, final Protocol protocol, final String hostname) throws IOException {
this.serverAddress = serverAddress;
this.port = port;
this.facility = facility;
final long pid = Process.getProcessId();
this.pid = (pid != -1 ? Long.toString(pid) : null);
this.appName = "java";
this.hostname = checkPrintableAscii("host name", hostname);
this.syslogType = (syslogType == null ? SyslogType.RFC5424 : syslogType);
if (protocol == null) {
this.protocol = Protocol.UDP;
delimiter = null;
useDelimiter = false;
} else {
this.protocol = protocol;
if (protocol == Protocol.UDP) {
delimiter = null;
useDelimiter = false;
} else if (protocol == Protocol.TCP || protocol == Protocol.SSL_TCP) {
delimiter = "\n";
useDelimiter = true;
}
}
useCountingFraming = false;
initializeConnection = true;
outputStreamSet = false;
truncate = true;
if (this.syslogType == SyslogType.RFC3164) {
maxLen = 1024;
} else if (this.syslogType == SyslogType.RFC5424) {
maxLen = 2048;
}
blockOnReconnect = false;
}
@Override
public final void doPublish(final ExtLogRecord record) {
// Don't log empty messages
if (record.getMessage() == null || record.getMessage().isEmpty()) {
return;
}
synchronized (outputLock) {
init();
if (out == null) {
throw new IllegalStateException("The syslog handler has been closed.");
}
try {
// Create the header
final byte[] header;
if (syslogType == SyslogType.RFC3164) {
header = createRFC3164Header(record);
} else if (syslogType == SyslogType.RFC5424) {
header = createRFC5424Header(record);
} else {
throw new IllegalStateException("The syslog type of '" + syslogType + "' is invalid.");
}
// Trailer in bytes
final byte[] trailer = delimiter == null ? new byte[] {0x00} : delimiter.getBytes("UTF-8");
// Buffer currently only has the header
final int maxMsgLen = maxLen - (header.length + (useDelimiter ? trailer.length : 0));
// Can't write the message if the header and trailer are bigger than the allowed length
if (maxMsgLen < 1) {
throw new IOException(String.format("The header and delimiter length, %d, is greater than the message length, %d, allows.",
(header.length + (useDelimiter ? trailer.length : 0)), maxLen));
}
// Get the message
final Formatter formatter = getFormatter();
String logMsg;
if (formatter != null) {
logMsg = formatter.format(record);
} else {
logMsg = record.getFormattedMessage();
}
if (!Normalizer.isNormalized(logMsg, Form.NFKC)) {
logMsg = Normalizer.normalize(logMsg, Form.NFKC);
}
// Create a message buffer
ByteStringBuilder message = new ByteStringBuilder(maxMsgLen);
// Write the message to the buffer, the len is the number of characters written
int len = message.write(logMsg, maxMsgLen);
sendMessage(header, message, trailer);
// If not truncating, chunk the message and send separately
if (!truncate && len < logMsg.length()) {
while (len > 0) {
// Get the next part of the message to write
logMsg = logMsg.substring(len + 1);
if (logMsg.isEmpty()) {
break;
}
message = new ByteStringBuilder(maxMsgLen);
// Write the message to the buffer, the len is the number of characters written
len = message.write(logMsg, maxMsgLen);
sendMessage(header, message, trailer);
}
}
} catch (IOException e) {
reportError("Could not write to syslog", e, ErrorManager.WRITE_FAILURE);
}
}
super.doPublish(record);
}
/**
* Writes the message to the output stream. The message buffer is cleared after it's written.
*
* @param message the message to write
*
* @throws IOException if there is an error writing the message
*/
private void sendMessage(final byte[] header, final ByteStringBuilder message, final byte[] trailer) throws IOException {
final ByteStringBuilder payload = new ByteStringBuilder(header.length + message.length());
// Prefix the size of the message if counting framing is being used
if (useCountingFraming) {
int len = header.length + message.length() + (useDelimiter ? trailer.length : 0);
payload.append(len).append(' ');
}
payload.append(header);
payload.append(message);
if (useDelimiter) payload.append(trailer);
out.write(payload.toArray());
// If this is a TcpOutputStream print any errors that may have occurred
if (out instanceof TcpOutputStream) {
final Collection-
*
- FQDN *
- Static IP address *
- hostname *
- Dynamic IP address *
- {@code null} *