[JAVA] Use LocationAwareLogger for your own Logger

Introduction

There is a story and a solution for log output.

Output of "logging request generator" in Logback

In Logback, the "logging request generator" information can be included in the log output by specifying the following conversion specifier in the pattern layout [^ warning-1].

Conversion specifier(alias) Output contents
C (class) Caller class name(FQCN)
M (method) Caller method name
F (file) Source code file name
L (line) Line numbers in the source code file
caller[^1] Caller location information

[^ warning-1] :: warning: Obtaining the "generator of logging request" is a costly process and slows down execution. Let's use it only in the development environment etc. [^ 1]: Due to the special output content, it is not covered in this article.

In this article, we will use the following settings as an example of log output. Notice the contents of the pattern element. With this setting, you can output the class name, file name, etc. to the log. Debugging progresses.

logback.xml


<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>[[%class.%method\(%file:%line\)]] %logger{0} - %msg%n</pattern>
    </encoder>
  </appender>

  <root level="debug">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

Log output example


[[com.example.demo.DemoLogApplication.main(DemoLogApplication.java:16)]] DemoLogApplication - hello

For details on the conversion specifier, refer to the manual.

-logback manual

[Problem] In the case of self-made Logger class

Due to various reasons, you may define your own Logger without using the SLF4J Logger directly. At this time, if you simply delegate the processing to the SLF4J Logger (so-called Wrapper), all the "logging request generators" will be your own Logger.

MyWrongLogger.java


package com.example.demo;

import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

public class MyWrongLogger {
  //Logger of SLF4J to be delegated
  private final org.slf4j.Logger logger;

  //Constructor private
  private MyWrongLogger(org.slf4j.Logger logger) {
    this.logger = logger;
  }

  //Factory method
  public static final MyWrongLogger getLogger(Class<?> clazz) {
    org.slf4j.Logger logger = LoggerFactory.getLogger(clazz);
    return new MyWrongLogger(logger);
  }

  //Below, delegation to logger

  public boolean isTraceEnabled() {
    return logger.isTraceEnabled();
  }

  public void trace(String msg) {
    logger.trace(msg);
  }

  // (The following is omitted)

}

DemoLogApplication.java


package com.example.demo;

public class DemoLogApplication {
  private static final MyWrongLogger WRONG_LOGGER = MyWrongLogger.getLogger(DemoLogApplication.class);

  public static void main(String[] args) {
    WRONG_LOGGER.info("hello");
    WRONG_LOGGER.info("hello {}", "xxx");
    WRONG_LOGGER.info("exception", new Exception("test"));
    WRONG_LOGGER.info("{} {} {} exception", 1, 2, 3, new Exception("test"));
  }
}

Log output example("Generator of logging request" has become MyWrongLogger)


[[com.example.demo.MyWrongLogger.info(MyWrongLogger.java:122)]] DemoLogApplication - hello
[[com.example.demo.MyWrongLogger.info(MyWrongLogger.java:126)]] DemoLogApplication - hello xxx
[[com.example.demo.MyWrongLogger.info(MyWrongLogger.java:138)]] DemoLogApplication - exception
java.lang.Exception: test
	at com.example.demo.DemoLogApplication.main(DemoLogApplication.java:11)
[[com.example.demo.MyWrongLogger.info(MyWrongLogger.java:134)]] DemoLogApplication - 1 2 3 exception
java.lang.Exception: test
	at com.example.demo.DemoLogApplication.main(DemoLogApplication.java:12)

Certainly, it is MyWrongLogger that actually calls the logger method ... There are such unfortunate logs once in a while.

[Solution] LocationAwareLogger

You can solve this problem by using SLF4J's LocationAwareLogger [^ FQCN-1]. You can specify the FQCN in the log method of LocationAwareLogger.

java:org.slf4j.spi.LocationAwareLogger


public void log(Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t);

Logback's Logger [^ FQCN-2] implements LocationAwareLogger. In other words, if you are using Logback to implement log output, you can cast Logger [^ FQCN-3] obtained from LoggerFactory to LocationAwareLogger.

The following is an example of using LocationAwareLogger.

MyLocationAwareLogger.java


package com.example.demo;

import static org.slf4j.spi.LocationAwareLogger.*;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

public class MyLocationAwareLogger {
  // Marker(Not used this time)
  private static final Marker MARKER = null;
  //FQCN of this class
  private static final String FQCN = MyLocationAwareLogger.class.getName();

  //Location Aware Logger of SLF4J to be delegated
  private final org.slf4j.spi.LocationAwareLogger logger;

  //Constructor private
  private MyLocationAwareLogger(org.slf4j.spi.LocationAwareLogger logger) {
    this.logger = logger;
  }

  //Factory method
  public static MyLocationAwareLogger getLogger(Class<?> clazz) {
    org.slf4j.spi.LocationAwareLogger logger = (org.slf4j.spi.LocationAwareLogger) LoggerFactory.getLogger(clazz);
    return new MyLocationAwareLogger(logger);
  }

  //<<< isXxxEnabled >>>
  public boolean isTraceEnabled() { return logger.isTraceEnabled(); }
  public boolean isDebugEnabled() { return logger.isDebugEnabled(); }
  public boolean isInfoEnabled () { return logger.isInfoEnabled (); }
  public boolean isWarnEnabled () { return logger.isWarnEnabled (); }
  public boolean isErrorEnabled() { return logger.isErrorEnabled(); }
  //---

  //<<< log >>>
  // (String msg)
  public void trace(String msg)                         { logger.log(MARKER, FQCN, TRACE_INT, msg,    null,      null); }
  public void debug(String msg)                         { logger.log(MARKER, FQCN, DEBUG_INT, msg,    null,      null); }
  public void info (String msg)                         { logger.log(MARKER, FQCN, INFO_INT,  msg,    null,      null); }
  public void warn (String msg)                         { logger.log(MARKER, FQCN, WARN_INT,  msg,    null,      null); }
  public void error(String msg)                         { logger.log(MARKER, FQCN, ERROR_INT, msg,    null,      null); }
  // (String msg, Throwable t)
  public void trace(String msg,    Throwable t)         { logger.log(MARKER, FQCN, TRACE_INT, msg,    null,      t   ); }
  public void debug(String msg,    Throwable t)         { logger.log(MARKER, FQCN, DEBUG_INT, msg,    null,      t   ); }
  public void info (String msg,    Throwable t)         { logger.log(MARKER, FQCN, INFO_INT,  msg,    null,      t   ); }
  public void warn (String msg,    Throwable t)         { logger.log(MARKER, FQCN, WARN_INT,  msg,    null,      t   ); }
  public void error(String msg,    Throwable t)         { logger.log(MARKER, FQCN, ERROR_INT, msg,    null,      t   ); }
  // (String format, Object... arguments)
  public void trace(String format, Object... arguments) { logger.log(MARKER, FQCN, TRACE_INT, format, arguments, null); }
  public void debug(String format, Object... arguments) { logger.log(MARKER, FQCN, DEBUG_INT, format, arguments, null); }
  public void info (String format, Object... arguments) { logger.log(MARKER, FQCN, INFO_INT,  format, arguments, null); }
  public void warn (String format, Object... arguments) { logger.log(MARKER, FQCN, WARN_INT,  format, arguments, null); }
  public void error(String format, Object... arguments) { logger.log(MARKER, FQCN, ERROR_INT, format, arguments, null); }
  //---
}

DemoLogApplication.java


package com.example.demo;

public class DemoLogApplication {
  private static final MyLocationAwareLogger MY_LOGGER = MyLocationAwareLogger.getLogger(DemoLogApplication.class);

  public static void main(String[] args) {
    MY_LOGGER.info("hello");
    MY_LOGGER.info("hello {}", "xxx");
    MY_LOGGER.info("exception", new Exception("test"));
    MY_LOGGER.info("{} {} {} exception", 1, 2, 3, new Exception("test"));
  }
}

Log output example(The correct "Generator of logging request" is output)


[[com.example.demo.DemoLogApplication.main(DemoLogApplication.java:16)]] DemoLogApplication - hello
[[com.example.demo.DemoLogApplication.main(DemoLogApplication.java:17)]] DemoLogApplication - hello xxx
[[com.example.demo.DemoLogApplication.main(DemoLogApplication.java:18)]] DemoLogApplication - exception
java.lang.Exception: test
	at com.example.demo.DemoLogApplication.main(DemoLogApplication.java:18)
[[com.example.demo.DemoLogApplication.main(DemoLogApplication.java:19)]] DemoLogApplication - 1 2 3 exception
java.lang.Exception: test
	at com.example.demo.DemoLogApplication.main(DemoLogApplication.java:19)

in conclusion

If you just want to imitate the SLF4J Logger method, you don't need your own Logger. If you create your own Logger, create a meaningful Logger, such as preparing methods for each log output purpose (audit log, performance log, etc.). (In this article, I dared to imitate the method of Logger for the sake of clarity as a sample. Please forgive me)

Recommended Posts

Use LocationAwareLogger for your own Logger
Create your own encode for String.getBytes ()
Create your own Android app for Java learning
Use your own docker-compose.yml on the command line
Use pagy for pagination in your Rails app.
Make your own pomodoro
Use your own classes in the lib directory with Rails6
Make your own Rails validate
Create your own Java annotations
Make your own Elasticsearch plugin