diff --git a/build.gradle b/build.gradle index a4b64a7f..0779d35b 100644 --- a/build.gradle +++ b/build.gradle @@ -243,6 +243,11 @@ project(":core") { compile group: 'io.netty', name: 'netty-all', version: nettyVersion } + dependencies { + compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3' + compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3' + } + dependencies { testCompile 'junit:junit:4.12' testCompile "com.badlogicgames.gdx:gdx-backend-headless:$gdxVersion" diff --git a/core/src/com/riiablo/log/CTX.java b/core/src/com/riiablo/log/CTX.java new file mode 100644 index 00000000..d15c139c --- /dev/null +++ b/core/src/com/riiablo/log/CTX.java @@ -0,0 +1,31 @@ +package com.riiablo.log; + +import org.apache.logging.log4j.ThreadContext; + +import com.badlogic.gdx.utils.OrderedMap; + +// TODO: support ThreadLocal or convert into ThreadContextMap impl +public enum CTX { + INSTANCE; + + public static String put(String key, String value) { + ThreadContext.put(key, value); + return INSTANCE.map.put(key, value); + } + + public static String remove(String key) { + ThreadContext.remove(key); + return INSTANCE.map.remove(key); + } + + public static void clear() { + ThreadContext.clearMap(); + INSTANCE.map.clear(); + } + + public static OrderedMap map() { + return INSTANCE.map; + } + + final OrderedMap map = new OrderedMap<>(); +} diff --git a/core/src/com/riiablo/log/Log.java b/core/src/com/riiablo/log/Log.java new file mode 100644 index 00000000..3a77109c --- /dev/null +++ b/core/src/com/riiablo/log/Log.java @@ -0,0 +1,34 @@ +package com.riiablo.log; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.Message; +import org.apache.logging.log4j.message.MessageFactory; +import org.apache.logging.log4j.message.StringFormatterMessageFactory; + +public class Log { + private static final MessageFactory FORMAT_MESSAGE_FACTORY = StringFormatterMessageFactory.INSTANCE; + + private static Message createMessage(String format, Object... args) { + return FORMAT_MESSAGE_FACTORY.newMessage(format, args); + } + + public static void tracef(Logger logger, String format, Object... args) { + logger.trace(createMessage(format, args)); + } + + public static void debugf(Logger logger, String format, Object... args) { + logger.debug(createMessage(format, args)); + } + + public static void infof(Logger logger, String format, Object... args) { + logger.info(createMessage(format, args)); + } + + public static void warnf(Logger logger, Throwable t, String format, Object... args) { + logger.warn(createMessage(format, args), t); + } + + public static void errorf(Logger logger, Throwable t, String format, Object... args) { + logger.error(createMessage(format, args), t); + } +} diff --git a/core/src/com/riiablo/log/LogManager.java b/core/src/com/riiablo/log/LogManager.java new file mode 100644 index 00000000..d386881b --- /dev/null +++ b/core/src/com/riiablo/log/LogManager.java @@ -0,0 +1,36 @@ +package com.riiablo.log; + +import java.util.SortedMap; +import org.apache.commons.collections4.Trie; +import org.apache.commons.collections4.trie.PatriciaTrie; +import org.apache.logging.log4j.Logger; + +public enum LogManager { + INSTANCE; + + public static Logger getLogger(Class clazz) { + return INSTANCE.get(clazz); + } + + public static Logger getLogger(String name) { + return INSTANCE.get(name); + } + + private final Trie loggers = new PatriciaTrie<>(); + + public Logger get(Class clazz) { + Logger logger = org.apache.logging.log4j.LogManager.getLogger(clazz); + loggers.put(logger.getName().toLowerCase(), logger); + return logger; + } + + public Logger get(String name) { + Logger logger = org.apache.logging.log4j.LogManager.getLogger(name); + loggers.put(logger.getName().toLowerCase(), logger); + return logger; + } + + public SortedMap prefixMap(String key) { + return loggers.prefixMap(key); + } +} diff --git a/core/src/com/riiablo/log/MDCLayout.java b/core/src/com/riiablo/log/MDCLayout.java new file mode 100644 index 00000000..f4419680 --- /dev/null +++ b/core/src/com/riiablo/log/MDCLayout.java @@ -0,0 +1,153 @@ +package com.riiablo.log; + +import java.nio.charset.Charset; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute; +import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginElement; +import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required; +import org.apache.logging.log4j.core.layout.AbstractStringLayout; +import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.PatternLayout; + +import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.ObjectMap; +import com.badlogic.gdx.utils.OrderedMap; + +@Plugin( + name = "MDCLayout", + category = Node.CATEGORY, + elementType = Layout.ELEMENT_TYPE, + printObject = true +) +public class MDCLayout extends AbstractStringLayout { + private final PatternLayout parent; + + private static final int MAX_DEPTH = 256; + private static final int DEPTH_STEP = 2; + + private int depth = 0; + private final OrderedMap.Entry tail = new ObjectMap.Entry<>(); + private final byte[] spaces = StringUtils.repeat(' ', MAX_DEPTH * DEPTH_STEP).getBytes(super.getCharset()); + private final byte[] endl = System.getProperty("line.separator").getBytes(super.getCharset()); + + public MDCLayout(Configuration config, Charset charset, PatternLayout parent) { + super(config, charset, null, null); + this.parent = parent; + } + + @Override + public String toSerializable(LogEvent event) { + return parent.toSerializable(event); + } + + @Override + public boolean requiresLocation() { + return parent.requiresLocation(); + } + + @Override + public Map getContentFormat() { + return parent.getContentFormat(); + } + + private void writeEntry( + ByteBufferDestination destination, + int depth, + OrderedMap.Entry entry + ) { + byte[] b = entry.toString().getBytes(getCharset()); + destination.writeBytes(spaces, 0, (depth - 1) * DEPTH_STEP + 1); + destination.writeBytes(b, 0, b.length); + destination.writeBytes(endl, 0, endl.length); + } + + @Override + public void encode(LogEvent event, ByteBufferDestination destination) { + OrderedMap ctx = CTX.map(); + int depth = ctx.size; + if (this.depth != depth) { + this.depth = depth; + } + + if (depth > 0) { + Array ordered = ctx.orderedKeys(); + String tailKey = ordered.peek(); + String tailValue = ctx.get(tailKey); + if (tailKey.equals(tail.key)) { + if (!tailValue.equals(tail.value)) { + tail.value = tailValue; + writeEntry(destination, depth, tail); + } + } else { + tail.key = tailKey; + tail.value = tailValue; + writeEntry(destination, depth, tail); + } + + destination.writeBytes(spaces, 0, depth * DEPTH_STEP); + } + + parent.encode(event, destination); + } + + @Override + public String toString() { + return parent.toString(); + } + + @PluginBuilderFactory + public static Builder newBuilder() { + return new Builder(); + } + + public static class Builder implements org.apache.logging.log4j.core.util.Builder { + @PluginElement("PatternLayout") + @Required + private PatternLayout patternLayout; + + // LOG4J2-783 use platform default by default + @PluginBuilderAttribute + private Charset charset = Charset.defaultCharset(); + + @PluginConfiguration + private Configuration configuration; + + private Builder() {} + + public Builder withPatternLayout(final PatternLayout patternLayout) { + this.patternLayout = patternLayout; + return this; + } + + public Builder withConfiguration(final Configuration configuration) { + this.configuration = configuration; + return this; + } + + public Builder withCharset(final Charset charset) { + // LOG4J2-783 if null, use platform default by default + if (charset != null) { + this.charset = charset; + } + return this; + } + + @Override + public MDCLayout build() { + // fall back to DefaultConfiguration + if (configuration == null) { + configuration = new DefaultConfiguration(); + } + return new MDCLayout(configuration, charset, patternLayout); + } + } +}