/*
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.steno;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.annotation.Nullable;

/**
 * Logger designed for use particularly with Steno encoder. Although not
 * interface compatible with the SLF4J Logger this class
 * attempts to provide some common methods to ease the transition. However,
 * its purpose is to provide more concrete methods of data and context
 * injection for Steno versus the general marker methods in the SFL4J
 * implementation.
 *
 * @since 1.3.0
 *
 * @author Stuart Siegrist (fsiegrist at groupon dot com)
 * @author Gil Markham (gil at groupon dot com)
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 */
public class Logger {

    /**
     * Determine if trace level logging is enabled.
     *
     * @since 1.6.0
     *
     * @return True if and only if trace level logging is enabled.
     */
    public boolean isTraceEnabled() {
        return getSlf4jLogger().isTraceEnabled();
    }

    /**
     * Create a new log event at the trace level. Do not pre-build and cache
     * LogBuilder instances.
     *
     * @since 1.12.0
     *
     * @return Instance of LogBuilder.
     */
    public LogBuilder trace() {
        if (getSlf4jLogger().isTraceEnabled()) {
            return new DefaultLogBuilder(this, LogLevel.TRACE);
        } else {
            return NO_OP_LOG_BUILDER;
        }
    }

    /**
     * Create a new log event at the trace level. The provided Consumer
     * populates a DeferredLogBuilder to define the log event. The
     * consumer may not be invoked if it is not necessary. Therefore it is
     * important not to include side-effects in the provided Consumer.
     *
     * @since 1.12.0
     *
     * @param consumer Function to populate the DeferredLogBuilder.
     */
    public void trace(final Consumer<DeferredLogBuilder> consumer) {
        if (_slf4jLogger.isTraceEnabled()) {
            final LogBuilder logBuilder = new DefaultLogBuilder(this, LogLevel.TRACE);
            consumer.accept(logBuilder);
            logBuilder.log();
        }
    }

    /**
     * Log a message at the trace level. Default values are used for all other
     * parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     */
    public void trace(@Nullable final String message) {
        log(LogLevel.TRACE, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message with a Throwable at the trace level. Default
     * values are used for all other parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void trace(@Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.TRACE, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event at the trace level. Default values
     * are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     */
    public void trace(@Nullable final String event, @Nullable final String message) {
        log(LogLevel.TRACE, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with a Throwable at the
     * trace level. Default values are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void trace(@Nullable final String event, @Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.TRACE, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the trace level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data) {
        trace(event, message, data, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the trace level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isTraceEnabled()) {
            LogLevel.TRACE.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromCollection(
                            data == null ? Collections.emptyList() : data.keySet(),
                            MESSAGE_DATA_KEY),
                    createValuesFromCollection(
                            data == null ? Collections.emptyList() : data.values(),
                            message),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the trace level.
     *
     * The number of elements in the dataNames array should
     * match the number of arguments provided as data
     * unless a Throwable is specified as the final argument in
     * data in which case there would be one fewer element
     * in the dataNames array compared to the number of
     * arguments provided as data.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues data values matching data keys by index. The
     * final data argument may be a Throwable which does
     * not require a matching data name.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object... dataValues) {
        final Throwable throwable = extractThrowable(dataKeys, dataValues);
        log(LogLevel.TRACE, event, message, dataKeys, chompArray(dataValues, throwable == null ? 0 : 1), throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the trace level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues Array of data values.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object[] dataValues,
            @Nullable final Throwable throwable) {
        log(
                LogLevel.TRACE,
                event,
                message,
                dataKeys,
                dataValues,
                throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the trace level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1) {
        trace(event, message, dataKey1, dataValue1, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the trace level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isTraceEnabled()) {
            LogLevel.TRACE.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1),
                    createValuesFromArgs(message, dataValue1),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the trace level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2) {
        trace(event, message, dataKey1, dataKey2, dataValue1, dataValue2, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the trace level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void trace(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isTraceEnabled()) {
            LogLevel.TRACE.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1, dataKey2),
                    createValuesFromArgs(message, dataValue1, dataValue2),
                    throwable);
        }
    }

    /**
     * Determine if debug level logging is enabled.
     *
     * @since 1.6.0
     *
     * @return True if and only if debug level logging is enabled.
     */
    public boolean isDebugEnabled() {
        return getSlf4jLogger().isDebugEnabled();
    }

    /**
     * Create a new log event at the debug level. Do not pre-build and cache
     * LogBuilder instances.
     *
     * @since 1.3.1
     *
     * @return Instance of LogBuilder.
     */
    public LogBuilder debug() {
        if (getSlf4jLogger().isDebugEnabled()) {
            return new DefaultLogBuilder(this, LogLevel.DEBUG);
        } else {
            return NO_OP_LOG_BUILDER;
        }
    }

    /**
     * Create a new log event at the debug level. The provided Consumer
     * populates a DeferredLogBuilder to define the log event. The
     * consumer may not be invoked if it is not necessary. Therefore it is
     * important not to include side-effects in the provided Consumer.
     *
     * @since 1.12.0
     *
     * @param consumer Function to populate the DeferredLogBuilder.
     */
    public void debug(final Consumer<DeferredLogBuilder> consumer) {
        if (_slf4jLogger.isDebugEnabled()) {
            final LogBuilder logBuilder = new DefaultLogBuilder(this, LogLevel.DEBUG);
            consumer.accept(logBuilder);
            logBuilder.log();
        }
    }

    /**
     * Log a message at the debug level. Default values are used for all other
     * parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     */
    public void debug(@Nullable final String message) {
        log(LogLevel.DEBUG, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message with a Throwable at the debug level. Default
     * values are used for all other parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void debug(@Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.DEBUG, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event at the debug level. Default values
     * are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     */
    public void debug(@Nullable final String event, @Nullable final String message) {
        log(LogLevel.DEBUG, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with a Throwable at the
     * debug level. Default values are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void debug(@Nullable final String event, @Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.DEBUG, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the debug level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data) {
        debug(event, message, data, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the debug level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isDebugEnabled()) {
            LogLevel.DEBUG.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromCollection(
                            data == null ? Collections.emptyList() : data.keySet(),
                            MESSAGE_DATA_KEY),
                    createValuesFromCollection(
                            data == null ? Collections.emptyList() : data.values(),
                            message),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the debug level.
     *
     * The number of elements in the dataNames array should
     * match the number of arguments provided as data
     * unless a Throwable is specified as the final argument in
     * data in which case there would be one fewer element
     * in the dataNames array compared to the number of
     * arguments provided as data.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues data values matching data keys by index. The
     * final data argument may be a Throwable which does
     * not require a matching data name.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object... dataValues) {
        final Throwable throwable = extractThrowable(dataKeys, dataValues);
        log(LogLevel.DEBUG, event, message, dataKeys, chompArray(dataValues, throwable == null ? 0 : 1), throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the debug level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues Array of data values.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object[] dataValues,
            @Nullable final Throwable throwable) {
        log(
                LogLevel.DEBUG,
                event,
                message,
                dataKeys,
                dataValues,
                throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the debug level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1) {
        debug(event, message, dataKey1, dataValue1, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the debug level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isDebugEnabled()) {
            LogLevel.DEBUG.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1),
                    createValuesFromArgs(message, dataValue1),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the debug level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2) {
        debug(event, message, dataKey1, dataKey2, dataValue1, dataValue2, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the debug level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void debug(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isDebugEnabled()) {
            LogLevel.DEBUG.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1, dataKey2),
                    createValuesFromArgs(message, dataValue1, dataValue2),
                    throwable);
        }
    }

    /**
     * Determine if info level logging is enabled.
     *
     * @since 1.6.0
     *
     * @return True if and only if info level logging is enabled.
     */
    public boolean isInfoEnabled() {
        return getSlf4jLogger().isInfoEnabled();
    }

    /**
     * Create a new log event at the info level. Do not pre-build and cache
     * LogBuilder instances.
     *
     * @since 1.3.1
     *
     * @return Instance of LogBuilder.
     */
    public LogBuilder info() {
        if (getSlf4jLogger().isInfoEnabled()) {
            return new DefaultLogBuilder(this, LogLevel.INFO);
        } else {
            return NO_OP_LOG_BUILDER;
        }
    }

    /**
     * Create a new log event at the info level. The provided Consumer
     * populates a DeferredLogBuilder to define the log event. The
     * consumer may not be invoked if it is not necessary. Therefore it is
     * important not to include side-effects in the provided Consumer.
     *
     * @since 1.12.0
     *
     * @param consumer Function to populate the DeferredLogBuilder.
     */
    public void info(final Consumer<DeferredLogBuilder> consumer) {
        if (_slf4jLogger.isInfoEnabled()) {
            final LogBuilder logBuilder = new DefaultLogBuilder(this, LogLevel.INFO);
            consumer.accept(logBuilder);
            logBuilder.log();
        }
    }

    /**
     * Log a message at the info level. Default values are used for all other
     * parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     */
    public void info(@Nullable final String message) {
        log(LogLevel.INFO, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message with a Throwable at the info level. Default
     * values are used for all other parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void info(@Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.INFO, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event at the info level. Default values
     * are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     */
    public void info(@Nullable final String event, @Nullable final String message) {
        log(LogLevel.INFO, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with a Throwable at the
     * info level. Default values are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void info(@Nullable final String event, @Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.INFO, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the info level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data) {
        info(event, message, data, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the info level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isInfoEnabled()) {
            LogLevel.INFO.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromCollection(
                            data == null ? Collections.emptyList() : data.keySet(),
                            MESSAGE_DATA_KEY),
                    createValuesFromCollection(
                            data == null ? Collections.emptyList() : data.values(),
                            message),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the info level.
     *
     * The number of elements in the dataNames array should
     * match the number of arguments provided as data
     * unless a Throwable is specified as the final argument in
     * data in which case there would be one fewer element
     * in the dataNames array compared to the number of
     * arguments provided as data.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues data values matching data keys by index. The
     * final data argument may be a Throwable which does
     * not require a matching data name.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object... dataValues) {
        final Throwable throwable = extractThrowable(dataKeys, dataValues);
        log(LogLevel.INFO, event, message, dataKeys, chompArray(dataValues, throwable == null ? 0 : 1), throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the info level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues Array of data values.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object[] dataValues,
            @Nullable final Throwable throwable) {
        log(
                LogLevel.INFO,
                event,
                message,
                dataKeys,
                dataValues,
                throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the info level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1) {
        info(event, message, dataKey1, dataValue1, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the info level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isInfoEnabled()) {
            LogLevel.INFO.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1),
                    createValuesFromArgs(message, dataValue1),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the info level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2) {
        info(event, message, dataKey1, dataKey2, dataValue1, dataValue2, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the info level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void info(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isInfoEnabled()) {
            LogLevel.INFO.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1, dataKey2),
                    createValuesFromArgs(message, dataValue1, dataValue2),
                    throwable);
        }
    }

    /**
     * Determine if warn level logging is enabled.
     *
     * @since 1.6.0
     *
     * @return True if and only if warn level logging is enabled.
     */
    public boolean isWarnEnabled() {
        return getSlf4jLogger().isWarnEnabled();
    }

    /**
     * Create a new log event at the warn level. Do not pre-build and cache
     * LogBuilder instances.
     *
     * @since 1.3.1
     *
     * @return Instance of LogBuilder.
     */
    public LogBuilder warn() {
        if (getSlf4jLogger().isWarnEnabled()) {
            return new DefaultLogBuilder(this, LogLevel.WARN);
        } else {
            return NO_OP_LOG_BUILDER;
        }
    }

    /**
     * Create a new log event at the warn level. The provided Consumer
     * populates a DeferredLogBuilder to define the log event. The
     * consumer may not be invoked if it is not necessary. Therefore it is
     * important not to include side-effects in the provided Consumer.
     *
     * @since 1.12.0
     *
     * @param consumer Function to populate the DeferredLogBuilder.
     */
    public void warn(final Consumer<DeferredLogBuilder> consumer) {
        if (_slf4jLogger.isWarnEnabled()) {
            final LogBuilder logBuilder = new DefaultLogBuilder(this, LogLevel.WARN);
            consumer.accept(logBuilder);
            logBuilder.log();
        }
    }

    /**
     * Log a message at the warn level. Default values are used for all other
     * parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     */
    public void warn(@Nullable final String message) {
        log(LogLevel.WARN, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message with a Throwable at the warn level. Default
     * values are used for all other parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void warn(@Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.WARN, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event at the warn level. Default values
     * are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     */
    public void warn(@Nullable final String event, @Nullable final String message) {
        log(LogLevel.WARN, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with a Throwable at the
     * warn level. Default values are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void warn(@Nullable final String event, @Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.WARN, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the warn level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data) {
        warn(event, message, data, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the warn level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isWarnEnabled()) {
            LogLevel.WARN.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromCollection(
                            data == null ? Collections.emptyList() : data.keySet(),
                            MESSAGE_DATA_KEY),
                    createValuesFromCollection(
                            data == null ? Collections.emptyList() : data.values(),
                            message),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the warn level.
     *
     * The number of elements in the dataNames array should
     * match the number of arguments provided as data
     * unless a Throwable is specified as the final argument in
     * data in which case there would be one fewer element
     * in the dataNames array compared to the number of
     * arguments provided as data.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues data values matching data keys by index. The
     * final data argument may be a Throwable which does
     * not require a matching data name.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object... dataValues) {
        final Throwable throwable = extractThrowable(dataKeys, dataValues);
        log(LogLevel.WARN, event, message, dataKeys, chompArray(dataValues, throwable == null ? 0 : 1), throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the warn level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues Array of data values.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object[] dataValues,
            @Nullable final Throwable throwable) {
        log(
                LogLevel.WARN,
                event,
                message,
                dataKeys,
                dataValues,
                throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the warn level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1) {
        warn(event, message, dataKey1, dataValue1, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the warn level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isWarnEnabled()) {
            LogLevel.WARN.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1),
                    createValuesFromArgs(message, dataValue1),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the warn level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2) {
        warn(event, message, dataKey1, dataKey2, dataValue1, dataValue2, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the warn level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void warn(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isWarnEnabled()) {
            LogLevel.WARN.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1, dataKey2),
                    createValuesFromArgs(message, dataValue1, dataValue2),
                    throwable);
        }
    }

    /**
     * Determine if error level logging is enabled.
     *
     * @since 1.6.0
     *
     * @return True if and only if error level logging is enabled.
     */
    public boolean isErrorEnabled() {
        return getSlf4jLogger().isErrorEnabled();
    }

    /**
     * Create a new log event at the error level. Do not pre-build and cache
     * LogBuilder instances.
     *
     * @since 1.3.1
     *
     * @return Instance of LogBuilder.
     */
    public LogBuilder error() {
        if (getSlf4jLogger().isErrorEnabled()) {
            return new DefaultLogBuilder(this, LogLevel.ERROR);
        } else {
            return NO_OP_LOG_BUILDER;
        }
    }

    /**
     * Create a new log event at the error level. The provided Consumer
     * populates a DeferredLogBuilder to define the log event. The
     * consumer may not be invoked if it is not necessary. Therefore it is
     * important not to include side-effects in the provided Consumer.
     *
     * @since 1.12.0
     *
     * @param consumer Function to populate the DeferredLogBuilder.
     */
    public void error(final Consumer<DeferredLogBuilder> consumer) {
        if (_slf4jLogger.isErrorEnabled()) {
            final LogBuilder logBuilder = new DefaultLogBuilder(this, LogLevel.ERROR);
            consumer.accept(logBuilder);
            logBuilder.log();
        }
    }

    /**
     * Log a message at the error level. Default values are used for all other
     * parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     */
    public void error(@Nullable final String message) {
        log(LogLevel.ERROR, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message with a Throwable at the error level. Default
     * values are used for all other parameters.
     *
     * This method is also found in the SLF4J Logger. It is
     * intended to simplify migration; however, it's use is not recommended
     * as it lacks an identifying event and typically includes serialized
     * as opposed to structured data.
     *
     * @since 1.3.0
     *
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void error(@Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.ERROR, DEFAULT_EVENT, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event at the error level. Default values
     * are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     */
    public void error(@Nullable final String event, @Nullable final String message) {
        log(LogLevel.ERROR, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with a Throwable at the
     * error level. Default values are used for all other parameters.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void error(@Nullable final String event, @Nullable final String message, @Nullable final Throwable throwable) {
        log(LogLevel.ERROR, event, message, EMPTY_STRING_ARRAY, EMPTY_OBJECT_ARRAY, throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the error level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data) {
        error(event, message, data, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the error level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param data Map of data key-value pairs.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final Map<String, Object> data,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isErrorEnabled()) {
            LogLevel.ERROR.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromCollection(
                            data == null ? Collections.emptyList() : data.keySet(),
                            MESSAGE_DATA_KEY),
                    createValuesFromCollection(
                            data == null ? Collections.emptyList() : data.values(),
                            message),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the error level.
     *
     * The number of elements in the dataNames array should
     * match the number of arguments provided as data
     * unless a Throwable is specified as the final argument in
     * data in which case there would be one fewer element
     * in the dataNames array compared to the number of
     * arguments provided as data.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues data values matching data keys by index. The
     * final data argument may be a Throwable which does
     * not require a matching data name.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object... dataValues) {
        final Throwable throwable = extractThrowable(dataKeys, dataValues);
        log(LogLevel.ERROR, event, message, dataKeys, chompArray(dataValues, throwable == null ? 0 : 1), throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the error level.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKeys Array of data keys.
     * @param dataValues Array of data values.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object[] dataValues,
            @Nullable final Throwable throwable) {
        log(
                LogLevel.ERROR,
                event,
                message,
                dataKeys,
                dataValues,
                throwable);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the error level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1) {
        error(event, message, dataKey1, dataValue1, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the error level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataValue1 First data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final Object dataValue1,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isErrorEnabled()) {
            LogLevel.ERROR.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1),
                    createValuesFromArgs(message, dataValue1),
                    throwable);
        }
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs at
     * the error level. This method is provided only for efficiency over the
     * var-args method above as it avoids an array creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2) {
        error(event, message, dataKey1, dataKey2, dataValue1, dataValue2, DEFAULT_THROWABLE);
    }

    /**
     * Log a message for a canonical event with supporting key-value pairs and
     * a Throwable at the error level. This method is provided
     * only for efficiency over the var-args method above as it avoids an array
     * creation during invocation.
     *
     * @since 1.3.0
     *
     * @param event The canonical event that occurred.
     * @param message The message to be logged.
     * @param dataKey1 First data name.
     * @param dataKey2 Second data name.
     * @param dataValue1 First data value.
     * @param dataValue2 Second data value.
     * @param throwable The exception (Throwable) to be logged.
     */
    public void error(
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String dataKey1,
            @Nullable final String dataKey2,
            @Nullable final Object dataValue1,
            @Nullable final Object dataValue2,
            @Nullable final Throwable throwable) {
        if (getSlf4jLogger().isErrorEnabled()) {
            LogLevel.ERROR.log(
                    getSlf4jLogger(),
                    event,
                    createKeysFromArgs(MESSAGE_DATA_KEY, dataKey1, dataKey2),
                    createValuesFromArgs(message, dataValue1, dataValue2),
                    throwable);
        }
    }

    /**
     * Accessor for underluing org.slf4j.Logger instance.
     *
     * @return Instance of underlying org.slf4j.Logger.
     */
    protected org.slf4j.Logger getSlf4jLogger() {
        return _slf4jLogger;
    }

    /**
     * Log for a particular level using the ARRAY_MARKER.
     *
     * @param level The log event level.
     * @param event The log event name.
     * @param message The log event message.
     * @param dataKeys The array of data keys.
     * @param dataValues The array of data values.
     * @param throwable The Throwable.
     */
    /* package private */ void log(
            final LogLevel level,
            @Nullable final String event,
            @Nullable final String message,
            @Nullable final String[] dataKeys,
            @Nullable final Object[] dataValues,
            @Nullable final Throwable throwable) {
        level.log(
                getSlf4jLogger(),
                event,
                createKeysFromArray(dataKeys, MESSAGE_DATA_KEY),
                createValuesFromArray(dataValues, message),
                throwable);
    }

    /**
     * Log for a particular level using the LISTS_MARKER.
     *
     * @param level The log event level.
     * @param event The log event name.
     * @param dataKeys The List of data keys. The
     * List may be modified.
     * @param dataValues The List of data values. The
     * List may be modified.
     * @param contextKeys The List of context keys. The
     * List may be modified.
     * @param contextValues The List of context values. The
     * List may be modified.
     * @param throwable The Throwable.
     */
    /* package private */ void log(
            final LogLevel level,
            @Nullable final String event,
            final List<String> dataKeys,
            final List<Object> dataValues,
            final List<String> contextKeys,
            final List<Object> contextValues,
            @Nullable final Throwable throwable) {
        level.log(
                getSlf4jLogger(),
                event,
                dataKeys,
                dataValues,
                contextKeys,
                contextValues,
                throwable);
    }

    /* package private */ static @Nullable String[] createKeysFromCollection(
            @Nullable final Collection<String> collection,
            @Nullable final String... keys) {
        if (isNullOrEmpty(keys)) {
            return collection == null ? null : collection.toArray(new String[collection.size()]);
        }
        if (isNullOrEmpty(collection)) {
            return keys;
        }
        final String[] combined = new String[collection.size() + keys.length];
        for (int i = 0; i < keys.length; ++i) {
            combined[i] = keys[i];
        }
        int i = 0;
        for (final String item : collection) {
            combined[keys.length + i++] = item;
        }
        return combined;
    }

    /* package private */ static @Nullable Object[] createValuesFromCollection(
            @Nullable final Collection<Object> collection,
            @Nullable final Object... values) {
        if (isNullOrEmpty(values)) {
            return collection == null ? null : collection.toArray(new Object[collection.size()]);
        }
        if (isNullOrEmpty(collection)) {
            return values;
        }
        final Object[] combined = new Object[collection.size() + values.length];
        for (int i = 0; i < values.length; ++i) {
            combined[i] = values[i];
        }
        int i = 0;
        for (final Object item : collection) {
            combined[values.length + i++] = item;
        }
        return combined;
    }

    /* package private */ static @Nullable  String[] createKeysFromArray(
            @Nullable final String[] array,
            @Nullable final String... keys) {
        if (isNullOrEmpty(keys)) {
            return array;
        }
        if (isNullOrEmpty(array)) {
            return keys;
        }
        final String[] combined = Arrays.copyOf(keys, array.length + keys.length);
        for (int i = 0; i < array.length; ++i) {
            combined[keys.length + i] = array[i];
        }

        return combined;
    }

    /* package private */ static @Nullable Object[] createValuesFromArray(
            @Nullable final Object[] array,
            @Nullable final Object... values) {
        if (isNullOrEmpty(values)) {
            return array;
        }
        if (isNullOrEmpty(array)) {
            return values;
        }
        final Object[] combined = Arrays.copyOf(values, array.length + values.length);
        for (int i = 0; i < array.length; ++i) {
            combined[values.length + i] = array[i];
        }
        return combined;
    }

    /* package private */ static @Nullable Throwable extractThrowable(
            @Nullable final String[] keys,
            @Nullable final Object[] values) {
        final int keyLength = keys == null ? 0 : keys.length;
        if (values != null && values.length == keyLength + 1) {
            final int throwableIndex = values.length - 1;
            if (values[throwableIndex] instanceof Throwable) {
                return (Throwable) values[throwableIndex];
            }
        }
        return null;
    }

    private static String[] createKeysFromArgs(final String... keys) {
        return keys;
    }

    private static Object[] createValuesFromArgs(final Object... values) {
        return values;
    }

    private static boolean isNullOrEmpty(@Nullable final String[] array) {
        if (array == null) {
            return true;
        }
        if (array.length == 0) {
            return true;
        }
        return false;
    }

    private static boolean isNullOrEmpty(@Nullable final Object[] array) {
        if (array == null) {
            return true;
        }
        if (array.length == 0) {
            return true;
        }
        return false;
    }

    private static boolean isNullOrEmpty(@Nullable final Collection<? extends Object> collection) {
        if (collection == null) {
            return true;
        }
        if (collection.size() == 0) {
            return true;
        }
        return false;
    }

    /* package private */ static @Nullable Object[] chompArray(@Nullable final Object[] in, final int count) {
        if (count == 0 || in == null) {
            return in;
        }
        return Arrays.copyOf(in, in.length - count);
    }

    /**
     * Protected constructor for extension.
     *
     * @param slf4jLogger Instance of underlying org.slf4j.Logger.
     */
    /* package private */ Logger(final org.slf4j.Logger slf4jLogger) {
        _slf4jLogger = slf4jLogger;
    }

    private org.slf4j.Logger _slf4jLogger;

    private static final String DEFAULT_EVENT = null;
    private static final Throwable DEFAULT_THROWABLE = null;
    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
    private static final LogBuilder NO_OP_LOG_BUILDER = new NoOpLogBuilder();

    /* package private */ static final String MESSAGE_DATA_KEY = "message";
}


/*
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.logback.serialization.steno;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.arpnetworking.logback.StenoEncoder;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.Serializable;
import java.io.StringWriter;
import java.util.Map;
import javax.annotation.Nullable;

/**
 * Serialization strategy for map based message specifications.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 * @since 1.3.1
 */
public class MapSerialziationStrategy implements Serializable {

    /**
     * Public constructor.
     *
     * @param encoder Instance of StenoEncoder.
     * @param jsonFactory Instance of JsonFactory.
     * @param objectMapper Instance of ObjectMapper.
     */
    public MapSerialziationStrategy(
            final StenoEncoder encoder,
            final JsonFactory jsonFactory,
            final ObjectMapper objectMapper) {
        _encoder = encoder;
        _jsonFactory = jsonFactory;
        _objectMapper = objectMapper;
    }

    /**
     * Serialize an event.
     *
     * @param event The event.
     * @param eventName The event name.
     * @param map The message key to value pairs.
     * @return Serialization of message as a String.
     * @throws Exception Serialization may throw any Exception.
     */
    public String serialize(
            final ILoggingEvent event,
            final String eventName,
            @Nullable final Map<String, ? extends Object> map)
            throws Exception {
        final StringWriter jsonWriter = new StringWriter();
        final JsonGenerator jsonGenerator = _jsonFactory.createGenerator(jsonWriter);

        // Start wrapper
        StenoSerializationHelper.startStenoWrapper(event, eventName, jsonGenerator, _objectMapper);

        // Write event data
        jsonGenerator.writeObjectFieldStart("data");
        if (map != null) {
            for (final Map.Entry<String, ? extends Object> entry : map.entrySet()) {
                if (StenoSerializationHelper.isSimpleType(entry.getValue())) {
                    jsonGenerator.writeObjectField(entry.getKey(), entry.getValue());
                } else {
                    jsonGenerator.writeFieldName(entry.getKey());
                    _objectMapper.writeValue(
                            jsonGenerator,
                            entry.getValue());
                }
            }
        }
        jsonGenerator.writeEndObject(); // End 'data' field

        // Output throwable
        StenoSerializationHelper.writeThrowable(event.getThrowableProxy(), jsonGenerator, _objectMapper);

        // End wrapper
        StenoSerializationHelper.endStenoWrapper(event, eventName, jsonGenerator, _objectMapper, _encoder);

        return jsonWriter.toString();
    }

    private final StenoEncoder _encoder;
    private final JsonFactory _jsonFactory;
    private final ObjectMapper _objectMapper;

    private static final long serialVersionUID = 1291513571694266158L;
}


/*
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.logback.serialization.steno;

import ch.qos.logback.classic.spi.ILoggingEvent;
import com.arpnetworking.logback.StenoEncoder;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.Serializable;
import java.io.StringWriter;

/**
 * Serialization strategy for standard message specification.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 * @since 1.3.1
 */
public class StandardSerializationStrategy implements Serializable {

    /**
     * Public constructor.
     *
     * @param encoder Instance of StenoEncoder.
     * @param jsonFactory Instance of JsonFactory.
     * @param objectMapper Instance of ObjectMapper.
     */
    public StandardSerializationStrategy(
            final StenoEncoder encoder,
            final JsonFactory jsonFactory,
            final ObjectMapper objectMapper) {
        _encoder = encoder;
        _jsonFactory = jsonFactory;
        _objectMapper = objectMapper;
    }

    /**
     * Serialize an event.
     *
     * @param event The event.
     * @param eventName The event name.
     * @return Serialization of message as a String.
     * @throws Exception Serialization may throw any Exception.
     */
    public String serialize(
        final ILoggingEvent event,
        final String eventName) throws Exception {

        final StringWriter jsonWriter = new StringWriter();
        final JsonGenerator jsonGenerator = _jsonFactory.createGenerator(jsonWriter);

        // Start wrapper
        StenoSerializationHelper.startStenoWrapper(event, eventName, jsonGenerator, _objectMapper);

        // Write event data
        jsonGenerator.writeObjectFieldStart("data");
        jsonGenerator.writeObjectField("message", event.getFormattedMessage());
        jsonGenerator.writeEndObject(); // End 'data' field

        // Output throwable
        StenoSerializationHelper.writeThrowable(event.getThrowableProxy(), jsonGenerator, _objectMapper);

        // End wrapper
        StenoSerializationHelper.endStenoWrapper(event, eventName, jsonGenerator, _objectMapper, _encoder);

        return jsonWriter.toString();
    }

    private final StenoEncoder _encoder;
    private final JsonFactory _jsonFactory;
    private final ObjectMapper _objectMapper;

    private static final long serialVersionUID = 6173546671328187044L;
}


/*
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.steno.aspect;

import com.arpnetworking.steno.LogBuilder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.SourceLocation;

/**
 * Aspect for LogBuilder weaving line, file and class into the
 * context block of each LogBuilder message.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 */
@Aspect
@SuppressFBWarnings("MS_SHOULD_BE_FINAL")
public class LogBuilderAspect {

    /**
     * Before outputting the message inject additional context.
     *
     * @param joinPoint The JoinPoint.
     */
    @Before("call(* com.arpnetworking.steno.LogBuilder.log())")
    public void addToContextLineAndMethod(final JoinPoint joinPoint) {
        final SourceLocation sourceLocation = joinPoint.getSourceLocation();
        final LogBuilder targetLogBuilder = (LogBuilder) joinPoint.getTarget();
        targetLogBuilder.addContext("line", String.valueOf(sourceLocation.getLine()));
        targetLogBuilder.addContext("file", sourceLocation.getFileName());
        targetLogBuilder.addContext("class", sourceLocation.getWithinType());
    }
}


/*
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.logback.serialization.steno;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.pattern.ClassicConverter;
import ch.qos.logback.classic.pattern.LoggerConverter;
import ch.qos.logback.classic.pattern.ThreadConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.classic.spi.ThrowableProxy;
import com.arpnetworking.logback.HostConverter;
import com.arpnetworking.logback.ProcessConverter;
import com.arpnetworking.logback.StenoClassOfCallerConverter;
import com.arpnetworking.logback.StenoEncoder;
import com.arpnetworking.logback.StenoFileOfCallerConverter;
import com.arpnetworking.logback.StenoLineOfCallerConverter;
import com.arpnetworking.logback.StenoMethodOfCallerConverter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Nullable;

/**
 * Helper functions and functors for Steno serialziation.
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 * @since 1.7.0
 */
public final class StenoSerializationHelper {

    /**
     * Start writing the Steno JSON wrapper.
     *
     * @since 1.8.0
     *
     * @param event Instance of ILoggingEvent.
     * @param eventName The name of the event.
     * @param jsonGenerator JsonGenerator instance.
     * @param objectMapper ObjectMapper instance.
     * @throws java.io.IOException If writing JSON fails.
     */
    public static void startStenoWrapper(
            final ILoggingEvent event,
            final String eventName,
            final JsonGenerator jsonGenerator,
            final ObjectMapper objectMapper)
            throws IOException {

        final StenoSerializationHelper.StenoLevel level = StenoSerializationHelper.StenoLevel.findByLogbackLevel(
                event.getLevel());
        jsonGenerator.writeStartObject();
        jsonGenerator.writeObjectField("time",
                ISO_DATE_TIME_FORMATTER.format(Instant.ofEpochMilli(event.getTimeStamp())));
        jsonGenerator.writeObjectField("name", eventName);
        jsonGenerator.writeObjectField("level", level.name());
    }

    /**
     * Complete writing the Steno JSON wrapper.
     *
     * @since 1.8.0
     *
     * @param event Instance of ILoggingEvent.
     * @param eventName The name of the event.
     * @param jsonGenerator JsonGenerator instance.
     * @param objectMapper ObjectMapper instance.
     * @param encoder StenoEncoder instance.
     * @throws IOException If writing JSON fails.
     */
    public static void endStenoWrapper(
            final ILoggingEvent event,
            final String eventName,
            final JsonGenerator jsonGenerator,
            final ObjectMapper objectMapper,
            final StenoEncoder encoder)
            throws IOException {
        endStenoWrapper(
                event,
                eventName,
                Collections.emptyList(),
                Collections.emptyList(),
                jsonGenerator,
                objectMapper,
                encoder);
    }

    /**
     * Complete writing the Steno JSON wrapper.
     *
     * @since 1.8.0
     *
     * @param event Instance of ILoggingEvent.
     * @param eventName The name of the event.
     * @param contextKeys The List of context keys.
     * @param contextValues The List of context values.
     * @param jsonGenerator JsonGenerator instance.
     * @param objectMapper ObjectMapper instance.
     * @param encoder StenoEncoder instance.
     * @throws IOException If writing JSON fails.
     */
    public static void endStenoWrapper(
            final ILoggingEvent event,
            final String eventName,
            @Nullable final List<String> contextKeys,
            @Nullable final List<Object> contextValues,
            final JsonGenerator jsonGenerator,
            final ObjectMapper objectMapper,
            final StenoEncoder encoder)
            throws IOException {

        jsonGenerator.writeFieldName("context");
        objectMapper.writeValue(
                jsonGenerator,
                StenoSerializationHelper.createContext(
                        encoder,
                        event,
                        objectMapper,
                        contextKeys,
                        contextValues));
        jsonGenerator.writeObjectField("id", StenoSerializationHelper.createId());
        jsonGenerator.writeObjectField("version", "0");
        jsonGenerator.writeEndObject(); // End log message
        jsonGenerator.writeRaw('\n');
        jsonGenerator.flush();
    }

    /**
     * Write specified key-value pairs into the current block.
     *
     * @since 1.7.0
     * @param keys The List of keys.
     * @param values The List of values.
     * @param jsonGenerator JsonGenerator instance.
     * @param objectMapper ObjectMapper instance.
     * @param encoder Instance of StenoEncoder.
     * @throws IOException If writing JSON fails.
     */
    public static void writeKeyValuePairs(
            @Nullable final List<String> keys,
            @Nullable final List<Object> values,
            final JsonGenerator jsonGenerator,
            final ObjectMapper objectMapper,
            final StenoEncoder encoder)
            throws IOException {
        if (keys != null) {
            final int contextValuesLength = values == null ? 0 : values.size();
            for (int i = 0; i < keys.size(); ++i) {
                final String key = keys.get(i);
                if (i >= contextValuesLength) {
                    jsonGenerator.writeObjectField(key, null);
                } else {
                    final Object value = values.get(i);
                    if (isSimpleType(value)) {
                        jsonGenerator.writeObjectField(key, value);
                    } else {
                        jsonGenerator.writeFieldName(key);
                        objectMapper.writeValue(
                                jsonGenerator,
                                value);
                    }
                }
            }
        }
    }

    /**
     * Write a Throwable via IThrowableProxy as JSON.
     *
     * @since 1.7.0
     * @param throwableProxy Throwable to serialize
     * @param jsonGenerator  JsonGenerator instance.
     * @param objectMapper ObjectMapper instance.
     * @throws IOException If writing JSON fails.
     */
    public static void writeThrowable(
            final IThrowableProxy throwableProxy,
            final JsonGenerator jsonGenerator,
            final ObjectMapper objectMapper)
            throws IOException {

        if (throwableProxy != null) {
            jsonGenerator.writeObjectFieldStart("exception");
            serializeThrowable(throwableProxy, jsonGenerator, objectMapper);
            jsonGenerator.writeEndObject();
        }
    }

    /**
     * This function assumes the field object has already been started for this throwable, this only fills in
     * the fields in the 'exception' or equivalent object and does not create the field in the containing object.
     *
     * @since 1.7.0
     * @param throwableProxy Throwable to serialize
     * @param jsonGenerator  JsonGenerator instance after exception object is started
     * @param objectMapper ObjectMapper instance.
     * @throws IOException If writing the Throwable as JSON fails.
     */
    public static void serializeThrowable(
            final IThrowableProxy throwableProxy,
            final JsonGenerator jsonGenerator,
            final ObjectMapper objectMapper)
            throws IOException {

        jsonGenerator.writeStringField("type", throwableProxy.getClassName());
        jsonGenerator.writeStringField("message", throwableProxy.getMessage());
        jsonGenerator.writeArrayFieldStart("backtrace");
        for (final StackTraceElementProxy ste : throwableProxy.getStackTraceElementProxyArray()) {
            jsonGenerator.writeString(ste.toString());
        }
        jsonGenerator.writeEndArray();
        jsonGenerator.writeObjectFieldStart("data");
        if (throwableProxy instanceof ThrowableProxy) {
            final JsonNode jsonNode = objectMapper.valueToTree(((ThrowableProxy) throwableProxy).getThrowable());
            for (final Iterator<Map.Entry<String, JsonNode>> iterator = jsonNode.fields(); iterator.hasNext();) {
                final Map.Entry<String, JsonNode> field = iterator.next();
                jsonGenerator.writeFieldName(field.getKey());
                objectMapper.writeValue(
                        jsonGenerator,
                        field.getValue());
            }
        }
        // Although Throwable has a final getSuppressed which cannot return a null array, the
        // proxy in Logback provides no such guarantees.
        if (throwableProxy.getSuppressed() != null && throwableProxy.getSuppressed().length > 0) {
            jsonGenerator.writeArrayFieldStart("suppressed");
            for (final IThrowableProxy suppressed : throwableProxy.getSuppressed()) {
                jsonGenerator.writeStartObject();
                serializeThrowable(suppressed, jsonGenerator, objectMapper);
                jsonGenerator.writeEndObject();
            }
            jsonGenerator.writeEndArray();
        }
        if (throwableProxy.getCause() != null) {
            jsonGenerator.writeObjectFieldStart("cause");
            serializeThrowable(throwableProxy.getCause(), jsonGenerator, objectMapper);
            jsonGenerator.writeEndObject();
        }
        jsonGenerator.writeEndObject();
    }

    /**
     * Create a context based on the StenoEncoder configuration.
     *
     * @since 1.7.0
     * @param encoder The StenoEncoder instance.
     * @param event The ILoggingEvent instance.
     * @param objectMapper ObjectMapper instance.
     * @param contextKeys The additional user provided context keys.
     * @param contextValues The additional user provided context values matching the keys.
     * @return Map with event context.
     */
    public static Map<String, Object> createContext(
            final StenoEncoder encoder,
            final ILoggingEvent event,
            final ObjectMapper objectMapper,
            @Nullable final List<String> contextKeys,
            @Nullable final List<Object> contextValues) {

        final Map<String, Object> context = new LinkedHashMap<>();
        if (encoder.isInjectContextHost()) {
            context.put("host", StenoSerializationHelper.HOST_CONVERTER.convert(event));
        }
        if (encoder.isInjectContextProcess()) {
            context.put("processId", StenoSerializationHelper.PROCESS_CONVERTER.convert(event));
        }
        if (encoder.isInjectContextThread()) {
            context.put("threadId", StenoSerializationHelper.THREAD_CONVERTER.convert(event));
        }
        if (encoder.isInjectContextLogger()) {
            if (encoder.isCompressLoggerName()) {
                context.put("logger", StenoSerializationHelper.CONDENSED_LOGGER_CONVERTER.convert(event));
            } else {
                context.put("logger", StenoSerializationHelper.LOGGER_CONVERTER.convert(event));
            }
        }
        if (encoder.isInjectContextFile()) {
            context.put("file", StenoSerializationHelper.FILE_CONVERTER.convert(event));
        }
        if (encoder.isInjectContextClass()) {
            context.put("class", StenoSerializationHelper.CLASS_CONVERTER.convert(event));
        }
        if (encoder.isInjectContextMethod()) {
            context.put("method", StenoSerializationHelper.METHOD_CONVERTER.convert(event));
        }
        if (encoder.isInjectContextLine()) {
            context.put("line", StenoSerializationHelper.LINE_CONVERTER.convert(event));
        }
        final Iterator<String> injectContextMdcIterator = encoder.iteratorForInjectContextMdc();
        while (injectContextMdcIterator.hasNext()) {
            final String key = injectContextMdcIterator.next();
            final String value = event.getMDCPropertyMap().get(key);
            context.put(key, value);
        }
        if (contextKeys != null) {
            final int contextValuesLength = contextValues == null ? 0 : contextValues.size();
            for (int i = 0; i < contextKeys.size(); ++i) {
                final String key = contextKeys.get(i);
                final Object value = i < contextValuesLength ? contextValues.get(i) : null;
                context.put(key, value);
            }
        }
        return context;
    }

    /**
     * Create a Steno compatible identifier.
     *
     * @since 1.7.0
     * @return New identifier as a String.
     */
    public static String createId() {
        return UUID.randomUUID().toString();
    }

    /**
     * Determine if an object represents a primitive Json type. These include
     * instances of Number, String and Boolean.
     *
     * @since 1.7.0
     * @param obj The Object to analyze.
     * @return True if and only if the Object is a simple type.
     */
    public static boolean isSimpleType(@Nullable final Object obj) {
        if (obj == null) {
            return true;
        }

        final Class<?> objClass = obj.getClass();
        if (String.class.isAssignableFrom(objClass)) {
            return true;
        }

        if (Number.class.isAssignableFrom(objClass)) {
            return true;
        }

        if (Boolean.class.isAssignableFrom(objClass)) {
            return true;
        }

        return false;
    }

    /**
     * Converter for host.
     * @since 1.7.0
     */
    public static final ClassicConverter HOST_CONVERTER = new HostConverter();
    /**
     * Converter for process.
     * @since 1.7.0
     */
    public static final ClassicConverter PROCESS_CONVERTER = new ProcessConverter();
    /**
     * Converter for thread.
     * @since 1.7.0
     */
    public static final ClassicConverter THREAD_CONVERTER = new ThreadConverter();
    /**
     * Converter for logger.
     * @since 1.7.0
     */
    public static final ClassicConverter LOGGER_CONVERTER = new LoggerConverter();
    /**
     * Converter for logger in condensed form.
     * @since 1.7.0
     */
    public static final ClassicConverter CONDENSED_LOGGER_CONVERTER = new LoggerConverter();
    /**
     * Converter for file.
     * @since 1.7.0
     */
    public static final ClassicConverter FILE_CONVERTER = new StenoFileOfCallerConverter();
    /**
     * Converter for class.
     * @since 1.7.0
     */
    public static final ClassicConverter CLASS_CONVERTER = new StenoClassOfCallerConverter();
    /**
     * Converter for method.
     * @since 1.7.0
     */
    public static final ClassicConverter METHOD_CONVERTER = new StenoMethodOfCallerConverter();
    /**
     * Converter for line.
     * @since 1.7.0
     */
    public static final ClassicConverter LINE_CONVERTER = new StenoLineOfCallerConverter();

    static {
        CONDENSED_LOGGER_CONVERTER.setOptionList(Collections.singletonList("1"));

        HOST_CONVERTER.start();
        PROCESS_CONVERTER.start();
        THREAD_CONVERTER.start();
        LOGGER_CONVERTER.start();
        CONDENSED_LOGGER_CONVERTER.start();
        FILE_CONVERTER.start();
        CLASS_CONVERTER.start();
        METHOD_CONVERTER.start();
        LINE_CONVERTER.start();
    }

    /**
     * Log levels used by Steno.
     *
     * @since 1.7.0
     */
    public enum StenoLevel {
        /**
         * Steno level debug. This maps to SLF4J debug and trace levels.
         *
         * @since 1.7.0
         */
        debug(Level.DEBUG, Level.TRACE),
        /**
         * Steno level info. This maps to SLF4J info level.
         *
         * @since 1.7.0
         */
        info(Level.INFO),
        /**
         * Steno level warn. This maps to the SLF4J warn level.
         *
         * @since 1.7.0
         */
        warn(Level.WARN),
        /**
         * Steno level crit (critical). This maps to the SLF4J error level.
         *
         * @since 1.7.0
         */
        crit(Level.ERROR);

        private final Level[] _logbackLevels;
        // CHECKSTYLE.OFF: IllegalInstantiation - No Guava dependency here.
        private static final Map<Level, StenoLevel> LOGBACK_LEVEL_MAP = new HashMap<>();
        // CHECKSTYLE.ON: IllegalInstantiation

        static {
            for (StenoLevel stenoLevel : values()) {
                for (Level logbackLevel : stenoLevel._logbackLevels) {
                    LOGBACK_LEVEL_MAP.put(logbackLevel, stenoLevel);
                }
            }
        }

        StenoLevel(final Level... logbackLevels) {
            _logbackLevels = logbackLevels;
        }

        /**
         * Find the StenoLevel corresponding to the Logback Level.
         *
         * @since 1.7.0
         * @param logbackLevel The Logback Level to map to a StenoLevel.
         * @return The matching StenoLevel or null if one is not found.
         */
        public static StenoLevel findByLogbackLevel(final Level logbackLevel) {
            return LOGBACK_LEVEL_MAP.get(logbackLevel);
        }
    }

    private StenoSerializationHelper() {}

    private static final DateTimeFormatter ISO_DATE_TIME_FORMATTER =
            DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ").withZone(ZoneId.of("UTC"));
}


/*
 * Copyright 2015 Groupon.com
 *
 * 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 com.arpnetworking.steno;

import java.time.Duration;

/**
 * Factory class creates instances of Logger. To include the
 * context class or name in the context block of the Steno encoder's
 * output remember to enable context logger injection via:
 *
 * setInjectContextLogger
 *
 * @since 1.3.0
 *
 * @author Ville Koskela (ville dot koskela at inscopemetrics dot com)
 */
public final class LoggerFactory {

    /**
     * Return a Steno Logger for a context class.
     *
     * @param clazz The Logger context class.
     * @return Steno Logger instance.
     */
    public static Logger getLogger(final Class<?> clazz) {
        return new Logger(org.slf4j.LoggerFactory.getLogger(clazz));
    }

    /**
     * Return a rate limited Steno Logger for a context class.
     *
     * @param clazz The Logger context class.
     * @param duration Minimum time between log message output.
     * @return Steno Logger instance.
     */
    public static Logger getRateLimitLogger(final Class<?> clazz, final Duration duration) {
        return new RateLimitLogger(org.slf4j.LoggerFactory.getLogger(clazz), duration);
    }

    /**
     * Return a Steno Logger for a context name.
     *
     * @param name The Logger context name.
     * @return Steno Logger instance.
     */
    public static Logger getLogger(final String name) {
        return new Logger(org.slf4j.LoggerFactory.getLogger(name));
    }

    /**
     * Return a rate limited Steno Logger for a context name.
     *
     * @param name The Logger context name.
     * @param duration Minimum time between log message output.
     * @return Steno Logger instance.
     */
    public static Logger getRateLimitLogger(final String name, final Duration duration) {
        return new RateLimitLogger(org.slf4j.LoggerFactory.getLogger(name), duration);
    }

    /**
     * Return a Steno Logger for an already instantiated org.slf4j.Logger instance.
     *
     * @param logger The org.slf4j.Logger instance.
     * @return Steno Logger instance.
     */
    public static Logger getLogger(final org.slf4j.Logger logger) {
        return new Logger(logger);
    }

    /**
     * Return a rate limited Steno Logger for an already instantiated org.slf4j.Logger
     * instance.
     *
     * @param logger The org.slf4j.Logger instance.
     * @param duration Minimum time between log message output.
     * @return Steno Logger instance.
     */
    public static Logger getRateLimitLogger(final org.slf4j.Logger logger, final Duration duration) {
        return new RateLimitLogger(logger, duration);
    }

    private LoggerFactory() {
        throw new UnsupportedOperationException("This class cannot be instantiated");
    }
}