public class CommonKeys {
    public static final String LOG_MESSAGE_KEY = "_message";

    public static final String UNNAMED_VALUES_KEY = "_unnamed_values";
} public class LogMessage extends HashMap<String, Object> {

} public interface Logger extends org.slf4j.Logger {

    /**
     * Adds the objects values to the logger.
     * When logged these values will be formatted and output in the log.
     * @param obj : Object to be added to the logs
     * @return logger instance
     */
    Logger with(Object obj);

    void error(Throwable t, String format, Object... args);

    void warn(Throwable t, String format, Object... args);

    Logger with(String key, Object value);

    void success(String format, Object... args);

    void dashboard(String format, Object... args);

} public final class LoggerFactory {

    public static Logger getLogger(Class<?> clazz) {
        return LoggingConfigs.getCurrent().getDefaultLogger(clazz);
    }

    public static Logger getLogger(Class<?> clazz, LoggingConfigs configs) {
        return configs.getConfiguredLogger(clazz, configs);
    }

} @Builder
@Data
public class LoggingConfigs {

    @Setter
    @Getter
    private static LoggingConfigs current = builder().build();

    /**
     * The custom mapper provides the ability to pass in a custom toString function for a specific class.
     */
    private final Map<Class<?>, Function<Object, String>> customMapper;

    /**
     * The recursiveLevel defines the number of inner class levels to be logged.
     *
     * Default value is "5".
     */
    private final Integer recursiveLevel;

    /**
     * Methods beginning with one of these defined prefixes will be logged.
     *
     * Default values are "get" and "is".
     */
    private final Set<String> methodPrefixes;

    /**
     * Values that contain these prefixes will not be logged.
     *
     * Default values are "val$" and "this$".
     */
    private final Set<String> excludesPrefixes;

    /**
     * Message builder to format logs.
     *
     * It uses the LoggerMessageBuilder by default.
     */
    private final MessageBuilderProvider<?> messageBuilderFunction;

    /**
     * Processor used to hash data.
     *
     * Using the MD5HashProcessor by default. The MD5HashProcessor uses Guava's MD5 hashing algorithm.
     * MD5 is not cryptographically secure, but it is extremely fast.
     * For a more robust encryption you can use your own HashProcessor.
     */
    private final HashProcessor hashProcessor;

    /**
     * Function to take any exceptions thrown by the logger and translate it to a string
     */
    private final ExceptionTranslator exceptionTranslator;

    /**
     * Function which returns a logger implementation. By default, LoggerImpl is used.
     */
    private final BiFunction<Class<?>, LoggingConfigs, Logger> logger;

    /**
     * Allows the ability to filter collections, if you only want a maximum of 10 elements to be logged per
     * collection you can do so by providing a filter function that will ensure only 10 elements are logged.
     * By default collections are filtered to only contain 50 items.
     */
    private final Function<Collection, Collection> collectionFilter;

    LoggingConfigs(
            Map<Class<?>, Function<Object, String>> customMapper,
            Integer recursiveLevel,
            Set<String> methodPrefixes,
            Set<String> excludesPrefixes,
            MessageBuilderProvider<?> messageBuilderFunction,
            HashProcessor hashProcessor,
            ExceptionTranslator exceptionTranslator,
            BiFunction<Class<?>, LoggingConfigs, Logger> logger,
            Function<Collection, Collection> collectionFilter) {

        this.customMapper = appendDefaultsToCustomMapper(customMapper);

        this.methodPrefixes = appendDefaults(ImmutableSet.of("get", "is"), methodPrefixes);

        this.excludesPrefixes = appendDefaults(ImmutableSet.of("val$", "this$"), excludesPrefixes);

        this.recursiveLevel = recursiveLevel == null ? 5 : recursiveLevel;

        this.logger = logger == null ? (clazz, configs) -> new LoggerImpl(new Slf4WrapperLogger(org.slf4j.LoggerFactory.getLogger(clazz)), configs)
                                     : logger;

        this.messageBuilderFunction = messageBuilderFunction != null ? messageBuilderFunction : new StringMessageBuilderProvider();
        this.hashProcessor = hashProcessor == null ? new Sha256HashProcessor() : hashProcessor;
        this.exceptionTranslator = exceptionTranslator == null ? i -> "<An error occurred logging!>" : exceptionTranslator;
        this.collectionFilter = collectionFilter == null ? collection -> (Collection) collection.stream().limit(50).collect(toList()) : collectionFilter;
    }

    public LoggingConfigs(LoggingConfigs loggingConfigs) {
        this.customMapper = Maps.newHashMap(loggingConfigs.getCustomMapper());
        this.methodPrefixes = Sets.newHashSet(loggingConfigs.getMethodPrefixes());
        this.excludesPrefixes = Sets.newHashSet(loggingConfigs.getExcludesPrefixes());
        this.recursiveLevel = loggingConfigs.getRecursiveLevel();
        this.messageBuilderFunction = loggingConfigs.getMessageBuilderFunction();
        this.hashProcessor = loggingConfigs.getHashProcessor();
        this.logger = loggingConfigs.getLogger();
        this.exceptionTranslator = loggingConfigs.getExceptionTranslator();
        this.collectionFilter = loggingConfigs.getCollectionFilter();
    }

    /**
     * Creates an Immutable copy of Logging Configs with a newly defined Recursive Level.
     *
     * @param recursiveLevel how deep do you want the logging to go for each object
     * @return logging configuration
     */
    public LoggingConfigs withRecursiveLevel(Integer recursiveLevel) {
        return new LoggingConfigs(this.getCustomMapper(),
                                  recursiveLevel,
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  this.getMessageBuilderFunction(),
                                  this.getHashProcessor(),
                                  this.getExceptionTranslator(),
                                  this.logger,
                                  this.getCollectionFilter());
    }

    /**
     * Creates an Immutable copy of Logging Configs with a newly defined Message Builder Function.
     *
     * @param messageBuilderFunction message builder provider
     * @return logging configuration
     */
    public LoggingConfigs withMessageBuilderFunction(MessageBuilderProvider<?> messageBuilderFunction) {
        return new LoggingConfigs(this.getCustomMapper(),
                                  this.getRecursiveLevel(),
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  messageBuilderFunction,
                                  this.getHashProcessor(),
                                  this.getExceptionTranslator(),
                                  this.logger,
                                  this.getCollectionFilter());
    }

    public LoggingConfigs withLogger(BiFunction<Class<?>, LoggingConfigs, Logger> logger) {
        return new LoggingConfigs(this.getCustomMapper(),
                                  this.getRecursiveLevel(),
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  messageBuilderFunction,
                                  this.getHashProcessor(),
                                  this.getExceptionTranslator(),
                                  logger,
                                  this.getCollectionFilter());
    }

    public LoggingConfigs useJson() {
        return new LoggingConfigs(this.getCustomMapper(),
                                  this.getRecursiveLevel(),
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  new LogstashMessageBuilderProvider(),
                                  this.getHashProcessor(),
                                  this.getExceptionTranslator(),
                                  (clazz, configs) -> new MarkerAppendingLogger(new Slf4WrapperLogger(org.slf4j.LoggerFactory.getLogger(clazz)), configs),
                                  this.getCollectionFilter());
    }

    public LoggingConfigs withCollectionFilter(Function<Collection, Collection> collectionFilter) {
        return new LoggingConfigs(this.getCustomMapper(),
                                  this.getRecursiveLevel(),
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  this.getMessageBuilderFunction(),
                                  this.getHashProcessor(),
                                  this.getExceptionTranslator(),
                                  this.logger,
                                  collectionFilter);
    }

    /**
     * Creates an Immutable copy of Logging Configs with a newly defined Hash Processor.
     *
     * @param hashProcessor hash processor
     * @return logging configuration
     */
    public LoggingConfigs withHashProcessor(HashProcessor hashProcessor) {
        return new LoggingConfigs(this.getCustomMapper(),
                                  this.getRecursiveLevel(),
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  this.getMessageBuilderFunction(),
                                  hashProcessor,
                                  this.getExceptionTranslator(),
                                  this.logger,
                                  this.getCollectionFilter());
    }

    /**
     * Creates an Immutable copy of Logging Configs with a newly exception translator
     *
     * @param exceptionTranslator exception translator
     * @return logging configuration
     */
    public LoggingConfigs withExceptionTranslator(ExceptionTranslator exceptionTranslator) {
        return new LoggingConfigs(this.getCustomMapper(),
                                  this.getRecursiveLevel(),
                                  this.getMethodPrefixes(),
                                  this.getExcludesPrefixes(),
                                  this.getMessageBuilderFunction(),
                                  this.getHashProcessor(),
                                  exceptionTranslator,
                                  this.logger,
                                  this.getCollectionFilter());
    }

    /**
     * Ability to add a custom mapping.
     *
     * @param clazz  - class to be mapped to a toString function.
     * @param mapper - toString mapping function for the specified clazz.
     * @param <T>    - type of generic class
     * @return logging configuration
     */
    public <T> LoggingConfigs addOverride(Class<T> clazz, Function<T, String> mapper) {
        customMapper.put(clazz, (Function<Object, String>) mapper);

        return this;
    }

    /**
     * This is deprecated because we don't want any of the with functionality mutating the LoggingConfigs.
     * Use addOverride instead.
     */
    @Deprecated
    public <T> LoggingConfigs withOverride(Class<T> clazz, Function<T, String> mapper) {
        customMapper.put(clazz, (Function<Object, String>) mapper);

        return this;
    }

    private Map<Class<?>, Function<Object, String>> appendDefaultsToCustomMapper(Map<Class<?>, Function<Object, String>> customMapper) {
        Map<Class<?>, Function<Object, String>> defaultMapper = new HashMap<Class<?>, Function<Object, String>>() {{
            put(UUID.class, Object::toString);
        }};

        //Includes ability to override defaults.
        if (customMapper != null) {
            customMapper.keySet().stream()
                        .forEach(clazz -> defaultMapper.put(clazz, customMapper.get(clazz)));
        }

        return defaultMapper;
    }

    private ImmutableSet<String> appendDefaults(ImmutableSet<String> defaults, Set<String> configs) {
        return configs == null ? defaults : new ImmutableSet.Builder<String>().addAll(defaults).addAll(configs).build();
    }

    public Logger getDefaultLogger(Class<?> clazz) {
        return getConfiguredLogger(clazz, LoggingConfigs.getCurrent());
    }

    public Logger getConfiguredLogger(Class<?> clazz, LoggingConfigs configs) {
        return logger.apply(clazz, configs);
    }
} public class Slf4WrapperLogger implements Logger {

    @Delegate
    private final org.slf4j.Logger logger;

    public Slf4WrapperLogger(final org.slf4j.Logger logger) {

        this.logger = logger;
    }

    @Override public Logger with(final Object obj) {
        return null;
    }

    @Override public void error(final Throwable t, final String format, final Object... args) {

    }

    @Override public void warn(final Throwable t, final String format, final Object... args) {

    }

    @Override public Logger with(final String key, final Object value) {
        return null;
    }

    @Override public void success(final String format, final Object... args) {

    }

    @Override public void dashboard(final String format, final Object... args) {

    }
} public class MarkerAppendingLogger extends LoggerImpl {
    private final Logger root;
    private final LoggingConfigs configs;

    public MarkerAppendingLogger(Logger root, LoggingConfigs configs) {
        super(root, configs);
        this.root = root;
        this.configs = configs;
    }

    @Override
    public Logger with(Object obj) {
        return new MarkerAppendingAnnotatingLogger(root, this, obj, configs);
    }

    protected LogContext getMessage(LogContext<?> runningLogContext) {
        return runningLogContext;
    }

    @Override
    public Logger with(final String key, final Object value) {
        LogMessage logMessage = new LogMessage();

        logMessage.put(key, value);

        return new MarkerAppendingAnnotatingLogger(root, this, logMessage, configs);
    }

    private LogContext getMessage(String msg) {
        return getMessage(new InitialLogContext(msg));
    }

    private Marker formatMessage(LogContext msg) {
        return (Marker) configs.getMessageBuilderFunction().formatPayload(msg);
    }

    @Override public void info(final String msg) {
        root.info(formatMessage(getMessage(msg)), msg);
    }

    @Override
    public void trace(String msg) {
        root.trace(formatMessage(getMessage(msg)), msg);
    }

    @Override
    public void trace(String format, Object arg) {
        root.trace(formatMessage(getMessage(format)), format, arg);
    }

    @Override
    public void trace(String format, Object arg1, Object arg2) {
        root.trace(formatMessage(getMessage(format)), format, arg1, arg2);
    }

    @Override
    public void trace(String format, Object... arguments) {
        root.trace(formatMessage(getMessage(format)), format, arguments);
    }

    @Override
    public void trace(String msg, Throwable t) {
        root.trace(formatMessage(getMessage(msg)), msg, t);
    }

    @Override
    public void trace(Marker marker, String msg) {
        marker.add(formatMessage(getMessage(msg)));
        root.trace(marker, msg);
    }

    @Override
    public void trace(Marker marker, String format, Object arg) {
        marker.add(formatMessage(getMessage(format)));
        root.trace(marker, format, arg);
    }

    @Override
    public void trace(Marker marker, String format, Object arg1, Object arg2) {
        marker.add(formatMessage(getMessage(format)));
        root.trace(marker, format, arg1, arg2);
    }

    @Override
    public void trace(Marker marker, String format, Object... argArray) {
        marker.add(formatMessage(getMessage(format)));
        root.trace(marker, format, argArray);
    }

    @Override
    public void trace(Marker marker, String msg, Throwable t) {
        marker.add(formatMessage(getMessage(msg)));
        root.trace(marker, msg, t);
    }

    @Override
    public void debug(String msg) {
        root.debug(formatMessage(getMessage(msg)), msg);
    }

    @Override
    public void debug(String format, Object arg) {
        root.debug(formatMessage(getMessage(format)), format, arg);
    }

    @Override
    public void debug(String format, Object arg1, Object arg2) {
        root.debug(formatMessage(getMessage(format)), format, arg1, arg2);
    }

    @Override
    public void debug(String format, Object... arguments) {
        root.debug(formatMessage(getMessage(format)), format, arguments);
    }

    @Override
    public void debug(String msg, Throwable t) {
        root.debug(formatMessage(getMessage(msg)), msg, t);
    }

    @Override
    public void debug(Marker marker, String msg) {
        marker.add(formatMessage(getMessage(msg)));
        root.debug(marker, msg);
    }

    @Override
    public void debug(Marker marker, String format, Object arg) {
        marker.add(formatMessage(getMessage(format)));
        root.debug(marker, format, arg);
    }

    @Override
    public void debug(Marker marker, String format, Object arg1, Object arg2) {
        marker.add(formatMessage(getMessage(format)));
        root.debug(marker, format, arg1, arg2);
    }

    @Override
    public void debug(Marker marker, String format, Object... arguments) {
        marker.add(formatMessage(getMessage(format)));
        root.debug(marker, format, arguments);
    }

    @Override
    public void debug(Marker marker, String msg, Throwable t) {
        marker.add(formatMessage(getMessage(msg)));
        root.debug(marker, msg, t);
    }

    @Override
    public void info(String format, Object arg) {
        root.info(formatMessage(getMessage(format)), format, arg);
    }

    @Override
    public void info(String format, Object arg1, Object arg2) {
        root.info(formatMessage(getMessage(format)), format, arg1, arg2);
    }

    @Override
    public void info(String format, Object... arguments) {
        root.info(formatMessage(getMessage(format)), format, arguments);
    }

    @Override
    public void info(String msg, Throwable t) {
        root.info(formatMessage(getMessage(msg)), msg, t);
    }

    @Override
    public void info(Marker marker, String msg) {
        marker.add(formatMessage(getMessage(msg)));
        root.info(marker, msg);
    }

    @Override
    public void info(Marker marker, String format, Object arg) {
        marker.add(formatMessage(getMessage(format)));
        root.info(marker, format, arg);
    }

    @Override
    public void info(Marker marker, String format, Object arg1, Object arg2) {
        marker.add(formatMessage(getMessage(format)));
        root.info(marker, format, arg1, arg2);
    }

    @Override
    public void info(Marker marker, String format, Object... arguments) {
        marker.add(formatMessage(getMessage(format)));
        root.info(marker, format, arguments);
    }

    @Override
    public void info(Marker marker, String msg, Throwable t) {
        marker.add(formatMessage(getMessage(msg)));
        root.info(marker, msg, t);
    }

    @Override
    public void warn(String msg) {
        root.warn(formatMessage(getMessage(msg)), msg);
    }

    @Override
    public void warn(String format, Object arg) {
        root.warn(formatMessage(getMessage(format)), format, arg);
    }

    @Override
    public void warn(String format, Object... arguments) {
        root.warn(formatMessage(getMessage(format)), format, arguments);
    }

    @Override
    public void warn(String format, Object arg1, Object arg2) {
        root.warn(formatMessage(getMessage(format)), format, arg1, arg2);
    }

    @Override
    public void warn(String msg, Throwable t) {
        root.warn(formatMessage(getMessage(msg)), msg, t);
    }

    @Override
    public void warn(Marker marker, String msg) {
        marker.add(formatMessage(getMessage(msg)));
        root.warn(marker, msg);
    }

    @Override
    public void warn(Marker marker, String format, Object arg) {
        marker.add(formatMessage(getMessage(format)));
        root.warn(marker, format, arg);
    }

    @Override
    public void warn(Marker marker, String format, Object arg1, Object arg2) {
        marker.add(formatMessage(getMessage(format)));
        root.warn(marker, format, arg1, arg2);
    }

    @Override
    public void warn(Marker marker, String format, Object... arguments) {
        marker.add(formatMessage(getMessage(format)));
        root.warn(marker, format, arguments);
    }

    @Override
    public void warn(Marker marker, String msg, Throwable t) {
        marker.add(formatMessage(getMessage(msg)));
        root.warn(marker, msg, t);
    }

    @Override
    public void error(String msg) {
        root.error(formatMessage(getMessage(msg)), msg);
    }

    @Override
    public void error(String format, Object arg) {
        root.error(formatMessage(getMessage(format)), format, arg);
    }

    @Override
    public void error(String format, Object arg1, Object arg2) {
        root.error(formatMessage(getMessage(format)), format, arg1, arg2);
    }

    @Override
    public void error(String format, Object... arguments) {
        root.error(formatMessage(getMessage(format)), format, arguments);
    }

    @Override
    public void error(String msg, Throwable t) {
        root.error(formatMessage(getMessage(msg)), msg, t);
    }

    @Override
    public void error(Marker marker, String msg) {
        marker.add(formatMessage(getMessage(msg)));
        root.error(marker, msg);
    }

    @Override
    public void error(Marker marker, String format, Object arg) {
        marker.add(formatMessage(getMessage(format)));
        root.error(marker, format, arg);
    }

    @Override
    public void error(Marker marker, String format, Object arg1, Object arg2) {
        marker.add(formatMessage(getMessage(format)));
        root.error(marker, format, arg1, arg2);
    }

    @Override
    public void error(Marker marker, String format, Object... arguments) {
        marker.add(formatMessage(getMessage(format)));
        root.error(marker, format, arguments);
    }

    @Override
    public void error(Marker marker, String msg, Throwable t) {
        marker.add(formatMessage(getMessage(msg)));
        root.error(marker, msg, t);
    }

    @Override
    public void error(Throwable t, String format, Object... args) {
        FormattingTuple ft = MessageFormatter.arrayFormat(format, args);
        root.error(formatMessage(getMessage(ft.getMessage())), ft.getMessage(), t);
    }

    @Override
    public void warn(final Throwable t, final String format, final Object... args) {
        FormattingTuple ft = MessageFormatter.arrayFormat(format, args);
        root.warn(formatMessage(getMessage(ft.getMessage())), ft.getMessage(), t);
    }

    @Override public void success(final String format, final Object... args) {
        successMarker.add(formatMessage(getMessage(format)));
        info(successMarker, format, args);
    }

    @Override public void dashboard(final String format, final Object... args) {
        successMarker.add(formatMessage(getMessage(format)));
        info(dashboardMarker, format, args);
    }
} public class LogstashMessageBuilderProvider extends JsonMessageBuilderProvider {

    @Override public Marker formatPayload(final LogContext<List<Map<String, Object>>> context) {
        final Map<String, Object> contextMap = getContextMap(context);
        // Removing the _message key so that the log message does not appear twice.
        contextMap.remove(CommonKeys.LOG_MESSAGE_KEY);

        return appendEntries(contextMap);
    }

} public class Car {
    @Setter
    @Getter
    private String model;
    @Getter
    @Setter
    private Integer year;
    @Getter
    @Setter
    private String make;
    @Getter
    @Setter
    private double cost;
    @Getter
    @Setter
    private Country country;
    @Getter
    @Setter
    private Engine engine;

    public String test = "HI";

    public Car(String model, Integer year, String make, double cost, Country country, Engine engine) {
        this.model = model;
        this.year = year;
        this.make = make;
        this.cost = cost;
        this.country = country;
        this.engine = engine;
    }

    public String toString() {
        return "My car is a " + year + " " + make + " " + model + ". It cost me $" + cost + ". I bought it in " + country +
               ". It has " + (engine != null ? "a " + engine.getName() : "no") + " Engine.";
    }

} public enum Country {
    USA, CANADA, JAPAN, GERMANY;
} public class CycleObject {
    public String text;

    public CycleObject cycle;
} public class EmptyObject {
} @Data
@AllArgsConstructor
public class Engine {
    private String name;
} public class GetterThrowsError {
    public String getText(){
        throw new RuntimeException("getter threw runtime error");
    }
} @Data
@AllArgsConstructor
public class Person {
    private String name;

    private Integer age;

    private boolean retired;

    private List<Car> cars;

    private Car myCar;
}
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.OutputStreamAppender;
import ch.qos.logback.core.read.ListAppender;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.godaddy.logging.CommonKeys;
import com.godaddy.logging.LogMessage;
import com.godaddy.logging.Logger;
import com.godaddy.logging.LoggerFactory;
import com.godaddy.logging.LoggingConfigs;
import com.godaddy.logging.Slf4WrapperLogger;
import com.godaddy.logging.logger.MarkerAppendingLogger;
import com.godaddy.logging.messagebuilders.providers.LogstashMessageBuilderProvider;
import com.godaddy.logging.models.Car;
import com.godaddy.logging.models.Country;
import com.godaddy.logging.models.CycleObject;
import com.godaddy.logging.models.EmptyObject;
import com.godaddy.logging.models.Engine;
import com.godaddy.logging.models.GetterThrowsError;
import com.godaddy.logging.models.Person;
import com.google.common.collect.Lists;
import net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder;
import net.logstash.logback.marker.MapEntriesAppendingMarker;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
/**
 * Copyright (c) 2015 GoDaddy
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.godaddy.logging.logstash;




public class LogstashTests {

    private ch.qos.logback.classic.Logger testLogger =
            (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger(LogstashTests.class);

    private final ListAppender<ILoggingEvent> listAppender = (ListAppender<ILoggingEvent>) testLogger.getAppender("listAppender");

    private final JsonFactory jsonFactory = new MappingJsonFactory().enable(JsonGenerator.Feature.ESCAPE_NON_ASCII);

    private Logger logger;

    private StringWriter writer;

    private JsonGenerator generator;

    @Before
    public void setup() throws IOException {
        listAppender.list.clear();

        LoggingConfigs configs = LoggingConfigs.getCurrent().useJson();

        logger = LoggerFactory.getLogger(LogstashTests.class, configs);

        writer = new StringWriter();
        generator = jsonFactory.createGenerator(writer);
        generator.useDefaultPrettyPrinter();
        generator.writeStartObject();
    }

    @Test
    public void test_logback_json_log() throws IOException {
        Object toLog = new Object() {
            String horse = "NEIGH";
            Object inner = new Object() {
                String car = "VROOM";
            };
        };

        logger.with(toLog).info("YO");

        Map<String, Object> output = getJson();

        assertEquals(output.get("horse"), "NEIGH");
        assertEquals(((LinkedHashMap) output.get("inner")).get("car"), "VROOM");
        assertEquals(output.get("level"), "INFO");
        assertEquals(output.get("customMessage"), "YO");
    }

    @Test
    public void testUUID() throws IOException {
        UUID uuid = UUID.randomUUID();

        logger.with(uuid).info("test");

        Map<String, Object> json = getJson();

        assertEquals(json.get(CommonKeys.UNNAMED_VALUES_KEY), Lists.newArrayList(uuid.toString()));
        assertEquals(json.get("customMessage"), "test");
    }

    @Test
    public void test_cycles() throws IOException {
        CycleObject cycleObject = new CycleObject();

        cycleObject.text = "test_cycles";

        cycleObject.cycle = cycleObject;

        logger.with(cycleObject).info("Cycles");

        assertEquals(getMarkers(), "{\n" +
                                   "  \"CycleObject\" : {\n" +
                                   "    \"text\" : \"test_cycles\"\n" +
                                   "  }\n" +
                                   "}");

        Map<String, Object> json = getJson();
        assertEquals(json.get("customMessage"), "Cycles");
    }

    @Test
    public void test_key_value() throws IOException {
        UUID uuid = UUID.randomUUID();

        Logger with = logger.with("uuid", uuid)
                            .with("foo", "bar");

        with.info("test");

        assertEquals(getMarkers(), "{\n" +
                                   "  \"uuid\" : \"" + uuid + "\",\n" +
                                   "  \"foo\" : \"bar\"\n" +
                                   "}");

        Map<String, Object> json = getJson();
        assertEquals(json.get("customMessage"), "test");
    }

    @Test
    public void test_custom_mapper() throws IOException {
        Logger customLogger = logger = LoggerFactory.getLogger(LogstashTests.class,
                                                               LoggingConfigs.builder()
                                                                             .customMapper(new HashMap<Class<?>, Function<Object, String>>() {{
                                                                                 put(Car.class, Object::toString);
                                                                             }})
                                                                             .logger((clazz, configs) -> new MarkerAppendingLogger(new Slf4WrapperLogger(org.slf4j.LoggerFactory.getLogger(clazz)), configs))
                                                                             .messageBuilderFunction(new LogstashMessageBuilderProvider()).build());

        Car car = new Car("911", 2015, "Porsche", 70000.00, Country.GERMANY, new Engine("V12"));

        customLogger.with(car).info("TEST");

        assertEquals(getMarkers(), "{\n" +
                                   "  \"_unnamed_values\" : [ \"My car is a 2015 Porsche 911. It cost me $70000.0. I bought it in GERMANY. It has a V12 Engine.\" ]\n" +
                                   "}");

        Map<String, Object> json = getJson();
        assertEquals(json.get("customMessage"), "TEST");
    }

    @Test
    public void test_double_brace() throws IOException {
        logger.with(new LogMessage() {