All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.logging.log4j.core.layout.GelfLayout Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.logging.log4j.core.layout;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Node;
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.net.Severity;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;

import com.fasterxml.jackson.core.io.JsonStringEncoder;

/**
 * Lays out events in the Graylog Extended Log Format (GELF) 1.1.
 * 

* This layout compresses JSON to GZIP or ZLIB (the {@code compressionType}) if * log event data is larger than 1024 bytes (the {@code compressionThreshold}). * This layout does not implement chunking. *

*

* Configure as follows to send to a Graylog2 server: *

* *
 * <Appenders>
 *        <Socket name="Graylog" protocol="udp" host="graylog.domain.com" port="12201">
 *            <GelfLayout host="someserver" compressionType="GZIP" compressionThreshold="1024">
 *                <KeyValuePair key="additionalField1" value="additional value 1"/>
 *                <KeyValuePair key="additionalField2" value="additional value 2"/>
 *            </GelfLayout>
 *        </Socket>
 * </Appenders>
 * 
* * @see GELF home page * @see GELF * specification */ @Plugin(name = "GelfLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) public final class GelfLayout extends AbstractStringLayout { public static enum CompressionType { GZIP { @Override public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) throws IOException { return new GZIPOutputStream(os); } }, ZLIB { @Override public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) throws IOException { return new DeflaterOutputStream(os); } }, OFF { @Override public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) throws IOException { return null; } }; public abstract DeflaterOutputStream createDeflaterOutputStream(OutputStream os) throws IOException; } private static final char C = ','; private static final int COMPRESSION_THRESHOLD = 1024; private static final char Q = '\"'; private static final String QC = "\","; private static final String QU = "\"_"; private static final long serialVersionUID = 1L; private static final BigDecimal TIME_DIVISOR = new BigDecimal(1000); private final KeyValuePair[] additionalFields; private final int compressionThreshold; private final CompressionType compressionType; private final String host; public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType, final int compressionThreshold) { super(StandardCharsets.UTF_8); this.host = host; this.additionalFields = additionalFields; this.compressionType = compressionType; this.compressionThreshold = compressionThreshold; } @PluginFactory public static GelfLayout createLayout( //@formatter:off @PluginAttribute("host") final String host, @PluginElement("AdditionalField") final KeyValuePair[] additionalFields, @PluginAttribute(value = "compressionType", defaultString = "GZIP") final CompressionType compressionType, @PluginAttribute(value = "compressionThreshold", defaultInt= COMPRESSION_THRESHOLD) final int compressionThreshold) { // @formatter:on return new GelfLayout(host, additionalFields, compressionType, compressionThreshold); } /** * http://en.wikipedia.org/wiki/Syslog#Severity_levels */ static int formatLevel(final Level level) { return Severity.getSeverity(level).getCode(); } static String formatThrowable(final Throwable throwable) { // stack traces are big enough to provide a reasonably large initial capacity here final StringWriter sw = new StringWriter(2048); final PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); pw.flush(); return sw.toString(); } static String formatTimestamp(final long timeMillis) { return new BigDecimal(timeMillis).divide(TIME_DIVISOR).toPlainString(); } private byte[] compress(final byte[] bytes) { try { final ByteArrayOutputStream baos = new ByteArrayOutputStream(compressionThreshold / 8); try (final DeflaterOutputStream stream = compressionType.createDeflaterOutputStream(baos)) { if (stream == null) { return bytes; } stream.write(bytes); stream.finish(); } return baos.toByteArray(); } catch (final IOException e) { StatusLogger.getLogger().error(e); return bytes; } } @Override public Map getContentFormat() { return Collections.emptyMap(); } @Override public String getContentType() { return JsonLayout.CONTENT_TYPE + "; charset=" + this.getCharset(); } @Override public byte[] toByteArray(final LogEvent event) { final byte[] bytes = getBytes(toSerializable(event)); return bytes.length > compressionThreshold ? compress(bytes) : bytes; } @Override public String toSerializable(final LogEvent event) { final StringBuilder builder = getStringBuilder(); final JsonStringEncoder jsonEncoder = JsonStringEncoder.getInstance(); builder.append('{'); builder.append("\"version\":\"1.1\","); builder.append("\"host\":\"").append(jsonEncoder.quoteAsString(toNullSafeString(host))).append(QC); builder.append("\"timestamp\":").append(formatTimestamp(event.getTimeMillis())).append(C); builder.append("\"level\":").append(formatLevel(event.getLevel())).append(C); if (event.getThreadName() != null) { builder.append("\"_thread\":\"").append(jsonEncoder.quoteAsString(event.getThreadName())).append(QC); } if (event.getLoggerName() != null) { builder.append("\"_logger\":\"").append(jsonEncoder.quoteAsString(event.getLoggerName())).append(QC); } for (final KeyValuePair additionalField : additionalFields) { builder.append(QU).append(jsonEncoder.quoteAsString(additionalField.getKey())).append("\":\"") .append(jsonEncoder.quoteAsString(toNullSafeString(additionalField.getValue()))).append(QC); } for (final Map.Entry entry : event.getContextMap().entrySet()) { builder.append(QU).append(jsonEncoder.quoteAsString(entry.getKey())).append("\":\"") .append(jsonEncoder.quoteAsString(toNullSafeString(entry.getValue()))).append(QC); } if (event.getThrown() != null) { builder.append("\"full_message\":\"").append(jsonEncoder.quoteAsString(formatThrowable(event.getThrown()))) .append(QC); } builder.append("\"short_message\":\"").append(jsonEncoder.quoteAsString(toNullSafeString(event.getMessage().getFormattedMessage()))) .append(Q); builder.append('}'); return builder.toString(); } private String toNullSafeString(final String s) { return s == null ? Strings.EMPTY : s; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy