[slf4j-dev] Proposal for SLF4J 2.0 Logger API

Ceki ceki at qos.ch
Thu Dec 19 15:45:57 CET 2019



On 19.12.2019 07:01, Remko Popma wrote:

> 
> Sure. One example application could look like this:
> 
> public class ExampleApp {
>      private Logger logger = LoggerFactory.getLogger(ExampleApp.class);
>      private StringBuilder sb = new StringBuilder();
> 
>      public void onNewOrder(IOrder order) {
>          log(order);
>          performBusinessLogic(order);
>      }
> 
>      private void log(IOrder order) {
>          sb.setLength(0); // clear previous content
>          sb.append("Received ");
>          order.toString(sb); // put text representation of the order into SB
>          logger.info(sb); // log it (without allocating temp objects)
>      }
>      // ...
> }
> 
> class OrderImpl implements IOrder {
>      /** Writes a textual representation of this order into the specified
>       * StringBuilder, without allocating temporary objects.
>       */
>      public void toString(StringBuilder sb) {
>          sb.append("NewOrder[")
>                  .append("account=").append(getAccount()) // CharSequence
>                  .append(", instrumentId=").append(getInstrumentId()) // int
>                  .append(", quantity=").append(getQty()) // long
>                  .append(", side=").append(getSide()) // enum (BUY,
> SELL, SHORT-SELL, etc)
>                  .append("]");
>      }
>      // ...
> }

In the example above, one could simply change a single line without 
requiring any change to the SLF4J API.

      private void log(IOrder order) {
           sb.setLength(0);
           sb.append("Received ");
           order.toString(sb);
           logger.info(sb.toString()); // changed line
      }

>> What would you do with the StringBuilder passed by the user?
> 
> 
> Well, the simplest thing that a Logger implementation could do with the
> Object that is passed as the message is to simply call toString on it
> (or String.valueOf(message) to deal with null values).
> This is all that slf4j-simple or logback would need to do.
> 
> I am guessing you are asking how one could go one step further and
> make the actual logger implementation completely garbage-free.
> For text-based loggers, one idea is to extract the text from the specified
> domain Object (it may not be a CharSequence, but it may implement
> some other interface that allows creating a text representation), and to
> copy this text representation into a StringBuilder owned by the Logger.
> 
> The next step is to turn the text representation into bytes which can be
> written to disk or sent over a network. For this, the logging
> implementation needs to do some work with java.nio.CharBuffer, ByteBuffer
> and CharsetEncoders (and making this thread-safe can be quite involved).
> 
>>
>> Or maybe the
>> StringBuilder is provided by the logging back-end and only borrowed by
>> the client?
> 
> That is a very good point!
> (And that is one of the reasons why focusing too much on just
> CharBuilder would be a mistake in my opinion.)
> 
> A SLF4J implementation could provide its own interface that applications
> could implement on objects that need to be logged without allocations.
> For example:
> 
> interface StringBuilderFormattable {
>      /**
>       * Writes a text representation of this object into the specified
>       * StringBuilder, ideally without allocating temporary objects.
>       *
>       * @param buffer the StringBuilder to write into
>       */
>      void formatTo(StringBuilder buffer);
> }
> 
> The SLF4J implementation detects that the logged Object implements
> this interface, then calls the formatTo(StringBuilder) method on it with
> the StringBuilder owned by the logger. This has the advantage that the
> application no longer needs to manage any StringBuilders, and it
> reduces copying between various buffers. For example:
> 
> // example SLF4J logging implementation
> @Override
> protected void handleNormalizedLoggingCall(Level level, Marker marker,
>          Object msg, Object[] arguments, Throwable throwable) {
> 
>      StringBuilder sb = getStringBuilder(); // owned by the logger
>      extractText(msg, sb);
> 
>      List<StringBuilder> textArgs = getStringBuilders(arguments.length);
>      for (int i = 0; i < arguments.length; i++) {
>          extractText(arguments[i], textArgs.get(i);
>      }
>      handleTextLoggingCall(level, marker, sb, textArgs, throwable);
> }
> 
> private void extractText(Object obj, StringBuilder sb) {
>      if (obj instanceof StringBuilderFormattable) {
>          ((StringBuilderFormattable) obj).formatInto(sb);
>      } else if (obj instanceof CharSequence) {
>          sb.append((CharSequence) obj));
> 
>      // unbox auto-boxed primitives to avoid calling toString()
>      } else if (obj instanceof Integer) {
>          sb.append(((Integer) obj).intValue());
>      } else if (obj instanceof Double) {
>          sb.append(((Double) obj).doubleValue());
>      //... etc for other primitive boxed types
> 
>      } else {
>          sb.append(obj.toString()); // fall back to toString
>      }
> }

> Custom interfaces like StringBuilderFormattable would require cooperation
> between the application and the logger implementation, so not everyone
> may like this, but SLF4J should not make this impossible.

Adding Logger.debug(Object) method would allow the above implementation 
in logging backends but would not guide/help/encourage usage of 
StringBuilderFormattable. It think more restricted typing (instead of 
just Object) would encourage adoption.


--
Ceki Gülcü


More information about the slf4j-dev mailing list