联通腾讯大王卡官网为什么用不成4G网,一直是3G网

java日志,需要知道的几件事(commons-logging,log4j,slf4j,logback) - singleAnt - ITeye技术网站
博客分类:
java日志,需要知道的几件事
如果对于commons-loging
等都已经非常清楚了,可以忽略本文。几次解决日志冲突问题时对这几个概念的简单总结,希望对这块基础没有理解透的同学能有所帮助,当然如果对这块有更深刻理解的同学,也贡献出自己的知识和见解。
Commons-logging
apache最早提供的日志的门面接口。避免和具体的日志方案直接耦合。类似于JDBC
接口,具体的的JDBC driver
实现由各数据库提供商实现。通过统一接口解耦,不过其内部也实现了一些简单日志方案。
经典的一种日志解决方案。内部把日志系统抽象封装成Logger
、appender
等实现。我们可以通过配置文件轻松的实现日志系统的管理和多样化配置。
全称为Simple Logging Facade for JAVA:java简单日志门面。
是对不同日志框架提供的一个门面封装。可以在部署的时候不修改任何配置即可接入一种日志实现方案。和commons-loging
应该有一样的初衷。个人感觉设从计上更好一些,没有commons
那么多潜规则。同时有两个额外特点:
能支持多个参数,并通过{}
占位符进行替换,避免老写logger.isXXXEnabled
这种无奈的判断,带来性能提升见:http://www.slf4j.org/faq.html#logging_performance
机制更好兼容支持
一图胜千言,官网上的一个图:
从上图可以发现,选择还是很多的。
作为一个通用可靠、快速灵活的日志框架,将作为Log4j
的替代和SLF4J
组成新的日志系统的完整实现。官网上称具有极佳的性能,在关键路径上执行速度是log4j
倍,且内存消耗更少。具体优势见:
http://logback.qos.ch/reasonsToSwitch.html
常见日志方案和注意事项
<mons-logging+log4j
经典的一个日志实现方案。出现在各种框架里。如spring
等等。直接使用log4j
即可满足我们的日志方案。但是一般为了避免直接依赖具体的日志实现,一般都是结合commons-logging
来实现。常见代码如下:
import mons.logging.L
import mons.logging.LogF
private static Log logger = LogFactory.getLog(CommonsLoggingTest.class);
代码上,没有依赖任何的log4j
内部的类。那么log4j
是如何被装载的?
是一个接口声明。LogFactory
的内部会去装载具体的日志系统,并获得实现该Log
接口的实现类。而内部有一个Log4JLogger
实现类对Log
接口同时内部提供了对log4j logger
的代理。LogFactory
内部装载日志系统流程:
首先,寻找mons.logging.LogFactory
否则,利用JDK1.3
开始提供的service
发现机制,会扫描classpah
下的META-INF/services/mons.logging.LogFactory
文件,若找到则装载里面的配置,使用里面的配置。
否则,从Classpath
里寻找commons-logging.properties
,找到则根据里面的配置加载。
否则,使用默认的配置:如果能找到Log4j
则默认使用log4j
实现,如果没有则使用JDK14Logger
实现,再没有则使用commons-logging
内部提供的SimpleLog
从上述加载流程来看,如果没有做任何配置,只要引入了log4j
并在classpath
配置了log4j.xml
,则commons-logging
就会使log4j
使用正常,而代码里不需要依赖任何log4j
2.Commons-logging+log4j+slf4j
如果在原有commons-logging
系统里,如果要迁移到slf4j,
替换commons-logging
,也是可以做到的。原理使用到了上述commons-logging
加载的第二点。需要引入Org.slf4j.jcl-over-slf4j-1.5.6.jar
包提供了一个桥接,让底层实现是基于slf4j
。原理是在该jar
包里存放了配置META-INF/services/mons.logging.LogFactory =mons.logging.impl.SLF4JLogFactory
,而commons-logging
在初始化的时候会找到这个serviceId
,并把它作为LogFactory
完成桥接后,那么那么简单日志门面SLF4J
内部又是如何来装载合适的log
原理是SLF4J
会在编译时会绑定import org.slf4j.impl.StaticLoggerB
该类里面实现对具体日志方案的绑定接入。任何一种基于slf4j
的实现都要有一个这个类。如:
org.slf4j.slf4j-log4j12-1.5.6:
的一种适配实现。
Org.slf4j.slf4j-simple-1.5.6:
实现,会将
直接打到控制台。
那么这个地方就要注意了:如果有任意两个实现slf4j
的包同时出现,那就有可能酿就悲剧,你可能会发现日志不见了、或都打到控制台了。原因是这两个jar
包里都有各自的org.slf4j.impl.StaticLoggerBinder
,编译时候绑定的是哪个是不确定的。这个地方要特别注意!!出现过几次因为这个导致日志错乱的问题。
3.Slf4j+logback
作者都是同一个人。
号称在性能各方面有很多优势,也很诱人。
直接使用SLf4j
也很简单:
import org.slf4j.L
import org.slf4j.LoggerF
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
("Hello World");
代码里也看不到任何具体日志实现方案的痕迹。
没用过,看到过一些诱人介绍。具体大家可以去研究。logback
使用日志配置的时候一定要明白需求,同时避免冲突。
如使用SLF4j
的时候为了避免冲突,一定要保障只有一种实现类jar
包在里面。
当遇到日志错乱等问题时,可以从这几个方面来排查
浏览 30456
论坛回复 /
(14 / 17808)
写的还是有点糙了,加油啊,lz!
谢谢你的指出,我会加油提高的!
奇怪楼主为何都不提jdk自带的logging啊?我觉得很好用啊。省去了发布软件时,带一大堆lib。
看来你对jdk自带logging很推崇,至少我遇到的用这个还比较少。如果好用,确实可以省去一些依赖lib
浏览: 223645 次
来自: 杭州
NICE,别的博客都是从resouce开始解读的,使我一直不理 ...
xiyuan1025 写道你这是在linux下吗,我在linu ...
项目继承不下来。。
楼主你这个是版本的问题吧。我3.2.3的版本并没有同时持有两个 ...Commons&Logging的使用方法
Commons-Loggin简介
Jakarta Commons Logging
(JCL)提供的是一个日志(Log)接口(interface),同时兼顾轻量级和不依赖于具体的日志实现工具。它提供给中间件/日志工具开发者一个简单的日志操作抽象,允许程序开发人员使用不同的具体日志实现工具。用户被假定已熟悉某种日志实现工具的更高级别的细节。JCL提供的接口,对其它一些日志工具,包括Log4J,
Avalon LogKit, and JDK
1.4等,进行了简单的包装,此接口更接近于Log4J和LogKit的实现.
JCL有两个基本的抽象类:Log(基本记录器)和LogFactory(负责创建Log实例)。当commons-logging.jar被加入到
CLASSPATH之后,它会心可能合理地猜测你想用的日志工具,然后进行自我设置,用户根本不需要做任何设置。默认的LogFactory是按照下列的步骤去发现并决定那个日志工具将被使用的(按照顺序,寻找过程会在找到第一个工具时中止)&#58;
寻找当前factory中名叫mons.logging.Log配置属性的值
&& 2. 寻找系统中属性中名叫mons.logging.Log的值
如果应用程序的classpath中有log4j,则使用相关的包装(wrapper)类(Log4JLogger)
&& 4. 如果应用程序运行在jdk1.4的系统中,使用相关的包装类(Jdk14Logger)
&& 5. 使用简易日志包装类(SimpleLog)
mons.logging.Log的具体实现有如下:
mons.logging.impl.Jdk14Logger 使用JDK1.4。
mons.logging.impl.Log4JLogger 使用Log4J。
mons.logging.impl.LogKitLogger 使用 。
mons.logging.impl.SimpleLog common-loggingの自带日志实现类。它实现了Log接口,把日志消息都输出到系统错误流System.err
mons.logging.impl.NoOpLog common-loggingの自带日志实现类。它实现了Log接口。 其输出日志的方法中不进行任何操作。
使用JCL开发
因为Log4j的强大,同时开发者又不希望对Log4j的依赖性太强。所以目前比较流行的是Commons-logging和Log4j结合使用。
1. 部署日志器&
&&&下载commons-logging.jar和log4j.jar包(下载参考地址:或),能后把它们放到工程的lib目录下,引入工程中。
2.&指定日志器
&&&在属性文件common-logging.properties中设置实现接口的类。如下:
#commons-logging.properties文件配置信息&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
mons.logging.Log=mons.logging.impl.SimpleLog&&&&&&&&
# Must be one of ("trace", "debug", "info", "warn", "error", or
"fatal").&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
#利用log4j为输出介质&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
mons.logging.Log=mons.logging.impl.Log4JCategoryLog
Logger&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
#mons.logging.Log=mons.logging.impl.Jdk14Logger&&&&&&&
&& 在属性文件log4j.properties中设置日志的输出信息
#log4j.properties文件配置信息
log4j.rootLogger=ERROR,stdout,LF5,,file&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.stdout=org.apache.log4j.ConsoleAppender&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.stdout.layout.ConversionPattern=[start]%d&#123;yyyy/MM/dd/
HH&#58;mm&#58;ss&#125;[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]
n%c[CATEGORY]%n%m[MESSAGE]%n%n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.file=org.apache.log4j.RollingFileAppender&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.file.File=sdcrm.log&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.file.MaxFileSize=100KB&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.file.MaxBackupIndex=5&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.file.layout=org.apache.log4j.PatternLayout&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.file.layout.ConversionPattern=[start]%d&#123;yyyy/MM/dd/
HH&#58;mm&#58;ss&#125;[DATE]%n%p[PRIORITY]%n%x[NDC]%n%t[THREAD]&n%c[CATEGORY]%n%m[MESSAGE]%n%n&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.LF5=org.apache.log4j.lf5.LF5Appender&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
log4j.appender.LF5_APPENDER.MaxNumberOfRecords=2000&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
最后要把properties属性文件保存到Web-INF/classes目录下。
//在程序文件头部import相关的类
import mons.logging.L
import mons.logging.LogF
//在类中获取一个实例
public class MYCLASS
private static Log log = LogFactory.getLog(MyCLASS.class);
日志信息被送往记录器,如上例中的log。这个发送过程,是通过调用Log接口中定义的方法完成的,不同方法跟不同的级别联系在一起,日志信息通过哪个级别的方法发送,就标明了日志信息的级别。mons.logging.Log接口中定义的方法,按严重性由高到低的顺序有:
log.fatal(Object message);
log.fatal(Object message, Throwable t);
log.error(Object message);
log.error(Object message, Throwable t);
log.warn(Object message);
log.warn(Object message, Throwable t);
<(Object message);
<(Object message, Throwable t);
log.debug(Object message);
log.debug(Object message, Throwable t);
log.trace(Object message);
log.trace(Object message, Throwable t);
除此以外,还提供下列方法以便代码保护.
log.isFatalEnabled();
log.isErrorEnabled();
log.isWarnEnabled();
log.isInfoEnabled();
log.isDebugEnabled();
log.isTraceEnabled();
  信息级别
  确保日志信息在内容上和反应问题的严重程度上的恰当,是非常重要的。
fatal非常严重的错误,导致系统中止。期望这类信息能立即显示在状态控制台上。
error其它运行期错误或不是预期的条件。期望这类信息能立即显示在状态控制台上。
warn使用了不赞成使用的API、非常拙劣使用API, '几乎就是'错误,
其它运行时不合需要和不合预期的状态但还没必要称为 "错误"。期望这类信息能立即显示在状态控制台上。
info运行时产生的有意义的事件。期望这类信息能立即显示在状态控制台上。
debug系统流程中的细节信息。期望这类信息仅被写入log文件中。
trace更加细节的信息。期望这类信息仅被写入log文件中。
通常情况下,记录器的级别不应低于info.也就是说,通常情况下debug的信息不应被写入log文件中。
  工作机理
JCL LogFactory必须实现建立/断开到日志工具的连接,实例化/初始化/解构一个日志工具.
JCL Log 接口没有指定任何异常处理,对接口的实现必须捕获并处理异常。
JCL Log 和 LogFactory 的实现,必须确保任何日志工具对并行的要求.
  记录器的设置
  JCL采用的记录器的不同其设置内容也不同。Log4J是默认首选记录器,对其设置可通过系统属性(system
properties)或一个属性文件进行设置,下面是其设置参数。
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。Java日志终极指南 - ImportNew
Java日志基础
Java使用了一种自定义的、可扩展的方法来输出日志。虽然Java通过java.util.logging包提供了一套基本的日志处理API,但你可以很轻松的使用一种或者多种其它日志解决方案。这些解决方案尽管使用不同的方法来创建日志数据,但它们的最终目标是一样的,即将日志从你的应用程序输出到目标地址。
在这一节中,我们会探索Java日志背后的原理,并说明如何通过日志来让你成为一个更好的Java开发人员。
Java日志组件
Java日志API由以下三个核心组件组成:
:Logger负责捕捉事件并将其发送给合适的Appender。
:也被称为Handlers,负责将日志事件记录到目标位置。在将日志事件输出之前,Appenders使用Layouts来对事件进行格式化处理。
:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
当Logger记录一个事件时,它将事件转发给适当的Appender。然后Appender使用Layout来对日志记录进行格式化,并将其发送给控制台、文件或者其它目标位置。另外,Filters可以让你进一步指定一个Appender是否可以应用在一条特定的日志记录上。在日志配置中,Filters并不是必需的,但可以让你更灵活地控制日志消息的流动。
在Java中,输出日志需要使用一个或者多个日志框架,这些框架提供了必要的对象、方法和配置来传输消息。Java在java.util.logging包中提供了一个默认的框架。除此之外,还有很多其它第三方框架,包括、以及。还有其它一些开发包,例如和,它们提供了一些抽象层,对你的代码和日志框架进行解耦,从而允许你在不同的日志框架中进行切换。
如何选择一个日志解决方案,这取决于你的日志需求的复杂度、和其它日志解决方案的兼容性、易用性以及个人喜好。Logback基于Log4j之前的版本开发(版本1),因此它们的功能集合都非常类似。然而,Log4j在最新版本(版本2)中引用了一些改进,例如支持多API,并提升了在用库的性能。而tinylog,由于缺少了一些功能,运行特别快,非常适合小项目。
另外一个考虑因素是框架在基于Java的各种不同项目上的支持程度。例如Android程序只能使用、或者第三方包来记录日志, Apache Tomcat可以使用Log4j来记录内部消息,但只能使用版本1的Log4j。
诸如SLF4J这样的抽象层,会将你的应用程序从日志框架中解耦。应用程序可以在运行时选择绑定到一个特定的日志框架(例如java.util.logging、Log4j或者Logback),这通过在应用程序的类路径中添加对应的日志框架来实现。如果在类路径中配置的日志框架不可用,抽象层就会立刻取消调用日志的相应逻辑。抽象层可以让我们更加容易地改变项目现有的日志框架,或者集成那些使用了不同日志框架的项目。
尽管所有的Java日志框架都可以通过代码进行配置,但是大部分配置还是通过外部配置文件完成的。这些文件决定了日志消息在何时通过什么方式进行处理,日志框架可以在运行时加载这些文件。在这一节中提供的大部分配置示例都使用了配置文件。
java.util.logging
默认的Java日志框架将其配置存储到一个名为 logging.properties 的文件中。在这个文件中,每行是一个配置项,配置项使用点标记(dot notation)的形式。Java在其安装目录的lib文件夹下面安装了一个全局配置文件,但在启动一个Java程序时,你可以通过指定 java.util.logging.config.file 属性的方式来使用一个单独的日志配置文件,同样也可以在个人项目中创建和存储 logging.properties 文件。
下面的示例描述了如何在全局的logging.properties文件中定义一个Appender:
# default file output is in user&#039;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
Log4j使用的语法和 java.util.logging 的语法很类似。使用了Log4j的程序会在项目目录中寻找一个名为 log4j.properties 的文件。默认情况下,Log4j配置会将所有日志消息输出到控制台上。Log4j同样也支持XML格式的配置文件,对应的配置信息会存储到 log4j.xml 文件中。
Log4j支持XML、JSON和YAML格式的配置,这些配置会分别存储到 log4j2.xml、log4j2.json 和 log4j2.yaml 文件中。和版本1类似,版本2也会在工程目录中寻找这些文件。你可以在每个版本的文档中找到相应的配置文件示例。
对于Logback来说,大部分配置都是在
文件中完成的,这个文件使用了和Log4j类似的XML语法。Logback同时也支持通过Groovy语言的方式来进行配置,配置信息会存储到
文件中。你可以通过每种类型配置文件的链接找到对应的配置文件示例。
Loggers是用来触发日志事件的对象,在我们的Java应用程序中被创建和调用,然后Loggers才会将事件传递给Appender。一个类中可以包含针对不同事件的多个独立的Loggers,你也可以在一个Loggers里面内嵌一个Loggers,从而创建一种Loggers。
创建新Logger
在不同的日志框架下面创建新Logger过程大同小异,尽管调用的具体方法名称可能不同。在使用 java.util.logging 时,你可以通过 Logger.getLogger().getLogger() 方法创建新Logger,这个方法接收一个string参数,用于指定Logger的名字。如果指定名字的Logger已经存在,那么只需要返回已经存在的Logger;否则,程序会创建一个新Logger。通常情况下,一种好的做法是,我们在当前类下使用 class.getName() 作为新Logger的名字。
Logger logger = Logger.getLogger(MyClass.class.getName());
记录日志事件
Logger提供了几种方法来触发日志事件。然而,在你记录一个事件之前,你还需要设置级别。日志级别用来确定日志的严重程度,它可以用来过滤日志事件或者将其发送给不同的Appender(想了解更多信息,请参考“”一节),Logger.log() 方法除了日志消息以外,还需要一个日志级别作为参数:
logger.log(Level.WARNING, “This is a warning!”);
大部分日志框架都针对输出特定级别日志提供了快捷方式。例如,下面语句的作用和上面语句的作用是一样的:
logger.warning(“This is a warning!”);
你还可以阻止Logger输出低于指定日志级别的消息。在下面的示例中,Logger只能输出高于WARNING级别的日志消息,并丢弃日志级别低于WARNING的消息:
logger.setLevel(Level.WARNING);
我们还有另外一些方法可以用来记录额外的信息。logp()(精确日志)可以让你指定每条日志记录的源类(source class)和方法,而 logrb()(使用资源绑定的日志)可以让你指定用于提取日志消息的资源。entering() 和 exiting() 方法可以让你记录方法调用信息,从而程序的执行过程。
Appenders将日志消息转发给期望的输出。它负责接收日志事件,使用Layout格式化事件,然后将其发送给对应的目标。对于一个日志事件,我们可以使用多个Appenders来将事件发送到不同的目标位置。例如,我们可以在控制台上显示一个简单的日志事件的同时,将其通过邮件的方式发送给指定的接收者。
请注意,在java.util.logging中,Appenders被称作Handlers。
增加Appender
大部分日志框架的Appender都会执行类似的功能,但在实现方面大相径庭。如果使用 java.util.logging,你可以使用 Logger.addHandler() 方法将Appender添加到Logger中。例如,下面的代码添加了一个新的ConsoleHandler,它会将日志输出到控制台:
logger.addHandler(new ConsoleHandler());
一种更常用的添加Appender的方式是使用配置文件。如果使用 java.util.logging,Appenders会定义一个以逗号隔开的列表,下面的示例将日志事件输出到控制台和文件:
handlers=java.util.logging.ConsoleHandler, java.util.logging.FileHandler
如果使用基于XML的配置文件,Appenders会被添加到&Appenders&元素下面,如果使用Log4j,我们可以很容易地添加一个新ConsoleAppender来将日志消息发送到System.out:
&Console name=&console& target=&SYSTEM_OUT&&
&PatternLayout pattern=&[%p] %t: %m%n& /&
&/Console&
Appenders类型
这一节描述了一些更通用的Appenders,以及它们在各种日志框架中是如何实现的。
ConsoleAppender
ConsoleAppender是最常用的Appenders之一,它只是将日志消息显示到控制台上。许多日志框架都将其作为默认的Appender,并且在基本的配置中进行预配置。例如,在Log4j中ConsoleAppender的配置参数如下所示。
用于决定是否需要使用该Appender来处理日志事件
用于决定如何对日志记录进行格式化,默认情况下使用“%m%n”,它会在每一行显示一条日志记录
用于决定Appender是否需要了解输出(system.out或者system.err)的变化,默认情况是不需要跟踪这种变化
用于设置Appender的名字
ignoreExceptions
用于决定是否需要记录在日志事件处理过程中出现的异常
用于指定输出目标位置,默认情况下使用SYSTEM_OUT,但也可以修改成SYSTEM_ERR
一个完整的Log4j2的配置文件如下所示:
&?xml version=&1.0& encoding=&UTF-8&?&
&Configuration status=&warn& name=&MyApp&&
&Appenders&
&Console name=&MyAppender& target=&SYSTEM_OUT&&
&PatternLayout pattern=&%m%n&/&
&/Console&
&/Appenders&
&Root level=&error&&
&AppenderRef ref=&MyAppender&/&
&/Loggers&
&/Configuration&
这个配置文件创建了一个名为MyAppender的ConsoleAppender,它使用PatternLayout来对日志事件进行格式化,然后再将其输出到System.out。&Loggers&元素对定义在程序代码中的Loggers进行了配置。在这里,我们只配置了一个LoggerConfig,即名为Root的Logger,它会接收哪些日志级别在ERROR以上的日志消息。如果我们使用logger.error()来记录一个消息,那么它就会出现在控制台上,就像这样:
An unexpected error occurred.
你也可以使用Logback实现完全一样的效果:
&configuration&
&appender name=&MyAppender& class=&ch.qos.Logback.core.ConsoleAppender&&
&pattern&%m%n&/pattern&
&/encoder&
&/appender&
&root level=&error&&
&appender-ref ref=&MyAppender& /&
&/configuration&
FileAppenders
FileAppenders将日志记录写入到文件中,它负责打开、关闭文件,向文件中追加日志记录,并对文件进行加锁,以免数据被破坏或者覆盖。
在Log4j中,如果想创建一个FileAppender,需要指定目标文件的名字,写入方式是追加还是覆盖,以及是否需要在写入日志时对文件进行加锁:
&Appenders&
&File name=&MyFileAppender& fileName=&myLog.log& append=&true& locking=&true&&
&PatternLayout pattern=&%m%n&/&
&/Appenders&
这样我们创建了一个名为MyFileAppender的FileAppender,并且在向文件中追加日志时会对文件进行加锁操作。
如果使用Logback,你可以同时启用prudent模式来保证文件的完整性。虽然Prudent模式增加了写入文件所花费的时间,但它可以保证在多个FileAppender甚至多个Java程序向同一个文件写入日志时,文件的完整性。
&appender name=&FileAppender& class=&ch.qos.Logback.core.FileAppender&&
&file&myLog.log&/file&
&append&true&/append&
&prudent&true&/prudent&
&pattern&%m%n&/pattern&
&/encoder&
&/appender&
SyslogAppender
SyslogAppenders将日志记录发送给本地或者远程系统的日志服务。是一个接收日志事件服务,这些日志事件来自操作系统、进程、其它服务或者其它设备。事件的范围可以从诊断信息到用户登录硬件失败等。syslog的事件按照设备进行分类,它指定了正在记录的事件的类型。例如,auth facility表明这个事件是和安全以及认证有关。
Log4j和Logback都内置支持SyslogAppenders。在Log4j中,我们创建SyslogAppender时,需要指定syslog服务监听的主机号、端口号以及协议。下面的示例演示了如何设定装置:
&Appenders&
&Syslog name=&SyslogAppender& host=&localhost& port=&514& protocol=&UDP& facility=&Auth& /&
&/Appenders&
在Logback中,我们可以实现同样的效果:
&appender name=&SyslogAppender& class=&ch.qos.Logback.classic.net.SyslogAppender&&
&syslogHost&localhost&/syslogHost&
&port&514&/port&
&facility&Auth&/facility&
&/appender&
其它Appender
我们已经介绍了一些经常用到的Appenders,还有很多其它Appender。它们添加了新功能或者在其它的一些Appender基础上实现了新功能。例如,Log4j中的RollingFileAppender扩展了FileAppender,它可以在满足特定条件时自动创建新的日志文件;SMTPAppender会将日志内容以邮件的形式发送出去;FailoverAppender会在处理日志的过程中,如果一个或者多个Appender失败,自动切换到其他Appender上。
如果想了解更多关于其他Appender的信息,可以查看以及。
Layouts将日志记录的内容从一种数据形式转换成另外一种。日志框架为纯文本、HTML、syslog、XML、JSON、序列化以及其它日志提供了Layouts。
请注意:在java.util.logging中Layouts也被称为Formatters。
例如,java.util.logging提供了两种Layouts:SimpleFormatter和XMLFormatter。默认情况下,ConsoleHandlers使用SimpleFormatter,它输出的纯文本日志记录就像这样:
Mar 31, :51 AM MyClass main
SEVERE: An exception occurred.
而默认情况下,FileHandlers使用XMLFormatter,它的输出就像这样:
&?xml version=&1.0& encoding=&UTF-8& standalone=&no&?&
&!DOCTYPE log SYSTEM &logger.dtd&&
&date&T10:47:51&/date&
&millis&3&/millis&
&sequence&0&/sequence&
&logger&MyClass&/logger&
&level&SEVERE&/level&
&class&MyClass&/class&
&method&main&/method&
&thread&1&/thread&
&message&An exception occurred.&/message&
配置Layout
我们通常使用配置文件对Layouts进行配置。从Java 7开始,我们也可以使用来配置SimpleFormatter。
例如,在Log4j和Logback中最常用的Layouts是PatternLayout。它可以让你决定日志事件中的哪些部分需要输出,这是通过转换模式()完成的,转换模式在每一条日志事件的数据中扮演了“占位符”的角色。例如,Log4j默认的PatternLayout使用了如下转换模式:
&PatternLayout pattern=&%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n&/&
%d{HH:mm:ss.SSS} 将日期转换成时、分、秒和毫秒的形式,%level显示日志事件的严重程度,%C显示生成日志事件的类的名字,%t显示Logger的当前线程,%m显示时间的消息,最后,%n为下一个日志事件进行了换行。
改变Layouts
如果在java.util.logging中使用一个不同的Layout,需要将Appender的formatter属性设置成你想要的Layout。在代码中,你可以创建一个新的Handler,调用setFormatter方法,然后通过logger.AddHandler()方法将Handler放到Logger上面。下面的示例创建了一个ConsoleAppender,它使用XMLFormatter来对日志进行格式化,而不是使用默认的SimpleFormatter:
Handler ch = new ConsoleHandler();
ch.setFormatter(new XMLFormatter());
logger.addHandler(ch);
这样Logger会将下面的信息输出到控制台上:
&?xml version=&1.0& encoding=&UTF-8& standalone=&no&?&
&!DOCTYPE log SYSTEM &logger.dtd&&
&date&T10:47:51&/date&
&millis&0&/millis&
&sequence&0&/sequence&
&logger&MyClass&/logger&
&level&SEVERE&/level&
&class&MyClass&/class&
&method&main&/method&
&thread&1&/thread&
&message&An exception occurred.&/message&
如果想了解更多信息,你可以查看以及。
使用自定义Layouts
自定义Layouts可以让你指定Appender应该如何输出日志记录。,尽管你可以调整SimpleLogger的输出,但有一个限制,即只能够调整简单的纯文本消息。对于更高级的格式,例如HTML或者JSON,你需要一个自定义Layout或者一个单独的框架。
如果想了解更多使用java.util.logging创建自定义Layouts的信息,你可以查看Jakob Jenkov的Java日志指南中的章节。
日志级别提供了一种方式,我们可以用它来根据严重程度对日志进行分类和识别。java.util.logging 按照严重程度从重到轻,提供了以下级别:
SEVERE(最高级别)
FINEST(最低级别)
另外, 还有两个日志级别:ALL和OFF。ALL会让Logger输出所有消息,而OFF则会关闭日志功能。
设置日志级别
在设定日志级别后,Logger会自动忽略那些低于设定级别的日志消息。例如,下面的语句会让Logger忽略那些低于WARNING级别的日志消息:
logger.setLevel(Level.WARNING);
然后,Logger会记录任何WARNING或者更高级别的日志消息。我们也可以在配置文件中设置Logger的日志级别:
&Logger name=&MyLogger& level=&warning&&
Log4j和Logback中的PatternLayout类都支持转换模式,它决定了我们如何从每一条日志事件中提取信息以及如何对信息进行格式化。下面显示了这些模式的一个子集,对于Log4j和Logback来说,虽然这些特定的字段都是一样的,但是并不是所有的字段都会使用相同的模式。想要了解更多信息,可以查看和的PatternLayout文档。
Log4j/Logback 模式
级别/严重程度
例如,下面的PatternLayout会在中括号内x显示日志级别,后面是线程名字和日志事件的消息:
[%p] %t: %m
下面是使用了上述转换模式后的日志输出示例:
[INFO] main: initializing worker threads
[DEBUG] worker: listening on port 12222[INFO] worker: received request from 192.168.1.200[ERROR] worker: unknown request ID from 192.168.1.200
记录栈跟踪信息
如果你在Java程序中使用过,那么很有可能已经看到过栈跟踪信息。它提供了一个程序中方法调用的快照,让你准确定位程序执行的位置。例如,下面的栈跟踪信息是程序试图打开一个不存在的文件后生成的:
[ERROR] main: Unable to open file! java.io.FileNotFoundException: foo.file (No such file or directory)
at java.io.FileInputStream.open(Native Method) ~[?:1.7.0_79]
at java.io.FileInputStream.&init&(FileInputStream.java:146) ~[?:1.7.0_79]
at java.io.FileInputStream.&init&(FileInputStream.java:101) ~[?:1.7.0_79]
at java.io.FileReader.&init&(FileReader.java:58) ~[?:1.7.0_79]
at FooClass.main(FooClass.java:47)
这个示例使用了一个名为FooClass的类,它包含一个main方法。在程序第47行,FileReader独享试图打开一个名为foo.file的文件,由于在程序目录下没有名字是foo.file的文件,因此Java虚拟机抛出了一个FileNotFoundException。因为这个方法调用被放到了try-catch语块中,所以我们能够捕获这个异常并记录它,或者至少可以阻止程序崩溃。
使用PatternLayout记录栈跟踪信息
在写本篇文章时最新版本的Log4j和Logback中,如果在Layout中没有和可抛异常相关的信息,那么都会自动将%xEx(这种栈跟踪信息包含了每次方法调用的包信息)添加到PatternLayout中。如果对于普通的日志信息的模式如下:
[%p] %t: %m
它会变为:
[%p] %t: %m%xEx
这样不仅仅错误信息会被记录下来,完整的栈跟踪信息也会被记录:
[ERROR] main: Unable to open file! java.io.FileNotFoundException: foo.file (No such file or directory)
at java.io.FileInputStream.open(Native Method) ~[?:1.7.0_79]
at java.io.FileInputStream.&init&(FileInputStream.java:146) ~[?:1.7.0_79]
at java.io.FileInputStream.&init&(FileInputStream.java:101) ~[?:1.7.0_79]
at java.io.FileReader.&init&(FileReader.java:58) ~[?:1.7.0_79]
at FooClass.main(FooClass.java:47)
%xEx中的包查询是一个代价昂贵的操作,如果你频繁的记录异常信息,那么可能会碰到性能问题,例如:
} catch (FileNotFoundException ex) {
logger.error(“Unable to open file!”, ex);
一种解决方法是在模式中显式的包含%ex,这样就只会请求异常的栈跟踪信息:
[%p] %t: %m%ex
另外一种方法是通过追加%xEx(none)的方法排除(在Log4j)中所有的异常信息:
[%p] %t: %m%xEx{none}
或者在Logback中使用%nopex:
[%p] %t: %m%nopex
使用结构化布局输出栈跟踪信息
如你在“”一节中所见,对于站跟踪信息来说,使用结构化布局来记录是最合适的方式,例如JSON和XML。 这些布局会自动将栈跟踪信息按照核心组件进行分解,这样我们可以很容易将其导出到其他程序或者日志服务中。对于上述站跟踪信息,如果使用JSON格式,部分信息显示如下:
&loggerName& : &FooClass&,
&message& : &Foo, oh no! &,
&thrown& : {
&commonElementCount& : 0,
&localizedMessage& : &foo.file (No such file or directory)&,
&message& : &foo.file (No such file or directory)&,
&name& : &java.io.FileNotFoundException&,
&extendedStackTrace& : [ {
&class& : &java.io.FileInputStream&,
&method& : &open&,
&file& : &FileInputStream.java&,
记录未捕获异常
通常情况下,我们通过捕获的方式来处理异常。如果一个异常没有被捕获,那么它可能会导致程序终止。如果能够留存任何日志,那么这是一个可以帮助我们调试为什么会发生异常的好办法,这样你就可以找到发生异常的根本原因并解决它。下面来说明我们如何建立一个默认的异常处理器来记录这些错误。
类中有两个方法,我们可以用它来为未捕获的异常指定一个ExceptionHandler:
可以让你在任何线程上处理任何异常。setUncaughtExceptionHandler可以让你针对一个指定的线程设定一个不同的处理方法。而ThreadGroup则允许你设定一个处理方法。大部分人会使用默认的异常处理方法。
下面是一个示例,它设定了一个默认的异常处理方法,来创建一个日志事件。它要求你传入一个UncaughtExceptionHandler:
import java.util.logging.*;
public class ExceptionDemo {
private static final Logger logger = Logger.getLogger(ExceptionDemo.class);
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
logger.log(Level.SEVERE, t + & ExceptionDemo threw an exception: &, e);
class adminThread implements Runnable {
public void run() {
throw new RuntimeException();
Thread t = new Thread(new adminThread());
t.start();
下面是一个未处理异常的输出示例:
May 29, :15 PM ExceptionDemo$1 uncaughtException
SEVERE: Thread[Thread-1,5,main] ExceptionDemo threw an exception:
java.lang.RuntimeException
at ExceptionDemo$1adminThread.run(ExceptionDemo.java:15)
at java.lang.Thread.run(Thread.java:745)
JSON(JavaScript Object Notation)是一种用来存储结构化数据的格式,它将数据存储成键值对的集合,类似于HashMap或者Hashtable。JSON具有的可移植性和通用性,大部分现代语言都内置支持它或者通过已经准备好的第三方类库来支持它。
JSON支持许多基本数据类型,包括字符串、数字、布尔、数组和null。例如,你可以使用下面的JSON格式来表示一个电脑:
&manufacturer&: &Dell&,
&model&: &Inspiron&,
&hardware&: {
&cpu&: &Intel Core i7&,
&ram&: 16384,
“cdrom”: null
&peripherals&: [
&type&: &monitor&,
&manufacturer&: &Acer&,
&model&: &S231HL&
JSON的可移植性使得它非常适合存储日志记录,使用JSON后,Java日志可以被任何数目的JSON解释器所读取。因为数据已经是结构化的,所以解析JSON日志要远比解析纯文本日志容易。
Java中的JSON
对于Java来说,有大量的JSON实现,其中一个是JSON.simple。JSON.simple是轻量级的、易于使用,并且全部符合JSON标准。
如果想将上面的computer对象转换成可用的Java对象,我们可以从文件中读取JSON内容,将其传递给JSON.simple,然后返回一个Object,接着我们可以将Object转换成JSONObject:
Object computer = JSONValue.parse(new FileReader(&computer.json&));
JSONObject computerJSON = (JSONObject)
另外,为了取得键值对的信息,你可以使用任何日志框架来记录一个JSONObject,JSONObject对象包含一个toString()方法, 它可以将JSON转换成文本:
14:54:32,878 INFO
JSONTest main {&peripherals&:[{&model&:&S231HL&,&manufacturer&:&Acer&,&type&:&monitor&}],&model&:&Inspiron&,&hardware&:{&cdrom&:null,&ram&:16384,&cpu&:&Intel Core i7&},&manufacturer&:&Dell&}
虽然这样做可以很容易的打印JSONObject,但如果你使用结构化的Layouts,例如JSONLayout或者XMLLayout,可能会导致意想不到的结果:
&message& : &{&peripherals&:[{&model&:&S231HL&,&manufacturer&:&Acer&,&type&:&monitor&}],&model&:&Inspiron&,&hardware&:{&cdrom&:null,&ram&:16384,&cpu&:&Intel Core i7&},&manufacturer&:&Dell&}&,
Log4j中的JSONLayout并内嵌JSON对象,但你可以通过创建自定义Layout的方式来添加一个JSONObject字段,这个Layout会继承或者替换JSONLayout。然而,如果你使用一个日志管理系统,需要记住许多日志管理系统会针对某些字段使用预定义的数据类型。如果你创建一个Layout并将JSONObject存储到message字段中,那么它可能会和日志系统中使用的String数据类型相冲突。一种解决办法是将JSON数据存储到一个字段中,然后将字符串类型的日志消息存储到另外一个字段中。
其它JSON库
除了JSON.simple,Java中还有很多其它JSON库。是由JSON创建者开发的一个参考实现,它包含了额外的一些功能,可以转换其它数据类型,包括web元素。但是目前JSON-java已经没有人来维护和提供支持了。
如果想将JSON对象转换成Java对象或者逆向转换,Google提供了一个Gson库。使用Gson时,可以很简单使用 toJson() 和 fromJson() 方法来解析JSON,这两个方法分别用来将Java对象转换成JSON字符串以及将JSON字符串转换成Java对象。Gson甚至可以应用在内存对象中,允许你映射到那些没有源代码的对象上。
是一个强大的、流行的、功能丰富的库,它可以在Java中管理JSON对象。有一些框架甚至使用Jackson作为它们的JSONLayouts。尽管它很大并且复杂,但Jackson对于初学者和高级用户来说,是很容易使用的。
Logback通过logback-jackson和logback-json-classic库继承了Jackson,这两个库也是项目的一部分。在集成了Jackson后,你可以将日志以JSON的格式导出到任何Appender中。
详细解释了如何将JSON添加到logback中,在Wiki页面中的示例使用了LogglyAppender,这里的配置也可以应用到其他Appender上。下面的示例说明了如何将JSON格式化的日志记录写入到名为myLog.json的文件中:
&appender name=&file& class=&ch.qos.Logback.core.FileAppender&&
&file&myLog.json&/file&
&encoder class=&ch.qos.Logback.core.encoder.LayoutWrappingEncoder&&
&layout class=&ch.qos.Logback.contrib.json.classic.JsonLayout&&
&jsonFormatter class=&ch.qos.Logback.contrib.jackson.JacksonJsonFormatter&/&
&/encoder&
&/appender&
你也可以通过找到更多关于Jackson的深度介绍。
了解更多JSON相关信息
你可以通过学习更多JSON相关信息,或者通过来通过学习一个交互式的快速上手教程(请注意这个课程是基于JavaScript的,而不是Java)。有一些在线工具例如和可以帮助你解析、验证以及格式化JSON代码。
NDC、MDC以及ThreadContext
当处理多线程应用程序,特别是web服务时,跟踪事件可能会变得困难。当针对多个同时存在的多个用户生成日志记录时,你如何区分哪个行为和哪个日志事件有关呢?如何两个用户没有成功打开一个相同的文件,或者在同一时间没有成功登陆,那么怎么处理日志记录?你可能需要一种方式来将日志记录和程序中的唯一标示符关联起来,这些标识符可能是用户ID,会话ID或者设备ID。而这就是NDC、MDC以及ThreadContext的用武之地。
NDC、MDC和ThreadContext通过向单独的日志记录中添加独一无二的数据戳,来创建日志足迹(log trails)。这些数据戳也被称为鱼标记(fish tagging),我们可以通过一个或者多个独一无二的值来区分日志。这些数据戳在每个线程级别上进行管理,并且一直持续到线程结束,或者直到数据戳被删掉。例如,如果你的Web应用程序为每个用户生成一个新的线程,那么你可以使用这个用户的ID来标记日志记录。当你想在一个复杂的系统中跟踪特定的请求、事务或者用户,这是一种非常有用的方法。
嵌套诊断上下文(NDC)
NDC或者嵌套诊断上下文(Nested Diagnostic Context)是基于栈的思想,信息可以被放到栈上或者从栈中移除。而栈中的值可以被Logger访问,并且Logger无需显示想日志方法中传入任何值。
下面的代码示例使用NDC和Log4j来将用户姓名和一条日志记录关联起来。NDC是一个静态类,因此我们可以直接访问它的方法,而无需实例化一个NDC对象。在这个示例中, NDC.oush(username) 和 NDC.push(sessionID) 方法在栈中存储了当前的用户名(admin)和会话ID(1234),而NDC.pop()方法将一些项从栈中移除,NDC.remove()方法让Java回收内存,以免造成内存溢出。
import java.io.FileR
import org.apache.Log4j.L
import org.apache.Log4j.NDC;
String username = &admin&;
String sessionID = &1234&;
NDC.push(username);
NDC.push(sessionID);
// tmpFile doesn&#039;t exist, causing an exception.
FileReader fr = new FileReader(&tmpFile&);
catch (Exception ex) {
logger.error(&Unable to open file.&);
NDC.pop();
NDC.pop();
NDC.remove();
Log4j的PatternLayout类通过%x转换字符从NDC中提取值。如果一个日志事件被触发,那么完整的NDC栈就被传到Log4j:
&PatternLayout pattern=&%x %-5p - %m%n& /&
运行程序后,我们可以得出下面的输出:
&admin 1234 ERROR – Unable to open file.&
映射诊断上下文(MDC)
MDC或者映射诊断上下文和NDC很相似,不同之处在于MDC将值存储在键值对中,而不是栈中。这样你可以很容易的在Layout中引用一个单独的键。MDC.put(key,value) 方法将一个新的键值对添加到上下文中,而 MDC.remove(key) 方法会移除指定的键值对。
如果想在日志中同样显示用户名和会话ID,我们需要使用 MDC.put() 方法将这两个变量存储成键值对:
import java.io.FileR
import org.apache.Log4j.L
import org.apache.Log4j.MDC;
MDC.put(&username&, &admin&);
MDC.put(&sessionID&, &1234&);
// tmpFile doesn&#039;t exist, causing an exception.
FileReader fr = new FileReader(&tmpFile&);
catch (Exception ex) {
logger.error(&Unable to open file!&);
MDC.clear();
这里再一次强调,在不需要使用Context后,我们需要使用 MDC.clear() 方法将所有的键值对从MDC中移除,这样会降低内存的使用量,并阻止MDC在后面试图调用那些已经过期的数据。
在日志框架中访问MDC的值时,也稍微有些区别。对于存储在上下文中的任何键,我们可以使用%X(键)的方式来访问对应的值。这样,我们可以使用 %X(username) 和 %X(sessionID) 来获取对应的用户名和会话ID:
&PatternLayout pattern=&%X{username} %X{sessionID} %-5p - %m%n& /&
&admin 1234 ERROR – Unable to open file!&
如果我们没有指定任何键,那么MDC上下文就会被以 {(key, value),(key, value)} 的方式传递给Appender。
Logback中的NDC和MDC
和Log4j不同,Logback内置没有实现NDC。但是包提供了一个NDC实现,它使用MDC作为基础。在Logback内部,你可以使用 MDC.put()、MDC.remove() 和 MDC.clear() 方法来访问和管理MDC:
import org.slf4j.MDC;
Logger logger = LoggerFactory.getLogger(MDCLogback.class);
MDC.put(&username&, &admin&);
MDC.put(&sessionID&, &1234&);
FileReader fr = new FileReader(&tmpFile&);
catch (Exception ex) {
logger.error(&Unable to open file.&);
MDC.clear();
在Logback中,你可以在Logback.xml中将如下模式应用到Appender上,它可以输出和上面Log4j相同的结果:
&Pattern&[%X{username}] %X{sessionID} %-5p - %m%n&/Pattern&
&[admin] 1234 ERROR - Unable to open file.&
针对MDC的访问并不仅仅限制在PatternLayout上,例如,当使用JSONFormatter时,MDC中的所有值都会被导出:
&timestamp&:&5&,
&level&:&ERROR&,
&thread&:&main&,
&username&:&admin&,
&sessionID&:&1234&
&logger&:&MyClass&,
&message&:&Unable to open file.&,
&context&:&default&
ThreadContext
Version 2 of Log4j merged MDC and NDC into a single concept known as the Thread Context. The Thread Context is an evolution of MDC and NDC, presenting them respectively as the Thread Context Map and Thread Context Stack. The Thread Context is managed through the static ThreadContext class, which is implemented similar to Log4j 1’s MDC and NDC classes.
Log4j版本2中将MDC和NDC合并到一个单独的组件中,这个组件被称为线程上下文。线程上下文是针对MDC和NDC的进化,它分别用线程上下文Map映射线程上下文栈来表示MDC和NDC。我们可以通过ThreadContext静态类来管理线程上下文,这个类在实现上类似于Log4j版本1中的MDC和NDC。
When using the Thread Context Stack, data is pushed to and popped from a stack just like with NDC:
当使用线程上下文栈时,我们可以向NDC那样向栈中添加或者删除数据:
import org.apache.logging.Log4j.ThreadC
ThreadContext.push(username);
ThreadContext.push(sessionID);
// Logging methods go here
ThreadContext.pop();
当使用线程上下文映射时,我们可以像MDC那样将值和键结合在一起:
import org.apache.logging.Log4j.ThreadC
ThreadContext.put(“username”,&admin&);
ThreadContext.put(&sessionID&, &1234&);
// Logging methods go here
ThreadContext.clearMap();
ThreadContext类提供了一些方法,用于清除栈、清除MDC、清除存储在上下文中的所有值,对应的方法是ThreadContext.clearAll()、ThreadContext.clearMap()和ThreadContext.clearStack()。
和在MDC以及NDC中一样,我们可以使用Layouts在线程上下文中访问这些值。使用PatternLayout时,%x转换模式会从栈中获取值,%X和%X(键)会从图中获取值。
ThreadContext过滤
一些框架允许你基于某些属性对日志进行过滤。例如,Log4j的 会在键满足特定条件的情况下,自动调整日志级别。再比如,如果我们想要触发TRACE级别的日志消息,我们可以创建一个名为trace-logging-enabled的键,并向log4j配置文件中添加一个过滤器:
&Configuration name=&MyApp&&
&DynamicThresholdFilter key=&trace-logging-enabled& onMatch=&ACCEPT& onMismatch=&NEUTRAL&&
&KeyValuePair key=&true& value=&TRACE& /&
&/DynamicThresholdFilter&
如果ThreadContext包含一个名为trace-logging-enabled的键,onMatch 和 onMismatch 会决定如何处理它。关于 onMatch 和 onMismatch,我们有三个可选项:ACCEPT,它会处理过滤器的规则;DENY,它会忽略过滤器的规则;NEUTRAL,它会推迟到下一个过滤器。除了这些,我们还定义一个键值对,当值为true时,我们启用TRACE级别的日志。
现在,当trace-logging-enabled被设置成true时,即使根Logger设置的日志级别高于TRACE,Appender也会记录TRACE级别的消息。
你可能还想过滤一些特定的日志到特定的Appender中,Log4j中提供了ThreadContextMapFilter来实现这一点。如果我们想要限制某个特定的Appender,只记录针对某个用户的TRACE级别的消息,我们可以基于username键添加一个ThreadContextMapFilter:
&Console name=&ConsoleAppender& target=&SYSTEM_OUT&&
&ThreadContextMapFilter onMatch=&ACCEPT& onMismatch=&DENY&&
&KeyValuePair key=&username& value=&admin& /&
&/ThreadContextMapFilter&
如果想了解更多信息,你可以查看和文档中关于DynamicThresholdFilter部分。
Markers允许你对单独的日志记录添加一些独一无二的数据。它可以用来对日志记录进行分组,触发一些行为,或者对日志记录进行过滤,并将过滤结果输出到指定的Appender中。你甚至可以将Markers和ThreadContext结合在一起使用,以提高搜索和过滤日志数据的能力。
例如,假设我们有一个可以连接到数据库的类,如果在打开数据库的时候发生了异常,我们需要把异常记录成fatal错误。我们可以创建一个名为DB_ERROR的Marker,然后将其应用到日志事件中:
import org.apache.logging.Log4j.M
import org.apache.logging.Log4j.MarkerM
final static Marker DB_ERROR = MarkerManager.getMarker(&DATABASE_ERROR&);
logger.fatal(DB_ERROR, &An exception occurred.&);
为了在日志输出中显示Marker信息,我们需要在PatternLayout中添加%marker转换模式:
&PatternLayout pattern=&%p %marker: %m%n& /&
[FATAL] DATABASE_ERROR: An exception occurred.
或者对于JSON和XML格式的Layouts,会自动在输出中包含Marker信息:
&thread& : &main&,
&level& : &FATAL&,
&loggerName& : &DBClass&,
&marker& : {
&name& : &DATABASE_ERROR&
&message& : &An exception occurred.&,
通过对Marker数据进行自动解析和排序,集中式的日志服务可以很容易对日志进行搜索处理。
Markers过滤
Marker过滤器可以让你决定哪些Marker由哪些Logger来处理。marker字段会比较在日志事件里面的Marker名字,如果名字匹配,那么Logger会执行后续的行为。例如,在中,我们可以配置一个Appender来只显示哪些使用了DB_ERROR Marker的消息,这可以通过log4j2.xml中的Appender添加如下信息来实现:
&MarkerFilter marker=&DATABASE_ERROR& onMatch=&ACCEPT& onMismatch=&DENY& /&
如果日志记录中某一条的Marker可以匹配这里的marker字段,那么onMatch会决定如何处理这条记录。如果不能够匹配,或者日志记录中没有Marker信息,那么onMismatch就会决定如何处理这条记录。对于onMatch和onMismatch来说,有3个可选项:ACCEPT,它允许记录事件;DENY,它会阻塞事件;NEUTRAL,它不会对事件进行任何处理。
在Logback中,我们需要更多一些设置。首先,想Appender中添加一个新的EvaluatorFilter,并如上所述指定onMatch和onMismatch行为。然后,添加一个OnMarkerEvaluator并将Marker的名字传递给它:
&filter class=&ch.qos.Logback.core.filter.EvaluatorFilter&&
&evaluator class=&ch.qos.Logback.classic.boolex.OnMarkerEvaluator&&
&marker&DATABASE_ERROR&/marker&
&/evaluator&
&onMatch&ACCEPT&/onMatch&
&onMismatch&DENY&/onMismatch&
将Markers和NDC、MDC以及ThreadContext结合使用
Marker的功能和ThreadContext类似,它们都是向日志记录中添加独一无二的数据,这些数据可以被Appender访问。如果把这两者结合使用,可以让你更容易的对日志数据进行索引和搜索。如果能够知道何时使用哪一种技术,会对我们有所帮助。
NDC、MDC和ThreadContext被用于将相关日志记录结合在一起。如果你的应用程序会处理多个同时存在的用户,ThreadContext可以让你将针对某个特定用户的一组日志记录组合在一起。因为ThreadContext针对每个线程都是不一样的,所以你可以使用同样的方法来对相关的日志记录进行自动分组。
另一方面,Marker通常用于标记或者高亮显示某些特殊事件。在上述示例中,我们使用DB_ERROR Marker来标明在方法中发生的SQL相关异常。我们可以使用DB_ERROR Marker来将这些事件的处理过程和其他事件区分开来,例如我们可以使用SMTP Appender来将这些事件通过邮件发送给数据库管理员。
指南和教程
(Jakob Jenkov)——使用Java Logging API进行日志开发教程
(Oracle)—— Oracle提供的在Java中进行日志开发的指南
(Tutorials Point)——使用log4j 版本1进行日志开发的指南
日志抽象层
(Apache)——针对Log4j、Avalon LogKit和java.util.logging的抽象层
(QOS.ch)——一个流程的抽象层,应用在多个日志框架上,包括Log4j、Logback以及java.util.logging
(Oracle)—— Java默认的日志框架
(Apache)——开源日志框架
(Logback Project)——开源项目,被设计成Log4j版本1的后续版本
(tinylog)——轻量级开源logger
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
(新浪微博:)
盲目改为fastjson。fastjson在序列化过程中,实测比jackson有30%的差距。反序列...
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
&#8211; 好的话题、有启发的回复、值得信赖的圈子
&#8211; 写了文章?看干货?去头条!
&#8211; 为IT单身男女服务的征婚传播平台
&#8211; 优秀的工具资源导航
&#8211; 活跃 &#038; 专业的翻译小组
&#8211; 国内外的精选博客文章
&#8211; UI,网页,交互和用户体验
&#8211; JavaScript, HTML5, CSS
&#8211; 专注Android技术分享
&#8211; 专注iOS技术分享
&#8211; 专注Java技术分享
&#8211; 专注Python技术分享
& 2016 ImportNew

我要回帖

更多关于 腾讯大王卡 官网 的文章

 

随机推荐