Java日志体系概览

关于Java的日志API可以概括为日志记录的封装API和日志记录实现两类。前一种的典型代表是Apache Commons Logging和SLF4J,后一种的典型代表有JDK自带的日志实现(java.util.logging 包,JUL)以及著名的Log4j。日志封装API是为日志使用者提供了一种统一的接口,使用者可以根据需求来切换具体日志实现方案。

Java日志API

一般来说,日志API一般包括:

  • 记录器(Logger):日志 API 的使用者通过记录器来发出日志记录请求,并提供日志的内容。在记录日志时,需要指定日志的严重性级别。
  • 格式化器(Formatter):对记录器所记录的文本进行格式化,并添加额外的元数据。
  • 处理器(Handler):把经过格式化之后的日志记录输出到不同的地方。常见的日志输出目标包括控制台、文件和数据库等。

Java日志封装API

在这主要介绍一下目前使用较多的SLF4J日志库,SLF4J 库中核心的 API 是提供工厂方法的 org.slf4j.LoggerFactory 类和记录日志的 org.slf4j.Logger 接口。通过 LoggerFactory 类的 getLogger 方法来获取日志记录器对象。Logger 接口中的方法也是按照不同的严重性级别来进行分组的。
参考:Java日志最佳实践

Jeesite中Log4j的使用分析

Jeesite中使用了Log4j日志记录作为实现,下面依次分析一下Log4j的配置文件和封装了Log4j的Manager类。

Log4j的配置

Log4j支持使用Java properties和xml作为配置文件。相对于xml的格式,Java properties更易读。下面以Jeesite中的log4j.properties为例,所有配置项均以log4j开头,主要配置项为rootlogger,输出终端,输出布局模式。

log4j.rootLogger=WARN, Console, RollingFile  

Logger是日志记录器,而rootLogger则是所有Logger的祖先,可以通过Logger.getRootLogger()来获取。

#Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

#RollingFile
log4j.appender.RollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.RollingFile.File=../logs/jeesite.log
log4j.appender.RollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.RollingFile.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n

Log4j的输出终端,由Appender接口定义。其有多种实现,不同实现就对应则不同的日志输出保存方式。例如:

  • ConsoleAppender(控制台)
  • FileAppender(文件)
  • DailyRollingFileAppender(每天都产生一个日志文件)
  • RollingFileAppender(文件大小达到指定尺寸时产生一个新的日志文件,文件名称上会自动添加数字序号。)
  • WriterAppender(将日志信息以流的格式发送到任意指定的地方)

而所有子类Logger均将继承父类Logger的Appender配置。 至于输出布局模式——由Layout接口定义——也有多种实现:

  • PatternLayout(可以灵活地指定布局模式)
  • HTMLLayout(以HTML表格形式布局)
  • SimpleLayout(包含日志信息的级别和信息字符串)
  • TTCCLayout(包含日志产生的时间、线程、类别等信息)

ConversionPattern使用的是类似于C语言中printf的格式化输出参数:

  • %c:输出所属的类目,通常就是所在类的全名。
  • %d:输出日志时间点的日期或时间,默认格式为ISO8601,推荐使用“%d{ABSOLUTE}”
  • %t:输出产生该日志线程的线程名。
  • %p:输出优先级。
  • %m:输出代码中指定的消息。
  • %n:输出一个回车换行符。Windows平台为“\r\n”,UNIX为“\n”。

所以jeesite中的日志输出格式为:

Output pattern : date [thread] priority category - message FATAL 0 ERROR 3 WARN 4 INFO 6 DEBUG 7

每个Logger都有一个日志记录级别,对应不同程度的信息,按照优先级可以排列如下:ALL<DEBUG<INFO<WARN<ERROR<FATAL<OFF

#Hibernate level
#log4j.logger.org.hibernate=ERROR
log4j.logger.org.hibernate.cache.ehcache.AbstractEhcacheRegionFactory=ERROR
log4j.logger.org.hibernate.search.impl.ConfigContext=ERROR
log4j.logger.net.sf.ehcache.config.CacheConfiguration=ERROR

#Project defalult level
log4j.logger.com.thinkgem.jeesite=DEBUG

这里分别配置了Hibernate操作数据库的日志记录级别和项目的默认日志记录级别,均为DEBUG,即与程序运行时的流程相关的详细信息。

Log4jManager类

Log4jManager类对于Log4j,主要是对于Log4j的日志记录级别进行管理配置。使用了叫JMX(Java Management Extensions,即Java管理扩展)——一个为应用程序、设备、系统等植入管理功能的框架——对日志记录级别进行管理。此处把Log4j作为Mbean(管理Bean组件)来管理。首先来看一些Getters/Setters函数

@ManagedAttribute(description = "Level of the root logger")
public String getRootLoggerLevel() {
    Logger logger = Logger.getRootLogger();
    return logger.getEffectiveLevel().toString();
}

@ManagedAttribute
public void setRootLoggerLevel(String newLevel) {
    Logger logger = Logger.getRootLogger();
    Level level = Level.toLevel(newLevel);
    logger.setLevel(level);
    managerLogger.info("设置Root Logger 级别为{}", newLevel);
}

这是对根日志记录器进行日志级别设置和获取。managerLogger对象是通过LoggerFactory.getLogger(Log4jManager.class)这一日志记录器工厂来获取的。

@ManagedOperation(description = "Get logging level of the logger")
@ManagedOperationParameters({ @ManagedOperationParameter(name = "loggerName", description = "Logger name") })
public String getLoggerLevel(String loggerName) {
    Logger logger = Logger.getLogger(loggerName);
    return logger.getEffectiveLevel().toString();
}

/**
 * 设置Logger的日志级别.
 * 如果日志级别名称错误, 设为DEBUG.
 */
@ManagedOperation(description = "Set new logging level to the logger")
@ManagedOperationParameters({ @ManagedOperationParameter(name = "loggerName", description = "Logger name"),
        @ManagedOperationParameter(name = "newlevel", description = "New level") })
public void setLoggerLevel(String loggerName, String newLevel) {
    Logger logger = Logger.getLogger(loggerName);
    Level level = Level.toLevel(newLevel);
    logger.setLevel(level);
    managerLogger.info("设置{}级别为{}", loggerName, newLevel);
}

同根Logger一样,此处的普通Logger和项目级别的Logger的Setter\Getter方法,也是大同小异的。

SYS模块中的Log部分

这部分的日志log,并未使用日志记录API,而是通过正常模块开发的形式,把一些操作信息存入数据库表(sys_log)中,其保存的信息包含:

private String id;          // 日志编号
private String type;        // 日志类型(1:接入日志;2:错误日志)
private User createBy;      // 创建者
private Date createDate;    // 日志创建时间
private String remoteAddr;  // 操作用户的IP地址
private String requestUri;  // 操作的URI
private String method;      // 操作的方式
private String params;      // 操作提交的数据
private String userAgent;   // 操作用户代理信息
private String exception;   // 异常信息

日志查询可以在登录后台系统后,通过host/sys/log这个url来访问。