后端开发日志规范
文章目录
- 后端开发日志规范
-
- 日志的作用
- 什么时候记录日志
- 日志记录原则
- 日志等级设置规范
- 日志使用实践
-
- 打印日志的代码不允许失败,阻断流程
- 禁止使用System.out.println()输出日志
- 禁止直接使用日志系统(Log4j、Logback)中的API
- 声明日志工具对象Logger应声明为private static final
- 对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断
- 捕获异常后不要使用e.printStackTrace()打印日志
- 打印异常日志一定要输出全部错误信息
- 日志打印时禁止直接用JSON工具将对象转换成String
- 不要打印无意义(无业务上下文、无关联日志链路id)的日志
- 不要在循环中打印INFO级别日志
- 不要打印重复的日志
- 避免敏感信息输出
- 日志语言尽量使用英文
- 重要方法可以记录调用日志
- 在核心业务逻辑中遇到if...else等条件,尽量每个分支首行都打印日志
- 建议只打印必要的参数,不要整个对象打印
日志的作用
故障排查和调试
- 日志记录了系统运行过程中的各种事件、操作和错误信息,有助于开发人员快速定位和解决问题,提高系统的稳定性和可靠性。
性能监控和优化
- 通过分析日志信息,可以了解系统的性能表现,包括请求响应时间、资源利用情况等,有助于发现性能瓶颈并进行优化。
安全审计和监控
- 日志记录了系统的操作轨迹和访问记录,可以用于安全审计和监控,及时发现异常行为和安全漏洞。
用户行为分析
- 通过分析用户的操作行为和访问记录,可以了解用户的偏好和行为习惯,为产品改进和优化提供参考。
什么时候记录日志
代码初始化时或进入逻辑入口时
- 系统或者服务的启动参数。核心模块或者组件初始化过程中往往依赖一些关键配置,根据参数不同会提供不一样的服务。务必在这里记录
INFO
日志,打印出参数以及启动完成态服务表述。
编程语言提示异常
- 这类捕获的异常是系统告知开发人员需要加以关注的,是质量非常高的报错。应当适当记录日志,根据实际结合业务的情况使用
WARN
或者ERROR级别。
业务流程预期不符
- 项目代码中结果与期望不符时也是日志场景之一,简单来说所有流程分支都可以加入考虑。取决于开发人员判断能否容忍情形发生。常见的合适场景包括外部参数不正确,数据处理问题导致返回码不在合理范围内等等。
系统/业务核心逻辑的关键动作
- 系统中核心角色触发的业务动作是需要多加关注的,是衡量系统正常运行的重要指标,建议记录
INFO
级别日志。
第三方服务远程调用
- 微服务架构体系中有一个重要的点就是第三方永远不可信,对于第三方服务远程调用建议打印请求和响应的参数,方便在和各个终端定位问题,不会因为第三方服务日志的缺失变得手足无措。
日志记录原则
清晰性和一致性
- 日志消息应该清晰、简洁,并且在格式上保持一致,以便开发人员更容易阅读和理解。
相关性
- 只记录与故障排查、监控或审计相关的信息。避免记录敏感信息,如密码或个人数据。
日志级别
- 适当使用不同的日志级别(如
DEBUG
、INFO
、WARN、ERROR
)来指示日志消息的严重程度。这有助于过滤和优先处理日志消息。
时间戳
- 在日志消息中包含时间戳,以提供事件的时间顺序记录。这有助于理解操作的顺序和诊断问题。
上下文信息
- 在日志消息中包含相关的上下文信息,如请求参数、用户ID和错误详情,以提供更多的调试上下文。
错误处理
- 当发生异常时,记录详细的错误消息,包括堆栈跟踪。这些信息对于识别错误的根本原因至关重要。
性能影响
- 注意日志记录对性能的影响。避免过度记录可能降低系统性能的日志信息。
日志轮换和保留
- 实施日志轮换和保留策略,有效管理日志文件,并防止占用过多的磁盘空间。
日志等级设置规范
DEBUG(调试)
DEUBG
级别的主要输出调试性质的内容,该级别日志主要用于在开发、测试阶段输出。该级别的日志应尽可能地详尽,开发人员可以将各类详细信息记录到DEBUG
里,起到调试的作用,包括参数信息,调试细节信息,返回值信息等等,便于在开发、测试阶段出现问题或者异常时,对其进行分析。
INFO(信息)
INFO
级别的主要记录系统关键信息,旨在保留系统正常工作期间关键运行指标,开发人员可以将初始化系统配置、业务状态变化信息,或者用户业务流程中的核心处理记录到INFO日志中,方便日常运维工作以及错误回溯时上下文场景复现。- 建议在项目完成后,在测试环境将日志级别调成
INFO
,然后通过INFO
级别的信息看看是否能了解这个应用的运用情况,如果出现问题后是否这些日志能否提供有用的排查问题的信息。
WARN(警告)
WARN
级别的主要输出警告性质的内容,这些内容是可以预知且是有规划的,比如,某个方法入参为空或者该参数的值不满足运行该方法的条件时。- 在
WARN
级别的时应输出较为详尽的信息,以便于事后对日志进行分析。
ERROR(错误)
ERROR
级别主要针对于一些不可预知的信息,诸如:错误、异常等,比如,在catch块中抓获的网络通信、数据库连接等异常,若异常对系统的整个流程影响不大,可以使用WARN
级别日志输出。- 在输出
ERROR
级别的日志时,尽量多地输出方法入参数、方法执行过程中产生的对象等数据,在带有错误、异常对象的数据时,需要将该对象一并输出。
TRACE(跟踪)
- 用于记录非常详细的信息,有助于跟踪请求或操作在应用程序中的流程。跟踪消息比调试消息更为细致。
OFF(关闭)
- 关闭所有日志记录。通常情况下不使用此级别,但可以在某些场景下设置为完全禁用日志记录。
日志使用实践
打印日志的代码不允许失败,阻断流程
- 一定要确保不会因为日志打印语句抛出异常造成业务流程中断,如下图所示,
shop
为null
的会导致抛出NPE
。
public void doSth(){
log.info("do sth and print log: {}", shop.getId()); // 业务逻辑 ... }
禁止使用System.out.println()输出日志
反例:
public void doSth(){
System.out.println("doSth..."); // 业务逻辑 ... }
分析:
- 通过分析
System.out.println
源码可知,System.out.println是一个同步方法,在高并发的情况下,大量执行println
方法会严重影响性能。
public void println(String x) {
synchronized (this) {
print(x); newLine(); } }
- 不能实现日志按等级输出。具体来说就是不能和日志框架一样,有
debug
,info
,error
等级别的控制。 - System.out、System.error打印的日志都并没有打印在日志文件中,而是直接打印在终端,无法对日志进行收集。
正例:
在日常开发或者调试的过程中,尽量使用标准日志记录系统log4j2
或者logback
(但不要直接使用其中的API
),异步的进行日志统一收集。
public void doSth(){
log.info("doSth..."); // 业务逻辑 ... }
禁止直接使用日志系统(Log4j、Logback)中的API
- 应用中不可直接使用日志系统(
Log4j
、Logback)中的API
,而应依赖使用日志框架 (SLF4J、JCL--Jakarta Commons Logging
)中的API。
分析:
直接使用Log4j
或者Logback
中的API会导致系统代码实现强耦合日志系统,后续需要切换日志实现时会产生比较大的改造成本,统一使用SLF4J
或者JCL
等日志框架的API
,其是使用门面模式的日志框架,可以做到解耦具体日志实现的作用,有利于后续维护和保证各个类的日志处理方式统一。
正例:
// 使用 SLF4J: import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(xxx.class); // 使用 JCL: import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; private static final Log log = LogFactory.getLog(xxx.class);
声明日志工具对象Logger应声明为private static final
分析:
- 声明为
private
防止logger
对象被其他类非法使用。 - 声明为
static
是为了防止重复new出logger
对象;防止logger
被序列化,导致出现安全风险;处于资源考虑,logger的构造方法参数是Class
,决定了logger
是根据类的结构来进行区分日志,所以一个类只要一个logger
,故static
。 - 声明为
final
是因为在类的生命周期无需变更logger,避免程序运行期对logger
进行修改。
正例:
private static final Logger LOGGER = LoggerFactory.getLogger(xxx.class);
对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断
反例:
public void doSth(){
String name = "xxx"; logger.trace("print debug log" + name); logger.debug("print debug log" + name); logger.info("print info log" + name); // 业务逻辑 ... }
分析:
如果配置的日志级别是warn
的话,上述日志不会打印,但是会执行字符串拼接操作,如果name
是对象, 还会执行toString()
方法,浪费了系统资源,执行了上述操作,最终日志却没有打印,因此建议加日志开关判断。
正例:
在trace
、debug
、info
级别日志打印前加上对应级别的日志开关判断,通常可以将开关判断逻辑包装在日志工具类中,统一实现。
public void doSth(){
if (logger.isTraceEnabled()) {
logger.trace("print trace log {}", name); } if (logger.isDebugEnabled()) {
logger.debug("print debug log {}", name); } if (logger.isInfoEnabled()) {
logger.info("print info log {}", name; } // 业务逻辑 ... }
捕获异常后不要使用e.printStackTrace()打印日志
反例:
public void doSth(){
try{
// 业务逻辑 ... } catch (Exception e){
e.printStackTrace(); } }
分析:
e.printStackTrace()
打印出的堆栈日志跟业务代码日志是交错混合在一起的,通常排查异常日志不太方便。e.printStackTrace()
语句产生的字符串记录的是堆栈信息,如果信息太长太多,字符串常量池所在的内存块没有空间了,即内存满了,系统请求将被阻塞。
正例:
public void doSth(){
try{
// 业务逻辑 ... } catch (Exception e){
log.error("execute failed", e); } }
打印异常日志一定要输出全部错误信息
反例:
- 没有打印异常
e
,无法定位出现什么类型的异常
public void doSth(){
try{
// 业务逻辑 ... } catch (Exception e){
log.error("execute failed"); } }
- 没有记录详细的堆栈异常信息,只记录错误基本描述信息,不利于排查问题。
public void doSth(){
try{
// 业务逻辑 ... } catch (Exception e){
log.error("execute failed", e.getMessage()); } }
正例:
一般日志框架中的warn
、error
级别均有存在传递Throwable
异常类型的API
,可以直接将抛出的异常传入日志API
中。
void error(String var1, Throwable var2); public void doSth(){
try{
// 业务逻辑 ... } catch (Exception e){
log.error("execute failed", e); } }
日志打印时禁止直接用JSON工具将对象转换成String
反例:
public void doSth(){
log.info("do sth and print log, data={}", JSON.toJSONString(data)); // 业务逻辑 ... }
分析:
fastjson
等序列化组件是通过调用对象的get
方法将对象进行序列化,如果对象里某些get
方法被覆写,存在抛出异常的情况,则可能会因为打印日志而影响正常业务流程的执行。- 打日志过程中对一些对象的序列化过程也是比较耗性能的。首先序列化过程本身时一个计算密集型过程,费
cpu
。其次这个过程会产生很多中间对象,对内存也不太友好。
正例:
可以使用对象的toString()
方法打印对象信息,如果代码中没有对toString()
有定制化逻辑的话,可以使用apache
的ToStringBulider
工具。
public void doSth(){
log.info("do sth and print log, data={}", data.toString()); log.info("do sth and print log, data={}", ToStringBuilder.reflectionToString(data, ToStringStyle.SHORT_PREFIX_STYLE)); }
不要打印无意义(无业务上下文、无关联日志链路id)的日志
反例:
- 不带任何业务信息的日志,对排查故障毫无意义。
public void doSth(){
log.info("do sth and print log"); // 业务逻辑 ... }
- 对于无异常分支的代码打印日志,一般流程下,异常分支都会打日志,如果没有出现异常,那就说明正常执行了。
public void doSth(){
doIt1(); log.info("do sth 111"); doIt2(); log.info("do sth 222"); }
正例:
- 日志一定要带相关的业务信息,有利于排查问题快速定位到原因。
public void doSth(){
log.info("do sth and print log, id={}", id); // 业务逻辑 ... }
- 对于打印过多的无意义日志,可以直接干掉或者以debug级别打印。
不要在循环中打印INFO级别日志
反例:
public void doSth(){
for(String s : strList) {
log.info("do sth and print log: {}", s); // 业务逻辑 ... } }
不要打印重复的日志
反例:
public void doSth(String s){
log.info("do sth and print log: {}", s); doStep(s); } private void doStep(String s){
log.info("do sth and print log: {}", s); // 业务逻辑 ... } public void doSth(String s) {
try {
doStep(s); } catch (Exception e){
log.error("something wrong", e); } } private void doStep(String s){
try {
// 业务逻辑 } catch (Exception e){
log.error("something wrong", e); throw e; } }
分析:
- 在每一个嵌套环节都打印了重复的日志。
- 不要记录日志后又抛出异常。抛出去的异常,一般外层会处理。如果不处理,那为什么还要抛出去?原则是,无论是否发生异常,都不要在不同地方重复记录针对同一事件的日志消息。
正例:
直接干掉或者将日志降级成debug级别日志
避免敏感信息输出
- 敏感信息输出会存在安全漏洞
日志语言尽量使用英文
建议:尽量在打印日志时输出英文,防止中文编码与终端不一致导致打印出现乱码的情况,对故障定位和排查存在一定的干扰。
重要方法可以记录调用日志
建议在重要方法入口记录方法调用日志,出口打印出参,对于排查问题会有很大的帮助。
public String doSth(String id, String type){
log.info("start: {}, {}", id, type); String res = process(id, type); log.info("end: {}, {}, {}", id, type, res}; }
在核心业务逻辑中遇到if…else等条件,尽量每个分支首行都打印日志
在编写核心业务逻辑代码时,如遇到if…else…或者switch这样的条件,可以在分支的首行就打印日志,这样排查问题时,就可以通过日志,确定进入了哪个分支,代码逻辑更清晰,也更方便排查问题了。
建议:
public void doSth(){
if(user.isVip()){
log.info("该用户是会员,Id:{},开始处理会员逻辑",user,getUserId()); //会员逻辑 }else{
log.info("该用户是非会员,Id:{},开始处理非会员逻辑",user,getUserId()) //非会员逻辑 } }
建议只打印必要的参数,不要整个对象打印
反例:
public void doSth(){
log.info("print log, data={}", data.toString()); // 业务逻辑 ... }
分析:
首先分析下自己是否必须把所有对象里的字段打印出来?如果对象中有50个字段,但只需其中两个参数就可以定位具体的原因,那么全量打印字段将浪费内容空间且因为字段过多,影响根因排查。
正例:
public void doSth(){
log.info("print log, id={}, type={}", data.getId(), data.getType()); // 业务逻辑 ... } //会员逻辑 }else{
log.info("该用户是非会员,Id:{},开始处理非会员逻辑",user,getUserId()) //非会员逻辑 } }
到此这篇前端开发日志_web后端的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!
版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/hdkf/3803.html