Java日志系统


0. 概述

在项目开发中,为了跟踪代码的运行情况,常常要使用日志来记录信息。

在Java世界中,有很多的日志工具库来实现日志功能,避免我们重复造轮子,下面我们就来逐一了解日志工具。

1. 最原始的日志记录方式

最早期的Java程序中,使用System.out.println()把需要显示的内容打印到屏幕,这种方式使用起来非常简单,但是缺点却是非常多的:

- 输出内容不可控

- 可读性差

- 大量的IO操作使程序变慢

public class SystemOutPrintln {

    public static boolean debug = false;
    public static void main(String[] args) {
        for(int count = 0; count < 4; count++) {
            if(count % 2 == 0) {
                debug = true;
            } else {
                debug = false;
            }
            if(debug) {
                System.out.println("系统信息:第" + count + "次打印。");
            }
        }
    }
}

2. JDK的Logging

从JDK1.4开始,JDK自带了一套日至系统,其最大的优点是不需要任何其他类库的支持,只要有JDK就可以运行,但是其易用性、功能和扩展性很差,因此在商业上很少使用。

JDK Logging把日志分为9个级别,分别为:ALL、FINEST、FINER、FINE、CONFIG、INFO、WARNING、SERVER、OFF,等级依次升高,较高等级屏蔽较低等级。

下面来看一个例子:

import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @JDK 1.8
 * @author weegee
 */
public class JDKLogging {

    public static final Logger logger = Logger.getLogger(JDKLogging.class.toString());

    //初始化日志
    static {
        //添加一个控制台输出
        Handler console = new ConsoleHandler();
        //添加到logger中
        logger.addHandler(console);
    }

    public static void main(String[] args) {
        //设置日志级别为CONFIG
        logger.setLevel(Level.CONFIG);
        logger.fine("FINE");
        logger.config("CONFIG");
        logger.info("INFO");
        logger.warning("WARNING");
        logger.log(Level.SEVERE,"SERVER");
    }
}

输出结果为:

十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
信息: INFO
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
信息: INFO
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
警告: WARNING
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
警告: WARNING
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
严重: SERVER
十月 07, 2016 6:34:16 下午 com.weegee.log.Logging.JDKLogging main
严重: SERVER

这里我们会碰到几个问题:

1. 我们会惊奇为什么明明设置Level为CONFIG,为什么CONFIG等级的信息没有输出?

2. 为什么信息输出了两次?

3. 输出的日志信息的格式、时间和方法名是怎么来的?

我们依次解答:

1. 对于JDK自带的Logging,会有一个对应的配置文件logging.properties,位置在$JAVA_HOME/jre/lib/logging.properties,我们首先看一个原始的配置文件。可以看到里面设置了默认的等级为INFO,因此INFO等级以下的信息就不会输出,要想输出其他等级的信息需要修改这里的level信息。

############################################################
#   Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################

############################################################
#   Global properties
############################################################

# "handlers" specifies a comma separated list of log Handler
# classes.  These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler

# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler

# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers.  For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO

############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################

# default file output is in user‘s home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

# Example to customize the SimpleFormatter output format
# to print one-line log message like this:
#     <level>: <log message> [<date/time>]
#
# java.util.logging.SimpleFormatter.format=%4$s: %5$s [%1$tc]%n

############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################

# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE
  1. 信息输出两次的原因是每个Logger都有一个默认的Handler,在配置文件中有这么一行

    handlers= java.util.logging.ConsoleHandler

    因此我们不需要再给logger添加Handle,在程序中去掉static代码块的内容就会使日志只打印一次。

  2. 在配置文件中
    java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
    java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

    指定了获取得方法名和输出格式。

这里我们就需要了解一下Logging的工作流程。

- Logger

1. 代码需要输入日志的地方都会用到Logger,这几乎是一个JDK logging模块的代言人,用Logger.getLogger(XXX)获得一个logger,然后使用logger做日志的输出;

2. Logger其实只是一个逻辑管理单元,其多数操作都只是作为一个中继者传递别的<角色>,比如说:Logger.getLogger(“xxx”)的调用将会依赖于LogManager类,使用Logger输入日志信息的时候会调用Logger中的所有Handler进行日志的输入;

3. Logger是有层次关系的,我们可一般性的理解为包名之间的父子继承关系。每个Logger通常以java包名为其名称。子Logger通常会从父Logger继承Logger级别、Handler、ResourceBundle名(与国际化信息有关)等,例如:

    ```
    public static final Logger logger = Logger.getLogger(JDKLogging.class.toString());
    ```

    和

    ```
    public static final Logger logger = Logger.getLogger("com.weegee.log.Logging");
    ```

JDKLogging是包com.weegee.log.Logging下的java程序,那么通过java程序获得的Logger就继承通过包名获得的Logger,子类会继承父类的输出格式等信息;

4. 整个JVM会存在一个名称为空的root logger,所有匿名的logger都会把root logger作为其父。

- LogManager:整个JVM内部所有logger的管理,logger的生成、获取等操作都依赖于它,也包括配置文件的读取。LogManager中会有一个Hashtable

[private Hashtable<String,WeakReference<Logger>> loggers]

用于存储目前所有的logger,如果需要获取logger的时候,Hashtable已经有存在logger的话就直接返回Hashtable中的,如果hashtable中没有logger,则新建一个同时放入Hashtable进行保存。

- Handler:用来控制日志输出的,比如JDK自带的ConsoleHanlder把输出流重定向到System.err输出,每次调用Logger的方法进行输出时都会调用Handler的publish方法,每个logger有多个handler。我们可以利用handler来把日志输入到不同的地方(比如文件系统或者是远程Socket连接)。

- Formatter:日志在真正输出前需要进行一定的格式话:比如是否输出时间?时间格式?是否输入线程名?是否使用国际化信息等都依赖于Formatter。

- Log Level:不必说,这是做容易理解的一个,也是logging为什么能帮助我们适应从开发调试到部署上线等不同阶段对日志输出粒度的不同需求。JDK Log级别从高到低为OFF(231-1)—>SEVERE(1000)—>WARNING(900)—>INFO(800)—>CONFIG(700)—>FINE(500)—>FINER(400)—>FINEST(300)—>ALL(-231),每个级别分别对应一个数字,输出日志时级别的比较就依赖于数字大小的比较。但是需要注意的是:不仅是logger具有级别,handler也是有级别,也就是说如果某个logger级别是FINE,客户希望输入FINE级别的日志,如果此时logger对应的handler级别为INFO,那么FINE级别日志仍然是不能输出的。

总结对应关系:

  • LogManagerlogger是1对多关系,整个JVM运行时只有一个LogManager,且所有的logger均在LogManager中;
  • loggerhandler是多对多关系,logger在进行日志输出的时候会调用所有的hanlder进行日志的处理;
  • handlerformatter是一对一关系,一个handler有一个formatter进行日志的格式化处理;
  • loggerlevel是一对一关系
  • hanlderlevel也是一对一关系 。

Logging的配置

JDK默认的logging配置文件为:$JAVA_HOME/jre/lib/logging.properties,可以使用系统属性java.util.logging.config.file指定相应的配置文件对默认的配置文件进行覆盖,配置文件中通常包含以下几部分定义:

1. handlers:用逗号分隔每个Handler,这些handler将会被加到root logger中。也就是说即使我们不给其他logger配置handler属性,在输出日志的时候logger会一直找到root logger,从而找到handler进行日志的输入;

2. .level是root logger的日志级别;

3. <handler>.xxx是配置具体某个handler的属性,比如java.util.logging.ConsoleHandler.formatter便是为ConsoleHandler配置相应的日志Formatter;

4. logger的配置,所有以[.level]结尾的属性皆被认为是对某个logger的级别的定义,如com.xyz.foo = SEVERE是给名为[com.xyz.foo]的logger定义级别为SEVERE。由于存在继承关系,因此[com.xyz.foo.level]的logger也是SEVERE。除了级别之外,还可以为logger定义handler和useParentHandlers(默认是为true)属性,如com.xyz.foo.handler=com.xyz.test.FooFileHandler(需要是一个extends java.util.logging.Handler的类)、com.xyz.foo.useParentHandlers=false(意味着com.bes.server这个logger进行日志输出时,日志仅仅被处理一次,用自己的handler输出,不会传递到父logger的handler)。

一个配置文件的例子:

#Level的五个等级SEVERE(最高值) 、WARNING 、INFO 、CONFIG 、FINE 、FINER 、FINEST(最低值)  

#为 Handler 指定默认的级别(默认为 Level.INFO)。
java.util.logging.ConsoleHandler.level=INFO
# 指定要使用的 Formatter 类的名称(默认为 java.util.logging.SimpleFormatter)。
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter  

# 为 Handler 指定默认的级别(默认为 Level.ALL)。
java.util.logging.FileHandler.level=INFO
# 指定要使用的 Formatter 类的名称(默认为 java.util.logging.XMLFormatter)。
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
# 指定要写入到任意文件的近似最大量(以字节为单位)。如果该数为 0,则没有限制(默认为无限制)。
java.util.logging.FileHandler.limit=1024000
# 指定有多少输出文件参与循环(默认为 1)。
java.util.logging.FileHandler.count=1
# 为生成的输出文件名称指定一个模式。有关细节请参见以下内容(默认为 "%h/java%u.log")。
java.util.logging.FileHandler.pattern=C:/SSLog%u.log
# 指定是否应该将 FileHandler 追加到任何现有文件上(默认为 false)。
java.util.logging.FileHandler.append=true  

handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler 

有关Logging的源码分析请参考JDK Logging深入分析

3. Log4j

Log4j是目前应用最广泛的日志控件,它把日志分为ALL、TRACE、DEBUG、INFO、WARNING、ERROR、FITAL、OFF等几个等级,等级依次升高,依然是高等级屏蔽低等级。使用Log4j需要下载相应的jar包

import org.apache.log4j.Logger;

public class Log4jExample {
    public static final Logger LOGGER = Logger.getLogger(Log4jExample.class);

    public static void main(String[] args) {

        try {
            String s = null;
            s.length();
        } catch (Exception e) {
            LOGGER.trace("TRACE",e);
            LOGGER.debug("DEBUG",e);
            LOGGER.info("INFO",e);
            LOGGER.warn("WARNING",e);
            LOGGER.error("ERROR",e);
        }
    }
}

此外还要为Log4j编写配置文件制定日志的输出信息,不然会提示错误,配置文件为名称为log4j.properties

# ERROR级别,输出到A1
log4j.rootLogger = ERROR, A1
# 定义A1为控制台输出
log4j.appender.A1 = org.apache.log4j.ConsoleAppender
# 定义A1输出格式
log4j.appender.A1.layout = org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

写好的配置文件要放到classpath路径下,这样程序会自动找到该文件,如果程序找不到log4j.properties文件会打印错误

log4j:WARN No appenders could be found for logger (com.weegee.log.Log4j.Log4jExample).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

另外,在使用Log4j打印日志的时候

LOGGER.debug(str1 + str2 + str3 + str4 + ...);

如果DEBUG级别不能使用(可能是要求的等级更高,比如ERROR),LOGGER.debug()会直接返回,但是上面的代码仍会浪费很多时间,因为括号内进行字符串拼接会浪费时间,因此在打印之前最好使用LOGGER.isDebugEnable()或者LOGGER.isEnableFoe(Priority.DEBUG)判断当前等级是否可用,可用再进行打印。

要是没有放到classpath路径下,我们需要额外的一行代码来指定配置文件

PropertyConfigurator.configure("src/log.properties");

配置文件不仅仅可以用.properties文件,也可以使用XML文件,上面的配置文件改成XML格式的文件后

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n" />
        </layout>
    </appender>
</log4j:configuration>

访问String类型的变量s的length()属性是ERROR级别的错误,因此输出结果为:

2016-10-07 20:19:35:0186 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR
java.lang.NullPointerException
    at com.weegee.log.Log4j.Log4jExample.main(Log4jExample.java:20)

Log4j在输出内容的时候并不同于常规的打开、写以及关闭文件,它只在初始化的时候打开文件,在程序结束的时候关闭文件,这样减少了I/O的次数,提高了运行的效率。

Log4j的配置文件相当灵活,可以输出了数据库、文件、邮箱等等,后面的内容我们再来详细介绍Log4j的配置文件。

4. commons-logging

commons-logging属于Apache commons类库,它并不是一个日志控件,仅仅是用来统一JDK的Logging和Log4j的API,如果classpath中又Log4j则使程序使用Log4j,如果没有则使用JDK的Logging,具体的日志功能则交给JDK的Logging和Log4j。对于不能确定日志方式的系统,commons-Logging是个不错的选择。Spring、Hibernate以及Struts等都是使用commons-logging。

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class CommonsLogging {
    public static final Log LOG = LogFactory.getLog(CommonsLogging.class);

    public static void main(String[] args) {

        try {
            String s = null;
            s.length();
        } catch (Exception e) {
            LOG.trace("TRACE",e);
            LOG.debug("DEBUG",e);
            LOG.info("INFO",e);
            LOG.warn("WARNING",e);
            LOG.error("ERROR",e);
        }
    }
}

如果有Log4j,commons-logging则会把输出日志的任务交给Log4j,则输出内容为

2016-10-07 20:47:08:0144 [com.weegee.log.commonslogging.CommonsLogging]-[ERROR] ERROR
java.lang.NullPointerException
    at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)

如果没有Log4j,则会使用JDK的Logging,输出为

十月 07, 2016 8:45:33 下午 com.weegee.log.commonslogging.CommonsLogging main
信息: INFO
java.lang.NullPointerException
    at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)
十月 07, 2016 8:45:33 下午 com.weegee.log.commonslogging.CommonsLogging main
警告: WARNING
java.lang.NullPointerException
    at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)
十月 07, 2016 8:45:33 下午 com.weegee.log.commonslogging.CommonsLogging main
严重: ERROR
java.lang.NullPointerException
    at com.weegee.log.commonslogging.CommonsLogging.main(CommonsLogging.java:16)

因为Logging的默认配置文件等级为INFO,因此输出了INFO及其以上等级的信息。

当然我们也可以显示的启用Log4j,需要编写commons-logging.properties配置文件并放在classpath下

org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger
org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl

5. Log4j详解

5.1 配置日志输出地为控制台

try {
            String s = null;
            s.length();
        } catch (Exception e) {
            LOGGER.trace("TRACE" + " : " + "null");
            LOGGER.debug("DEBUG" + " : " + "null");
            LOGGER.info("INFO" + " : " + "null");
            LOGGER.warn("WARNING" + " : " + "null");
            LOGGER.error("ERROR" + " : " + "null");
        }

配置文件为

# 根记录器级别为ERROR,输出到A1
log4j.rootLogger=ERROR, A1
# com.weegee.log包下的记录器为DEBUG级别
log4j.category.com.weegee.log=DEBUG
# 控制台输出
log4j.appender.A1=org.apache.log4j.ConsoleAppender
# DEBUG以上级别输出
log4j.appender.A1.Threshold=DEBUG
# 编码方式
log4j.appender.A1.Encoding=UTF-8
# 是否立即输出
log4j.appender.A1.ImmediateFlush=true
# 使用System.err输出
log4j.appender.A1.Target=System.err
# 输出格式
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
# 输出时间格式
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

输出内容

2016-10-07 21:09:48:0123 [com.weegee.log.Log4j.Log4jExample]-[DEBUG] DEBUG : null
2016-10-07 21:09:48:0128 [com.weegee.log.Log4j.Log4jExample]-[INFO] INFO : null
2016-10-07 21:09:48:0128 [com.weegee.log.Log4j.Log4jExample]-[WARN] WARNING : null
2016-10-07 21:09:48:0131 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null

5.2 配置日志输出地为文件

# 根记录器级别为ERROR,输出到文件
log4j.rootLogger=DEBUG,f
# Log4jExample类为DEBUG级别
log4j.category.com.weegee.log.Log4j.Log4jExample=DEBUG
# 输出到文件
log4j.appender.f = org.apache.log4j.FileAppender
# 输出文件位置
log4j.appender.f.File = log/log1.log
# 编码方式
log4j.appender.f.Encoding=UTF-8
# 是否在文件末尾追加内容
log4j.appender.f.Append=true
# 输出格式
log4j.appender.f.layout=org.apache.log4j.PatternLayout
# 输出时间格式
log4j.appender.f.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

5.3 配置输出按大小滚动的文件

输出文件达到指定的大小会自动更名,比如日志文件为logInfo.log,指定大小为1K,当logInfo.log达到1K的时候则会自动生成新的文件logInfo.log.1、logInfo.log.2、…,最多的文件数也可以设置。

# 根记录器级别为ERROR,输出到文件,滚动文件
log4j.rootLogger=DEBUG, rolling_file
# Log4jExample类为DEBUG级别
log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
# 输出到滚动文件
log4j.appender.rolling_file=org.apache.log4j.RollingFileAppender
# DEBUG以上输出
log4j.appender.rolling_file.Threshold=DEBUG
# 滚动文件位置
log4j.appender.rolling_file.File=log/rolling.log
# 是否追加
log4j.appender.rolling_file.Append=true
# 文件大小
log4j.appender.rolling_file.MaxFileSize=1KB
# 最多文件数
log4j.appender.rolling_file.MaxBackupIndex=10
# 是否立即输出
log4j.appender.rolling_file.ImmediateFlush=true
# 编码方式
log4j.appender.rolling_file.Encoding=UTF-8
log4j.appender.rolling_file.layout=org.apache.log4j.PatternLayout
log4j.appender.rolling_file.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

5.4 配置输出按日期滚动的文件

每当时间发生变化,文件自动更名(原来的文件还存在)。

# 根记录器级别为ERROR,输出到文件,滚动文件
log4j.rootLogger=DEBUG, dailly_rolling
# Log4jExample类为DEBUG级别
log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
# 输出到日期文件
log4j.appender.dailly_rolling=org.apache.log4j.DailyRollingFileAppender
# 输出文件位置
log4j.appender.dailly_rolling.File=log/daily_rolling.log
# 滚动日期格式
log4j.appender.dailly_rolling.DatePattern=.yyyy-MM-dd
log4j.appender.dailly_rolling.layout=org.apache.log4j.PatternLayout
log4j.appender.dailly_rolling.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%C]-[%p] %m%n

5.5 输出到数据库

# 根记录器级别为ERROR,输出到文件,滚动文件
log4j.rootLogger=DEBUG, DATABASE
# Log4jExample类为DEBUG级别
log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.Threshold=DEBUG
log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/log4j
log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver
log4j.appender.DATABASE.user=root
log4j.appender.DATABASE.password=123456
log4j.appender.DATABASE.sql=INSERT INTO tb_log (date, priority, message, classname ) VALUES (‘%d‘, ‘%p‘, ‘%m‘, ‘%c‘)
log4j.appender.DATABASE.layout=org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%C]-[%p] %m%n

5.6 输出到SMTP邮箱

发送到邮箱需要使用Java Mail包,下载地址mail.jar

SMTPAppender的默认级别书ERROR,低于该级别的信息不会被发送邮件,在使用SMTP邮件的时候要注意,如果短时间发送大量邮件会被网易、谷歌等大多数邮件服务器封掉IP,因此要谨慎使用。

# 根记录器级别为ERROR,输出到文件,滚动文件

log4j.rootLogger=DEBUG, MAIL
# Log4jExample类为DEBUG级别
log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG

## MAIL
log4j.appender.MAIL=org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold=DEBUG
# 缓存文件大小到达512K时发送邮件
log4j.appender.MAIL.BufferSize=512
# 发送邮件的服务器
log4j.appender.MAIL.SMTPHost=smtp.163.com
# 邮件主题
log4j.appender.MAIL.Subject=Log4J Error Message
# 用户名
log4j.appender.MAIL.SMTPUsername=username
# 密码
log4j.appender.MAIL.SMTPPassword=password
# 发件人地址
log4j.appender.MAIL.From=email_address
# 日志邮件的接收者
log4j.appender.MAIL.To=email_address
log4j.appender.MAIL.layout=org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern=[ErrorMessage] %d - %c -%-4r [%t] %-5p %c %x - %m%n

5.7 输出到SOCKET套接字

# 根记录器级别为ERROR,输出到文件,滚动文件
log4j.rootLogger=DEBUG, SOCKET
# Log4jExample类为DEBUG级别
log4j.logger.com.weegee.log.Log4j.Log4jExample=DEBUG
# 套接字输出
log4j.appender.SOCKET=org.apache.log4j.net.SocketAppender
# 远程主机名称
log4j.appender.SOCKET.RemoteHost=127.0.0.1
# 端口
log4j.appender.SOCKET.Port=9876
# 连接超时时间
log4j.appender.SOCKET.ReconnectionDelay=30000
# 是否发送本地信息给服务器
log4j.appender.SOCKET.LocationInfo=true
log4j.appender.SOCKET.layout=org.apache.log4j.PatternLayout
log4j.appender.SOCET.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSS} [%C]-[%p] %m%n

这里的服务器设置的是127.0.0.1,代表本机,然后监听端口是9876,我们还需要一个程序用来监听日志的输出并把日志显示在某个地方,Log4j自带一个简单的服务器。

import org.apache.log4j.net.SimpleSocketServer;
public class RunSimpleSocketServer {
    public static void main(String[] args) {
        String port = "9876";
        String file = "logProperties/socketFile.properties";
        /**
         * SimpleSocketServer.main有两个参数,第一个是监听的端口,要和
         * 配置文件中一致,第二个参数是接收道德日志信息输出的位置,这里我们
         * 使用file的日志记录输出到文件
         */
        SimpleSocketServer.main(new String[] {port, file});
    }
}

然后在socketFile.properties中配置日志的输出信息(这里是输出到文件),即可记录日志信息。

2016-10-08 20:25:51:0876 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Listening on port 9876
2016-10-08 20:25:51:0882 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Waiting to accept a new client.
2016-10-08 20:25:59:0081 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Connected to client at /127.0.0.1
2016-10-08 20:25:59:0082 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Starting new socket node.
2016-10-08 20:25:59:0217 [org.apache.log4j.net.SimpleSocketServer]-[INFO] Waiting to accept a new client.
2016-10-08 20:25:59:0084 [com.weegee.log.Log4j.Log4jExample]-[DEBUG] DEBUG : null
2016-10-08 20:25:59:0116 [com.weegee.log.Log4j.Log4jExample]-[INFO] INFO : null
2016-10-08 20:25:59:0117 [com.weegee.log.Log4j.Log4jExample]-[WARN] WARNING : null
2016-10-08 20:25:59:0121 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
2016-10-08 20:25:59:0442 [org.apache.log4j.net.SocketNode]-[INFO] Caught java.net.SocketException closing conneciton.

5.8 自定义输出

集成自AppenderSkeleton类就可以自定义日志输出信息,比如输出到某个级别的信息采取一些行动、输出信息达到一定次数采取一些行动等等。这里我们模拟输出信息达到一定次数采取一些行动。

# 根记录器
log4j.rootLogger=DEBUG
# Log4jExample类为DEBUG级别
log4j.category.com.weegee.log.Log4j.Log4jExample=ERROR, COUNTING
# 自定义输出的类
log4j.appender.COUNTING=com.weegee.log.Log4j.CountingConsoleAppender
# 最大输出次数
log4j.appender.COUNTING.limit=4
log4j.appender.COUNTING.layout=org.apache.log4j.PatternLayout
log4j.appender.COUNTING.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss:SSSS} [%C]-[%p] %m%n

com.weegee.log.Log4j.CountingConsoleAppender

package com.weegee.log.Log4j;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LoggingEvent;

public class CountingConsoleAppender extends AppenderSkeleton {

    int counter = 0;

    int limit = 16;

    public CountingConsoleAppender() {
    }

    public void setLimit(int limit) {
        this.limit = limit;
    }

    public int getLimit() {
        return limit;
    }

    public void append(LoggingEvent event) {
        if (this.layout == null) {
            // WRITING YOUR OWN APPENDER 111
            errorHandler.error("No layout set for the appender named [" + name
                    + "].", null, ErrorCode.MISSING_LAYOUT);
            return;
        }
        if (counter >= limit) {
            errorHandler.error("Counter limit[" + limit + "] reached in ["
                    + getName() + "] appender", null, ErrorCode.WRITE_FAILURE);
            return;
        }
        // output the events as formatted by our layout
        System.out.print(this.layout.format(event));
        // if our layout does not handle exceptions, we have to do it.
        if (layout.ignoresThrowable()) {
            String[] t = event.getThrowableStrRep();
            if (t != null) {
                int len = t.length;
                for (int i = 0; i < len; i++) {
                    System.out.println(t[i]);
                }
            }
        }
        // prepare for next event
        counter++;
    }

    public void close() {
        if (this.closed) // closed is defined in AppenderSkeleton
            return;
        this.closed = true;
    }

    public boolean requiresLayout() {
        return true;
    }
}

测试程序

package com.weegee.log.Log4j;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

public class Log4jExample {
    public static final Logger LOGGER = Logger.getLogger(Log4jExample.class);

    public static void main(String[] args) {
    //加载定义的配置文件
        PropertyConfigurator.configure("logProperties/userdefined.properties");

        int count = 5;
        while(count > 0) {
            try {
                String s = null;
                s.length();
            } catch (Exception e) {
                LOGGER.trace("TRACE" + " : " + "null");
                LOGGER.debug("DEBUG" + " : " + "null");
                LOGGER.info("INFO" + " : " + "null");
                LOGGER.warn("WARNING" + " : " + "null");
                LOGGER.error("ERROR" + " : " + "null");
            }
            count--;
            //输出4次后让系统休眠一段时间给自定义的日志处理类一些时间进行代码处理
            try {
                if(count == 1) {
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出结果

2016-10-08 20:37:12:0618 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
2016-10-08 20:37:12:0622 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
2016-10-08 20:37:12:0622 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
2016-10-08 20:37:12:0627 [com.weegee.log.Log4j.Log4jExample]-[ERROR] ERROR : null
log4j:ERROR Counter limit[4] reached in [COUNTING] appender

5.9 PatternLayout布局定义

如果您希望基于某种模式生成特定格式的日志信息,可使用 org.apache.Log4j.PatternLayout 格式化您的日志信息。PatternLayout 继承自抽象类 org.apache.Log4j.Layout,覆盖了其 format() 方法,通过提供的模式,来格式化日志信息。PatternLayout 是一个简单的 Layout 对象,提供了如下属性,该属性可通过配置文件更改:

序号 属性 & 描述
1 conversionPattern设置转换模式,默认为 %r [%t] %p %c %x - %m%n。

模式转换字符

下面的表格解释了上面模式中用到的字符,以及所有定制模式时能用到的字符:

转换字符 含义
c 使用它为输出的日志事件分类,比如对于分类 “a.b.c”,模式 %c{2} 会输出 “b.c” 。
C 使用它输出发起记录日志请求的类的全名。比如对于类 “org.apache.xyz.SomeClass”,模式 %C{1} 会输出 “SomeClass”。
d 使用它输出记录日志的日期,比如 %d{HH:mm:ss,SSS} 或 %d{dd MMM yyyy HH:mm:ss,SSS}。
F 在记录日志时,使用它输出文件名。
l 用它输出生成日志的调用者的地域信息。
L 使用它输出发起日志请求的行号。
m 使用它输出和日志事件关联的,由应用提供的信息。
M 使用它输出发起日志请求的方法名。
n 输出平台相关的换行符。
p 输出日志事件的优先级。
r 使用它输出从构建布局到生成日志事件所花费的时间,以毫秒为单位。
t 输出生成日志事件的线程名。
x 输出和生成日志事件线程相关的 NDC (嵌套诊断上下文)。
X 该字符后跟 MDC 键,比如 X{clientIP} 会输出保存在 MDC 中键 clientIP 对应的值。
% 百分号, %% 会输出一个 %。

格式修饰符

缺省情况下,信息保持原样输出。但是借助格式修饰符的帮助,就可调整最小列宽、最大列宽以及对齐。

下面的表格涵盖了各种修饰符:

格式修饰符 左对齐 最小宽度 最大宽度 注释
%20c 20 如果列名少于 20 个字符,左边使用空格补齐。
%-20c 20 如果列名少于 20 个字符,右边使用空格补齐。
%.30c 不适用 30 如果列名长于 30 个字符,从开头剪除。
%20.30c 20 30 如果列名少于 20 个字符,左边使用空格补齐,如果列名长于 30 个字符,从开头剪除。
%-20.30c 20 30 如果列名少于 20 个字符,右边使用空格补齐,如果列名长于 30 个字符,从开头剪除。
%d{yyyy-MM-dd}-%t-%x-%-5p-%-10c:%m%n

输出的信息则如下

2016-10-08-main--DEBUG-Log4jExample:Hello this is an debug message

5.10 HTMLLayout布局

# 根记录器级别为ERROR,输出到文件
log4j.rootLogger=DEBUG
# Log4jExample类为DEBUG级别
log4j.category.com.weegee.log.Log4j.Log4jExample=DEBUG, f
# 输出到文件
log4j.appender.f = org.apache.log4j.FileAppender
# 输出文件位置
log4j.appender.f.File = log/log.html
# 编码方式
log4j.appender.f.Encoding=UTF-8
# 是否在文件末尾追加内容
log4j.appender.f.Append=true
# 输出格式
log4j.appender.f.layout=org.apache.log4j.HTMLLayout

5.11 XMLLayout布局

# 根记录器级别为ERROR,输出到文件
log4j.rootLogger=DEBUG
# Log4jExample类为DEBUG级别
log4j.category.com.weegee.log.Log4j.Log4jExample=DEBUG, f
# 输出到文件
log4j.appender.f = org.apache.log4j.FileAppender
# 输出文件位置
log4j.appender.f.File = log/xml.log
# 编码方式
log4j.appender.f.Encoding=UTF-8
# 是否在文件末尾追加内容
log4j.appender.f.Append=true
# 输出格式
log4j.appender.f.layout=org.apache.log4j.xml.XMLLayout

生成的文件并不能直接通过XML解析,需要一个额外的文件加载输出的内容

<?xml version="1.0"?>
<!DOCTYPE log4j:eventSet SYSTEM "log4j.dtd"
        [
                <!ENTITY data SYSTEM "xml.log">
        ]
>
<log4j:eventSet version="1.2" xmlns:log4j="http://jakarta.apache.org/log4j/">
    &data;
</log4j:eventSet>

6.持续更新(logback和slf4j)…

时间: 2024-10-12 22:34:07

Java日志系统的相关文章

Java日志系统---Logger之简单入门

Java 中自带的日志系统,今天抽空了解了一点,算是入了门,所以将自己的一些心得记录下来,以备日后查看,有兴趣的朋友,看到此文章,觉得有错误或需要添加的地方,请在下方评论留言,大家可以共同进步,谢谢:) Java中关于日志系统的API,在 java.util.logging 包中,在这个包中,Logger类很重要. Logger类是用来记录 某个级别的日志消息: 级别共分为以下几类,从上倒下,级别依次下降: SEVERE(严重)------级别最高 WARNING(警告) INFO CONFIG

Java日志系统框架的设计与实现

推荐一篇好的文章介绍java日志系统框架的设计的文章:http://soft.chinabyte.com/database/438/11321938.shtml 文章内容总结: 日志系统对跟踪调试.程序状态记录.数据恢复等功能有重要作用 日志系统一般作为服务进程或者系统调用存在,我们一般程序中使用系统调用 常用日志系统包括log4j的简单介绍 日志系统的系统架构 日志系统的信息分级 日志输出的设计 下面是全文的引用: 在Java领域,存在大量的日志组件,open-open收录了21个日志组件.日

走进JavaWeb技术世界9:Java日志系统的诞生与发展

> 微信公众号[黄小斜]大厂程序员,互联网行业新知,终身学习践行者.关注后回复「Java」.「Python」.「C++」.「大数据」.「机器学习」.「算法」.「AI」.「Android」.「前端」.「iOS」.「考研」.「BAT」.「校招」.「笔试」.「面试」.「面经」.「计算机基础」.「LeetCode」 等关键字可以获取对应的免费学习资料. ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9waWMzLnpoaW1nLmNvbS84MC92Mi1jND

java日志系统 @Slf4j注解的正确使用

在maven项目的pom.xml中添加依赖: <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <arti

Java日志系统学习之log4j!

尽管slf4j+logback看起来很不错.但是,由于自己熟悉的很多项目都还是用log4j.而且springmvc默认使用log4j.所以 自己还是学习总结下.      我习惯到官网去看资料学习使用.我觉得这样不会让我在众多转来转去文章中.搞得晕头转向,而不得要领.   log4j apache官网手册页面. 手册页上有一段话,吸引了我的注意.本来下午的时候,我还在想理由解释为什么要使用日志.下面这段话太经典了. As Brian W. Kernighan and Rob Pike put i

Java日志系统【logback】的示例2

<?xml version="1.0" encoding="UTF-8"?> <configuration>     <!-- 控制台输出 -->     <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">         <encoder>             <patt

atitit. 日志系统的原则and设计and最佳实践(1)-----原理理论总结.

atitit. 日志系统的原则and设计and最佳实践总结. 1. 日志系统是一种不可或缺的单元测试,跟踪调试工具 1 2. 日志系统框架通常应当包括如下基本特性 1 1. 所输出的日志拥有自己的分类. 2 2. 日志按照某种标准分成不同级别. 2 3. 支持多线程. 2 4. 稳定性. 2 3. 一个理想的日志模式 2 4. 判断指定的方法是否被调用了 3 5. 给方法的输入输出加上日志通过Aop 3 6. 日志易读,易解析  对日志感兴趣的可以分为两类: 3 7. 输出日志使用的性能 3 8

Java日志体系 —— logback

1.1 简介 师出同门,与log4j一样,logback也是由Ceki Gülcü开发的开源日志组件,可以说是log4j的改进版:在现如今的项目中,logback的出现次数越来越多,是目前主流首选的日志记录工具. 1.2 logback结构 logback分成三个模块:logback-core,logback- classic,logback-access. logback-core提供了logBack的核心功能,是另外两个组件的基础: logback-classic模块实现了SLF4J API

java日志框架与日志系统

日志框架:提供日志调用的接口,实际的日志输出委托给日志系统实现. JCL(Jakarta Commons Logging):比较流行的日志框架,很多框架都依赖JCL,例如Spring等. SLF4j:提供新的API,初衷是配合Logback使用,但同时兼容Log4j. 日志系统:负责输出日志 Log4j:较早的日志系统,可以单独使用,也可配合日志框架JCL使用 Logback:Log4j的替代产品,需要配合日志框架SLF4j使用 JUL(java.util.logging):JDK提供的日志系统