org.apache.samza.logging.log4j2.serializers.LoggingEventJsonSerde.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.samza.logging.log4j2.serializers.LoggingEventJsonSerde.java

Source

/*
 * 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.samza.logging.log4j2.serializers;

import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.samza.serializers.JsonSerde;
import org.apache.samza.serializers.Serde;
import org.apache.samza.util.Util;

/**
 * A JSON serde that serializes Log4J2 LogEvent objects into JSON using the
 * standard logstash LogEvent format defined <a
 * href="https://github.com/logstash/log4j-jsonevent-layout">here</a>.
 */
public class LoggingEventJsonSerde implements Serde<LogEvent> {
    /**
     * The JSON format version.
     */
    public static final int VERSION = 1;

    /**
     * The date format to use for the timestamp field.
     */
    public static final Format DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

    // Have to wrap rather than extend due to type collisions between
    // Serde<LoggingEvent> and Serde<Object>.
    @SuppressWarnings("rawtypes")
    private final JsonSerde jsonSerde;

    /**
     * Defines whether to include LocationInfo data in the serialized
     * LoggingEvent. This information includes the file, line, and class that
     * wrote the log line.
     */
    private final boolean includeLocationInfo;

    /**
     * Constructs the serde without location info.
     */
    public LoggingEventJsonSerde() {
        this(false);
    }

    /**
     * Constructs the serde.
     *
     * @param includeLocationInfo
     *          Whether to include location info in the logging event or not.
     */
    @SuppressWarnings("rawtypes")
    public LoggingEventJsonSerde(boolean includeLocationInfo) {
        this.includeLocationInfo = includeLocationInfo;
        this.jsonSerde = new JsonSerde();
    }

    @SuppressWarnings("unchecked")
    @Override
    public byte[] toBytes(LogEvent loggingEvent) {
        Map<String, Object> loggingEventMap = encodeToMap(loggingEvent, includeLocationInfo);
        return jsonSerde.toBytes(loggingEventMap);
    }

    @SuppressWarnings("unchecked")
    @Override
    public LogEvent fromBytes(byte[] loggingEventMapBytes) {
        Map<String, Object> loggingEventMap = (Map<String, Object>) jsonSerde.fromBytes(loggingEventMapBytes);
        return decodeFromMap(loggingEventMap);
    }

    /**
     * Encodes a LoggingEvent into a HashMap using the logstash JSON format.
     *
     * @param loggingEvent
     *          The LoggingEvent to encode.
     * @param includeLocationInfo
     *          Whether to include LocationInfo in the map, or not.
     * @return A Map representing the LoggingEvent, which is suitable to be
     *         serialized by a JSON encoder such as Jackson.
     */
    @SuppressWarnings("rawtypes")
    public static Map<String, Object> encodeToMap(LogEvent loggingEvent, boolean includeLocationInfo) {
        Map<String, Object> logstashEvent = new LoggingEventJsonSerde.LoggingEventMap();
        String threadName = loggingEvent.getThreadName();
        long timestamp = loggingEvent.getTimeMillis();
        HashMap<String, Object> exceptionInformation = new HashMap<String, Object>();
        Map mdc = loggingEvent.getContextData().toMap();
        ThreadContext.ContextStack ndc = loggingEvent.getContextStack();

        logstashEvent.put("@version", VERSION);
        logstashEvent.put("@timestamp", dateFormat(timestamp));
        logstashEvent.put("source_host", getHostname());
        logstashEvent.put("message", loggingEvent.getMessage());

        if (loggingEvent.getThrown() != null) {
            final Throwable throwableInformation = loggingEvent.getThrown();
            if (throwableInformation.getClass().getCanonicalName() != null) {
                exceptionInformation.put("exception_class", throwableInformation.getClass().getCanonicalName());
            }
            if (throwableInformation.getMessage() != null) {
                exceptionInformation.put("exception_message", throwableInformation.getMessage());
            }
            if (throwableInformation.getMessage() != null) {
                StringBuilder stackTrace = new StringBuilder(ExceptionUtils.getStackTrace(throwableInformation));
                exceptionInformation.put("stacktrace", stackTrace);
            }
            logstashEvent.put("exception", exceptionInformation);
        }

        if (includeLocationInfo) {
            StackTraceElement info = loggingEvent.getSource();
            logstashEvent.put("file", info.getFileName());
            logstashEvent.put("line_number", info.getLineNumber());
            logstashEvent.put("class", info.getClassName());
            logstashEvent.put("method", info.getMethodName());
        }

        logstashEvent.put("logger_name", loggingEvent.getLoggerName());
        logstashEvent.put("mdc", mdc);
        logstashEvent.put("ndc", ndc);
        logstashEvent.put("level", loggingEvent.getLevel().toString());
        logstashEvent.put("thread_name", threadName);

        return logstashEvent;
    }

    /**
     * This method is not currently implemented.
     *
     * @param loggingEventMap a map of logging events
     *
     * @return {@link LogEvent} decoded from the given logging event map.<br>
     *     Currently it throws an {@link UnsupportedOperationException} as the method is not implemented yet!
     */
    public static LogEvent decodeFromMap(Map<String, Object> loggingEventMap) {
        throw new UnsupportedOperationException("Unable to decode LoggingEvents.");
    }

    public static String dateFormat(long time) {
        return DATE_FORMAT.format(new Date(time));
    }

    /**
     * @return The hostname to use in the hostname field of the encoded
     *         LoggingEvents.
     */
    public static String getHostname() {
        try {
            return Util.getLocalHost().getHostName();
        } catch (Exception e) {
            return "unknown-host";
        }
    }

    /**
     * A helper class that only puts non-null values into the encoded LoggingEvent
     * map. This helps to shrink over-the-wire byte payloads for encoded
     * LoggingEvents.
     */
    @SuppressWarnings("serial")
    public static final class LoggingEventMap extends HashMap<String, Object> {
        public Object put(String key, Object value) {
            if (value == null) {
                return get(key);
            } else {
                return super.put(key, value);
            }
        }
    }
}