package tc.oc.commons.core.logging; import java.util.Objects; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; import javax.annotation.Nullable; import tc.oc.commons.core.util.ArrayUtils; /** * Logger that is created by giving it a class and an explicit parent logger. * The name of the logger is generated by appending the simple name of the class * to the name of the parent logger. An arbitrary string can also be given to * append to the name. A prefix is automatically inserted before log messages. * * It also does some horrible things to bypass the logging mess that Bukkit creates. * * Instances of this class should ONLY be acquired by calling the static {@link #get} * methods. These will deduplicate loggers by name, and register them with the * java.util.logging subsystem. * * TODO: Make the constructor private. */ public class ClassLogger extends Logger { private final String prefix; private static Class<?> getNamedAncestor(Class<?> klass) { if(klass == null) { return Object.class; } else if(!klass.isAnonymousClass()) { return klass; } else if(klass.getSuperclass() == Object.class && klass.getInterfaces().length > 0) { return klass.getInterfaces()[0]; } else { return klass.getSuperclass(); } } private static String getName(Class<?> klass, @Nullable String instanceKey) { final Class<?> namedAncestor = getNamedAncestor(klass); String name = namedAncestor.getCanonicalName(); if(klass.isAnonymousClass()) { name += "." + ArrayUtils.fromEnd(klass.getName().split("\\."), 0); } if(instanceKey != null) { name += "." + instanceKey; } return name; } public static ClassLogger get(Class<?> klass) { return get(null, klass, null); } public static ClassLogger get(@Nullable Logger parent, Class<?> klass) { return get(parent, klass, null); } public static ClassLogger get(@Nullable Logger parent, Class<?> klass, @Nullable String instanceKey) { if(parent == null) { parent = Logger.getLogger(""); } ClassLogger logger = find(parent, klass, instanceKey); if(logger == null) { logger = new ClassLogger(parent, klass, instanceKey); // TODO: call addLogger from the constructuor, when it's no longer public final LogManager logManager = LogManager.getLogManager(); if(logManager.addLogger(logger)) { // addLogger will set the parent, so we have to set it back again logger.setParent(parent); } else { // Logger was created by another thread logger = find(parent, klass, instanceKey); if(logger == null) { throw new IllegalStateException("Failed to register logger " + getName(klass, instanceKey)); } } } return logger; } public static @Nullable ClassLogger find(@Nullable Logger parent, Class<?> klass, @Nullable String instanceKey) { if(parent == null) { parent = Logger.getLogger(""); } String name = getName(klass, instanceKey); if(parent instanceof ClassLogger && Objects.equals(parent.getName(), name)) { // If the given parent logger looks exactly like the logger // we are supposed to return, just use it. This makes it easy // to replace a parent logger with a child once only e.g. // // logger = ClassLogger.get(logger, getClass(), "myInstance") return (ClassLogger) parent; } LogManager lm = LogManager.getLogManager(); Logger logger = lm.getLogger(name); if(logger instanceof ClassLogger) { if(parent != logger.getParent()) { throw new IllegalStateException("Already registred logger " + name + " has a different parent than the one requested:\n old = " + logger.getParent() + "\n new = " + parent); } return (ClassLogger) logger; } else { return null; } } // TODO: make this ctors private and fix everything that calls it to use the static getter instead @Deprecated public ClassLogger(@Nullable Logger parent, Class<?> klass) { this(parent, klass, null); } private ClassLogger(@Nullable Logger parent, Class<?> klass, @Nullable String instanceKey) { super(getName(klass, instanceKey), null); this.prefix = "[" + klass.getSimpleName() + (instanceKey == null ? "" : ":" + instanceKey) + "] "; if(parent != null) this.setParent(parent); this.setLevel(null); } public Level getEffectiveLevel() { return Logging.getEffectiveLevel(this); } @Override public void log(LogRecord record) { record.setMessage(this.prefix + record.getMessage()); // Don't trust loggers to show anything below INFO. // Check the level ourselves and then promote the record // to make sure it gets through. if(record.getLevel().intValue() < Level.INFO.intValue() && record.getLevel().intValue() >= getEffectiveLevel().intValue()) { record.setLevel(Level.INFO); } super.log(record); } }