Log4j打印日志添加请求ID(包含多线程)

多种实现方式

在多线程环境下,我们可能需要输出很多信息,每个线程产生的日志信息可能都是类似的,我们如何区分出哪些信息是同一个线程输出的呢?其实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 com.test.log4j;

import org.apache.log4j.PatternLayout;
import org.apache.log4j.helpers.PatternParser;

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 com.test.log4j;

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=com.test.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:581 test.log.Log4jTest  INFO - ok

时间后面的'1'就表示线程id,当在多线程环境下,例如web环境,用这种方式就能很容易区分出一次web请求过程中打印出的日志信息,而不会和其他web请求打印出的日志信息混淆。这样即增加的日志的可读性,也不会输出太多的无用信息。


MDC方式

package com.log4j.web;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

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

import com.log4j.util.MDCUtil;

public class UserServlet extends HttpServlet {
	private static final long serialVersionUID = -1101749787101355292L;
	
	protected Log log = LogFactory.getLog(this.getClass());

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.doPost(request, response);
	}

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		for (int i = 0; i < 5; i++) {
			log.info("toUnBind---"+ i);
			String requestId = MDCUtil.get();
			MDCUtil.set(requestId, i+"");
			log.info("toUnBind---end---"+ i);
		}
		
	}
	
}

package com.log4j.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

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

import com.log4j.util.MDCUtil;

public class DisableUrlSessionFilter implements Filter {
	protected Log logger = LogFactory.getLog(this.getClass());
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		HttpServletRequest httpRequest = (HttpServletRequest)request;
		
		MDCUtil.set();
		long t1 = -1l;
		if (logger.isInfoEnabled()) {
			t1 = System.nanoTime();
			logger.info("Begin Request["+ getRequestUri(httpRequest,true) +"]...");
		}
		try{
			chain.doFilter(request, response);
		}finally{
			if(logger.isInfoEnabled()){
				logger.info(">>>>>>> Completed request["+ getRequestUri(httpRequest,false) +"]["+ (System.nanoTime()-t1)/1000/1000.0 +"ms].");
			}
		}
	}

	protected String getRequestUri(HttpServletRequest request, boolean includeQueryString){
		String path = request.getRequestURI();
		String qs = "";
		if(includeQueryString){
			qs=request.getQueryString();
			if(qs!=null && qs.length()>0){
				qs="?"+qs;
			}else{
				qs="";
			}
		}
		return path+qs;
	}

	@Override
	public void destroy() { }

	@Override
	public void init(FilterConfig arg0) throws ServletException { }
}

log4j.rootLogger = INFO,default
log4j.additivity.org.apache=false

#log4j.logger.taskOrder = INFO,taskOrder
 
# ConsoleAppender DailyRollingFileAppender#
### set default appender ###
log4j.appender.default = org.apache.log4j.ConsoleAppender
log4j.appender.default.layout=org.apache.log4j.PatternLayout
log4j.appender.default.layout.ConversionPattern=[%d{HH:mm:ss.SSS}] [%-3p] %X{T} %X{U} %c{1}: %m%n
#log4j.appender.default.layout.ConversionPattern=[xxxx] %d %5p %X{T} %X{U} (%c.%M:%L) - %m%n

### set taskOrder appender ###
#log4j.appender.taskOrder = org.apache.log4j.DailyRollingFileAppender
#log4j.appender.taskOrder.file = /app/applogs/sfbuy/ISNLog/taskOrder/taskOrder.log
#log4j.appender.taskOrder.DatePattern='.'yyyy-MM-dd
#log4j.appender.taskOrder.layout=org.apache.log4j.PatternLayout
#log4j.appender.taskOrder.layout.ConversionPattern=[%-5p] [%c] [%d{yyyy-MM-dd HH:mm:ss,SSS}] [%t] [%l] - [%m]%n


转载地址:http://blog.csdn.net/liulin_good/article/details/5995884

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
 课程目标:你将对研发框架的代码封装和开发规范制定的底层实现逻辑有所掌握,并形成自己的开发封装套路,告别CRUD课程简介:   1. 课程背景: 能帮你解决什么问题?企业中通常由架构师搭建好开发框架,里面包含了很多封装好的基础结构,日志规范以及响应的异常统一处理,还有相应的参数校验等。很多初中高级开发工程师没有机会接触这部分代码的逻辑,而这部分代码逻辑又是非线性的,单纯看代码逻辑是看不出来执行顺序的,需要大量调试总结。不像mvc代码结构,你知道从Controller层看到Service再到Dao层,而这样底层的基础性代码则像积木,不熟悉的前提下需要一块块的插入拔出,不断总结,而沿着本课程的顺序梳理与实战你将会豁然开朗。市面上的大多文章结构较散,并且处理不够全面,比如返回结构通常是 具体的正例与反例日志统一处理 统一异常处理及特殊的情况处理 参数优雅校验 1基础的校验 2. 自定义校验 3.分组校验最重要的是给大家捋顺了一个清晰的实现结构以上在我们后面章节Spring Boot HelloWorld的至少10个可扩展点里有更多的相关介绍,在核心章节里有更细致的讲解实现。理解框架升级底层逻辑: 全网首套基于Spring Boot 3.x+Java 17开发系列 SpringBoot3的升级背景和路线逻辑掌握代码重构及编码效率提升技巧学习基于最为前沿的Spring Boot 3.x 和 Java 17 开发代码依赖于Spring Boot 3.x Java 17环境开发穿插相应新版本的变化讲解可编写完成一个生产级开发规范框架的制定 包含不限于 统一响应结构统一异常处理基于ThreadLocal处理请求RequestId基于日志框架的MDC 统一记录日志统一状态码处理Filter中的异常及状态码处理,以及日志的完整性处理自定义优雅参数校验学习源码剖析方式方法等等 3. 课程规划1.  课程章节规划 2. 讲课方式代码实战为主+图文演示为辅例如说明后端校验的重要性 以一张图清晰明了的展示说明解答了为何前端有了前端校验还要后端校验的问题 3. 特别说明本课程所有代码使用版本会随着Spring Boot 3.x的开发进度而更新,直到Spring Boot 3.x的正式版本发布,可放心食用本课程的重点会侧重放在研发框架的基础规范编码上,并非专注于SpringBoot3.x和Java17的新特性上讲解。主要是两点原因,1课程的方向侧重点, 2. SpringBoot3.x 官方尚处于MileStone/Snapshot版并未Release,所以大家可关注本人编程燃风后续的产品课程。本课程代码同样适用于Spring Boot 1.x 2.x 只需微调API和相关库版本的即可(注意1.x已经归档不维护) 常见问题:问:是否讲解Java17和SpringBoot3新特性答:本课程的重点会侧重放在研发框架的基础规范编码上,并非专注于SpringBoot3.x和Java17的新特性上讲解。主要是两点原因,1课程的方向侧重点是框架基础规范编码实战 2. SpringBoot3.x 官方尚处于MileStone/Snapshot版并未Release,所以大家可关注本人编程燃风后续的讲解课程。问:代码仅限于SpringBoot3吗? SpringBoot2和SpringBoot1是否适用?答:本课程代码同样适用于Spring Boot 1.x 2.x 只需微调API和相关库版本的即可(注意1.x已经归档不维护)。另外本课程重点讲解代码封装和底层实现逻辑和具体API版本关联不大,只是基于最新的SpringBoot3和Java17实现而已,请放心使用。
好的,下面是使用Log4j打印日志的步骤: 1. 在pom.xml文件中添加log4j的依赖,如下: ```xml <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> ``` 2. 在src/main/resources目录下创建log4j.properties文件,配置log4j的相关参数,如下: ```properties # Global logging configuration log4j.rootLogger=INFO, stdout # Console output... log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c %x - %m%n # Log file log4j.appender.file=org.apache.log4j.RollingFileAppender log4j.appender.file.File=/var/log/myapp.log log4j.appender.file.MaxFileSize=10MB log4j.appender.file.MaxBackupIndex=10 log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-5p %c %x - %m%n ``` 上述配置中,rootLogger设置为INFO级别,输出到stdout和log文件中;stdout输出到console,log文件输出到/var/log/myapp.log文件中。 3. 在需要打印日志的类中,使用log4j进行日志打印,如下: ```java import org.apache.log4j.Logger; public class MyService { private static final Logger logger = Logger.getLogger(MyService.class); public void doSomething() { logger.info("something happened"); logger.error("something went wrong"); } } ``` 在上述代码中,使用Logger.getLogger()方法获取Logger对象,然后使用info()和error()方法打印日志。 这就是使用Log4j打印日志的基本步骤,希望能对你有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值