Uploaded image for project: 'Log4j 2'
  1. Log4j 2
  2. LOG4J2-1278

Garbage-free logging API (no varargs/autoboxing)

    Details

    • Type: Improvement
    • Status: Closed
    • Priority: Major
    • Resolution: Fixed
    • Affects Version/s: 2.5
    • Fix Version/s: 2.6
    • Component/s: API
    • Labels:
      None

      Description

      Looking at the current Logger API, there are a few methods (for each log level) that take a vararg parameter:

      // org.apache.logging.log4j.Logger
      debug(String, Object...) // arguably this method is used the most
      debug(String, Supplier<?>...)
      debug(Marker, String, Object...)
      debug(Marker, String, Supplier<?>...)
      

      This ticket is to explore options for providing similar functionality without creating temporary objects (see LOG4J2-1270 for motivation).

      Here are some of the options I can think of:

      1. Thread-local StringBuilderMessage
      One option is that we provide a new MessageFactory that always returns the same Message object (cached in a ThreadLocal). This Message exposes its StringBuilder. Users build the log message text by appending to the StringBuilder. When the Logger.log method is called, and the Message is enqueued in the RingBufferLogEvent, the StringBuilder text is copied to the RingBufferLogEvent.

      Pros: feasible, low effort, no Logger API changes
      Cons: not the nicest user-facing API

      2. Logger with unrolled varargs
      Another way to avoid the varargs is adding extra methods to the Logger interface that take a varying number of parameters. For example:

      // add methods to org.apache.logging.log4j.Logger
      trace(String, Object)
      trace(String, Object, Object)
      trace(String, Object, Object, Object)
      trace(String, Object, Object, Object, Object)
      trace(String, Object, Object, Object, Object, Object)
      trace(String, Object, Object, Object, Object, Object, Object)
      trace(String, Object...) // fallback to vararg if 7 parameters or more
      ...
      // same for DEBUG, INFO, WARN, ERROR, FATAL, and LOG(Level)
      

      Pros: transparent to users
      Cons: grows Logger interface from 200+ methods(!) to an even larger number

      3. New interface (LevelLogger) with unrolled varargs
      Another option that avoids the method explosion is providing a new interface (e.g. LevelLogger). This interface has extra methods for a varying number of parameters. For example:

      // new interface LevelLogger
      log(String, Object)
      log(String, Object, Object)
      log(String, Object, Object, Object)
      log(String, Object, Object, Object, Object)
      log(String, Object, Object, Object, Object, Object)
      log(String, Object, Object, Object, Object, Object, Object)
      log(String, Object...) // fallback to vararg if 7 parameters or more
      

      For each log level, the same interface can be used, so the number of methods can stay small. There are several ways in which client code could obtain a LevelLogger. One option is from the Logger, but other things are possible too.

      Logger logger = LogManager.getLogger(MyClass.class);
      logger.info().log("This a {} message with {} params", "vararg-free", 2);
      

      Avoiding Autoboxing
      To avoid auto-boxing of primitive parameters (like the integer in the above example), one option is to provide a utility method. Client code would look like this:

      // static import Unboxer.*;
      logger.info().log("This a {} message with {} params", "GC-free", box(2));
      

      Unboxer.box(int) returns a StringBuilder containing the primitive value converted to text. Unboxer internally can be implemented with a cached ring buffer of StringBuilders, with as many slots as there are unrolled varargs.

      4. LevelLogger with method chaining
      A different way to avoid auto-boxing is method chaining.
      Client code would look like this:

      String type = "GC-free";
      int count = 2;
      logger.info().log("This a ").add(type).add(" message with ").add(count).add(" params").commit();
      

      Pros: feasible, medium effort
      Cons: API very similar to StringBuilder, but more fragile since users must remember to call commit()

        Attachments

        1. Latency.png
          56 kB
          Remko Popma
        2. Throughput.png
          63 kB
          Remko Popma

          Activity

            People

            • Assignee:
              remkop@yahoo.com Remko Popma
              Reporter:
              remkop@yahoo.com Remko Popma
            • Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: