每日一博 - 关于日志记录的最佳实践

文章目录

  • 概述
  • 选择合适的日志等级
  • 打印函数的入参、出参
  • 打印日志对象要做判空处理,避免阻断流程
  • 推荐使用 Slf4j
  • 不用e.printStackTrace()打印日志
  • 低级别的日志输出,必须进行日志级别开关判断
  • 不打印重复日志
  • 打印全部的异常信息,方便定位问题
  • 核心业务逻辑,在每个分支首行都打印日志
  • 不打印无意义的日志(不携带上下文、日志链路 id)
  • 日志尽量使用英文

在这里插入图片描述


概述

记录日志是任何应用程序中至关重要的一部分,它可以帮助开发人员了解应用程序的行为、调试问题以及监控系统的健康状态。

  1. 使用日志框架

    • 选择一个成熟、广泛使用且功能丰富的日志框架,如Log4j2、Logback或java.util.logging (JUL)。
    • Log4j2和Logback是目前较为流行的选择,它们提供了丰富的功能和灵活的配置选项。
  2. 使用SLF4J进行日志抽象

    • SLF4J (Simple Logging Facade for Java) 提供了一种日志框架的抽象,可以在运行时绑定到不同的日志框架。
    • 这样可以使得你的应用程序代码与具体的日志框架解耦,方便后期切换日志框架。
  3. 选择合适的日志级别

    • 根据日志信息的重要性选择合适的日志级别,常见的级别包括DEBUG、INFO、WARN、ERROR和FATAL。
    • DEBUG用于调试信息,INFO用于一般的信息记录,WARN用于警告,ERROR用于错误信息,FATAL用于严重的致命错误。
  4. 记录有意义的信息

    • 确保记录的日志信息具有可读性和实用性,包括时间戳、线程信息、异常信息等。
    • 避免记录过于冗长或无用的信息,以免日志文件变得过大。
  5. 避免直接打印日志

    • 避免在代码中直接使用System.out.println()或System.err.println()等方式打印日志,而是应该使用日志框架提供的API来记录日志。
    • 这样可以更好地控制日志的输出格式、级别和目的地。
  6. 使用合适的日志格式

    • 配置日志格式以适应你的应用程序需求,包括时间戳格式、日志级别、线程信息等。
    • 可以考虑使用JSON格式或者结构化日志格式,以便后续的日志分析和处理。
  7. 配置日志输出

    • 配置日志输出目的地,可以输出到控制台、文件、数据库等不同的地方。
    • 针对不同的环境(如开发、测试、生产),可以配置不同的日志输出策略和目的地。
  8. 定期维护日志

    • 定期清理和归档日志文件,以防止日志文件过大影响系统性能和存储空间。
  9. 记录异常信息

    • 在捕获和处理异常时,确保记录足够的信息以便于后续排查问题。
    • 可以记录异常的堆栈跟踪、异常类型、异常发生的位置等信息。
  10. 开启日志异步记录

    • 对于高并发的应用程序,可以考虑开启日志的异步记录,以减少对系统性能的影响。

在这里插入图片描述

选择合适的日志等级

  1. Error:

    • 严重的问题,可能导致系统崩溃或者业务受到重大影响。
    • 应该关注系统的稳定性和安全性,运维团队需要重点监控并及时处理。
    • 例如:数据库连接失败、关键服务无法启动、未处理的异常等。
  2. Warn:

    • 不会导致系统崩溃,但可能会影响系统的正常运行。
    • 开发人员需要关注,可能需要进一步调查和处理,以防问题进一步恶化。
    • 例如:潜在的性能问题、不符合预期的业务流程、资源使用超出预期等。
  3. Info:

    • 关键的系统运行信息,用于保留系统运行的关键指标。
    • 记录重要的业务流程、函数的入参和出参、关键操作的执行情况等。
    • 这些信息可以帮助开发人员了解系统的运行情况,以及后续的故障排查和性能优化。
    • 例如:用户登录、订单创建、支付成功等重要操作的记录。
  4. Debug:

    • 用于开发和调试阶段,记录开发人员在关键处理步骤中的变化情况,便于快速定位问题。
    • 包含详细的调试信息,如对象数据的变化、条件语句的执行结果等。
    • 在生产环境中应该关闭或者限制输出,避免影响系统性能和日志文件大小。
    • 例如:方法的参数值、中间变量的取值、特定条件下的执行路径等。

根据具体情况选择合适的日志级别,以确保日志既能够提供足够的信息用于故障排查和性能分析,又不会造成过多的日志噪音。


打印函数的入参、出参

在日志记录过程中,关键是确保只记录关键有效的信息,而不是把所有信息都记录下来。过多的无效日志会导致日志文件变得庞大,增加了存储和维护的成本,也会增加后续日志分析的难度。

因此,有效日志应该是日志记录中的杀手锏,它们提供了足够的信息用于故障排查、性能分析和业务监控,而不会造成不必要的负担。

举个例子:

public String doBiz(Request req, Integer id){// 记录函数入参log.debug("Entering GetName method. Request: {}", req);// 在打印日志时避免直接打印敏感信息如 uid、traceId,可以考虑在日志配置中做处理,或者在代码中做脱敏处理// 执行业务逻辑String name = "artisan";// 记录函数出参及执行时间log.debug("Exiting GetName method. Result: {}, Execution time: {}ms", name, System.currentTimeMillis());return name;
}

有效日志-------》比如函数的入口处,打印入参,还包括用户唯一标识 (uid)、链路标识 (traceId) 等。函数出口打印返回值及时间等

  1. 函数入参记录

    • 使用log.debug()记录函数的入参时,将整个请求对象req作为参数传入,确保了记录了函数的所有入参信息。
  2. 函数出参及执行时间记录

    • 使用log.debug()记录函数的出参时,打印了方法的返回值name和执行时间。
    • 打印了方法的执行时间,以便于后续性能分析。

这样做的好处是保留了关键有效的日志信息,同时避免了记录过多的日志导致日志文件过大。


打印日志对象要做判空处理,避免阻断流程

通过在日志记录之前进行null检查,可以避免空指针异常的发生,同时在日志中记录了警告信息,表明接收到了空的book对象。这样既确保了程序的健壮性,又不会因为一行简单的日志记录而引发异常。

为了避免这种情况,可以先检查对象是否为null,然后再进行日志记录。

public void doSome(Book book){if (book != null) {log.info("do do and print log: {}", book.getName());} else {log.warn("Received null book object.");}// do something......
}

推荐使用 Slf4j

Slf4j是一种使用门面模式的日志框架,它提供了统一的API接口,可以在不修改代码的情况下,灵活地切换底层的日志实现。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;private static final Logger logger = LoggerFactory.getLogger(Artisan.class);
  • LoggerFactory.getLogger(JavaPub.class)会返回一个与Artisan类相关联的Logger对象,通过这个Logger对象,我们可以记录日志。

通过这种方式,我们可以利用Slf4j的门面模式来记录日志,而无需关心具体的日志实现,从而实现了日志框架的解耦。


不用e.printStackTrace()打印日志

在日志记录中,应避免使用e.printStackTrace()来打印异常信息。这种方式打印的日志包含了完整的堆栈信息,使得日志不够规整,增加了定位问题的难度。同时,如果使用ELK等日志分析工具,处理这种格式的日志也会非常困难。

看个错误的例子

public void doBiz(){try{// 业务代码...} catch (Exception e){e.printStackTrace();}
}

另外,e.printStackTrace()产生的字符串记录的是堆栈信息,如果信息过长过多,会导致字符串常量池所在的内存块溢出,从而使系统请求被阻塞。

推荐做法:

public void doBiz(){try{// 业务逻辑...} catch (Exception e){log.error("程序异常 failed", e);}
}

建议使用日志框架提供的相应方法来记录异常信息,如log.error("程序异常 failed", e)。这样可以将异常信息记录在日志中,方便查看和分析,同时保持日志的规整性和可读性。

在这里插入图片描述


低级别的日志输出,必须进行日志级别开关判断

在低级别的日志输出(如trace、debug)中,必须进行日志级别开关的判断,以避免不必要的资源浪费。这样的开关判断逻辑通常放在日志工具类中。

示例代码:

public void doSomething(){User user = new User(1, "aritsan", "log practice");if (logger.isDebugEnabled()) {logger.debug("print debug log. name is {}", user.getName());}
}

我们通过判断日志级别是否为DEBUG,来决定是否记录DEBUG级别的日志。这样做可以避免在日志级别不符合条件时,执行字符串拼接操作或者执行对象的toString()方法,从而避免不必要的资源浪费。

public void doSth(){String name = "artisan";logger.trace("print debug log" + name);logger.debug("print debug log" + name);logger.info("print info log" + name);// 业务逻辑...
}

这个栗子中没有进行日志级别开关的判断,即使日志级别为WARN时,仍然会执行字符串拼接操作,可能会浪费系统资源。因此,建议在低级别的日志输出中加上日志级别开关判断,以提高系统的性能和效率。

在这里插入图片描述


不打印重复日志

在嵌套逻辑代码中重复打印日志会增加系统资源消耗,因此应避免这种情况的发生。

对于重复的日志,可以直接删除或者将其级别设置为debug,这样就不会在生产环境中打印出这些冗余的信息。

举个例子:

public void doSomething(String s){log.info("do something and print log: {}", s);doSubSomething(s);
}private void doSubSomething(String s){log.debug("do sub something and print log: {}", s);// 写点业务逻辑...
}

这样做可以减少不必要的日志输出,提高系统的性能和效率。


打印全部的异常信息,方便定位问题

在异常处理中,应该打印完整的异常信息,以便更好地定位问题。

看个错误的示例:

public void doBiz(){try{// 业务逻辑...} catch (Exception e){log.error("发生了一个异常");}
}

反例中的代码没有打印具体的异常信息e,这样就无法准确地了解到底发生了什么类型的异常。

建议修改为:

public void doSth(){try{// 业务逻辑...} catch (Exception e){log.error("发生了一个异常", e);}
}

这样做可以打印出完整的异常信息,包括异常类型、异常消息和堆栈信息,有助于更快地定位和解决问题。


核心业务逻辑,在每个分支首行都打印日志

在编写核心业务逻辑代码时,在行首打印日志可以帮助快速排查和定位异常。

public void doBiz(User user){if(user.isVip()){log.info("User is a JavaPub member. Id: {}. Starting processing for member logic.", user.getUserId());// TODO: Member logic}else{log.info("User is not a member. Id: {}. Starting processing for non-member logic.", user.getUserId());// TODO: Non-member logic}
}

通过这样的日志记录方式,可以清晰地了解到程序的执行流程,便于后续的排查和定位异常。


不打印无意义的日志(不携带上下文、日志链路 id)

在编写日志时,确保日志携带有意义的业务信息,这样可以帮助快速定位问题原因。

看个反例: 日志并没有携带任何业务信息,因此对故障排查没有太大的帮助。

public void doBiz(Request req, User user){log.info("do something and print log.  ");// TODO 业务逻辑...
}

正例中的日志携带了业务相关的信息,如用户ID和日志链路ID,这样可以在出现异常时更容易地定位到具体的业务场景,有利于快速解决问题。

public void doBiz(Request req, User user){log.info("do something and print log, id={}, trace_id={}", user.getId(), req.getTraceId());// TODO 业务逻辑...
}

通过在日志中打印关键信息,可以让程序运行过程更加透明,有利于快速定位问题,提高系统的可维护性和可靠性。


日志尽量使用英文

建议在打印日志时尽量使用英文,以避免中文编码与终端不一致导致打印出现乱码,从而影响排查故障的效率。

比如:

log.info("Start processing...");
log.debug("Processing data: {}", data);
log.error("An error occurred while processing data: {}", error);

通过使用英文打印日志,可以确保日志在不同环境中都能正常显示,有利于排查和解决问题。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/782468.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cocos2.x => node 属性修改

简介 与节点属性相关的几个核心变量_trs、_matrix、_worldMatrix、_localMatDirty、_worldMatDirty。 _trs:存储节点的position、rotation、scale _matrix:存储节点的缩放、位移、旋转三者合一的变化矩陈(仿射矩陈) _worldMat…

python多方式操作elasticsearch介绍

python多方式操作elasticsearch介绍 1. requests模块操作ES ​ requests 是一个 Python HTTP 库,它简化了发送 HTTP 请求和处理响应的过程。通过 requests 模块,开发人员可以轻松地与 Web 服务进行通信,包括获取网页内容、执行 API 请求等。…

Qt for WebAssembly 环境搭建 - Windows新手入门

Qt for WebAssembly 环境搭建 - Windows新手入门 一、所需工具软件1、安装Python2、安装Git2.1 注册Github账号2.2 下载安装Git2.2.1配置Git:2.2.2 配置Git环境2.2.3解决gitgithub.com: Permission denied (publickey) 3 安装em编译器 二、Qt配置编译器三、参考链接…

怎么让ChatGPT批量写作原创文章

随着人工智能技术的不断发展,自然语言处理模型在文本生成领域的应用也日益广泛。ChatGPT作为其中的佼佼者之一,凭借其强大的文本生成能力和智能对话特性,为用户提供了一种高效、便捷的批量产出内容的解决方案。以下将就ChatGPT批量写作内容进…

厦门攸信技术亮相新技术研讨会,展现物流自动化解决方案新高度!

今日,厦门攸信信息技术有限公司受邀参加了一场备受行业关注的电子制造高端盛会——一步步新技术研讨会,凭借卓越的智能制造与物流自动化技术在会议中大放异彩。作为一家引领行业发展的企业,厦门攸信技术不仅展示了其深厚的技术底蕴&#xff0…

算法系列--动态规划--背包问题(4)--完全背包拓展题目

💕"这种低水平质量的攻击根本就不值得我躲!"💕 作者:Lvzi 文章主要内容:算法系列–动态规划–背包问题(4)–完全背包拓展题目 大家好,今天为大家带来的是算法系列--动态规划--背包问题(4)--完全背包拓展题目…

《web应用技术》第一次课后练习

上机任务(利用好chatgpt,文心一言等工具。): 1、下载软件,并安装。相关安装文件已上传至群文件。 JDK,TOMCAT,IDEA 2、学会用记事本编写jsp文件,并放进tomcat的相关目录下,运行。 …

使用Windows自带服务(BitLocker)加密U盘

第一步:启用 BitLocker 服务 1.1快捷键:WinR 调出运行框,输入services.msc 1.2找到服务列表中的BitLocker Drive Encryption Service,启动此项 第二步:加密U盘 把你的U盘插入电脑,打开“我的电脑”&#…

EFCore的空迁移(EFCore操作已存在的数据库表,不影响其中的数据)

背景:EFCore默认的会自动创建数据表,但是有时又是DBFirst,数据库写好了要用现成的表。这个时候就需要进行一些特殊的操作了 1、写出跟要对接数据库的实体类 比如我的表是这样创建的 create table mail_test (user_id bigint auto_increment …

OSCP靶场--Twiggy

OSCP靶场–Twiggy 考点(CVE-2020-11651[RCE]) 1.nmap扫描 ## ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.216.62 -sV -sC -Pn --min-rate 2500 -p- Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-30 06:43 EDT Nmap scan report for 192.168.216.62 Host i…

基础拓扑排序

前言 拓扑排序是一种针对“有向无环图”的算法,用于解决一些有“依赖关系”的问题。 拓扑排序保证了当处理到某个电时,其所有的如电都已经处理过了。 例如右边这个图,拓扑序可以保证处理点2之前,点4和点6都处理过了、处理点3之…

IDEA的使用(概念,安装,配置,)以及什么是字符集,模版

目录 Intellij IDEA IDE的概念 IntelliJ IDEA的安装 IntelliJ IDEA的使用 基本配置 JDK配置 创建Module 基本用法 字体配置 主题配置 字符集 设置IDEA默认字符集 注释模板 字符集 字符集简介 常见字符集 Intellij IDEA 我们不可能一直使用记事本之类变成&#…

何恺明重提十年之争——模型表现好是源于能力提升还是捕获数据集偏置?

想象一下,如果把世界上所有的图片都找来,给它们放到一块巨大的空地上,其中内容相似的图片放得近一些,内容不相似的图片放得远一些(类比向量嵌入)。然后,我随机地向这片空地撒一把豆子&#xff0…

【C#】知识点速通

前言: 笔者是跟着哔站课程(Trigger)学习unity才去学习的C#,并且C语言功底尚存,所以只是简单地跟着课程将unity所用的C#语言的关键部分进行了了解,然后在后期unity学习过程中加以深度学习。如需完善的C#知识…

码支付个人支付宝永不掉线使用教程

​支付宝免CK添加操作稍微繁琐点,请耐心观看 此通道必须关闭你的余额宝自动转入功能,否则可能造成不跳转 支付宝添加的所有通道均支持H5免输入收款 第一步:打开支付宝开发平台,然后用你的支付宝注册登陆:https://op…

算法学习——LeetCode力扣动态规划篇5

算法学习——LeetCode力扣动态规划篇5 198. 打家劫舍 198. 打家劫舍 - 力扣(LeetCode) 描述 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统…

python学习16:python中的布尔类型和条件语句的学习

python中的布尔类型和条件语句的学习 1.布尔(bool)类型的定义: 布尔类型的字面量:True表示真(是、肯定) False表示假(否、否定) True本质上是一个数字记作1,False记作0 …

遥感数字图像处理的学习笔记

相关链接: 遥感数字图像处理实验教程(韦玉春)--部分实验问题回答 目录 1.什么是图像,什么是数字图像? 2.什么是遥感数字图像?模拟图像(照片)与遥感数字图像有什么区别? 3.什么是遥感数字图像…

构建操作可靠的数据流系统

文章目录 前言数据流动遇到的困难先从简单开始可靠性延迟丢失 性能性能损失性能——分层重试 可扩展性总结 前言 在流式架构中,任何对非功能性需求的漏洞都可能导致严重后果。如果数据工程师没有将可伸缩性、可靠性和可操作性等非功能性需求作为首要考虑因素来构建…

智慧公厕的全域感知、全网协同、全业务融合和全场景智慧赋能

公共厕所是城市的重要组成部分,为市民提供基本的生活服务。然而,传统的公厕管理模式存在诸多问题,如排队等候时间长、卫生状况差、空气质量差等,严重影响市民的出行和生活质量。为了解决这些问题,智慧公厕应运而生&…