biz.paluch.logging.gelf.log4j2.GelfLogAppender Maven / Gradle / Ivy
package biz.paluch.logging.gelf.log4j2;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Filter;
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.helpers.Strings;
import org.apache.logging.log4j.status.StatusLogger;
import biz.paluch.logging.gelf.DynamicMdcMessageField;
import biz.paluch.logging.gelf.LogMessageField;
import biz.paluch.logging.gelf.MdcGelfMessageAssembler;
import biz.paluch.logging.gelf.MdcMessageField;
import biz.paluch.logging.gelf.StaticMessageField;
import biz.paluch.logging.gelf.intern.ErrorReporter;
import biz.paluch.logging.gelf.intern.GelfMessage;
import biz.paluch.logging.gelf.intern.GelfSender;
import biz.paluch.logging.gelf.intern.GelfSenderFactory;
/**
* Logging-Handler for GELF (Graylog Extended Logging Format). This Java-Util-Logging Handler creates GELF Messages and posts
* them using UDP (default) or TCP. Following parameters are supported/needed:
*
*
* - host (Mandatory): Hostname/IP-Address of the Logstash Host
*
* - tcp:(the host) for TCP, e.g. tcp:127.0.0.1 or tcp:some.host.com
* - udp:(the host) for UDP, e.g. udp:127.0.0.1 or udp:some.host.com
* - (the host) for UDP, e.g. 127.0.0.1 or some.host.com
*
*
* - port (Optional): Port, default 12201
* - extractStackTrace (Optional): Post Stack-Trace to StackTrace field, default false
* - filterStackTrace (Optional): Perform Stack-Trace filtering (true/false), default false
* - mdcProfiling (Optional): Perform Profiling (Call-Duration) based on MDC Data. See MDC
* Profiling, default false
* - facility (Optional): Name of the Facility, default gelf-java
*
*
* Fields
*
*
* Log4j v2 supports an extensive and flexible configuration in contrast to other log frameworks (JUL, log4j v1). This allows
* you to specify your needed fields you want to use in the GELF message. An empty field configuration results in a message
* containing only
*
*
*
* - timestamp
* - level (syslog level)
* - host
* - facility
* - message
* - short_message
*
*
*
*
* You can add different fields:
*
*
*
* - Static Literals
* - MDC Fields
* - Log-Event fields (using Pattern
* Layout)
*
*
*
* In order to do so, use nested Field elements below the Appender element.
*
* Static Literals
<Field name="fieldName1" literal="your literal value" />
*
*
* MDC Fields
<Field name="fieldName1" mdc="name of the MDC entry" />
*
*
* Dynamic MDC Fields
<DynamicMdcFields regex="mdc.*" />
*
*
*
* Log-Event fields
*
* See also: Pattern Layout
*
*
*
* You can use all built-in Pattern Fields:
*
*
<Field name="simpleClassName" pattern="%C{1}" />
<Field name="timestamp" pattern="%d{dd MMM yyyy HH:mm:ss,SSS}" />
<Field name="level" pattern="%level" />
*
*
* Additionally, you can add the host-Field, which can supply you either the FQDN hostname, the simple hostname
* or the local address.
*
*
*
*
* Option
* Description
*
*
*
* host
* {["fqdn"
* |"simple"
* |"address"]}
*
*
* Outputs either the FQDN hostname, the simple hostname or the local address.
*
*
* You can follow the throwable conversion word with an option in the form %host{option}.
*
*
* %host{fqdn} default setting, outputs the FQDN hostname, e.g. www.you.host.name.com.
*
*
* %host{simple} outputs simple hostname, e.g. www.
*
*
* %host{address} outputs the local IP address of the found hostname, e.g. 1.2.3.4 or affe:affe:affe::1.
*
*
*
*
*
*
*
*
*
* MDC Profiling
*
* MDC Profiling allows to calculate the runtime from request start up to the time until the log message was generated. You must
* set one value in the MDC:
*
* - profiling.requestStart.millis: Time Millis of the Request-Start (Long or String)
*
*
* Two values are set by the Log Appender:
*
* - profiling.requestEnd: End-Time of the Request-End in Date.toString-representation
* - profiling.requestDuration: Duration of the request (e.g. 205ms, 16sec)
*
*
*
*/
@Plugin(name = "Gelf", category = "Core", elementType = "appender", printObject = true)
public class GelfLogAppender extends AbstractAppender implements ErrorReporter {
private static final Logger LOGGER = StatusLogger.getLogger();
protected GelfSender gelfSender;
private MdcGelfMessageAssembler gelfMessageAssembler;
public GelfLogAppender(String name, Filter filter, MdcGelfMessageAssembler gelfMessageAssembler) {
super(name, filter, null);
this.gelfMessageAssembler = gelfMessageAssembler;
}
/**
*
* @param name
* @param filter
* @param fields
* @param graylogHost
* @param host
* @param graylogPort
* @param port
* @param extractStackTrace
* @param facility
* @param filterStackTrace
* @param mdcProfiling
* @param maximumMessageSize
* @param testSenderClass
* @return GelfLogAppender
*/
@PluginFactory
public static GelfLogAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Filter") Filter filter,
@PluginElement("Field") final GelfLogField[] fields,@PluginElement("DynamicMdcFields") final GelfDynamicMdcLogFields[] dynamicFieldArray, @PluginAttribute("graylogHost") String graylogHost,
@PluginAttribute("host") String host, @PluginAttribute("graylogPort") String graylogPort,
@PluginAttribute("port") String port, @PluginAttribute("extractStackTrace") String extractStackTrace,
@PluginAttribute("facility") String facility, @PluginAttribute("filterStackTrace") String filterStackTrace,
@PluginAttribute("mdcProfiling") String mdcProfiling,
@PluginAttribute("maximumMessageSize") String maximumMessageSize,
@PluginAttribute("testSenderClass") String testSenderClass) {
MdcGelfMessageAssembler mdcGelfMessageAssembler = new MdcGelfMessageAssembler();
if (name == null) {
LOGGER.error("No name provided for " + GelfLogAppender.class.getSimpleName());
return null;
}
if (Strings.isEmpty(host) && Strings.isEmpty(graylogHost)) {
LOGGER.error("No host provided for " + GelfLogAppender.class.getSimpleName());
return null;
}
if (Strings.isNotEmpty(host)) {
mdcGelfMessageAssembler.setHost(host);
}
if (Strings.isNotEmpty(graylogHost)) {
mdcGelfMessageAssembler.setHost(graylogHost);
}
if (Strings.isNotEmpty(port)) {
mdcGelfMessageAssembler.setPort(Integer.parseInt(port));
}
if (Strings.isNotEmpty(graylogPort)) {
mdcGelfMessageAssembler.setPort(Integer.parseInt(graylogPort));
}
if (facility != null) {
mdcGelfMessageAssembler.setFacility(facility);
}
if (extractStackTrace != null) {
mdcGelfMessageAssembler.setExtractStackTrace(extractStackTrace.equals("true"));
}
if (filterStackTrace != null) {
mdcGelfMessageAssembler.setFilterStackTrace(filterStackTrace.equals("true"));
}
if (mdcProfiling != null) {
mdcGelfMessageAssembler.setMdcProfiling(mdcProfiling.equals("true"));
}
if (maximumMessageSize != null) {
mdcGelfMessageAssembler.setMaximumMessageSize(Integer.parseInt(maximumMessageSize));
}
configureFields(mdcGelfMessageAssembler, fields, dynamicFieldArray);
GelfLogAppender result = new GelfLogAppender(name, filter, mdcGelfMessageAssembler);
if (testSenderClass != null) {
result.setTestSenderClass(testSenderClass);
}
return result;
}
/**
* Configure fields (literals, MDC, layout).
*
* @param mdcGelfMessageAssembler
* @param fields
* @param dynamicFieldArray
*/
private static void configureFields(MdcGelfMessageAssembler mdcGelfMessageAssembler, GelfLogField[] fields,
GelfDynamicMdcLogFields[] dynamicFieldArray) {
if (fields == null) {
mdcGelfMessageAssembler.addFields(LogMessageField.getDefaultMapping());
return;
}
for (GelfLogField field : fields) {
if (Strings.isNotEmpty(field.getMdc())) {
mdcGelfMessageAssembler.addField(new MdcMessageField(field.getName(), field.getMdc()));
}
if (Strings.isNotEmpty(field.getLiteral())) {
mdcGelfMessageAssembler.addField(new StaticMessageField(field.getName(), field.getLiteral()));
}
if (field.getPatternLayout() != null) {
mdcGelfMessageAssembler.addField(new PatternLogMessageField(field.getName(), null, field.getPatternLayout()));
}
}
if(dynamicFieldArray != null)
{
for (GelfDynamicMdcLogFields gelfDynamicMdcLogFields : dynamicFieldArray)
{
mdcGelfMessageAssembler.addField(new DynamicMdcMessageField(gelfDynamicMdcLogFields.getRegex()));
}
}
}
@Override
public void append(LogEvent event) {
if (event == null) {
return;
}
try {
GelfMessage message = createGelfMessage(event);
if (!message.isValid()) {
reportError("GELF Message is invalid: " + message.toJson(), null);
}
if (null == gelfSender || !gelfSender.sendMessage(message)) {
reportError("Could not send GELF message", null);
}
} catch (Exception e) {
reportError("Could not send GELF message: " + e.getMessage(), e);
}
}
protected GelfMessage createGelfMessage(final LogEvent logEvent) {
return gelfMessageAssembler.createGelfMessage(new Log4j2LogEvent(logEvent));
}
public void reportError(String message, Exception exception) {
LOGGER.error(message, exception, 0);
}
@Override
public void stop() {
if (null != gelfSender) {
gelfSender.close();
gelfSender = null;
}
super.stop();
}
@Override
public void start() {
if (null == gelfSender) {
gelfSender = GelfSenderFactory.createSender(gelfMessageAssembler, this);
}
super.start();
}
public void setTestSenderClass(String testSender) {
// This only used for testing
try {
if (null != testSender) {
final Class clazz = Class.forName(testSender);
gelfSender = (GelfSender) clazz.newInstance();
}
} catch (final Exception e) {
// ignore
}
}
}