Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 2.0-rc1
    • Component/s: API
    • Labels:
      None

      Description

      It is desirable to have the Level be an enum. However, it is also desirable to let users add new log levels. These goals are in opposition to each other since enum classes are final. In addition, adding new levels implies adding new methods to the Logger interface (or some counterpart to it). This would be unworkable.

        Issue Links

          Activity

          Hide
          Ralph Goers added a comment -

          Support for custom levels was added in revision 1561537.

          Show
          Ralph Goers added a comment - Support for custom levels was added in revision 1561537.
          Hide
          Ralph Goers added a comment -

          Reopening as we agreed to address this.

          Show
          Ralph Goers added a comment - Reopening as we agreed to address this.
          Hide
          Gary Gregory added a comment -

          (c) is not much of an argument as Log4J itself is the main plugin author and I am guessing the others are much less then (a).

          Show
          Gary Gregory added a comment - (c) is not much of an argument as Log4J itself is the main plugin author and I am guessing the others are much less then (a).
          Hide
          Noel Grandin added a comment -

          Hmmm,
          I don't see this issue "keep coming up".
          Generally about once a year and mostly people are pointed in the direction of a reasonable solution that works with the existing API.

          I'll repeat my comment from earlier on the mailing list:
          Every extension to an API adds complexity and cost
          When
          (a) that extension is used by a vanishingly small fraction of the userbase
          (b) there are other mechanisms that can accomplish the given ends (e.g. Markers)
          (c) the cost of the extension will be borne by every plugin author
          then the cost of the extension is very dubious.

          Show
          Noel Grandin added a comment - Hmmm, I don't see this issue "keep coming up". Generally about once a year and mostly people are pointed in the direction of a reasonable solution that works with the existing API. I'll repeat my comment from earlier on the mailing list: Every extension to an API adds complexity and cost When (a) that extension is used by a vanishingly small fraction of the userbase (b) there are other mechanisms that can accomplish the given ends (e.g. Markers) (c) the cost of the extension will be borne by every plugin author then the cost of the extension is very dubious.
          Hide
          Gary Gregory added a comment -

          Well, now is a good time to review since we are not at 2.0 yet.

          A couple of points: While I have myself longed for an extra Level from time to time, like a VERBOSE levl that would sit between DEBUG and INFO. I've found that in most cases I can do with a specially named Logger instead of a new Level.

          I have seen some truly nasty Log4J 1 code that involves absurd use of custom levels, where, in fact, using better named Loggers would have been the 'correct' solution. No matter what you write in the manual, if you make a gun, some people are bound to shoot themselves in the proverbial foot.

          But here we are on the cusp of 2.0.

          There is an in between solution, may in fact. For example: We can add as many Levels as we can think of to the Level enum as part of 2.0 and, this is the key, do nothing else, so people can use the current API. Sure, they won't have the nicer APIs with the level names built-in but at least the extra levels are there.

          This brings me to my second point: I do not care how big an API is, as long as it is the right API. If we think that adding another level is needed and that means adding 18 new methods, then so be it.

          So I propose to divide this issue into steps:

          1. What are the levels we should provide?
          2. Should the levels still be represented as an enum?
          3. Should the extra levels be presented in the API, like Logger#isDebug() and debug(), and so on.
            1. Do nothing, use the Level APIs
            2. Add APIs for the new levels
            3. Add an int based API as suggested.
          Show
          Gary Gregory added a comment - Well, now is a good time to review since we are not at 2.0 yet. A couple of points: While I have myself longed for an extra Level from time to time, like a VERBOSE levl that would sit between DEBUG and INFO. I've found that in most cases I can do with a specially named Logger instead of a new Level. I have seen some truly nasty Log4J 1 code that involves absurd use of custom levels, where, in fact, using better named Loggers would have been the 'correct' solution. No matter what you write in the manual, if you make a gun, some people are bound to shoot themselves in the proverbial foot. But here we are on the cusp of 2.0. There is an in between solution, may in fact. For example: We can add as many Levels as we can think of to the Level enum as part of 2.0 and, this is the key, do nothing else, so people can use the current API. Sure, they won't have the nicer APIs with the level names built-in but at least the extra levels are there. This brings me to my second point: I do not care how big an API is, as long as it is the right API. If we think that adding another level is needed and that means adding 18 new methods, then so be it. So I propose to divide this issue into steps: What are the levels we should provide? Should the levels still be represented as an enum? Should the extra levels be presented in the API, like Logger#isDebug() and debug(), and so on. Do nothing, use the Level APIs Add APIs for the new levels Add an int based API as suggested.
          Hide
          Nick Williams added a comment -

          So I hate to reopen old wounds, but I'm going to give a little bit of a gripe here. I'm in the review stage for my book, and I just read the following sentences in my logging chapter:

          Because Level is an enum, it cannot be extended. These are the only levels you can use in Log4j 2.

          This made me cringe, and reminded me of this issue. This keeps coming up in the mailing list, and for good reason. The logging levels we have defined work well for most developers. But the Level enum is not an exhaustive list of every level every developer will ever need. Yes, that's a good thing—an enum with dozens of levels in it (and a Logger API to match) would be useless. However, the use of an enum makes it very hard for developers to adapt Log4j to suit their needs. I'm going to use a hypothetical CONFIG level, which logically falls somewhere between WARN and INFO for my purposes. I would use this level to log when configuration settings changed or to log the current value of configuration settings on application startup.

          Currently, the Logger API provides no mechanism for me to use anything outside the Level levels. I must choose one of those for logging my CONFIG events. So then how do I use CONFIG? Well, I have to define a Marker. Then I have to use both a Level (probably INFO) and a Marker on my CONFIG events. Then I have to properly configure a Filter, and get it in the correct place, to intercept those logging events and inspect the Marker. All of this is much more difficult than simply using this line of code:

                  logger.log(MyLevels.CONFIG, "The current config setting is {}.", setting);

          Now, what could MyLevels.CONFIG be? Two comments above this one, Paul suggests that it should be an int, and that we should add methods to the Logger API to accept an int that can be compared to the other Level constants and logged appropriately. Why? So that Logger can have SIXTEEN more methods than it already does? Why not just re-use the methods that accept a Level? And that's not even including the throwing and catching methods, so actually eighteen new methods. How does this improve the API but making Level extendable does not?

          One way or another, I think it's a must for us to resolve a way to define your own levels before Log4j can be released (and I so want to release it). Whether that's making Level extendable or adding eighteen int methods to Logger is a matter that merits discussion, but however it's done, it needs to be done.

          Four comments above, I provide a detailed example of how Level can be made extendable without breaking the API. It's a binary-compatible change. Existing code compiled against Log4j would continue to work with my proposed change. I've read five or six "seconds" to my proposal since I posted it in March. It seems like a safe and powerful change to make, so I'm bringing it back up for discussion in the hopes that it will at least trigger some new ideas, if not some agreement.

          Show
          Nick Williams added a comment - So I hate to reopen old wounds, but I'm going to give a little bit of a gripe here. I'm in the review stage for my book, and I just read the following sentences in my logging chapter: Because Level is an enum, it cannot be extended. These are the only levels you can use in Log4j 2. This made me cringe, and reminded me of this issue. This keeps coming up in the mailing list, and for good reason. The logging levels we have defined work well for most developers. But the Level enum is not an exhaustive list of every level every developer will ever need. Yes, that's a good thing—an enum with dozens of levels in it (and a Logger API to match) would be useless. However, the use of an enum makes it very hard for developers to adapt Log4j to suit their needs. I'm going to use a hypothetical CONFIG level, which logically falls somewhere between WARN and INFO for my purposes. I would use this level to log when configuration settings changed or to log the current value of configuration settings on application startup. Currently, the Logger API provides no mechanism for me to use anything outside the Level levels. I must choose one of those for logging my CONFIG events. So then how do I use CONFIG ? Well, I have to define a Marker . Then I have to use both a Level (probably INFO ) and a Marker on my CONFIG events. Then I have to properly configure a Filter , and get it in the correct place, to intercept those logging events and inspect the Marker . All of this is much more difficult than simply using this line of code: logger.log(MyLevels.CONFIG, "The current config setting is {}." , setting); Now, what could MyLevels.CONFIG be? Two comments above this one, Paul suggests that it should be an int , and that we should add methods to the Logger API to accept an int that can be compared to the other Level constants and logged appropriately. Why? So that Logger can have SIXTEEN more methods than it already does? Why not just re-use the methods that accept a Level ? And that's not even including the throwing and catching methods, so actually eighteen new methods. How does this improve the API but making Level extendable does not? One way or another, I think it's a must for us to resolve a way to define your own levels before Log4j can be released (and I so want to release it). Whether that's making Level extendable or adding eighteen int methods to Logger is a matter that merits discussion, but however it's done, it needs to be done. Four comments above, I provide a detailed example of how Level can be made extendable without breaking the API . It's a binary-compatible change. Existing code compiled against Log4j would continue to work with my proposed change. I've read five or six "seconds" to my proposal since I posted it in March. It seems like a safe and powerful change to make, so I'm bringing it back up for discussion in the hopes that it will at least trigger some new ideas, if not some agreement.
          Hide
          Nick Williams added a comment -

          But isn't your proposal less safe and more complex? Is it really accurate to say that we know best what every single user will need, and therefore we should make it difficult for them to do other than what we want them to do?

          I've actually changed my mind since I said, "I'm fine leaving it the way it is, but this could achieve the functional equivalent and appease those who wish to see this extensible." I think this is a significant improvement and has zero risk. This is actually the way java.util.logging.Level is implemented.

          Can you name any downsides to creating an extensible enum? I'm trying to think of some, and I can't. I can, however, name some downsides to your proposal:

          • Adds yet another set of 14 more methods to the Logger interface
          • Adds the complexity of a developer having to define a custom strategy
          • Makes it difficult (impossible?) to use custom log levels from a the tag library I'm working on

          The extensible enum proposal has none of these downsides.

          Show
          Nick Williams added a comment - But isn't your proposal less safe and more complex? Is it really accurate to say that we know best what every single user will need, and therefore we should make it difficult for them to do other than what we want them to do? I've actually changed my mind since I said, "I'm fine leaving it the way it is, but this could achieve the functional equivalent and appease those who wish to see this extensible." I think this is a significant improvement and has zero risk. This is actually the way java.util.logging.Level is implemented. Can you name any downsides to creating an extensible enum? I'm trying to think of some, and I can't. I can, however, name some downsides to your proposal: Adds yet another set of 14 more methods to the Logger interface Adds the complexity of a developer having to define a custom strategy Makes it difficult (impossible?) to use custom log levels from a the tag library I'm working on The extensible enum proposal has none of these downsides.
          Hide
          Paul Benedict added a comment -

          I don't support departing from Java enums. We should keep a strict and limited set of provided levels. To help others who definitely believe they need custom levels, we should provide (1) a method that takes an int that identifies their level and (2) a custom strategy that knows how to deal with it.

          Show
          Paul Benedict added a comment - I don't support departing from Java enums. We should keep a strict and limited set of provided levels. To help others who definitely believe they need custom levels, we should provide (1) a method that takes an int that identifies their level and (2) a custom strategy that knows how to deal with it.
          Hide
          Nick Williams added a comment -

          Apparently this JIRA has disabled code formatting. Unfortunate. You get the idea.

          Show
          Nick Williams added a comment - Apparently this JIRA has disabled code formatting. Unfortunate. You get the idea.
          Hide
          Nick Williams added a comment -

          Since there was discussion on the user list about this today, I decided I'd add something here. I agree with NOT changing Level to an int. However, we CAN still make it the functional equivalent of an enum but ALSO allow it to be extensible. This change would be almost completely API-compatible: it could be dropped in right now and, as long as the code isn't using an {{EnumMap}}s, {{EnumSet}}s, etc, there should be no other changes needed. Here's the code:

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

          If a developer wanted to extend this, it could be done as simply as:

          public final class ExtendedLevels {
              public static final Level MY_LEVEL = new Level("MY_LEVEL", 250) {};
          }

          The stock levels' ints have been multiplied by 100 to allow room for extended values falling between.

          Just an idea. Like I said, I'm fine leaving it the way it is, but this could achieve the functional equivalent and appease those who wish to see this extensible.

          Show
          Nick Williams added a comment - Since there was discussion on the user list about this today, I decided I'd add something here. I agree with NOT changing Level to an int. However, we CAN still make it the functional equivalent of an enum but ALSO allow it to be extensible. This change would be almost completely API-compatible: it could be dropped in right now and, as long as the code isn't using an {{EnumMap}}s, {{EnumSet}}s, etc, there should be no other changes needed. Here's the code: 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); } } If a developer wanted to extend this, it could be done as simply as: public final class ExtendedLevels { public static final Level MY_LEVEL = new Level( "MY_LEVEL" , 250) {}; } The stock levels' ints have been multiplied by 100 to allow room for extended values falling between. Just an idea. Like I said, I'm fine leaving it the way it is, but this could achieve the functional equivalent and appease those who wish to see this extensible.
          Hide
          Ralph Goers added a comment -

          Using an enum provides value. The added flexibility can be achieved using Markers.

          Show
          Ralph Goers added a comment - Using an enum provides value. The added flexibility can be achieved using Markers.
          Hide
          Ralph Goers added a comment - - edited

          After more work and thought I am more inclined to believe that this is not something we should do. Using markers in combination with the existing set of log levels should provide all the flexibility anyone could want.

          I could easily picture the mapping mechanism you describe for in-house logging frameworks where there is a routine that accepts the JUL Level (extended) and returns an object containing the Marker and Level to use. This would easily allow an adapter mapping the in-house API to Logj2. Of course, Log4j2 is designed so that anyone can plug in an alternate implementation behind the API so going the other way shouldn't be a problem.

          Show
          Ralph Goers added a comment - - edited After more work and thought I am more inclined to believe that this is not something we should do. Using markers in combination with the existing set of log levels should provide all the flexibility anyone could want. I could easily picture the mapping mechanism you describe for in-house logging frameworks where there is a routine that accepts the JUL Level (extended) and returns an object containing the Marker and Level to use. This would easily allow an adapter mapping the in-house API to Logj2. Of course, Log4j2 is designed so that anyone can plug in an alternate implementation behind the API so going the other way shouldn't be a problem.
          Hide
          Ralph Goers added a comment -

          I'm not suggesting that this issue be closed at this point, just that it changes my perspective on the value of fixing it. There are distinct advantages in using Enums over static ints. The other thing to consider is that every level has a corresponding set of methods on the Logger interface. These won't exist for custom levels unless a custom Logger interface is used. It would take a bit of convincing for me to understand how this would be a good thing.

          Show
          Ralph Goers added a comment - I'm not suggesting that this issue be closed at this point, just that it changes my perspective on the value of fixing it. There are distinct advantages in using Enums over static ints. The other thing to consider is that every level has a corresponding set of methods on the Logger interface. These won't exist for custom levels unless a custom Logger interface is used. It would take a bit of convincing for me to understand how this would be a good thing.
          Hide
          Curt Arnold added a comment -

          I agree that most urges to use extensible levels are misguided attempts to use levels for something other than the significance of the message. Every now and again someone will will say they are having problems while trying to implement an AUDIT level when that is not a significance but an audience and is the appropriate domain of the logger path or an SLF4J marker. Even for "audit" events, there would be a range of significance ($100: DEBUG, $10000 INFO, $1,000,000,000,000 FATAL) and by using the level for AUDIT, you lose any mechanism for communicating the significance.

          However, that it can be misused does not invalidate proper use and they are a few legitimate uses (mapping the use of an in-house logging framework with more levels, for example). Unless the core supports some mechanism to extend the level space, then it would not be possible to provide a plug compatible experience for those log4j and java.util.Logging users who used level extension in a proper manner.

          If it becomes obvious that there is a huge cost in implementing extensible levels and the proportion of users affected by the elimination of the feature is small, then it could be abandoned. However, putting in the JIRA was an attempt to keep in the mix of design features that could be desirable so that something that might be a migration block isn't overlooked in the design process.

          Show
          Curt Arnold added a comment - I agree that most urges to use extensible levels are misguided attempts to use levels for something other than the significance of the message. Every now and again someone will will say they are having problems while trying to implement an AUDIT level when that is not a significance but an audience and is the appropriate domain of the logger path or an SLF4J marker. Even for "audit" events, there would be a range of significance ($100: DEBUG, $10000 INFO, $1,000,000,000,000 FATAL) and by using the level for AUDIT, you lose any mechanism for communicating the significance. However, that it can be misused does not invalidate proper use and they are a few legitimate uses (mapping the use of an in-house logging framework with more levels, for example). Unless the core supports some mechanism to extend the level space, then it would not be possible to provide a plug compatible experience for those log4j and java.util.Logging users who used level extension in a proper manner. If it becomes obvious that there is a huge cost in implementing extensible levels and the proportion of users affected by the elimination of the feature is small, then it could be abandoned. However, putting in the JIRA was an attempt to keep in the mix of design features that could be desirable so that something that might be a migration block isn't overlooked in the design process.
          Hide
          Ralph Goers added a comment -

          A brief discussion just took place on the Logback mailing list on this topic. A user wanted custom log levels. When asked why it was so they could split the existing levels into finer pieces. It was pointed out that this is what markers are for and serve the purpose well. Since Markers have been added to my implementation of log4j 2.0 I am going to conclude that this feature (exensible log levels) is related to LOG4J2-16, if not a duplicate of it.

          IMO this approach makes the most sense since the Level can continue to be an Enum while still allowing the individual levels to be decorated.

          Show
          Ralph Goers added a comment - A brief discussion just took place on the Logback mailing list on this topic. A user wanted custom log levels. When asked why it was so they could split the existing levels into finer pieces. It was pointed out that this is what markers are for and serve the purpose well. Since Markers have been added to my implementation of log4j 2.0 I am going to conclude that this feature (exensible log levels) is related to LOG4J2-16 , if not a duplicate of it. IMO this approach makes the most sense since the Level can continue to be an Enum while still allowing the individual levels to be decorated.
          Hide
          Curt Arnold added a comment -

          Both java.util.logging and log4j 1.2 support user defined levels using extensions of their level class. While often the capability is misused to imply something about the audience (like an audit level) that would be better done using the logger name, there will be code that depends on the capability. In addition, some logging frameworks may not have the concept of level or use an integer level.

          Both log4j and java.util.logging's Level have integer values associated with the predefined levels. The assigned values are not compatible, all of java.util.logging levels other than OFF have values less than log4j's DEBUG.

          The current take in my log4j 2.0 branch is to allow mapping of existing API level values to a "generic" int value for quick threshold testing but also allow filtering based on the specific level passed in case you want to distinguish between jul's INFO and log4j's INFO.

          If you can't map to the generic level int space, then you can return a generic int of Integer.MAX_VALUE guaranteeing passage through the int threshold and then filter on the specific level object.

          The current code requires passing an implementation of org.apache.logging.core.Level which would likely require managing a set of mapping objects as you would want to avoid unnecessary object creation prior to the threshold evaluation. While that it likely tolerable as long as the predefined levels are used, a naive implementation would likely exhaust memory if the user called with random int values.

          While this would not be exposed at the client API level, I'm thinking that the backend may need to take an Object level paired with a LevelFormatter class that handles conversion to the generic int space. The bridge between a specific API to the backend would be responsible to making sure they were matched.

          Show
          Curt Arnold added a comment - Both java.util.logging and log4j 1.2 support user defined levels using extensions of their level class. While often the capability is misused to imply something about the audience (like an audit level) that would be better done using the logger name, there will be code that depends on the capability. In addition, some logging frameworks may not have the concept of level or use an integer level. Both log4j and java.util.logging's Level have integer values associated with the predefined levels. The assigned values are not compatible, all of java.util.logging levels other than OFF have values less than log4j's DEBUG. The current take in my log4j 2.0 branch is to allow mapping of existing API level values to a "generic" int value for quick threshold testing but also allow filtering based on the specific level passed in case you want to distinguish between jul's INFO and log4j's INFO. If you can't map to the generic level int space, then you can return a generic int of Integer.MAX_VALUE guaranteeing passage through the int threshold and then filter on the specific level object. The current code requires passing an implementation of org.apache.logging.core.Level which would likely require managing a set of mapping objects as you would want to avoid unnecessary object creation prior to the threshold evaluation. While that it likely tolerable as long as the predefined levels are used, a naive implementation would likely exhaust memory if the user called with random int values. While this would not be exposed at the client API level, I'm thinking that the backend may need to take an Object level paired with a LevelFormatter class that handles conversion to the generic int space. The bridge between a specific API to the backend would be responsible to making sure they were matched.

            People

            • Assignee:
              Ralph Goers
              Reporter:
              Ralph Goers
            • Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved:

                Development