log4j的各种类的配置
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的各种类的配置的更多相关文章
- log4j.properties 详解与配置步骤(转)
找的文章,供参考使用 转自 log4j.properties 详解与配置步骤 一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR ...
- springmvc+log4j操作日志记录,详细配置
没有接触过的,先了解一下:log4j教程 部分内容来:log4j教程 感谢! 需要导入包: log包:log4j-12.17.jar 第一步:web.xml配置 <!-- log4j配置,文件路 ...
- 转!数据库连接池概念、种类、配置(DBCP\C3P0\JndI与Tomact配置连接池)
数据库连接池概念.种类.配置(DBCP\C3P0\JndI与Tomact配置连接池) 一.DBCP 连接:DBCP 连接池是 Apache 软件基金组织下的一个开源连接池实现. 需要的 java 包c ...
- log4j.properties 详解与配置步骤
一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...
- 【配置】log4j.properties 详解与配置步骤
一.Log4j基本使用方法 Log4j由三个重要的组件构成:[日志信息的优先级],[日志信息的输出目的地],[日志信息的输出格式]. 日志信息的优先级从高到低有ERROR.WARN. INFO.DEB ...
- 转--log4j.properties 详解与配置步骤
一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...
- log4j.properties 详解与配置步骤总结
先提供一个项目中使用log4j.properties配置 #log4j.rootLogger=WARN, stdout, file log4j.rootLogger=INFO,console,dail ...
- 【转】log4j.properties 详解与配置步骤 - edward0830ly的专栏 - 博客频道 - CSDN.NET
一.log4j.properties 的使用详解 1.输出级别的种类 ERROR.WARN.INFO.DEBUGERROR 为严重错误 主要是程序的错误WARN 为一般警告,比如session丢失IN ...
- 从零开始学 Java - log4j 项目中的详细配置
你还会用笔来写字么 我是不怎么会了,有时候老是拿起笔之后不知道这个字怎么写,这时候就会拿起手机去打出来:有时候还会写出来这个字之后越看越不像,这时候就开始怀疑自己的能力了:有时候写出来了一大堆字之后, ...
随机推荐
- javabeans 内省 introspector BeanUtils
javaBeans 属性的概念 不只是字段,而是其get set 方法 且该get方法有返回值的称为属性,继承Object类的getClass方法 package com.swift.demo1; p ...
- js复习,预编译
注意:函数声明整体提升.变量 声明提升 1.imply global 暗示全局变量:即任何变量,如果变量未声明就赋值,此变量就为全局对象所有 ==> eg: a = 122;==> e ...
- Linux下文件的压缩与解压缩
一.zip格式 zip可能是目前使用的最多的文档压缩格式.它最大的优点就是在不同的操作系统平台上使用.缺点就是支持 的压缩率不是很高,而tar.gz和tar.bz2在压缩率方面做得非常好. 我们可以使 ...
- Zeppelin interperter 模式设置总结图解1
如有错漏,望请指正,不胜感激. 参考:[zeppelin官网]:https://zeppelin.apache.org/docs/latest/interpreter/spark.html#inter ...
- 执行pip命令时遇到 Fatal error in launcher: Unable to create process using '"'
电脑同时安装了python-2.7.13跟python-3.6.1,安装时勾选了pip,环境变量也已经配置好. 为了方便运行,同时修改了可执行文件为 python2和python3.此时在cmd命令行 ...
- windows和linux上mysql的安装
mysql基于多平台,多版本的安装 mysql.tar.gz 链接:https://pan.baidu.com/s/1lG9BNL1mG4qbjM8xLHtrjQ 密码:s4tk MySQL 是一个 ...
- VS2013未能正确加载的问题【转载】
今天使用电脑,关机重启时,WINDOWS提示“正在配置中,请勿关机” 的提醒,等重启后,打开VS2013就提示了未加载成功的问题,如下图: 我的解决方法是:找到VS2013开发人员命令提示:在窗口中输 ...
- 【Effective C++ 读书笔记】条款02: 尽量以 const, enum, inline 替换 #define
条款02: 尽量以 const, enum, inline 替换 #define 这个条款或许可以改为“宁可以编译器替换预处理器”. 编译过程: .c文件--预处理-->.i文件--编译--&g ...
- 怎样通过互联网ssh访问家里电脑
需求:用可以上网的公司windows电脑连接家里的manjaro linux电脑.. 环境情况:公司电脑为内网,通过登录出口服务器连接互联网.家里的电脑也为内网,通过连接无线路由器连接外网.路由器有公 ...
- decltype和新的返回值语法
新的返回值语法 让我们讲一下新的返回值语法,这个语法还能看到auto的另一个用处.在以前版本的C和C++中,返回值的类型必须写在函数的前面: int multiply(int x, int y) 在C ...