Log4J 2 API

概述

Log4j2Api提供了编码程接口和定制者创建日志需要实现的适配器组件。Log4j 2将API和实现分开,尽管有可能做到,但这样做的主要目的不是为了支持多重实现而是要清晰地界定哪些类和方法在“普通的”应用程序中可以安全使用。

Hello World!

没有HelloWorld例子的介绍是不完整的介绍。下面是Log4j 2的Hello World。首先,从LogManager中获取一个名字为“HelloWorld”的Logger。接着,用这个logger输出“Hello World”信息。但是该消息只有当这个logger被配置为允许输出时才能输出消息。

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class HelloWorld {
    private static Logger logger = LogManager.getLogger("HelloWorld");
    public static void main(String[] args) {
        logger.info("Hello, World!");
    }
}

logger.info()调用的输出完全依赖于所使用的配置。更多细节请参见Configuration章节。

替换参数

通常记录日志的目的是提供发生的信息,例如系统中发生了什么,包括操作对象的信息。在Log4j 的1.x版本中可以通过执行下面代码达到此目的:

if (logger.isDebugEnabled()) {
    logger.debug("Logging in user " + user.getName() + " with birthday " + user.getBirthdayCalendar());
}

不断重复这样的代码会使得代码看起更像在出入记录日志而不是解决手头的问题。此外,这样会导致日志级别被验证两次:一次是在isDebugEnabled的情况下另一次在debug方法中。替代方案是:

logger.debug("Logging in user {} with birthday {}", user.getName(), user.getBirthdayCalendar());

上面的代码只会对日志级别验证一次,而且只有当debug日志启动时才会构造字符串。

格式化参数

如果toString()不能满足你的需要,替换数可以自定义格式。为了方便设置格式,可以使用与Java Formatter相似的格式来自定义格式化。例如:

public static Logger logger = LogManager.getFormatterLogger("Foo");
logger.debug("Logging in user %s with birthday %s", user.getName(), user.getBirthdayCalendar());
logger.debug("Logging in user %1$s with birthday %2$tm %2$te,%2$tY", user.getName(), user.getBirthdayCalendar());
logger.debug("Integer.MAX_VALUE = %,d", Integer.MAX_VALUE);
logger.debug("Long.MAX_VALUE = %,d", Long.MAX_VALUE)

要使用格式化的Logger,必须调用LogManager中的某一个getFormatterLogger方法。从这个例子的输出看以看出Calender的toString()相对于自定义格式而言显得十分繁琐。

2012-12-12 11:56:19,633 [main] DEBUG: User John Smith with birthday java.util.GregorianCalendar[time=?,areFieldsSet=false,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=1995,MONTH=4,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=23,DAY_OF_YEAR=?,DAY_OF_WEEK=?,DAY_OF_WEEK_IN_MONTH=?,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=?,ZONE_OFFSET=?,DST_OFFSET=?]
2012-12-12 11:56:19,643 [main] DEBUG: User John Smith with birthday 05 23, 1995
2012-12-12 11:56:19,643 [main] DEBUG: Integer.MAX_VALUE = 2,147,483,647
2012-12-12 11:56:19,643 [main] DEBUG: Long.MAX_VALUE = 9,223,372,036,854,775,807

过程追踪

Logger 类提供的日志方法对跟踪程序的执行路径非常有用。生成的日志事件可以从别的调试日志中过滤出来。建议自由使用这些方法,因为他们的输出:

  • 在不需要请求调试session的情况下,有助于诊断开发中的问题
  • 在不能调试的情况下,有助于诊断产品中的问题
  • 可以帮助新手学习应用程序

最常用的两个方法是entry()和exit().entry()应该放在除了getter和setter之外所有方法的开头。entry()可以接受0到4个参数。entry()方法记录TRACE级别的日志并且使用名为“ENTER”的Marker,后者也是一种“FLOW” Marker。

exit()方法应该放在任何return语句之前,如果没有return语句就作为最后一条语句使用。exit()可以接受带参数或不带参数的调用。通常,返回值为void的方法使用exit(),而返回Object类型的方法使用exit(Object obj)。exit()方法的记录日志运用TRACE等级还使用名字为“EXIT”的Marker,后者也是一种“FLOW”Marker。

当程序遇到无法处理的异常时,比如RuntimeException,可以使用throwing()方法确保提供正确的诊断。生成的日志事件具有ERROR等级并使用名字为“THROWING”的相关的Marker,后者也是一种”EXCEPTION” Marker。

当程序捕获一个异常并且不打算继续向外抛时,可以使用catching()方法。生成的日志事件具有ERROR等级并用名字为“CATCHING”的相关的Marker,后者也是一种”EXCEPTION” Marker。

下面的例子给出一个使用了这些方法的简单应用程序,这些用法非常典型。示例中没有抛出但未被处理的异常,所以没用到throwing()方法。

package com.test;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import java.util.Random;

public class TestService {
    private Logger logger = LogManager.getLogger(TestService.class.getName());

    private String[] messages = new String[] {
        "Hello, World",
        "Goodbye Cruel World",
        "You had me at hello"
    };
    private Random rand = new Random(1);

    public String retrieveMessage() {
        logger.entry();
        String testMsg = getMessage(getKey());
        return logger.exit(testMsg);
    }

    public void exampleException() {
        logger.entry();
        try {
            String msg = messages[messages.length];
            logger.error("An exception should have been thrown");
         } catch (Exception ex) {
            logger.catching(ex);
        }
        logger.exit();
    }

    public String getMessage(int key) {
        logger.entry(key);
        String value = messages[key];
        return logger.exit(value);
    }

    private int getKey() {
        logger.entry();
        int key = rand.nextInt(messages.length);
        return logger.exit(key);
    }
}

此测试程序使用了上面描述的服务来生成日志事件。

package com.test;

public class App {
    public static void main( String[] args ) {
        TestService service = new TestService();
        service.retrieveMessage();
        service.retrieveMessage();
        service.exampleException();
    }
}

下面的配置会使得所有的日志输出路由到target/test.log文件。FileAppender的模式包括类名、行数和方法名。这些在日志中都是非常有价值的信息。

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="error">
<appenders>
    <Console name="Console" target="SYSTEM_OUT">
        <ThresholdFilter level="ERROR" onMatch="ACCEPT" onMismatch="DENY"/>
        <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
    </Console>
    <File name="log" fileName="target/test.log" append="false">
        <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n"/>
    </File>
</appenders>
<loggers>
    <root level="trace">
        <appender-ref ref="log"/>
    </root>
</loggers>
</configuration>

下面是Java类和配置的结果的输出。

19:08:07.056 TRACE com.test.TestService 19 retrieveMessage -  entry
19:08:07.060 TRACE com.test.TestService 46 getKey -  entry
19:08:07.060 TRACE com.test.TestService 48 getKey -  exit with (0)
19:08:07.060 TRACE com.test.TestService 38 getMessage -  entry parms(0)
19:08:07.060 TRACE com.test.TestService 42 getMessage -  exit with (Hello, World)
19:08:07.060 TRACE com.test.TestService 23 retrieveMessage -  exit with (Hello, World)
19:08:07.061 TRACE com.test.TestService 19 retrieveMessage -  entry
19:08:07.061 TRACE com.test.TestService 46 getKey -  entry
19:08:07.061 TRACE com.test.TestService 48 getKey -  exit with (1)
19:08:07.061 TRACE com.test.TestService 38 getMessage -  entry parms(1)
19:08:07.061 TRACE com.test.TestService 42 getMessage -  exit with (Goodbye Cruel World)
19:08:07.061 TRACE com.test.TestService 23 retrieveMessage -  exit with (Goodbye Cruel World)
19:08:07.062 TRACE com.test.TestService 27 exampleException -  entry
19:08:07.077 DEBUG com.test.TestService 32 exampleException - catching java.lang.ArrayIndexOutOfBoundsException: 3
       at com.test.TestService.exampleException(TestService.java:29) [classes/:?]
       at com.test.App.main(App.java:9) [classes/:?]
       at com.test.AppTest.testApp(AppTest.java:15) [test-classes/:?]
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29]
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29]
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29]
       at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29]
       at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52) [junit-4.3.1.jar:?]
       at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35) [surefire-junit4-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115) [surefire-junit4-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97) [surefire-junit4-2.7.2.jar:2.7.2]
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29]
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29]
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29]
       at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29]
       at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103) [surefire-booter-2.7.2.jar:2.7.2]
       at $Proxy0.invoke(Unknown Source) [?:?]
       at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150) [surefire-booter-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91) [surefire-booter-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69) [surefire-booter-2.7.2.jar:2.7.2]
19:08:07.087 TRACE com.test.TestService 34 exampleException -  exit

上述例子中,简单地将根Logger等级改为DEBUG就可以大大地减少日志输出。

19:13:24.963 DEBUG com.test.TestService 32 exampleException - catching java.lang.ArrayIndexOutOfBoundsException: 3
       at com.test.TestService.exampleException(TestService.java:29) [classes/:?]
       at com.test.App.main(App.java:9) [classes/:?]
       at com.test.AppTest.testApp(AppTest.java:15) [test-classes/:?]
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29]
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29]
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29]
       at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29]
       at org.junit.internal.runners.TestMethodRunner.executeMethodBody(TestMethodRunner.java:99) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestMethodRunner.runUnprotected(TestMethodRunner.java:81) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestMethodRunner.runMethod(TestMethodRunner.java:75) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestMethodRunner.run(TestMethodRunner.java:45) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassMethodsRunner.invokeTestMethod(TestClassMethodsRunner.java:66) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassMethodsRunner.run(TestClassMethodsRunner.java:35) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassRunner$1.runUnprotected(TestClassRunner.java:42) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.BeforeAndAfterRunner.runProtected(BeforeAndAfterRunner.java:34) [junit-4.3.1.jar:?]
       at org.junit.internal.runners.TestClassRunner.run(TestClassRunner.java:52) [junit-4.3.1.jar:?]
       at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:35) [surefire-junit4-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:115) [surefire-junit4-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:97) [surefire-junit4-2.7.2.jar:2.7.2]
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.6.0_29]
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) ~[?:1.6.0_29]
       at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) ~[?:1.6.0_29]
       at java.lang.reflect.Method.invoke(Method.java:597) ~[?:1.6.0_29]
       at org.apache.maven.surefire.booter.ProviderFactory$ClassLoaderProxy.invoke(ProviderFactory.java:103) [surefire-booter-2.7.2.jar:2.7.2]
       at $Proxy0.invoke(Unknown Source) [?:?]
       at org.apache.maven.surefire.booter.SurefireStarter.invokeProvider(SurefireStarter.java:150) [surefire-booter-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.booter.SurefireStarter.runSuitesInProcess(SurefireStarter.java:91) [surefire-booter-2.7.2.jar:2.7.2]
       at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:69) [surefire-booter-2.7.2.jar:2.7.2]

Marker

日志框架主要目的之一是在需要时生成调试和诊断信息,并且支持过滤功能避免因为信息过量使得想利用它的系统和人们望而却步。例如一个应用期望记录入口、出口以及其他与SQL无关的操作,还希望数据查询和更新操作的日志独立开来。实现此功能的方式之一如下:

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.util.Map;

public class MyApp {
    private Logger logger = LogManager.getLogger(MyApp.class.getName());
    private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL");
    private static final Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE", SQL_MARKER);
    private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY", SQL_MARKER);

    public String doQuery(String table) {
        logger.entry(param);
        logger.debug(QUERY_MARKER, "SELECT * FROM {}", table);
        return logger.exit();
    }

    public String doUpdate(String table, Map<String, String> params) {
        logger.entry(param);
        if (logger.isDebugEnabled()) {
        logger.debug(UPDATE_MARKER, "UPDATE {} SET {}", table, formatCols);
        return logger.exit();
    }

    private String formatCols(Map<String, String> cols) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Map.Entry<String, String> entry : cols.entrySet()) {
            if (!first) {
                sb.append(", ");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
            first = false;
        }
        return sb.toString();
    }
}

在上述的例子中,通过添加MarkerFilter可以实现:只允许记录SQL更新操作,记录所有SQL更新或者记录程序的所有操作。

使用Marker时,必须注意下面一些重要的规则:

  1. Marker必须是唯一的。他们都以名字进行注册,因此需要格外小心确保将程序中的Marker与依赖中的Marker不同,除非你希望两者一样。
  2. 与Log4j2中的许多对象一样,Marker是不可变的。如果一个Marker有父Marker,那么必须在此Marker创建的时候给他的父Marker命名,之后父Marker不能再改变。
  3. Marker只能有一个父Marker,然而可以允许有多个子Marker。

事件记录

EventLogger类提供了一个简单的机制用以记录程序中发生的事件。虽然EventLogger作为始发事件处理审计记录系统非常有用,但是它本身不实现任何审计日志系统功能,例如保证发送。

在典型的Web应用中,推荐使用EventLogger的方式是将与整个请求寿命相关的数据填充到ThreadContextMap中,比如用户的id和ip地址,产品名称等。在Servlet过滤器中可以轻松实现,并且可以请求结束时清空ThreadContextMap。当需要记录的一个事件发生时,应该创建并填充一个StructuredDataMessage。然后调用EventLogger.logEvent(msg),其中msg是StructuredDataMessage的引用。

import org.apache.logging.log4j.ThreadContext;
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;
      ThreadContext.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) {
              ThreadContext.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);
                      }
                  }
              }
          }
      }
      ThreadContext.put("hostname", servletRequest.getServerName());
      ThreadContext.put("productName", filterConfig.getInitParameter("ProductName"));
      Threadcontext.put("locale", servletRequest.getLocale().getDisplayName());
      if (timeZone == null) {
          timeZone = TimeZone.getDefault();
      }
      ThreadContext.put("timezone", timeZone.getDisplayName());
      filterChain.doFilter(servletRequest, servletResponse);
      ThreadContext.clear();
  }

  public void destroy() {
  }
}

使用EventLogger的示例Sample类:

import org.apache.logging.log4j.StructuredDataMessage;
import org.apache.logging.log4j.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);
            String confirm = UUID.randomUUID().toString();
            StructuredDataMessage msg = new StructureDataMessage(confirm, null, "transfer");
            msg.put("toAccount", toAccount);
            msg.put("fromAccount", fromAccount);
            msg.put("amount", amount);
            EventLogger.logEvent(data);
            return confirm;
    }
}

EventLogger类使用名为“EventLogger”的Logger。EventLogger使用OFF等级日志作为默认值用以表示它不能被过滤。这些事件可以使用StructuredDataLayout格式化输出样式。

消息

尽管Log4j 2提供的方法可以接受字符串和对象,但最终都被与当前日志事件相关联的消息对象所捕获。应用程序可以自由构建自己的消息并将这些消息传给Logger。尽管这看起来比直接将消息格式和参数传给事件花销更大,但是测试结果显示,在现今的JVM中尤其是当复杂的任务封装在消息中而不是程序中的时候时,事件的创建和销毁所带来的花销很小。此外,当使用接受字符串和参数的方法时,如果任何配置的全局过滤器或者Logger日志等级允许的消息被处理时,就会创建底层的消息对象。

考虑到一个程序中有一个Map对象,其中包含{"Name" = "John Doe", "Address" = "123 Main St.", "Phone" = "(999) 555-1212"}和一个用户对象,用户对象拥有一个getId方法返回”jdoe”。开发者想要增加一条报告信息“User John Doe has logged in using id jdoe”,可以通过下面方式实现:

logger.info("User {} has logged in using id {}", map.get("Name"), user.getId());

虽然这本身并没有错误,但是如果是使用复杂性的对象和获得人们所期望的输出这就会增加这种技术的使用难度。作为替代方案可以使用消息:

logger.info(new LoggedInMessage(map, user));

在这个替代方案中,格式化委托给了LoggedInMessage对象getFormattedMessage方法。虽然这种替代方案创建了一个新的对象,但是在LoggedInMessage格式化之前,传给LoggedInMessage的对象的所有方法都没被调用。

消息的另一个好处是简化了Layout编写。在其他日志框架中,Layout必须单独地遍历所有的参数然后根据遇到的对象来决定如何继续使用。使用消息,Layout就可以选择将格式化委托给消息或者基于遇到的消息类型来执行格式化。

借用前面的例子来说明Marker可以识别记录的SQL语句和消息。首先定义消息:

public class SQLMessage implements Message {
    public enum SQLType {
        UPDATE,
        QUERY
    };

    prviate final SQLType type;
    private final String table;
    private final Map<String, String> cols;

    public SQLMessage(SQLType type, String table) {
        this(type, table, null);
    }

    public SQLMessage(SQLType type, String table, Map<String, String> cols) {
        this.type = type;
        this.table = table;
        this.cols = cols;
    }

    public String getFormattedMessage() {
        switch (type) {
        case UPDATE:
          return createUpdateString();
          break;
        case QUERY:
          return createQueryString();
          break;
        default;
        }
    }

    public String getMessageFormat() {
     return type + " " + table;
    }

    public Object getParameters() {
        return cols;
    }

    private String createUpdateString() {
    }

    private String createQueryString() {
    }

    private String formatCols(Map<String, String> cols) {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Map.Entry<String, String> entry : cols.entrySet()) {
            if (!first) {
                sb.append(", ");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
            first = false;
        }
        return sb.toString();
    }
}

接下来我们可以在程序中使用定义好的消息:

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import java.util.Map;

public class MyApp {
  private Logger logger = LogManager.getLogger(MyApp.class.getName());
  private static final Marker SQL_MARKER = MarkerManager.getMarker("SQL");
  private static final Marker UPDATE_MARKER = MarkerManager.getMarker("SQL_UPDATE", SQL_MARKER);
  private static final Marker QUERY_MARKER = MarkerManager.getMarker("SQL_QUERY", SQL_MARKER);

  public String doQuery(String table) {
      logger.entry(param);
      logger.debug(QUERY_MARKER, new SQLMessage(SQLMessage.SQLType.QUERY, table);
      return logger.exit();
  }

  public String doUpdate(String table, Map<String, String> params) {
      logger.entry(param);
      logger.debug(UPDATE_MARKER, new SQLMessage(SQLMessage.SQLType.UPDATE, table, parmas);
      return logger.exit();
  }
}

注意与之前的示例版本相比,doUpdate中的logger.debug不用再封装在isDebugEnabled中,从而来调用创建拥有相同顺序级别表现的SQLMessage的检查。此外,现在所有SQL列的格式化都藏在SQLMessage中没再与业务逻辑混在一起。最后,当遇到SQLMessage时如果有需要可以重写Filter和Layout来执行一些特殊的行为。

格式化消息

消息模式传给 FormattedMessage 之后,首先检查是不是有效的java.text.MessageFormat的消息模式。如果是,一个FormattedMessage将用来对其进行格式化。如果不是,它将被检查是否包含一些String.format()的有效的格式说明符标记。如果是这样,一个StringFormattedMessage将用来对其进行格式化。最后,如果模式还是不匹配上面任何一种,会用一个ParameterizedMessage 来对其进行格式化。

LocalizedMessage

LocalizedMessage 的用途主要是提供对Log4j 1.x的兼容。一般地来说,最好的办法是本地化,也就是在客户端的UI来处理事件。

LocalizedMessage 包含了ResourceBundle,并且允许消息模式参数作为消息模式绑定中的键值。如果绑定没有被指定,LocalizedMessage将会根据记录事件中使用的Logger姓名来尝试查找绑定。根据绑定而恢复的消息将使用FormattedMessage来格式化。

LoggerNameAwareMessage

LoggerNameAwareMessage 是一个有setLoggerName方法的接口。这个方法在事件创建时将被调用,所以当这个消息被格式化时,这个消息的Logger的名字将被用于记录这个事件。

MapMessage

一个MapMessage包含了一个以String为键和值的Map。MapMessage实现了FormattedMessage接口并且接受”XML”格式的标识符。这样Map将能转化为XML格式。否则Map将会被格式化为”key1=value1 key2=value2…”。

MessageFormatMessage

MessageFormatMessage处理的消息使用一个可转换的格式。虽然这个消息比ParameterizedMessage更具灵活性,但是它却比后者慢了约2倍。

MultiformatMessage

MultiformatMessage拥有getFormats和getFormattedMessage方法,这样可以就接受字符串格式数组。这个getFormats方法可以被Layout调用来,并提供给Layout关于这个消息所能支持的各种格式的选项信息。Layout可能为了格式一次或多次调用getFormattedMessage。如果消息不识别格式的名称,它会简单地使用它的默认格式格式化数据。这样一个例子是接受”XML”格式字符串的StructuredDataMessage,这导致它将使用XML格式来格式化事件数据,而不是使用RFC5424格式。

ObjectMessage

通过调用自己的toString方法来格式化一个对象。

ParameterizedMessage

ParameterizedMessage 处理的消息包含了用”{}”来表示可更换的令牌和替换的参数。

SimpleMessage

SimpleMessage 包含了一个不需要格式化的String。

StringFormattedMessage

StringFormattedMessage 处理的消息使用一个可转换的格式,并且符合于java.lang.String.format().。虽然这个消息比ParameterizedMessage更具灵活性,但是它却比后者慢了约5到10倍。

StructuredDataMessage

StructuredDataMessage 允许应用程序添加项目到Map中,以及允许一个信息格式化为根据RFC 5424格式格式化的一个结构化的数据元素来设置id。

ThreadDumpMessage

一个ThreadDumpMessage如果被记录,将会为所有的进程产生堆跟踪。如果是在Java 6及以上版本这个堆跟踪将包含一些被持有的锁。

TimestampMessage

TimestampMessage将提供一个getTimestamp方法,此方法在事件创建时会被调用。这个消息中的时间戳将会用于代替当前的时间戳。

原文链接: apache 翻译: ImportNew.com - 杨帆
译文链接: http://www.importnew.com/5383.html
[ 转载请保留原文出处、译者和译文链接。]



相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部