SLF4J etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
SLF4J etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

25 Ocak 2021 Pazartesi

SLF4J - Mapped Diagnostic Context (MDC)

Giriş
Açıklaması şöyle
The basic idea of Mapped Diagnostic Context is to provide a way to enrich log messages with pieces of information that could be not available in the scope where the logging actually occurs, but that can be indeed useful to better track the execution of the program.

In most of the modern client-server applications, servers are made to handle different client requests in separate worker threads. But traditional logging methods are not able to capture the thread/event specific information without doing a complete logging implementation manually.

MDC is introduced to serve this gap. It will help the developers to store thread/event specific information in its internal data structure (Map). And append the information to the log messages according to the configuration.

Simple Logging Facade for Java (SLF4J) APIs supports MDC feature and through Log4j and LOGBack implementations, developers can use the MDC feature.

... developer should clear the MDC context at the execution start point as it will contain garbage values if the threads are being reused. (eg: executer service with reusable threads)
Açıklaması şöyle
Both Logback and Log4j2 support a feature called “Mapped Diagnostic Context”, or MDC, where a dictionary/map of context is attached to each thread, which you can then read from and write to as needed. Data stored in a thread’s MDC can be logged using the %X variable. You can either log all of the MDC, or just specific pieces of information, e.g. if your log pattern included %X{orderNumber}, just the value of the "orderNumber" key from a thread's MDC would be logged.
Örnek
SLF kodunda şöyle yaparız
import org.slf4j.MDC;
...
try {
  MDC.put("field_name", "value");
  log.info("This message will have the field field_name set to value");
  …
  log.info("This one also");
  …
}
finally {
  MDC.remove("field_name");
}
Örnek
Şöyle yaparız. Burada AccountId=%X{accountId} şeklindeki kullanımda MDC içine "accountId" değişkeni yerleştiriliyor. Appender da bunu logluyor.
<configuration>
  <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
      <pattern>%d{YYYY-MM-DD'T'hh:mm:ss} [%thread] %5p %c{1}
- %m AccountId=%X{accountId}
CustomerId=%X{customerId}
RequestId=%X{requestId} %n</pattern>
    </encoder>
  </appender>

  <root level="TRACE">
    <appender-ref ref="stdout" />
  </root>

</configuration>
SLF4J kodu şöyle.
public void run() {
  MDC.put("threadId",String.valueOf(Thread.currentThread().getId()));
  MDC.put("accountId", transfer.getAccount());
  MDC.put("requestId", transfer.getRequest());
  ... 
}
Örnek
Log4J ile şöyle yaparız. Burada ThreadContext sınıfı kullanılıyor
import org.apache.logging.log4j.ThreadContext;

public List<OrderItem> getOrderItems(@PathVariable("orderNumber") @Required
  String orderNumber) {
  ThreadContext.put("orderNumber", orderNumber);
  
  List<OrderItem> orderItems = null;
  try {
    orderItems = d.getItemsForOrder(orderNumber);
  } catch (Exception ex) {
    logger.error("Unable to fetch items from database for order", ex);
  }
  logger.info("Got items, returning");
  ThreadContext.clearAll();
  return orderItems;
}
Burada ise CloseableThreadContext kullanılıyor. Bu sınıf ThreadContext'i otomatik olarak temizler. Şöyle yaparız. 
public List<OrderItem> getOrderItems(...) {
try (final CloseableThreadContext.Instance ignored = CloseableThreadContext .put("orderNumber", orderNumber) .put("retailer", retailer)) { ... } }
Log4J örneği ama mantık aynı. Şöyle yaparız
<Property name="LOG_PATTERN">
    %d{yyyy-MM-dd HH:mm:ss.SSS} %5p [%15.15t] %-40.40c{1.} : %X : %m%n%ex
</Property>
Çıktı olarak şunu alırız
2021-06-08 12:25:10.774 INFO [nio-8080-exec-1] c.z.s.s.OrderController : {orderNumber=2313} : Getting items
2021-06-08 12:25:10.774 INFO [nio-8080-exec-1] c.z.s.s.OrderController : {orderNumber=2313} : Got items, returning
2021-06-08 12:25:26.732 INFO [nio-8080-exec-2] c.z.s.s.OrderController : {orderNumber=8291} : Getting items
2021-06-08 12:25:26.732 INFO [nio-8080-exec-2] c.z.s.s.OrderController : {orderNumber=8291} : Got items, returning
Örnek
Elimizde şöyle bir appender olsun. %X{key} ile MDC nesnesine "key" isimli bir alan ekleyeceğimizi belirtiriz.
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 
  <layout>
    <Pattern>%X{key}  - %m%n</Pattern>
  </layout> 
</appender>
Şöyle yaparız.
public String doAnyAction(long key) {
  MDC.put("key", key);
  logger.info(key);
}

20 Ocak 2019 Pazar

SLF4J - Simple Logging Facade for Java Kullanımı

Maven
SLF4J bir sürü backend ile kullanılabilir. logback-classic, log4j, JUL (java.util.logging), JCL (Jakarta Commons Logging) vs. Şeklen şöyle
1. slf4j-api dahil edilir.
2. Sonra backend olarak ne kullanacaksak onu dahil ederiz. 
- slf4j-log4j12
- slf4j-jdk14
- Eğer logback-classic kullanacaksa sadece logback-classic bağımlılığını eklemek yeterli.
Açıklaması şöyle
If SLF4J cannot find a suitable implementation, it will default to a no-operation (NOP) implementation, which does not log anything.

slf4j-parent Projesi
Bu projenin ne işe yaradığını anlayamadım

1. logback-classic Backend
Logback yazısına bakabilirsiniz.

SLF4J ile logback-classic backend'i kullanmak için Maven'da şöyle yaparız. Burada fazladan slf4j-api dahil edilmiş.
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
</dependency>
Örnek
Şöyle yaparız. Burada SLF4J logback backend ile kullanılıyor. Ayrıca Log4J ve Apache Jakarta common logging de SLF4J'ye yönlendiriliyor. Yani XXX-over-slf4j kütüphaneleri her şeyi SLF4'ye yönlendiriliyor
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.36</version>
</dependency>
<!-- use logback -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-core</artifactId>
  <version>1.2.11</version>
</dependency>
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.2.11</version>
</dependency>


<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>log4j-over-slf4j</artifactId>
  <version>1.7.36</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.36</version>aaa
</dependency>
2. Simple BackEnd - Unit Test
SLF4J ile birim testi yapmak için şöyle yaparız. Burada backend olarak simple logger kullanılıyor.
<!-- Depend on slf4j API -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.12</version>
</dependency>

<!-- Use SimpleLogger as the slf4j implementation in test -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-simple</artifactId>
   <version>1.7.12</version>
   <scope>test</scope>
</dependency>
Örnek
Şöyle yaparız
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-simple</artifactId>
  <version>1.8.0-beta4</version>
</dependency>
3. Log4J2
SLF4J ve Log4J2 Backend yazısına taşıdım

3. Log4J
Pattern
 %d - Tarih ve saati göseterir
 %m veya %message - Bizim mesajımızı gösterir.
%n - yeni satır içindir
%t
 - Thread ismini gösterir.
%X - MDC içindeki değeri gösterir.
Örnek
Şöyle yaparız
logging.pattern.console=%d|%level|${spring.application.name}|%X{Correlation-Id}|%message%n
Çıktı olarak şunu alırız
2021-09-04 13:58:58,010|INFO|service-a|314159|Service-A is called 
4. JUL
Örnek
Şöyle yaparız
implementation "org.slf4j:slf4j-api:2.0.3"
implementation "org.slf4j:slf4j-jdk14:2.0.3"

SLF4J Logger Sınıfı
info metodu
Çalışma mantığı şöyle.
public void log(int logLevel, String ... msg) {
  if (logLevel >= currentLogLevel) {
    for (String m : msg) {
      ...
    }
  }
}
// calling
log(2, "text1", var1, "text2");
String birleştirmekten kaçtığı için şöyle yapmamalıyız.
logger.info("List size is: " + list.size());
String birleştirme yapmadığı için çağrının maliyeti düşük. Açıklaması şöyle
You can turn off logging entirely by setting the level of the root logger to Level.OFF, the highest possible level. When logging is turned off entirely, the cost of a log request consists of a method invocation plus an integer comparison. On a 3.2Ghz Pentium D machine this cost is typically around 20 nanoseconds.
info metodu - template string
Template yeri için {} karakterleri kullanılır.

Örnek - 1 parametre
Template string yöntemini kullanarak şöyle yaparız.
log.info("Something like this {}", variable);
Şöyle yaparız.
Object entry = new SomeObject(); 
logger.debug("The entry is {}.", entry);
Örnek - 2 parametre
Şöyle yaparız.
log.info("ID: {} NAME: {}", id, name);
Şöyle yaparız.
log.info("A is {} and B is {}. The difference is {}.", a, b, a-b);
Örnek - 3 parametre
Şöyle yaparız.
Object[] paramArray = {newVal, below, above};
logger.debug("Value {} was inserted between {} and {}.", paramArray);

SLF4J LoggerFactory Sınıfı
getLogger metodu
Root logger'î almak için şöyle yaparız.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main
{
  static final Logger logger = LoggerFactory.getLogger(Main.class);

  public static void main(String[] args)
  {
    logger.info("Main started");
  }
}
Örnek
Elimizde şöyle bir xml olsun
<?xml version="1.0"?>
<configuration>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>logfile.log</file>
        <append>true</append>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>
    <appender name="ANALYTICS-FILE" class="ch.qos.logback.core.FileAppender">
        <file>analytics.log</file>
        <append>true</append>
        <encoder>
            <pattern>%-4relative [%thread] %-5level %logger{35} - %msg %n</pattern>
        </encoder>
    </appender>
    <!-- additivity=false ensures analytics data only goes to the analytics log -->
    <logger name="analytics" level="DEBUG" additivity="false">
        <appender-ref ref="ANALYTICS-FILE"/>
    </logger>
    <root>
        <appender-ref ref="FILE"/>
    </root>
</configuration>
Belli bir logger'a isim ile erişmek için şöyle yaparız
Logger analytics = LoggerFactory.getLogger("analytics");
SLF4J Mapped Diagnostic Context (MDC) 
SLF4J - MDC yazısına taşıdım