A malicious app could do for (int i=0; i < 100000; ++i) { new Level(“Level” + i, 1000 + i){}; }
Sure idiots can do lots of bad things but I don’t think Levels should be quite that flexible. Ralph On Jan 25, 2014, at 9:39 PM, Remko Popma <remko.po...@gmail.com> wrote: > I don't think client code can do new Level(){} as the constructor requires > String and int arguments. > > By the way, I am unclear on what went wrong with the enum approach you > originally took. > You said: > StdLevel isn’t a Level because it can’t extend it if it is an enum, so I > can’t initialize the levels using that. > > I don't understand. StdLevel implements the Level interface, right? So what > do you mean by "it can't extend it"? > > The reason I ask is that for the code generation, "real" java enums may be > easier to deal with than the extensible enum approach. > > > On Sun, Jan 26, 2014 at 2:30 PM, Ralph Goers <ralph.go...@dslextreme.com> > wrote: > Out of curiosity, what exactly is the benefit of declaring the class abstract > when it has a protected constructor? It seems like all you are accomplishing > is making the instantiation syntax uglier. It also bothers me that open code > can just do a new Level(){} - which will do nothing but cause problems. I’m > beginning to think that we should require an annotation on the Level > declaration of the extension class to avoid that. > > Ralph > > On Jan 25, 2014, at 9:13 PM, Remko Popma <remko.po...@gmail.com> wrote: > >> Ralph, >> I copied Nick's code _as is_ and had no compile errors. >> The class is abstract, but instances are defined in the static block as: >> OFF = new Level("OFF", 0) {}; // note the {}: this creates an anonymous >> concrete subclass >> >> I agree that read access needs to be synchronized as well, not just write >> access (the constructor). >> I experimented with several options: >> * synchronizing on plain Object in both constructor and when accessing the >> static Map(s) >> * a ReentrantReadWriteLock >> * a lock-free implementation >> >> I decided against ReentrantReadWriteLock as it has more overhead than plain >> synchronized access and the write access (in the constructors) is going to >> be extremely rare: not worth paying the overhead in the more common reads. >> It is also cumbersome to code. >> >> The lock-free implementation uses an AtomicInteger for the ordinals, and an >> AtomicReference for the Map<String, Level>. >> In the constructor, create a new Map<String, Level> instance based on the >> old copy, add the new instance, and try to call compareAndSet to replace the >> old instance with the new instance. Retry on failure. >> >> Finally, simply synchronizing on the constructorLock object in the >> Level.toLevel() and Level.values() methods may be simplest. >> >> Which of the last two is best depends on how often the toLevel() and >> values() levels are called. >> It turns out they are only called during reconfiguration, so no real need to >> optimize these methods. >> I would argue that simple synchronization may be best in this case. >> >> Remko >> >> >> >> On Sun, Jan 26, 2014 at 1:49 PM, Ralph Goers <ralph.go...@dslextreme.com> >> wrote: >> As I am working on this I just want to point out a number of issues with the >> code below: >> >> 1. The class is abstract. The static block is doing a bunch of new Level() >> invocations which obviously generate compile errors on an abstract class. I >> had to make it be a non-abstract class. >> 2. As I pointed out before there is no way to access the “standard” levels >> as an enum. I have addressed that. >> 3. Although the constructor is synchronized access to the Map is not. Trying >> to get from the map while a Level is being added will result in a >> ConcurrentModificationException. I am using a ConcurrentMap instead. >> 3. The constructor requires synchronization because it is modifying both the >> map and the ordinal. However, since this isn’t an enum the ordinal value is >> of dubious value. Removing that would allow the removal of the >> synchronization in the constructor. I am considering that but I haven’t done >> it yet. >> 4. Your example of creating the extension shows doing a new Level(). This >> doesn’t work because a) the class is abstract and b) the constructor is >> protected. I am leaving the constructor protected so extension will require >> doing new ExtendedLevel(name, value) and creating a constructor. Not >> requiring that means applications can do a new Level() anywhere and I am >> opposed to allowing that. >> >> Ralph >> >> On Jan 23, 2014, at 12:42 AM, Nick Williams <nicho...@nicholaswilliams.net> >> wrote: >> >> > Okay, I finally got a minute to read all of these emails, and... >> > >> > EVERYBODY FREEZE! >> > >> > What if I could get you an extensible enum that required no interface >> > changes and no binary-incompatible changes at all? Sound too good to be >> > true? I proposed this months ago (LOG4J2-41) and it got shot down multiple >> > times, but as of now I've heard THREE people say "extensible enum" in this >> > thread, so here it is, an extensible enum: >> > >> > public abstract class Level implements Comparable<Level>, Serializable { >> > public static final Level OFF; >> > public static final Level FATAL; >> > public static final Level ERROR; >> > public static final Level WARN; >> > public static final Level INFO; >> > public static final Level DEBUG; >> > public static final Level TRACE; >> > public static final Level ALL; >> > >> > >> > private static final long serialVersionUID = 0L; >> > private static final Hashtable<String, Level> map; >> > private static final TreeMap<Integer, Level> values; >> > private static final Object constructorLock; >> > >> > >> > static { >> > // static variables must be constructed in certain order >> > constructorLock = new Object(); >> > map = new Hashtable<String, Level>(); >> > values = new TreeMap<Integer, Level>(); >> > OFF = new Level("OFF", 0) {}; >> > FATAL = new Level("FATAL", 100) {}; >> > ERROR = new Level("ERROR", 200) {}; >> > WARN = new Level("WARN", 300) {}; >> > INFO = new Level("INFO", 400) {}; >> > DEBUG = new Level("DEBUG", 500) {}; >> > TRACE = new Level("TRACE", 600) {}; >> > ALL = new Level("ALL", Integer.MAX_VALUE) {}; >> > } >> > >> > >> > private static int ordinals; >> > >> > >> > private final String name; >> > private final int intLevel; >> > private final int ordinal; >> > >> > >> > protected Level(String name, int intLevel) { >> > if(name == null || name.length() == 0) >> > throw new IllegalArgumentException("Illegal null Level >> > constant"); >> > if(intLevel < 0) >> > throw new IllegalArgumentException("Illegal Level int less than >> > zero."); >> > synchronized (Level.constructorLock) { >> > if(Level.map.containsKey(name.toUpperCase())) >> > throw new IllegalArgumentException("Duplicate Level >> > constant [" + name + "]."); >> > if(Level.values.containsKey(intLevel)) >> > throw new IllegalArgumentException("Duplicate Level int [" >> > + intLevel + "]."); >> > this.name = name; >> > this.intLevel = intLevel; >> > this.ordinal = Level.ordinals++; >> > Level.map.put(name.toUpperCase(), this); >> > Level.values.put(intLevel, this); >> > } >> > } >> > >> > >> > public int intLevel() { >> > return this.intLevel; >> > } >> > >> > >> > public boolean isAtLeastAsSpecificAs(final Level level) { >> > return this.intLevel <= level.intLevel; >> > } >> > >> > >> > public boolean isAtLeastAsSpecificAs(final int level) { >> > return this.intLevel <= level; >> > } >> > >> > >> > public boolean lessOrEqual(final Level level) { >> > return this.intLevel <= level.intLevel; >> > } >> > >> > >> > public boolean lessOrEqual(final int level) { >> > return this.intLevel <= level; >> > } >> > >> > >> > @Override >> > @SuppressWarnings("CloneDoesntCallSuperClone") >> > public Level clone() throws CloneNotSupportedException { >> > throw new CloneNotSupportedException(); >> > } >> > >> > >> > @Override >> > public int compareTo(Level other) { >> > return intLevel < other.intLevel ? -1 : (intLevel > other.intLevel >> > ? 1 : 0); >> > } >> > >> > >> > @Override >> > public boolean equals(Object other) { >> > return other instanceof Level && other == this; >> > } >> > >> > >> > public Class<Level> getDeclaringClass() { >> > return Level.class; >> > } >> > >> > >> > @Override >> > public int hashCode() { >> > return this.name.hashCode(); >> > } >> > >> > >> > public String name() { >> > return this.name; >> > } >> > >> > >> > public int ordinal() { >> > return this.ordinal; >> > } >> > >> > >> > @Override >> > public String toString() { >> > return this.name; >> > } >> > >> > >> > public static Level toLevel(String name) { >> > return Level.toLevel(name, Level.DEBUG); >> > } >> > >> > >> > public static Level toLevel(String name, Level defaultLevel) { >> > if(name == null) >> > return defaultLevel; >> > name = name.toUpperCase(); >> > if(Level.map.containsKey(name)) >> > return Level.map.get(name); >> > return defaultLevel; >> > } >> > >> > >> > public static Level[] values() { >> > return Level.values.values().toArray(new >> > Level[Level.values.size()]); >> > } >> > >> > >> > public static Level valueOf(String name) { >> > if(name == null) >> > throw new IllegalArgumentException("Unknown level constant [" + >> > name + "]."); >> > name = name.toUpperCase(); >> > if(Level.map.containsKey(name)) >> > return Level.map.get(name); >> > throw new IllegalArgumentException("Unknown level constant [" + >> > name + "]."); >> > } >> > >> > >> > public static <T extends Enum<T>> T valueOf(Class<T> enumType, String >> > name) { >> > return Enum.valueOf(enumType, name); >> > } >> > >> > >> > // for deserialization >> > protected final Object readResolve() throws ObjectStreamException { >> > return Level.valueOf(this.name); >> > } >> > } >> > >> > Extending it is easy: >> > >> > public final class ExtendedLevels { >> > public static final Level MY_LEVEL = new Level("MY_LEVEL", 250) {}; >> > } >> > >> > I still and have ALWAYS believed this was the best option. If we used this >> > option, I would be fine with not adding any new Levels because I could add >> > them myself. >> > >> > Nick >> > >> > On Jan 22, 2014, at 7:04 PM, Remko Popma wrote: >> > >> >> This is only a problem for webapps, right? >> >> Putting log4j jars in WEB-INF/lib avoids that problem (different class >> >> loader). >> >> Apps that really want to share log4j jars with other apps would need to >> >> play nice. Such apps would do well to use a naming convention like Gary >> >> suggests. >> >> Otherwise, the last to register would overwrite any previous level with >> >> the same name. (Should probably emit a StatusLogger warning.) >> >> >> >> Same intLevel for different names should not be a problem. >> >> >> >> >> >> On Thursday, January 23, 2014, Gary Gregory <garydgreg...@gmail.com> >> >> wrote: >> >> Playing devils advocate: >> >> >> >> What happens when different apps register levels with the same name and >> >> different intLevels? >> >> What happens when different apps register levels with the same intLevel >> >> and different names? >> >> Should there be a convention that custom level names be FQNs? >> >> >> >> Gary >> >> >> >> >> >> On Wed, Jan 22, 2014 at 10:05 PM, Paul Benedict <pbened...@apache.org> >> >> wrote: >> >> As Gary wanted, a new thread.... >> >> >> >> First, each enum needs an inherit strength. This would be part of the >> >> interface. Forgive me if the word "strength" is wrong; but it's the 100, >> >> 200, 300, etc. number that triggers the log level. So make sure the >> >> interface contains the intLevel() method. >> >> >> >> Second, we need to know the name, right? The name probably requires a new >> >> method since it can't be extracted from the enum anymore. >> >> >> >> public interface Level { >> >> int intLevel(); >> >> String name(); >> >> } >> >> >> >> PS: The intStrength() name seems hackish. What about strength() or >> >> treshold()? >> >> >> >> Third, the registration can be done manually by providing a static method >> >> (as your did Remko) that the client needs to invoke, or you could have a >> >> class-path scanning mechanism. For the latter, you could introduce a new >> >> annotation to be placed on the enum class. >> >> >> >> @CustomLevels >> >> public enum MyCustomEnums { >> >> } >> >> >> >> Paul >> >> >> >> On Wed, Jan 22, 2014 at 8:52 PM, Remko Popma <remko.po...@gmail.com> >> >> wrote: >> >> Paul, can you give a bit more detail? >> >> >> >> I tried this: copy the current Level enum to a new enum called "Levels" >> >> in the same package (other name would be fine too). Then change Level to >> >> an interface (removing the constants and static methods, keeping only the >> >> non-static methods). Finally make the Levels enum implement the Level >> >> interface. >> >> >> >> After this, we need to do a find+replace for the references to >> >> Level.CONSTANT to Levels.CONSTANT and Level.staticMethod() to >> >> Levels.staticMethod(). >> >> >> >> Finally, the interesting part: how do users add or register their custom >> >> levels and how do we enable the Levels.staticLookupMethod(String, Level) >> >> to recognize these custom levels? >> >> >> >> >> >> >> >> On Thursday, January 23, 2014, Paul Benedict <pbened...@apache.org> wrote: >> >> Agreed. This is not an engineering per se, but really more about if the >> >> feature set makes sense. >> >> >> >> Well if you guys ever look into the interface idea, you'll give log4j the >> >> feature of getting enums to represent custom levels. That's pretty cool, >> >> IMO. I don't know if any other logging framework has that and that would >> >> probably get some positive attention. It shouldn't be so hard to do a >> >> find+replace on the code that accepts Level and replace it with another >> >> name. Yes, there will be some minor refactoring that goes with it, but >> >> hard? It shouldn't be. >> >> >> >> A name I propose for the interface is LevelDefinition. >> >> >> >> Paul >> >> >> >> >> >> On Wed, Jan 22, 2014 at 6:48 PM, Gary Gregory <garydgreg...@gmail.com> >> >> wrote: >> >> Hi, I do not see this as an engineering problem but more a feature set >> >> definition issue. So while there may be lots of more or less internally >> >> complicated ways of solving this with interfaces, makers and whatnots, >> >> the built in levels are the most user friendly. >> >> >> >> I have have lots of buttons, knobs and settings on my sound system that I >> >> do not use, just like I do not use all the methods in all the classes in >> >> the JRE... >> >> >> >> Gary >> >> >> >> >> >> >> >> >> >> -- >> >> E-Mail: garydgreg...@gmail.com | ggreg...@apache.org >> >> Java Persistence with Hibernate, Second Edition >> >> JUnit in Action, Second Edition >> >> Spring Batch in Action >> >> Blog: http://garygregory.wordpress.com >> >> Home: http://garygregory.com/ >> >> Tweet! http://twitter.com/GaryGregory >> > >> > >> > --------------------------------------------------------------------- >> > To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org >> > For additional commands, e-mail: log4j-dev-h...@logging.apache.org >> > >> >> >> --------------------------------------------------------------------- >> To unsubscribe, e-mail: log4j-dev-unsubscr...@logging.apache.org >> For additional commands, e-mail: log4j-dev-h...@logging.apache.org >> >> > >