package biz.paluch.logging.gelf.log4j2; import static biz.paluch.logging.gelf.LogMessageField.NamedLogField.*; import static org.apache.logging.log4j.core.layout.PatternLayout.newBuilder; import java.util.Collections; 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.appender.AppenderLoggingException; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.plugins.*; import org.apache.logging.log4j.core.impl.Log4jLogEvent; import org.apache.logging.log4j.core.layout.PatternLayout; import org.apache.logging.log4j.status.StatusLogger; import org.apache.logging.log4j.util.Strings; import biz.paluch.logging.RuntimeContainer; import biz.paluch.logging.gelf.*; import biz.paluch.logging.gelf.intern.*; /** * 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: * <ul> * <li>host (Mandatory): Hostname/IP-Address of the Logstash Host * <ul> * <li>(the host) for UDP, e.g. 127.0.0.1 or some.host.com</li> * <li>See docs for more details</li> * </ul> * </li> * <li>port (Optional): Port, default 12201</li> * <li>version (Optional): GELF Version 1.0 or 1.1, default 1.0</li> * <li>originHost (Optional): Originating Hostname, default FQDN Hostname</li> * <li>extractStackTrace (Optional): Post Stack-Trace to StackTrace field (true/false/throwable reference [0 = throwable, 1 = throwable.cause, -1 = root cause]), default false</li> * <li>filterStackTrace (Optional): Perform Stack-Trace filtering (true/false), default false</li> * <li>mdcProfiling (Optional): Perform Profiling (Call-Duration) based on MDC Data. See <a href="#mdcProfiling">MDC * Profiling</a>, default false</li> * <li>facility (Optional): Name of the Facility, default gelf-java</li> * <li>additionalFieldTypes (Optional): Type specification for additional and MDC fields. Supported types: String, long, Long, * double, Double and discover (default if not specified, discover field type on parseability). Eg. * field=String,field2=double</li> * <li>ignoreExceptions (Optional): The default is <code>true</code>, causing exceptions encountered while appending events to * be internally logged and then ignored. When set to <code>false</code> exceptions will be propagated to the caller, instead. * You must set this to false when wrapping this Appender in a <code>FailoverAppender</code>.</li> * </ul> * * <h2>Fields</h2> * * <p> * 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 * </p> * * <ul> * <li>timestamp</li> * <li>level (syslog level)</li> * <li>host</li> * <li>facility</li> * <li>message</li> * <li>short_message</li> * </ul> * * <p> * You can add different fields: * </p> * * <ul> * <li>Static Literals</li> * <li>MDC Fields</li> * <li>Log-Event fields (using <a href="http://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout">Pattern * Layout</a>)</li> * </ul> * * In order to do so, use nested Field elements below the Appender element. * * <h3>Static Literals</h3> <code> <Field name="fieldName1" literal="your literal value" /> * </code> * * <h3>MDC Fields</h3> <code> <Field name="fieldName1" mdc="name of the MDC entry" /> * </code> * * <h3>Dynamic MDC Fields</h3> <code> <DynamicMdcFields regex="mdc.*" /> * </code> * * * <h3>Log-Event fields</h3> * <p> * See also: <a href="http://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout">Pattern Layout</a> * </p> * * <p> * You can use all built-in Pattern Fields: * </p> * <code> <Field name="simpleClassName" pattern="%C{1}" /> <Field name="timestamp" pattern="%d{dd MMM yyyy HH:mm:ss,SSS}" /> <Field name="level" pattern="%level" /> </code> * * <p> * Additionally, you can add the <strong>host</strong>-Field, which can supply you either the FQDN hostname, the simple hostname * or the local address. * </p> * <table class="overviewSummary" border="0" cellpadding="3" cellspacing="0" style="border-bottom:1px solid #9eadc0;" summary= * "Details for the %host formatter"> * <tbody> * <tr> * <th class="colFirst">Option</th> * <th class="colLast">Description</th> * </tr> * <tr class="altColor"> * <td class="colFirst" align="center"><b>host</b>   {["fqdn"<br> *   |"simple"<br> *   |"address"]}</td> * <td class="colLast"> * <p> * Outputs either the FQDN hostname, the simple hostname or the local address. * </p> * <p> * You can follow the throwable conversion word with an option in the form <b>%host{option}</b>. * </p> * <p> * <b>%host{fqdn}</b> default setting, outputs the FQDN hostname, e.g. www.you.host.name.com. * </p> * <p> * <b>%host{simple}</b> outputs simple hostname, e.g. www. * </p> * <p> * <b>%host{address}</b> outputs the local IP address of the found hostname, e.g. 1.2.3.4 or affe:affe:affe::1. * </p> * </td> * </tr> * </tbody> * </table> * * * <a name="mdcProfiling"></a> * <h2>MDC Profiling</h2> * <p> * 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: * <ul> * <li>profiling.requestStart.millis: Time Millis of the Request-Start (Long or String)</li> * </ul> * <p> * Two values are set by the Log Appender: * </p> * <ul> * <li>profiling.requestEnd: End-Time of the Request-End in Date.toString-representation</li> * <li>profiling.requestDuration: Duration of the request (e.g. 205ms, 16sec)</li> * </ul> * * The {@link #append(LogEvent)} method is thread-safe and may be called by different threads at any time. */ @Plugin(name = "Gelf", category = "Core", elementType = "appender", printObject = true) public class GelfLogAppender extends AbstractAppender { private static final Logger LOGGER = StatusLogger.getLogger(); private static final ErrorReporter ERROR_REPORTER = new ErrorReporter() { @Override public void reportError(String message, Exception e) { LOGGER.error(message, e, 0); } }; private final ErrorReporter PROPAGATING_ERROR_REPORTER = new ErrorReporter() { @Override public void reportError(String message, Exception e) { if (e != null) { throw new AppenderLoggingException(e); } LOGGER.error(message, null, 0); } }; protected GelfSender gelfSender; private final MdcGelfMessageAssembler gelfMessageAssembler; private final ErrorReporter errorReporter; public GelfLogAppender(String name, Filter filter, MdcGelfMessageAssembler gelfMessageAssembler, boolean ignoreExceptions) { super(name, filter, null, ignoreExceptions); this.gelfMessageAssembler = gelfMessageAssembler; if (ignoreExceptions) { errorReporter = ERROR_REPORTER; } else { errorReporter = PROPAGATING_ERROR_REPORTER; } } @PluginFactory public static GelfLogAppender createAppender(@PluginConfiguration final Configuration config, @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("version") String version, @PluginAttribute("extractStackTrace") String extractStackTrace, @PluginAttribute("originHost") String originHost, @PluginAttribute("includeFullMdc") String includeFullMdc, @PluginAttribute("facility") String facility, @PluginAttribute("filterStackTrace") String filterStackTrace, @PluginAttribute("mdcProfiling") String mdcProfiling, @PluginAttribute("maximumMessageSize") String maximumMessageSize, @PluginAttribute("additionalFieldTypes") String additionalFieldTypes, @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) boolean ignoreExceptions) { RuntimeContainer.initialize(ERROR_REPORTER); 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 (Strings.isNotEmpty(version)) { mdcGelfMessageAssembler.setVersion(version); } if (Strings.isNotEmpty(originHost)) { PatternLayout patternLayout = newBuilder().withPattern(originHost).withConfiguration(config) .withNoConsoleNoAnsi(false).withAlwaysWriteExceptions(false).build(); mdcGelfMessageAssembler.setOriginHost(patternLayout.toSerializable(new Log4jLogEvent())); } if (facility != null) { mdcGelfMessageAssembler.setFacility(facility); } if (extractStackTrace != null) { mdcGelfMessageAssembler.setExtractStackTrace(extractStackTrace); } if (filterStackTrace != null) { mdcGelfMessageAssembler.setFilterStackTrace("true".equals(filterStackTrace)); } if (mdcProfiling != null) { mdcGelfMessageAssembler.setMdcProfiling("true".equals(mdcProfiling)); } if (includeFullMdc != null) { mdcGelfMessageAssembler.setIncludeFullMdc("true".equals(includeFullMdc)); } if (maximumMessageSize != null) { mdcGelfMessageAssembler.setMaximumMessageSize(Integer.parseInt(maximumMessageSize)); } if (additionalFieldTypes != null) { ConfigurationSupport.setAdditionalFieldTypes(additionalFieldTypes, mdcGelfMessageAssembler); } configureFields(mdcGelfMessageAssembler, fields, dynamicFieldArray); return new GelfLogAppender(name, filter, mdcGelfMessageAssembler, ignoreExceptions); } /** * Configure fields (literals, MDC, layout). * * @param mdcGelfMessageAssembler the assembler * @param fields static field array * @param dynamicFieldArray dynamic field array */ private static void configureFields(MdcGelfMessageAssembler mdcGelfMessageAssembler, GelfLogField[] fields, GelfDynamicMdcLogFields[] dynamicFieldArray) { if (fields == null || fields.length == 0) { mdcGelfMessageAssembler.addFields(LogMessageField.getDefaultMapping(Time, Severity, ThreadName, SourceClassName, SourceMethodName, SourceLineNumber, SourceSimpleClassName, LoggerName, Marker)); 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); return; } if (null == gelfSender || !gelfSender.sendMessage(message)) { reportError("Could not send GELF message", null); } } catch (AppenderLoggingException e) { throw e; } 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) { errorReporter.reportError(message, exception); } @Override public void stop() { if (null != gelfSender) { Closer.close(gelfSender); gelfSender = null; } super.stop(); } @Override public void start() { if (null == gelfSender) { gelfSender = createGelfSender(); } super.start(); } protected GelfSender createGelfSender() { return GelfSenderFactory.createSender(gelfMessageAssembler, errorReporter, Collections.EMPTY_MAP); } }