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);
}
}