图片来自宝途网选择合适的日志级别常见的日志级别有5种,分别是error、warn、info、debug、trace:正常业务。需要运维配置监控。warn:警告日志,一般错误,对业务影响不大,但需要开发注意。info:信息日志,记录调用时间、输入输出参数等故障排除的关键信息。debug:用于开发DEBUG,关键逻辑中的runtime数据。trace:最详细的信息,一般这些信息只记录在日志文件中。在日常开发中,我们需要选择合适的日志级别,不要反手打印信息。在日志中打印出方法的输入输出参数,我们不需要打印很多日志,只需要打印有效的日志,可以快速定位问题。有效的日志是甩锅的利器!哪些日志被认为是有效的和关键的?比如当一个方法进来的时候,打印入参。然后,当方法返回时,它会打印出参数并返回值。输入参数一般是userId或者bizSeq等关键信息。示例如下:publicStringtestLogMethod(Documentdoc,Modemode){log.debug("methodenterparam:{}",userId);Stringid="666";log.debug("methodexitparam:{}",id);returnid;}选择合适的理想的日志格式应该包括最基本的信息:比如当前时间戳(一般毫秒精度)、日志级别、线程名称等等。在logback日志中,可以这样配置:%d{HH:mm:ss.SSS}%-5level[%thread][%logger{0}]%m%n如果我们的日志格式连当前时间都没有记录,那我们连时间都不知道的请求?当遇到if...else...等条件时,尽量在每个分支的第一行打印log当遇到if...else...或switch等条件时,可以打印log在分支的第一行,这样在排查问题的时候,可以通过日志来判断自己进入了哪个分支,代码逻辑更加清晰,排查问题也更加容易。正例:if(user.isVip()){log.info("用户是会员,Id:{},开始处理会员逻辑",user,getUserId());//会员逻辑}else{log.info("用户为非会员,Id:{},开始处理非会员逻辑",user,getUserId())//非会员逻辑}当日志级别比较低时,日志开关判断为执行。对于trace/debug这些比较低级别的日志,必须进行日志级别的切换判断。正例:Useruser=newUser(666L,"公众号","捡蜗牛的小男孩");if(log.isDebugEnabled()){log.debug("userIdis:{}",user.getId());}因为目前有如下日志代码:logger.debug("Processingtradewithid:"+id+"andsymbol:"+symbol);如果配置的日志级别为warn,则不会打印上述日志,但会进行字符串拼接操作,如果symbol是对象,也会执行toString()方法,浪费系统资源。执行以上操作后,并没有打印出最终的日志,所以建议加一个日志开关来判断。您不能直接使用日志系统中的API。不能直接使用日志系统(Log4j、Logback)中的API,而是使用日志框架SLF4J中的API。SLF4J是一个门面模式的日志框架,有利于维护和统一各个类的日志处理方式,无需修改代码即可轻松替换底层日志框架。正例:importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;privatestaticfinalLoggerlogger=LoggerFactory.getLogger(TianLuoBoy.class);推荐使用参数占位符{},不要用+拼接。反例:logger.info("Processingtradewithid:"+id+"andsymbol:"+symbol);在上面的例子中,使用+操作符来拼接字符串有一定的性能损失。例如:logger.info("Processingtradewithid:{}andsymbol:{}",id,symbol);我们在日志中使用大括号{}作为占位符,比使用+运算符更优雅简洁。而且,与反例相比,占位符的使用只是一个替换动作,可以有效提升性能。推荐使用异步方式输出日志。日志最终会输出到文件或其他输出流,对IO性能有要求。如果是异步的,可以显着提高IO性能。除非有特殊要求,否则建议使用异步方式输出日志。以logback为例。配置异步非常简单,使用AsyncAppender即可。不使用e.printStackTrace()反例:try{//业务代码处理}catch(Exceptione){e.printStackTrace();}正例:try{//业务代码处理}catch(Exceptione){log.error("你的程序有异常",e);}原因:e.printStackTrace()打印的堆栈日志与业务代码日志交错,异常日志通常不方便查看。e.printStackTrace()语句生成的字符串记录了堆栈信息。如果信息太长,字符串常量池所在的内存块没有空间,也就是内存满了,那么用户的请求就会卡住!不要只打一半的异常日志,而是输出所有的错误信息反例1:try{//业务代码处理}catch(Exceptione){//errorLOG.error('你的程序有异常');}没有exceptione打印出来,所以根本不知道发生了什么类型的exception。反例2:try{//业务代码处理}catch(Exceptione){//errorLOG.error('你的程序有异常',e.getMessage());}e.getMessage()不会记录详细堆栈异常信息只会记录错误的基本描述信息,不利于排查。正例:try{//业务代码处理}catch(Exceptione){//errorLOG.error('Yourprogramhasanexception',e);}DisabledebuggingintheonlineenvironmentDisabledebuggingintheonlineenvironment,这个点很重要。由于一般系统中调试日志较多,而各种框架中使用了大量的调试日志,开启在线调试后可能磁盘已满,影响业务系统的正常运行。不记录异常,然后抛出异常如下:log.error("IOexception",e);thrownewMyException(e);如果这样实现,堆栈信息通常会被打印两次。这是因为捕获到MyException的地方,会重新打印出来。这样的日志记录,或者打包抛出,不要两者都用!否则你的日志看起来会很混乱。避免重复打印日志避免重复打印日志,酱子会浪费磁盘空间。如果你已经有一行log可以清楚地表达意思,请避免重复打印。反例如下:if(user.isVip()){log.info("用户是会员,Id:{}",user,getUserId());//冗余,可以和前面合并loglog.info("开始处理会员逻辑,id:{}",user,getUserId());//会员逻辑}else{//非会员逻辑}如果你使用的是log4j日志框架,一定要在log4j.xml中设置additivity=false,因为这样可以避免重复打印日志。正例:日志文件分离我们可以把不同类型的日志分开,比如access.log,或者错误级别error.log,可以单独打印到一个文件中。当然也可以根据不同的业务模块打印到不同的日志文件中,方便我们排查问题和做数据统计的时候。对于核心功能模块,建议打印更完整的日志。在我们日常开发中,如果是核心或者逻辑复杂的代码,建议添加详细的注释和更详细的日志。日志应该有多详细?想一想,如果你的核心程序哪一步出错了,你可以通过日志定位到,那就没事了。作者:捡到蜗牛的小男孩责任编辑:陶家龙来源:转载自公众号捡到蜗牛的小男孩