JSON格式打印日志的坑

  • Post author:
  • Post category:其他


调试程序的时候,我们总是希望日志越多越好,越详细越好,恨不得把每个变量的每次变化都打印出来。碰到对象,也转换成JSON格式打印出来,方便又好看。所以,有时候,我们会不小心写出下面这样的代码。

ObjectMapper objectMapper = new ObjectMapper()
log.info(objectMapper.writeValueAsString(something));

上面的代码里有个坑,很坑的那种坑。

这个坑在功能测试时不会表现出来,要在生成环境上,在产品大火,用户猛涨,请求暴增的关键时刻,它才会蹦出来,保证让你在老板面前露个脸。

这个坑其实很明显,有经验的小伙伴一眼就能看出来,不论日志等级怎么设置,objectMapper.writeValueAsString(something) 这段代码是一定会执行的。虽然对象转JSON效率很高,消耗很小,但这很小的消耗,相对于打日志来说,还是很大的。所以,在高并发的时候,这段代码就会成为性能瓶颈。

logback 官网上也提到了类似问题,他给的例子是这样的:

logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));

他给出的第一个解决方法是这样的,打日志前判断一下要不要打:

if(logger.isDebugEnabled()) { 
  logger.debug("Entry number: " + i + " is " + String.valueOf(entry[i]));
}

我们也可以用同样的方式处理,打日志前也判断一下:

		if ( log.isInfoEnabled())
		{
			ObjectMapper objectMapper = new ObjectMapper();
			log.info(objectMapper.writeValueAsString(something));
		}

但logback官网推荐的方法是下面这种方式,用带参数的日志函数。

Object entry = new SomeObject(); 
logger.debug("The entry is {}.", entry);
logger.debug("The new entry is {}. It replaces {}.", entry, oldEntry);
logger.debug("Value {} was inserted between {} and {}.", newVal, below, above);

这种方式我们好像没法直接用,如果我们写成这样:

log.info("{}" , objectMapper.writeValueAsString(something));

问题一样存在,不论日志等级怎么设置,objectMapper.writeValueAsString(something) 这段代码还是一定会执行的。

得变通一下。

先重写被打印的对象 toString 函数,在 toString 函数里把对象转换成JSON

	public class ToBeLog
	{
		@Override
		public String toString()
		{
			try {
				ObjectMapper objectMapper = new ObjectMapper();
	            return objectMapper.writeValueAsString(this);
	        } catch (JsonProcessingException e) {
	            System.out.println(e.getMessage());
	            return e.getMessage();
	        }
		}
	}

然后再调用带参数的日志函数

	ToBeLog tobelog = new ToBeLog();
	log.info("{}" , tobelog);

上面的代码,如果日志等级设置成打印info日志,logback就会调用 tobelog的 toString函数,把 tobelog转换成JSON格式,打印出来。如果日志等级设置成不打印info日志,logback就不会调用 tobelog的 toString 函数了,也就没有了性能问题。

当然,带来了一些其他问题。

每个类都要写 toString 函数,多麻烦是吧。有些类还可能因为业务原因不能重写了。有没有简单点的方法?

有,写一个类专门干这事就行了。

	public class JSONLogWrap
	{
		private final static  ObjectMapper objectMapper = new ObjectMapper();
		private final Object value  ;
		
		public JSONLogWrap(Object value)
		{
			this.value = value ;
		}

		@Override
		public String toString()
		{
	    	if ( value == null )
    			return "" ;
			try {
	            return objectMapper.writeValueAsString(value);
	        } catch (JsonProcessingException e) {
	            System.out.println(e.getMessage());
	            return e.getMessage();
	        }
		}
	}

然后,要写日志时,把要转换成JSON的对象用这个专业类包裹一下。

log.info("{}" ,new JSONLogWrap(tobelog));

如果日志等级设置成打印info日志,logback就会调用 JSONLogWrap的 toString函数,把 tobelog转换成JSON格式,打印出来。如果日志等级设置成不打印info日志,logback就不会调用 JSONLogWrap的 toString 函数了,也就没有了性能问题。

还有没有更好的方法?

有,还有一个方法,更专业,更高端,更能体现小伙伴们的技术修养与个人品味。

写个MessageConverter ,

以json格式打印日志_对象转json打印日志_hweiyu00的博客-CSDN博客

,看起来很不错的样子。



版权声明:本文为fuwenjing原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。