package tc.oc.commons.core.logging;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.sk89q.minecraft.util.commands.CommandException;
import net.md_5.bungee.api.ChatColor;
import org.apache.logging.log4j.core.impl.Log4jContextFactory;
import org.apache.logging.log4j.simple.SimpleLogger;
import org.apache.logging.log4j.simple.SimpleLoggerContextFactory;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.spi.LoggerContextFactory;
import tc.oc.commons.core.reflect.Fields;
import static tc.oc.commons.core.util.Nullables.castOrNull;
public class Logging {
private static final Map<Level, String> JUL_ABBREVS = ImmutableMap.<Level, String>builder()
.put(Level.ALL, "A")
.put(Level.FINEST, "F")
.put(Level.FINER, "F")
.put(Level.FINE, "F")
.put(Level.CONFIG, "C")
.put(Level.INFO, "I")
.put(Level.WARNING, "W")
.put(Level.SEVERE, "E")
.put(Level.OFF, "O")
.build();
private static final Map<org.apache.logging.log4j.Level, String> L4J_ABBREVS = ImmutableMap.<org.apache.logging.log4j.Level, String>builder()
.put(org.apache.logging.log4j.Level.ALL, "A")
.put(org.apache.logging.log4j.Level.TRACE, "T")
.put(org.apache.logging.log4j.Level.DEBUG, "D")
.put(org.apache.logging.log4j.Level.INFO, "I")
.put(org.apache.logging.log4j.Level.WARN, "W")
.put(org.apache.logging.log4j.Level.FATAL, "E")
.put(org.apache.logging.log4j.Level.OFF, "O")
.build();
private static final Map<Level, ChatColor> JUL_COLORS = ImmutableMap.<Level, ChatColor>builder()
.put(Level.ALL, ChatColor.GREEN)
.put(Level.FINEST, ChatColor.AQUA)
.put(Level.FINER, ChatColor.AQUA)
.put(Level.FINE, ChatColor.AQUA)
.put(Level.CONFIG, ChatColor.AQUA)
.put(Level.INFO, ChatColor.BLUE)
.put(Level.WARNING, ChatColor.YELLOW)
.put(Level.SEVERE, ChatColor.RED)
.build();
private static final Map<org.apache.logging.log4j.Level, ChatColor> L4J_COLORS = ImmutableMap.<org.apache.logging.log4j.Level, ChatColor>builder()
.put(org.apache.logging.log4j.Level.ALL, ChatColor.GREEN)
.put(org.apache.logging.log4j.Level.TRACE, ChatColor.AQUA)
.put(org.apache.logging.log4j.Level.DEBUG, ChatColor.AQUA)
.put(org.apache.logging.log4j.Level.INFO, ChatColor.BLUE)
.put(org.apache.logging.log4j.Level.WARN, ChatColor.YELLOW)
.put(org.apache.logging.log4j.Level.FATAL, ChatColor.RED)
.build();
private Logging() {}
public static <T> T getParam(LogRecord record, Class<T> type) {
if(record.getParameters() != null) {
for(Object param : record.getParameters()) {
if(type.isInstance(param)) return type.cast(param);
}
}
return null;
}
public static void addParam(LogRecord record, Object param) {
Object[] params = record.getParameters();
if(params == null) {
params = new Object[]{ param };
} else {
Object[] oldParams = params;
params = new Object[oldParams.length + 1];
System.arraycopy(oldParams, 0, params, 0, oldParams.length);
params[oldParams.length] = param;
}
record.setParameters(params);
}
public static boolean isError(LogRecord record) {
return record.getLevel().intValue() >= Level.WARNING.intValue();
}
public static Level getEffectiveLevel(Logger logger) {
if(logger.getLevel() != null) {
return logger.getLevel();
}
if(logger.getParent() != null) {
return getEffectiveLevel(logger.getParent());
}
return null;
}
/**
* Get the JUL logging properties using dark magic. Throws if this fails,
* possibly due to incorrect assumptions about the private implementation of
* {@link LogManager}.
*/
public static Properties getLoggingProperties() throws IllegalAccessException, NoSuchFieldException {
Field field = LogManager.class.getDeclaredField("props");
field.setAccessible(true);
return castOrNull(field.get(LogManager.getLogManager()), Properties.class);
}
public static void updateFromLoggingProperties() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method method = LogManager.class.getDeclaredMethod("setLevelsOnExistingLoggers");
method.setAccessible(true);
method.invoke(LogManager.getLogManager());
}
/**
* Set the default level for the given logger name. This will affect any current
* or future logger with that name. This is equivalent to setting the level through
* the logging properties file.
*/
public static void setDefaultLevel(String loggerName, Level level) throws NoSuchFieldException, IllegalAccessException {
getLoggingProperties().setProperty(loggerName + ".level", level.getName());
}
public static Stream<String> loggerNames() {
return Collections.list(LogManager.getLogManager().getLoggerNames()).stream();
}
public static @Nullable Logger findLogger(String search) throws CommandException {
LogManager lm = LogManager.getLogManager();
if("".equals(search)) return lm.getLogger("");
String name = findLogger(search, Collections.list(lm.getLoggerNames()));
return name == null ? null : lm.getLogger(name);
}
public static @Nullable String findLogger(String search, Collection<String> names) {
String searchLower = search.toLowerCase();
String bestName = null;
int bestScore = 0;
for(String name : names) {
String nameLower = name.toLowerCase();
int pos = nameLower.indexOf(searchLower);
if(pos == -1) continue;
int score = 0;
if(pos == 0) score += 2; // match at start
if(pos + searchLower.length() == nameLower.length()) score += 1; // match at end
if(bestName != null) {
if(score < bestScore) continue;
if(score == bestScore && name.length() >= bestName.length()) continue;
}
bestName = name;
bestScore = score;
}
return bestName;
}
public static ChatColor levelColor(Level level) {
if(level == null) {
return ChatColor.DARK_GRAY;
} else if(JUL_COLORS.containsKey(level)) {
return JUL_COLORS.get(level);
} else {
return ChatColor.LIGHT_PURPLE;
}
}
public static ChatColor levelColor(org.apache.logging.log4j.Level level) {
if(level == null) {
return ChatColor.DARK_GRAY;
} else if(L4J_COLORS.containsKey(level)) {
return L4J_COLORS.get(level);
} else {
return ChatColor.LIGHT_PURPLE;
}
}
public static String levelAbbrev(Level level) {
if(level != null && JUL_ABBREVS.containsKey(level)) {
return JUL_ABBREVS.get(level);
} else {
return "?";
}
}
public static String levelAbbrev(org.apache.logging.log4j.Level level) {
if(level != null && L4J_ABBREVS.containsKey(level)) {
return L4J_ABBREVS.get(level);
} else {
return "?";
}
}
public static String debugSimpleClassName(Class<?> cls) {
if(!cls.isAnonymousClass()) {
return cls.getSimpleName();
}
while(cls != null && cls.isAnonymousClass()) {
cls = cls.getSuperclass();
}
if(cls == null) cls = Object.class;
return "(anonymous " + cls.getSimpleName() + ")";
}
/**
* Fuck L4J in its tight asshole for leaving every useful method out of
* its public API, and forcing me to write all this garbage.
*/
public static class L4J {
public static Stream<? extends LoggerContext> contexts() {
return getContexts().stream();
}
public static List<? extends LoggerContext> getContexts() {
LoggerContextFactory factory = org.apache.logging.log4j.LogManager.getFactory();
if(factory instanceof SimpleLoggerContextFactory) {
return Collections.singletonList(factory.getContext(null, null, null, true));
}
return ((Log4jContextFactory) org.apache.logging.log4j.LogManager.getFactory()).getSelector().getLoggerContexts();
}
public static String getContextName(LoggerContext context) {
if(context instanceof org.apache.logging.log4j.core.LoggerContext) {
return ((org.apache.logging.log4j.core.LoggerContext) context).getName();
} else {
return context.getClass().getSimpleName();
}
}
public static Stream<org.apache.logging.log4j.Logger> loggers() {
return contexts().flatMap(context -> getLoggers(context).values().stream());
}
public static Stream<String> loggerNames() {
return contexts().flatMap(context -> getLoggers(context).keySet().stream());
}
public static Iterable<org.apache.logging.log4j.Logger> getLoggers() {
return Iterables.concat(Iterables.transform(getContexts(), context -> getLoggers(context).values()));
}
public static Map<String, org.apache.logging.log4j.Logger> getLoggers(LoggerContext context) {
return Fields.read(context.getClass(), Map.class, "loggers", context);
}
public static @Nullable org.apache.logging.log4j.Logger findLogger(String search) {
for(LoggerContext context : getContexts()) {
String name = Logging.findLogger(search, getLoggers(context).keySet());
if(name != null) return (org.apache.logging.log4j.core.Logger) context.getLogger(name);
}
return null;
}
public static org.apache.logging.log4j.Level getLevel(org.apache.logging.log4j.Logger logger) {
if(logger instanceof org.apache.logging.log4j.core.Logger) {
return ((org.apache.logging.log4j.core.Logger) logger).getLevel();
} else if(logger instanceof SimpleLogger) {
return ((SimpleLogger) logger).getLevel();
} else {
return null;
}
}
public static void setLevel(org.apache.logging.log4j.Logger logger, org.apache.logging.log4j.Level level) {
if(logger instanceof org.apache.logging.log4j.core.Logger) {
((org.apache.logging.log4j.core.Logger) logger).setLevel(level);
} else if(logger instanceof SimpleLogger) {
((SimpleLogger) logger).setLevel(level);
}
}
public static org.apache.logging.log4j.Logger getParent(org.apache.logging.log4j.Logger logger) {
if(logger instanceof org.apache.logging.log4j.core.Logger) {
return ((org.apache.logging.log4j.core.Logger) logger).getParent();
} else {
return null;
}
}
public static org.apache.logging.log4j.Level getEffectiveLevel(org.apache.logging.log4j.Logger logger) {
org.apache.logging.log4j.Level level = getLevel(logger);
if(level != null) return level;
org.apache.logging.log4j.Logger parent = getParent(logger);
if(parent != null) return getEffectiveLevel(parent);
return null;
}
}
}