閱讀867 返回首頁    go 技術社區[雲棲]


SLF4J 擴展(二)

事件日誌(Event Logging)

EventLogger 類提供了一個簡單的機製,這個機製可用於記錄應用中發生的事件。

EventLogger 的推薦使用方式,例如在網站應中,將數據填入到 SLF4J 的 MDC,這裏數據貫
穿一個請求的始末,這些數據中包含用戶 ID,用戶的 IP 地址,商品名稱等等。這些可以非常容易
地在 Servlet 過濾器(filter)中完成,在這裏 MDC 也可以請求結束後清理掉。當一個事件需
要被記錄並重現時,一個 EventData 應該被創建並發布。然後調用 EventLogger.logEvent(data)
這裏 data 就是指向 EventData 對象的引用。

import org.slf4j.MDC;
import org.apache.commons.lang.time.DateUtils;

import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.TimeZone;

public class RequestFilter implements Filter
{
  private FilterConfig filterConfig;
  private static String TZ_NAME = "timezoneOffset";

  public void init(FilterConfig filterConfig) throws ServletException {
    this.filterConfig = filterConfig;
  }

  /**
   * Sample filter that populates the MDC on every request.
   */
  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                       FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)servletRequest;
    HttpServletResponse response = (HttpServletResponse)servletResponse;
    MDC.put("ipAddress", request.getRemoteAddr());
    HttpSession session = request.getSession(false);
    TimeZone timeZone = null;
    if (session != null) {
      // Something should set this after authentication completes
      String loginId = (String)session.getAttribute("LoginId");
      if (loginId != null) {
        MDC.put("loginId", loginId);
      }
      // This assumes there is some javascript on the user's page to create the cookie.
      if (session.getAttribute(TZ_NAME) == null) {
        if (request.getCookies() != null) {
          for (Cookie cookie : request.getCookies()) {
            if (TZ_NAME.equals(cookie.getName())) {
              int tzOffsetMinutes = Integer.parseInt(cookie.getValue());
              timeZone = TimeZone.getTimeZone("GMT");
              timeZone.setRawOffset((int)(tzOffsetMinutes * DateUtils.MILLIS_PER_MINUTE));
              request.getSession().setAttribute(TZ_NAME, tzOffsetMinutes);
              cookie.setMaxAge(0);
              response.addCookie(cookie);
            }
          }
        }
      }
    }
    MDC.put("hostname", servletRequest.getServerName());
    MDC.put("productName", filterConfig.getInitParameter("ProductName"));
    MDC.put("locale", servletRequest.getLocale().getDisplayName());
    if (timeZone == null) {
      timeZone = TimeZone.getDefault();
    }
    MDC.put("timezone", timeZone.getDisplayName());
    filterChain.doFilter(servletRequest, servletResponse);
    MDC.clear();
  }

  public void destroy() {
  }
}

使用 EventLogger 的示例類。

import org.slf4j.ext.EventData;
import org.slf4j.ext.EventLogger;

import java.util.Date;
import java.util.UUID;

public class MyApp {

  public String doFundsTransfer(Account toAccount, Account fromAccount, long amount) {
    toAccount.deposit(amount);
    fromAccount.withdraw(amount);
    EventData data = new EventData();
    data.setEventDateTime(new Date());
    data.setEventType("transfer");
    String confirm = UUID.randomUUID().toString();
    data.setEventId(confirm);
    data.put("toAccount", toAccount);
    data.put("fromAccount", fromAccount);
    data.put("amount", amount);
    EventLogger.logEvent(data);
    return confirm;
  }
}

EventLogger 類使用一個被命名為 EventLogger 的日誌。EventLogger 使用 INFO 級別
的日誌。下麵是一個使用 Logback 的配置。

<configuration>
  <appender name="STDOUT" >
    <layout >
      <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</Pattern>
    </layout>
  </appender>

  <appender name="events" >
    <layout >
      <Pattern>%d{HH:mm:ss.SSS} %X - %msg%n</Pattern>
    </layout>
  </appender>

  <logger name="EventLogger" additivity="false">
    <level value="INFO"/>
    <appender appender-ref="events"/>
  </logger>

  <root level="DEBUG">
    <appender-ref ref="STDOUT" />
  </root>

</configuration>

通過Java 代理(agent)來添加日誌

入門概要:

  1. Use Java 5 or later.
  2. 使用 Java 5 或者更高。
  3. Download slf4j-ext-1.7.19.jar and javassist.jar, and put them both in the same directory.
  4. 下載 slf4j-ext-1.7.19.jar 和 javassist.jar,並將它們放置在同一個目錄中。
  5. Ensure your application is properly configured with slf4j-api-1.7.19.jar and a
    suitable backend.
  6. 確保你的應用已經恰當地配置 slf4j-api-1.7.19.jar 和一個適當的後端。
  7. Instead of “java …​” use “java –javaagent:PATH/slf4j-ext-1.7.19.jar=time,verbose,level=info …​” (replace PATH with the path to the jar)
  8. 使用 java --javaagent:PATH/slf4j-ext-1.7.19.jar=time,verbose,level=info …​ (使用指向 Jar 的路徑來替換 PATH) 代替 java …​
  9. That’s it!
  10. 就這些!

在一些應用中,日誌是用於追蹤程序實際執行情況的,而不是用於記錄偶然發生的事情。一種實現方
式是使用擴展日誌在程序適當的地方添加語句;但還有一種方式,是使用工具通過修改編譯後的字節
碼的方式來添加語句。還有其他很多方式存在,當時包含在 slf4j-ext 中的並不是為比較而生的。
這隻是提供了一個方法,在既定程序中,快速獲取基本的追蹤信息。

在 Java 5 中,增加了 Instrumentation 機製,這個機製提供了 Java 代理(agent)功能,它
允許你在字節碼加載時,檢查和修改字節碼。這就可以讓原來的類文件保存不變,這種字節碼轉換隻
是在需要加載時才進行。

public class HelloWorld {
  public static void main(String args[]) {
    System.out.println("Hello World");
  }
}

一個典型的轉換入下: (import 語句別忽略)

public class LoggingHelloWorld {
  final static Logger _log = LoggerFactory.getLogger(LoggingHelloWorld.class.getName());

  public static void main(String args[]) {
    if (_log.isInfoEnabled()) {
      _log.info("> main(args=" + Arrays.asList(args) + ")");
    }
    System.out.println("Hello World");
    if (_log.isInfoEnabled()) {
      _log.info("< main()");
    }
  }
}

當執行類似 java LoggingHelloWorld 1 2 3 4 時,輸出也大致如下:

1 [main] INFO LoggingHelloWorld - > main(args=[1, 2, 3, 4])
Hello World
1 [main] INFO LoggingHelloWorld - < main()

可以使用下麵的命令,來達到同樣的效果(javassist.jar 和 slf4j-ext-1.7.19.jar 放在了相對路徑 ../jars 中)

java -javaagent:../jars/slf4j-ext-1.7.19.jar HelloWorld 1 2 3 4

如何使用

javaagent 可以指定一到多個使用逗號分割的選項。所支持的選項如下:

level=X
The log level to use for the generated log statements. X is one of “info”, “debug” or “trace”. Default is “info”.對於生成日誌語句所使用的日誌級別。其中 X 可取的值為: infodebugtrace。默認的級別為:info
time
打印出程序啟動的當前日期,並且在程序結尾再輸出以毫秒計算的程序執行時間。
verbose
Print out when a class is processed as part of being loaded打印出當一個類被裝載部分的處理
ignore=X:Y:…​
(高級特性)提供不不需要輸出日誌的類名前綴,使用冒號分割。默認的列表為:org/slf4j/:ch/qos/logback/:org/apache/log4j/
這還有一個不言自明的事實就是,為了能夠輸出日誌,類必須能夠訪問 slf4j-api 的類,如果這
些類不能訪問給點的類,則肯定不能重塑。

一些類使用 object.toString() 進行呈現時,也許變現行為不當。所以,應該在 logback 配
置文件明確聲明日誌不可用。在 Apache Jakarta commons lang 包中的 ToStringBuilder 就
是一個極好的例子。對於 logback,可以將下麵這個代碼片段添加到 logback.xml 中:

<logger name="org.apache.commons.lang.builder" level="OFF" />

這些還沒有最終確定,也許還可能會變。

jar 文件的存放位置

javassist 庫是用來實際進行字節碼操縱的,為了添加任意的日誌語句,它必須可用。slf4j-ext-1.7.19
可以像下麵這樣進行配置:

  • “javassist-3.4.GA.jar” 和 “slf4j-ext-1.7.19.jar” 可以使用 Maven 從倉庫下載。而且,
    “slf4j-ext-1.7.19.jar” 在 Maven 庫中,直接以 “-javaagent” 參數引用。
  • “javassist-3.4.GA.jar” 和 “slf4j-ext” 在同一個目錄下。

當 javassist 沒有被代理發現的話,會打印出警告信息。並且指定的字節碼轉換也不會工作。

其它注意事項

  • Java 代理不能用於已經被類加載器加載過的任何類。
  • Java 代理中的異常通常會被打印出來,也可能被 Java 虛擬機巧巧吞下。
  • Java 代理隻會打印到錯誤輸出(System.err)。
  • 日誌名稱變量是固定的(似乎也沒有太大的利用價值),所以,如果這個名字被使用了,將會發生
    錯誤。這樣確定一個沒有被使用的名字,然後使用它。
  • 空方法不會被重塑。(針對接口的正確性檢查)

(Java 代理是 java.util.logging 版本的一種適配。具體描述在 https://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html。)

轉載自 並發編程網 - ifeve.com

最後更新:2017-05-19 17:02:06

  上一篇:go  Java網絡教程: InetAddress
  下一篇:go  SLF4J 擴展(一)