<div><br></div><div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Dec 19, 2019 at 23:46 Ceki <<a href="mailto:ceki@qos.ch">ceki@qos.ch</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
<br>
On 19.12.2019 07:01, Remko Popma wrote:<br>
<br>
> <br>
> Sure. One example application could look like this:<br>
> <br>
> public class ExampleApp {<br>
>      private Logger logger = LoggerFactory.getLogger(ExampleApp.class);<br>
>      private StringBuilder sb = new StringBuilder();<br>
> <br>
>      public void onNewOrder(IOrder order) {<br>
>          log(order);<br>
>          performBusinessLogic(order);<br>
>      }<br>
> <br>
>      private void log(IOrder order) {<br>
>          sb.setLength(0); // clear previous content<br>
>          sb.append("Received ");<br>
>          order.toString(sb); // put text representation of the order into SB<br>
>          <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(sb); // log it (without allocating temp objects)<br>
>      }<br>
>      // ...<br>
> }<br>
> <br>
> class OrderImpl implements IOrder {<br>
>      /** Writes a textual representation of this order into the specified<br>
>       * StringBuilder, without allocating temporary objects.<br>
>       */<br>
>      public void toString(StringBuilder sb) {<br>
>          sb.append("NewOrder[")<br>
>                  .append("account=").append(getAccount()) // CharSequence<br>
>                  .append(", instrumentId=").append(getInstrumentId()) // int<br>
>                  .append(", quantity=").append(getQty()) // long<br>
>                  .append(", side=").append(getSide()) // enum (BUY,<br>
> SELL, SHORT-SELL, etc)<br>
>                  .append("]");<br>
>      }<br>
>      // ...<br>
> }<br>
<br>
In the example above, one could simply change a single line without <br>
requiring any change to the SLF4J API.<br>
<br>
      private void log(IOrder order) {<br>
           sb.setLength(0);<br>
           sb.append("Received ");<br>
           order.toString(sb);<br>
           <a href="http://logger.info" rel="noreferrer" target="_blank">logger.info</a>(sb.toString()); // changed line<br>
      }</blockquote><div dir="auto">Yes, that is my main complaint: SLF4J currently _forces_ applications to transform the original raw data to a text representation. Why? Or rather, why so soon? This is too soon! The original data had information that is lost by the time it reaches the logging backend. A whole range of interesting possibilities are now no longer available because we don’t have the original data but only a text representation to work with.</div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
<br>
>> What would you do with the StringBuilder passed by the user?<br>
> <br>
> <br>
> Well, the simplest thing that a Logger implementation could do with the<br>
> Object that is passed as the message is to simply call toString on it<br>
> (or String.valueOf(message) to deal with null values).<br>
> This is all that slf4j-simple or logback would need to do.<br>
> <br>
> I am guessing you are asking how one could go one step further and<br>
> make the actual logger implementation completely garbage-free.<br>
> For text-based loggers, one idea is to extract the text from the specified<br>
> domain Object (it may not be a CharSequence, but it may implement<br>
> some other interface that allows creating a text representation), and to<br>
> copy this text representation into a StringBuilder owned by the Logger.<br>
> <br>
> The next step is to turn the text representation into bytes which can be<br>
> written to disk or sent over a network. For this, the logging<br>
> implementation needs to do some work with java.nio.CharBuffer, ByteBuffer<br>
> and CharsetEncoders (and making this thread-safe can be quite involved).<br>
> <br>
>><br>
>> Or maybe the<br>
>> StringBuilder is provided by the logging back-end and only borrowed by<br>
>> the client?<br>
> <br>
> That is a very good point!<br>
> (And that is one of the reasons why focusing too much on just<br>
> CharBuilder would be a mistake in my opinion.)<br>
> <br>
> A SLF4J implementation could provide its own interface that applications<br>
> could implement on objects that need to be logged without allocations.<br>
> For example:<br>
> <br>
> interface StringBuilderFormattable {<br>
>      /**<br>
>       * Writes a text representation of this object into the specified<br>
>       * StringBuilder, ideally without allocating temporary objects.<br>
>       *<br>
>       * @param buffer the StringBuilder to write into<br>
>       */<br>
>      void formatTo(StringBuilder buffer);<br>
> }<br>
> <br>
> The SLF4J implementation detects that the logged Object implements<br>
> this interface, then calls the formatTo(StringBuilder) method on it with<br>
> the StringBuilder owned by the logger. This has the advantage that the<br>
> application no longer needs to manage any StringBuilders, and it<br>
> reduces copying between various buffers. For example:<br>
> <br>
> // example SLF4J logging implementation<br>
> @Override<br>
> protected void handleNormalizedLoggingCall(Level level, Marker marker,<br>
>          Object msg, Object[] arguments, Throwable throwable) {<br>
> <br>
>      StringBuilder sb = getStringBuilder(); // owned by the logger<br>
>      extractText(msg, sb);<br>
> <br>
>      List<StringBuilder> textArgs = getStringBuilders(arguments.length);<br>
>      for (int i = 0; i < arguments.length; i++) {<br>
>          extractText(arguments[i], textArgs.get(i);<br>
>      }<br>
>      handleTextLoggingCall(level, marker, sb, textArgs, throwable);<br>
> }<br>
> <br>
> private void extractText(Object obj, StringBuilder sb) {<br>
>      if (obj instanceof StringBuilderFormattable) {<br>
>          ((StringBuilderFormattable) obj).formatInto(sb);<br>
>      } else if (obj instanceof CharSequence) {<br>
>          sb.append((CharSequence) obj));<br>
> <br>
>      // unbox auto-boxed primitives to avoid calling toString()<br>
>      } else if (obj instanceof Integer) {<br>
>          sb.append(((Integer) obj).intValue());<br>
>      } else if (obj instanceof Double) {<br>
>          sb.append(((Double) obj).doubleValue());<br>
>      //... etc for other primitive boxed types<br>
> <br>
>      } else {<br>
>          sb.append(obj.toString()); // fall back to toString<br>
>      }<br>
> }<br>
<br>
> Custom interfaces like StringBuilderFormattable would require cooperation<br>
> between the application and the logger implementation, so not everyone<br>
> may like this, but SLF4J should not make this impossible.<br>
<br>
Adding Logger.debug(Object) method would allow the above implementation <br>
in logging backends but would not guide/help/encourage usage of <br>
StringBuilderFormattable. It think more restricted typing (instead of <br>
just Object) would encourage adoption.<br>
</blockquote><div dir="auto"><br></div><div dir="auto">Perhaps we got a bit sidetracked with the discussion about Message and StringBuilderFormattable. </div><div dir="auto">Garbage-free logging backends is only one of the possibilities that is precluded by the current String-based API. </div><div dir="auto"><br></div><div dir="auto">For the SLF4J API to be genuinely generic enough to span the requirements and abilities of many possible logging backends, it should not be too opinionated. </div><div dir="auto"><br></div><div dir="auto">Have you had a chance to look at Yang et al’s work on NanoLog (<div><a href="https://www.usenix.org/system/files/conference/atc18/atc18-yang.pdf">https://www.usenix.org/system/files/conference/atc18/atc18-yang.pdf</a>, <div><a href="https://github.com/PlatformLab/NanoLog">https://github.com/PlatformLab/NanoLog</a>)?</div></div></div><div dir="auto"><br></div><div dir="auto">One of the reasons they could achieve those performance numbers is by _postponing_ formatting. </div><div dir="auto"><br></div><div dir="auto">I believe there are many advantages in allowing applications to log the original domain objects instead of a derived representation. </div><div dir="auto"><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><br>
<br>
--<br>
Ceki Gülcü<br>
_______________________________________________<br>
slf4j-dev mailing list<br>
<a href="mailto:slf4j-dev@qos.ch" target="_blank">slf4j-dev@qos.ch</a><br>
<a href="http://mailman.qos.ch/mailman/listinfo/slf4j-dev" rel="noreferrer" target="_blank">http://mailman.qos.ch/mailman/listinfo/slf4j-dev</a></blockquote></div></div>