log4j看上去像是一种简单的,易配置的日志打印技术。但是实际使用的时候发现,还有各种很相似的日志技术。很多地方的配置一乱就不知道怎么对应了。所以应该把log4j的一切做个简单的分类记录。

(一)java.util.logging.Logger

这个在java的util包里面,不需要任何Maven依赖,是最最基本的日志打印。功能很不完善,基本没有人在大型软件中使用这个。

示例代码:(日志重要级从上往下递增)

 public class TestLog {
public static void main(String[] args) {
Logger logger = Logger.getLogger(TestLog.class.toString());
logger.log(Level.ALL, "ALL");
logger.log(Level.FINEST, "FINEST");
logger.log(Level.FINER, "FINER");
logger.log(Level.FINE, "FINE");
logger.log(Level.CONFIG, "CONFIG");
logger.log(Level.INFO, "INFO");
logger.log(Level.WARNING, "WARNING");
logger.log(Level.SEVERE, "SEVERE");
logger.log(Level.OFF, "OFF");
}
}

控制台输出:

一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main
信息: INFO
一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main
警告: WARNING
一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main
严重: SEVERE
一月 17, 2017 2:36:23 下午 com.jd.Test.TestLog main
禁用: OFF

那么为什么从info开始输出呢?原因是在很多实际应用中会只在控制台打印某个级别以上的日志信息。否则在调试的时候有可能会面对海量的杂乱日志而不知如何下手。所以这里也用了这样的设计。

想要修改默认的日志输出级别的话,去jre安装目录的lib下面:

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

那么,如何制定日志的输出目录呢?这个可以用java代码指定:

 public class TestLog {
public static void main(String[] args) throws SecurityException, IOException {
Logger logger = Logger.getLogger(TestLog.class.toString()); FileHandler fileHandler = new FileHandler("/Users/Davie/File/foo.log");
fileHandler.setLevel(Level.ALL);
logger.addHandler(fileHandler); logger.log(Level.ALL, "ALL");
logger.log(Level.FINEST, "FINEST");
logger.log(Level.FINER, "FINER");
logger.log(Level.FINE, "FINE");
logger.log(Level.CONFIG, "CONFIG");
logger.log(Level.INFO, "INFO");
logger.log(Level.WARNING, "WARNING");
logger.log(Level.SEVERE, "SEVERE");
logger.log(Level.OFF, "OFF");
}
}

但是结果是一个xml,比较难debug。

(二)common-logging

首先,它是apache提供的一个通用的日志接口,它的存在的意义是把普遍意义上的日志和真正的日志实现解耦合。那么它本身需要什么配置呢?其实什么都不需要。因为在运行时,它会自动去找实现。这里的实现可能是上文的java.util.logging.Logger这个JDK自带的最基本的日志实现,也可能是使用广泛的Log4j。

首先在Maven加入依赖:

 <dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>

然后在java类中写入:

 class Foo {

     private Log log = LogFactory.getLog(TestLog.class);

     public void method() {
// log.debug("debug");
log.info("info");
log.warn("warn");
log.error("error");
// log.fatal("fatal");
}
}

注释的这两行去掉注释是不起作用的。原因是目前只在Maven中添加了common-logging,它会使用JDK的实现,而JDK的实现是没有debug和fatal的。那么问题是到底这个common-logging会使用哪一个Log呢?它会按照如下规则寻找其实现类(转载自:http://jiangzhengjun.iteye.com/blog/520733):

1) 首先在classpath下寻找自己的配置文件commons-logging.properties,如果找到,则使用其中定义的Log实现类;

2) 如果找不到commons-logging.properties文件,则在查找是否已定义系统环境变量org.apache.commons.logging.Log,找到则使用其定义的Log实现类;

如果在Tomact中可以建立一个叫 :CATALINA_OPTS 的环境变量 
给 他的 值 : - Dorg.apache.commons.logging.Log = org.apache.commons.logging.impl.SimpleLog - Dorg.apache.commons.logging.simplelog.defaultlog = warn

3) 否则,查看classpath中是否有Log4j的包,如果发现,则自动使用Log4j作为日志实现类;

4) 否则,使用JDK自身的日志实现类(JDK1.4以后才有日志实现类);

5) 否则,使用commons-logging自己提供的一个简单的日志实现类SimpleLog;

这样呢,往往就可以保证按照用户的意愿实现log。

另外,以上的Maven依赖中如果加入了log4j相关的的依赖,反倒会在运行时报错,原因是common-logging发现了log4j,所以会使用log4j进行输出,但是这个时候log4j还没有配置于是会输出:

log4j:WARN No appenders could be found for logger (com.jd.logTest.Foo).

log4j:WARN Please initialize the log4j system properly.

log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.

(三)SLF4j

这个其实也是一种接口。它自己不实现任何真正的日志打印,而是通过再引入别的包来转换。它的最特别的好处有两个,第一是可以通过占位符来打印日志,第二是配置具体实现简单且解耦合。

对于第一点,在日志系统的使用中,往往不只是要打印出:“**异常”,还要打印出具体的异常,和异常产生时的参数环境。最简单的例子如下:

     public void say(String word) {
if (word == null) {
log.error("输入参数异常,word:" + word);
return ;
}
System.out.println(word);
}

这样的问题是,当要打印的参数多时,要产生多个String对象。其次是代码很不直观。

对于第二点,如果没有使用这个接口,而是直接耦合了log4j。那么如果你在代码中引入了一个模块,而这个模块使用了别的日志系统,那么你就必须在你的代码里面引入那一个日志系统并且配置好,维护好。这很明显引入了不必要的工作。

那么你要怎么才能使用一个SLF4j呢?官网上有一张图:

这张图体现了SLF4j的核心思想:你只需要把正确的包引入你的工程,日志系统就能正常的工作。

那么具体如何使用SLF4j呢?首先,你需要一个最基本的SLF4j的API包:

 <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>

这个包提供了SLF4j的基本API。接着,你需要一个“转换的包”。说起转换,当然就能联想到适配器设计模式。这个地方可以理解为一个适配器。这个地方的理解是指宏观的理解:你引入了如果所示的对应的包,就把SLF4j对应的实现包引入,SLF4j就会自动去找这个包的实现。

同时这个地方记录一下SLF4j和common-logging的实现方式的不同:

common-logging是通过动态查找的机制,在程序运行时自动找出真正使用的日志库。

SLF4j在编译时静态绑定真正的Log库。

(四)log4j

这个可以说是最享有大名的一个日志系统。

说起这个日志系统,里面的坑其实不少。我一次上线测试的时候,出了bug。但是比出bug更可怕的是:本地是可以打出日志的,而在线上的服务器就无法打出日志。那个错误直接导致我无法正常debug。

要使用这个日志工具,首先添加依赖:

         <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>

然后再添加配置文件。这里的具体的配置有用log4j.xml和log4j.properties两种方式,这里使用xml来配置。把xml加入编译路径中:src/main/resources:

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="ALL" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file" value="/Users/Davie/File/export/Logs/TestProject/ALL.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%7r] %6p - %30.30c - %m \n" />
</layout>
</appender> <appender name="FILE_MONITORFOLDER" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file"
value="/Users/Davie/File/export/Logs/TestProject/FILE_MONITORFOLDER.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%7r] %6p - %30.30c - %m \n" />
</layout>
</appender> <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%7r] %6p - %30.30c - %m \n" />
</layout>
</appender> <logger name="FILE_MONITORFOLDER" additivity="false">
<level value="${task.log.level}" />
<appender-ref ref="FILE_MONITORFOLDER" />
</logger> <logger name="CONSOLE" additivity="false">
<level value="${task.log.level}" />
<appender-ref ref="CONSOLE" />
</logger> <root>
<priority value="${task.log.level}" />
<appender-ref ref="ALL" />
${log4j.log.appender.console}
</root>
</log4j:configuration>

在实际的使用中,往往由一些意想不到的点。

比如说,在dev时(可以理解为Profile处于dev),我们应当允许其不单单向日志文件写日志,还可以同时向控制台打印日志。这样在工程师调试的时候可以说是极其方便的。但是一旦到了线上的环境,恐怕就没有控制台这个东西了,日志全部写入到了日志文件,一般是通过SHELL语句去跟踪log日志文件的打印情况来实现的。这也就是为什么我一直没找到我的日志。在上面的配置文件中,最后的:${log4j.log.appender.console}到底是什么呢?实际上,在log4j中,如果你没有配置<logger>或者<category>这种标签,日志就会去找<root>来实现打印。那么<root>这里的这句话其实是一句控制是否往控制台打印日志的语句。

在dev的profile中,配置是:

 <log4j.log.appender.console><![CDATA[<appender-ref ref="CONSOLE"/>]]></log4j.log.appender.console>

在其他的profile中,配置是:

 <log4j.log.appender.console></log4j.log.appender.console>

当然也就是不往控制台打印日志的意思了。那么当时我的日志没有打印出来的逻辑也就是:

1,使用了:private Logger log = Logger.getLogger(Foo.class);

2,配置文件里面没有配置这个logger,于是自动去找<root>,

3,本地的dev的profile配置了要打印到控制台,于是正常,而线上的test profile则根本没有配置打印到控制台(当然实际上打印了也是没有意义的,单纯浪费效率罢了)。

然后,往往在调试的时候会有很多调试时的日志,也就是一些局部的过程,用:log.debug打印出来。很明显,如果任由这种日志挤成一大堆,那么首先是性能的开销,其次是一旦出了问题,在浩如烟海的杂乱日志中恐怕要很费力气才能得到自己想要的信息。所以在不同的Maven的Profile中,一般会配置一个不同的量来控制当前环境下的日志打印级别。比如上文中的:${task.log.level}

在dev的profile中:

 <task.log.level>DEBUG</task.log.level>

在test的profile中:

 <task.log.level>INFO</task.log.level>

那么这样就可以很细节的控制打印日志的颗粒度。

第三个问题是这样的,上文已经说过形如:

 private Logger log = Logger.getLogger(“foo”);

这样写,结果是你配置了foo这个logger,就会去找这个logger,否则会去root里面。那么在一个多module的工程中,就不能像写小玩意一样随便选择当前的类作为日志了。一般在这样的大工程中,都会为某个module,或者某个模块写一个logger:

     <logger name="FILE_MONITORFOLDER" additivity="false">
<level value="${task.log.level}" />
<appender-ref ref="FILE_MONITORFOLDER" />
</logger>
<appender name="FILE_MONITORFOLDER" class="org.apache.log4j.DailyRollingFileAppender">
<param name="file"
value="/Users/Davie/File/export/Logs/TestProject/FILE_MONITORFOLDER.log" />
<param name="DatePattern" value="'.'yyyy-MM-dd" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d [%7r] %6p - %30.30c - %m \n" />
</layout>
</appender>

这样在MONITORFOLDER这个模块中,就这么用:

 private Logger log = Logger.getLogger(“FILE_MONITORFOLDER”);

这样,在线上的时候,就可以去对应的日志文件中看这个模块独有的日志信息了。

以上都是描述这些东西单独怎么用,但实际上很多时候会结合这些东西来用。

比如你想用SLF4J+log4j,那么Maven里面你这么配置:

         <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>

JAVA代码代码里面这么写:

 private Logger log = LoggerFactory.getLogger("FILE_MONITORFOLDER");

那么实际运行的时候,效果和上面直接使用log4j一样,因为SLF4J通过依赖包找到了其底层实现是log4j。

如果情况更极端一点:你最开始的代码使用了common-logging,那么你需要先把和这个转化为SLF4J,再按照SLF4J的规则打印日志。那么是否意味着你要改动代码?不需要,你只需要添加一个依赖:

         <dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
</dependency>

这样的话,common-logging就会通过SLF4J去打印日志了。

log4j的各种类的配置的更多相关文章

  1. log4j.properties 详解与配置步骤(转)

    找的文章,供参考使用 转自 log4j.properties 详解与配置步骤 一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR ...

  2. springmvc+log4j操作日志记录,详细配置

    没有接触过的,先了解一下:log4j教程 部分内容来:log4j教程 感谢! 需要导入包: log包:log4j-12.17.jar 第一步:web.xml配置 <!-- log4j配置,文件路 ...

  3. 转!数据库连接池概念、种类、配置(DBCP\C3P0\JndI与Tomact配置连接池)

    数据库连接池概念.种类.配置(DBCP\C3P0\JndI与Tomact配置连接池) 一.DBCP 连接:DBCP 连接池是 Apache 软件基金组织下的一个开源连接池实现. 需要的 java 包c ...

  4. log4j.properties 详解与配置步骤

    一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...

  5. 【配置】log4j.properties 详解与配置步骤

    一.Log4j基本使用方法 Log4j由三个重要的组件构成:[日志信息的优先级],[日志信息的输出目的地],[日志信息的输出格式]. 日志信息的优先级从高到低有ERROR.WARN. INFO.DEB ...

  6. 转--log4j.properties 详解与配置步骤

    一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...

  7. log4j.properties 详解与配置步骤总结

    先提供一个项目中使用log4j.properties配置 #log4j.rootLogger=WARN, stdout, file log4j.rootLogger=INFO,console,dail ...

  8. 【转】log4j.properties 详解与配置步骤 - edward0830ly的专栏 - 博客频道 - CSDN.NET

    一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...

  9. 从零开始学 Java - log4j 项目中的详细配置

    你还会用笔来写字么 我是不怎么会了,有时候老是拿起笔之后不知道这个字怎么写,这时候就会拿起手机去打出来:有时候还会写出来这个字之后越看越不像,这时候就开始怀疑自己的能力了:有时候写出来了一大堆字之后, ...

随机推荐

  1. windows 编译安卓iconv 库

    由于NDK r15后,谷歌要统一将来的设备都要支持64位,而iconv只支持32位,后续的ndk都会去除iconv的支持,所以只能在iconv的官网下载源码编译库文件使用, 下载地址:https:// ...

  2. 深入理解 SVG 系列(一) —— SVG 基础

    来源:https://segmentfault.com/a/1190000015652209 本系列文章分为三个部分: 第一部分是 SVG 基础. 主要讲 SVG 的一些基础知识,包括 SVG 基本元 ...

  3. 【杂题总汇】HDU多校赛第十场 Videos

    [HDU2018多校赛第十场]Videos 最后一场比赛也结束了…… +HDU传送门+ ◇ 题目 <简要翻译> 有n个人以及m部电影,每个人都有一个快乐值.每场电影都有它的开始.结束时间和 ...

  4. Swoole 创建服务

    1: 创建TCP 服务器 $serv = new swoole_server(‘127.0.0.1’,9501); 2:创建UDP服务器 $serv =  new swoole_server('127 ...

  5. graphviz使用

    官方网站:http://www.graphviz.org/ Graphviz (Graph Visualization Software) 是一个由AT&T实验室启动的开源工具包.DOT是一种 ...

  6. 状压DP详解(位运算)

    前言: 状压DP是一种非常暴力的做法(有一些可以排除某些状态的除外),例如dp[S][v]中,S可以代表已经访问过的顶点的集合,v可以代表当前所在的顶点为v.S代表的就是一种状态(二进制表示),比如 ...

  7. POJ:3977-Subset(双向搜索)

    Subset Time Limit: 30000MS Memory Limit: 65536K Total Submissions: 5961 Accepted: 1129 Description G ...

  8. Evevt Loop、任务队列、定时器等

    上周五,一个朋友发给我一道面试题,代码如下: console.log(1); setTimeout(console.log(2), 0); Promise.resolve().then(res =&g ...

  9. 笔记-git-协作开发

    笔记-git-协作开发 1.      git协作开发 git协作的典型做法是,创建一个git服务器,被多个人操作. 示意图如下: 一般来说协作分为如下几个步骤: 创建一个git裸服务器 (git i ...

  10. Android面试收集录18 Android Context详解

    Activity mActivity =new Activity() 作为Android开发者,不知道你有没有思考过这个问题,Activity可以new吗?Android的应用程序开发采用JAVA语言 ...