多线程环境下使用log4j输出各线程的标识

本文介绍如何在Log4j中通过扩展PatternLayout类来实现在多线程环境中输出线程ID,以此区分不同线程产生的日志信息。此外还介绍了使用PatternLayout的%t、%x及%X参数等方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在多线程环境下,我们可能需要输出很多信息,每个线程产生的日志信息可能都是类似的,我们如何区分出哪些信息是同一个线程输出的呢?其实log4j已经提供了多种实现方式:
 1.使用PatternLayout,在设定输出格式的时候增加%t参数,这样会输出各个线程的线程名称,这样我们就可以根据线程名称区分哪些内容是同一个线程输出出来的。
 2.使用NDC,也是基于PatternLayout,在设定输出格式的时候增加%x参数,不过需要再代码中增加相关内容,再线程开始的时候,调用NDC.push("标识信息");在这之后,所有的输出的日志内容%x的位置都会输出指定的"标识信息",这样也能区分开不同线程输出的内容,NDC内部是各堆栈实现的,所以可以多次调用push方法加入"标识信息",然后调用pop方法"弹出"当前表示信息,使上一个标识信息可用,最后,载线程处理结束先,要再次调用NDC.remove();方法,清空堆栈信息。
 3.使用MDC,和NDC类似,都是基于PatternLayout,的,需要使用%X{paramName}参数(注意MDC使用大写'X',NDC使用小写'x'),不过MDC可以同时指定多个%X,例如:%X{param1} %X{param2},在程序中处理的时候也需要代码配合,开始的时候需要调用MDC.put(String key,Object o);方法一次或多次,将输出格式中的%X占位符指定实际的标识信息,然后再正常输出日志信息,因为MDC内部是用MAP实现的,而不是堆栈,所以最后不用清理MAP内容。
以上三种方式是log4j已经提供的方式,但是都有其缺点:
 1.输出的是线程名称,当tomcat环境下使用的时候,个别线程名称会比较长,影响日志信息的可读性,而且我们只是为了区别各线程输出的信息,对线程名称具体是什么内容不关心。
 2和3的实现方式类似,不过需要代码的支持,每次使用时候,需要设定参数的内容,然后才能使用,如果忘记,可能会带来一些影响。
另外还有一个方法就是扩展log4j的某些类,在log4j的日志中输出线程的ID(从jdk5开始,Thread类增加了getId()方法),线程ID是个long型数字,大多数情况下可能只有几十到几百之间的数字,比较短,足够作为区分线程的标识用了,扩展方式如下:
首先扩展PatternLayout类,
 package ex.log4j;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.helpers.PatternParser;
 import ex.log4j.helper.ExPatternParser;
public class ExPatternLayout extends PatternLayout {
 public ExPatternLayout(String pattern) {
 super(pattern);
 }
 public ExPatternLayout() {
 super();
 }
 
/**
 * 重写createPatternParser方法,返回PatternParser的子类
 */
 @Override
 protected PatternParser createPatternParser(String pattern) {
 return new ExPatternParser(pattern);
 }
}
 扩展PatternParser类,
 package ex.log4j.helper;
import org.apache.log4j.helpers.FormattingInfo;
import org.apache.log4j.helpers.PatternConverter;
import org.apache.log4j.helpers.PatternParser;
import org.apache.log4j.spi.LoggingEvent;
public class ExPatternParser extends PatternParser {
 public ExPatternParser(String pattern) {
 super(pattern);
 }
 /**
 * 重写finalizeConverter,对特定的占位符进行处理,T表示线程ID占位符
 */
 @Override
 protected void finalizeConverter(char c) {
 if (c == 'T') {
 this.addConverter(new ExPatternConverter(this.formattingInfo));
 } else {
 super.finalizeConverter(c);
 }
 }
 private static class ExPatternConverter extends PatternConverter {
 public ExPatternConverter(FormattingInfo fi) {
 super(fi);
 }
 /**
 * 当需要显示线程ID的时候,返回当前调用线程的ID
 */
 @Override
 protected String convert(LoggingEvent event) {
 return String.valueOf(Thread.currentThread().getId());
 }
 }
}
 到此已经扩展完成,将以上内容编译后(可以打成jar包)和log4j.jar一同使用(使用同一个类装载器装载),然后配置log4j.properties类,修改
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 为
log4j.appender.stdout.layout=ex.log4j.ExPatternLayout
 在输出格式中增加%T(log4j定义%t表示线程名称,%T没有定义,所以这里使用%T表示线程ID),
 log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %T %c %5p - %m%n
下面就按照平常的习惯使用log4j即可,再输出的日志中就会包含线程ID,例如:
 2009-03-29 10:43:58 1 test.log.Log4jTest INFO - ok
 时间后面的'1'就表示线程id,当在多线程环境下,例如web环境,用这种方式就能很容易区分出一次web请求过程中打印出的日志信息,而不会和其他web请求打印出的日志信息混淆。这样即增加的日志的可读性,也不会输出太多的无用信息。


本文来自优快云博客,转载请标明出处:http://blog.youkuaiyun.com/benq998/archive/2009/03/29/4033334.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值