前言

本文总结了作者在Java代码检视中遇到的一些关于日志打印的问题,并给出修改建议。因能力有限,难免存在错漏,欢迎指正。

. 不规范的异常打印

使用slf4j日志组件时,logger.error(与log.warn)接受Throwable参数,以打印异常名和详细的堆栈信息(可能内部调用e.printStackTrace())。

但书写打印语句时,需要注意格式。例如:

 logger.error("Best print: ", e);
logger.error("Good print: {}", e); //a.
logger.error("Bad print: " + e); //b. 或 + e.toString()
logger.error("Bad print: " + e.getMessage()); //c. 或: {}", e.getMessage())

a句仍可打印异常名和堆栈信息,但多输出一对花括号"{}";b句仅打印异常名;c句打印异常消息字符串。以空指针异常(Runtime异常)为例,b句打印"java.lang.NullPointerException",c句打印"null"(过于简陋)。

可使用如下正则表达式排查Java代码里不规范的异常打印:

^\s*[Ll][Oo][Gg](ger|GER)*\.(error|warn)\("(.+?)"\s*\+\s*(e|ex|e.getMessage\(\))\s*\);.*

该正则表达式可排查出形如上文b、c句的打印缺陷。考虑到实际代码书写风格的差异,会存在少量的漏判和误判。此外应注意,某些异常(如SQL或IO异常)可能泄露敏感信息,打印异常堆栈之前应根据网络安全要求做必要的”加工”。

二. 不规范的变量打印

使用slf4j日志组件打印变量时,建议使用”{}”占位符风格(C风格),而不是”+”拼接符(C++风格)。例如:

 logger.error("Country: {}, Province: {}, City: {}", ctry, prov, city); //占位符
logger.error("Country: "+ctry+", Province:" +prov +", City: "+city); //拼接符

显然,占位符风格更直观,而且不容易出现笔误——上面的拼接符语句有问题,谁能发现?

但最常见的变量打印问题,是打印时未使用占位符或拼接符:

 logger.error("print cannotBePrinted: ", cannotBePrinted);  //Wrong
logger.error("print canBePrinted: " + canBePrinted); //Good
logger.error("print canBePrinted: {}", canBePrinted); //Better

注意,cannotBePrinted或canBePrinted为非Throwable的变量。亦即,异常变量e不适用上述规则,但e.getMessage()适用。

可使用如下正则表达式排查Java代码里不规范的变量打印:

^\s*[Ll][Oo][Gg](ger|GER)*\.[a-zA-Z]{4,5}\("([^({})]+?)"\s*\,\s*([^e\s]|\w{2,})\s*\);.*

考虑到实际代码书写风格的差异,该正则表达式会存在漏判和误判。例如,"logger.error("Best print: ", ex);"会被误判。

. 多余的DEBUG级别判断

使用slf4j日志组件以Debug级别打印时,debug()方法内部会调用isDebugEnabled()判断调试级别。因此,下述代码里的判断没有必要:

if(logger.isDebugEnabled())
{
logger.debug("handleChanges end:{}:{}(ms) ", new Object[] {changes, useTime});
}

四. 日志对象logger的声明

一般建议日志对象logger声明为private static final。声明为private可防止logger对象被其他类非法使用。声明为static可防止重复new出logger对象,造成资源浪费;还可以防止logger被序列化,造成安全风险。声明为final是因为在类的生命周期内无需变更logger。

然而,实际编码中可根据使用场景稍作变通。例如,static final成员变量通常要求大写(如LOGGER),如果觉得大写别扭,可以去除final修饰。又如,期望logger对象可复用时,可如下定义:

protected final Logger logger = LoggerFactory.getLogger(this.getClass());

这样,子类可以直接使用继承来的logger对象打印输出,而无需再次getLogger()获取日志对象。

五. 日志级别应合理

如果日志不分级别或级别不合理,则定位问题时就无法快速有效地屏蔽大量低级别信息,给快速定位带来难度。建议与具体实现有关的日志使用debug级,一般的业务处理日志用info级,不影响业务进行的错误用warn级,而记录异常或重要错误的日志应为error级。

Java编码常见的Log日志打印问题的更多相关文章

  1. android的Log日志打印管理工具类(一)

    android的Log日志的打印管理工具类: package com.gzcivil.utils; import android.util.Log; /** * 日志打印管理 * * @author ...

  2. 超高逼格Log日志打印

    代码地址如下:http://www.demodashi.com/demo/12646.html 前言 Log日志的打印一直是一个比较头疼的事,怎样才能让自己的log显示更多信息,怎样才能让自己的log ...

  3. Javascript 将 console.log 日志打印到 html 页面中

    如何将 console.log() 打印的日志输出到 html 页面中 (function () { var old = console.log; var logger = document.getE ...

  4. 树莓派 log 日志 打印到 TXT

    #include<stdio.h> #include <stdarg.h> #include <unistd.h> #include <stdint.h> ...

  5. java最简单实现Log打印和生成日志文件

    导包 1.commons-logging.jar包 下载 2.log4j.jar包 下载 配置log4j 1.在src根目录下创建一个log4j.properties文件. 文件全部内容如下: log ...

  6. 大数据项目中js中代码和java中代码(解决Tomcat打印日志中文乱码)

    Idea2018中集成Tomcat9导致OutPut乱码找到tomcat的安装目录,打开logging.properties文件,增加一行代码,覆盖默认设置,将日志编码格式修改为GBK.java.ut ...

  7. Xposed框架Hook Android应用的所有类方法打印Log日志

    本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80954759 在进行Android程序的逆向分析的时候,经常需要Android程 ...

  8. java log日志的输出。

    在Spring框架中添加日志功能: pom.xml <dependency> <groupId>log4j</groupId> <artifactId> ...

  9. java中关于log日志

    博:http://zhw2527.iteye.com/blog/1006302 http://zhw2527.iteye.com/blog/1099658 在项目开发中,记录错误日志是一个很有必要功能 ...

随机推荐

  1. Java基础知识总结--final、finally、finalize的区别

    谈谈final.finally.finalize的区别 1.final修饰符:如果一个类被声明为final,意味着这个类不能再被派生出新的子类,不能作为父类被别的类继承.因此,一个类不能即被声明为ab ...

  2. 利用Ajax和JSON实现关于查找省市名称的二级联动功能

    功能实现的思路:我们经常碰见网上购物时候填写收件地址会用到这个查找省市县的三级联动查找功能,我们可以利用Ajax和JSON技术模拟这个功能,说白了同样是使用Ajax的局部数据更新功能这个特性.因为省市 ...

  3. ES6语法(一)let 和 const 命令

    1.let命令 与 var 的区别 用法类似于var,但是所声明的变量,只在 let命令所在的代码块内有效. var 声明的变量可以在声明之前使用,值为 undefined ;let命令改变了语法行为 ...

  4. SqlBulkCopy 批量插入

    ASP.NET批量插入的一种方式,直接上代码(方便以后查找): public static int ExcuteAddMany(string cmdStr, string[] tempArray, p ...

  5. C# Xamarin移动开发项目实战篇

    一.课程介绍 在前面阿笨的<C# Xamarin移动开发基础进修篇>课程中,大家已经熟悉和了解了Xamarin移动App开发的基础知识和原理.本次分享课<C# Xamarin移动开发 ...

  6. scipy.stats

    scipy.stats Scipy的stats模块包含了多种概率分布的随机变量,随机变量分为连续的和离散的两种.所有的连续随机变量都是rv_continuous的派生类的对象,而所有的离散随机变量都是 ...

  7. spring注解之@PostConstruct在项目启动时执行指定方法

    一.注解解释 Spring的@PostConstruct注解在方法上,表示此方法是在Spring实例化该Bean之后马上执行此方法,之后才会去实例化其他Bean,并且一个Bean中@PostConst ...

  8. 人人网框架导入uidGenerator的ID生成方式

    人人网框架导入uidGenerator的ID生成方式 2019-03-11 LIUREN    SpringBoot2.0  uidGenerator  SpringBoot2.0  uidGener ...

  9. 每天一个linux命令:iostat

    1.命令简介 iostat(I/O statistics 输入/输出统计) 命令对系统的磁盘操作活动进行监视.它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况 2.用法 iostat [ ...

  10. SELECT INTO和INSERT INTO SELECT的区别 类似aaa?a=1&b=2&c=3&d=4,如何将问号以后的数据变为键值对 C# 获取一定区间的随即数 0、1两个值除随机数以外的取值方法(0、1两个值被取值的概率相等) C# MD5 加密,解密 C#中DataTable删除多条数据

    SELECT INTO和INSERT INTO SELECT的区别   数据库中的数据复制备份 SELECT INTO: 形式: SELECT value1,value2,value3 INTO Ta ...