日志功能在ECP项目中的使用指导
摘要:在ECP项目中,日志是一个重要的组成部分,它能够记录系统运行时的关键信息,帮助开发人员进行调试和故障排查。本文将为您提供使用ECP项目中日志功能的指导,并结合个人使用经验给出切实可行的步骤。您将了解如何配置日志记录器、以及如何使用日志记录功能,提升项目开发和运维的效率与质量。
场景介绍
故障排查和问题诊断:日志记录是排查故障和诊断问题的重要手段。通过记录系统运行时的关键信息、异常和错误,可以帮助开发人员和运维人员追踪问题的根源,分析问题的原因,并进行相应的修复和优化
系统性能分析和优化:日志记录可以提供系统的运行时性能信息,如请求响应时间、资源利用情况等。通过分析这些日志数据,可以识别系统中的性能瓶颈,找出潜在的性能问题,并进行性能优化,提升系统的响应速度和吞吐量
数据分析和业务智能:日志记录可以作为数据源,用于数据分析和业务智能。通过对日志数据的挖掘和分析,可以获得有关用户行为、趋势分析、用户画像等有价值的信息。举例:
通过分析用户登录日志记录,可以了解患者访问系统的行为习惯和偏好,进而进行用户画像分析,为个性化的医疗服务提供支持。
通过对系统日志的挖掘,还可以进行趋势分析,例如分析医生就诊时段的变化,为排班和资源调配提供决策依据
法律和合规要求:根据法律法规或行业标准的要求,一些业务需要对关键操作和数据进行严格的记录。日志记录可以提供可追溯性和法律保护,以满足法律和合规性的要求
举例:
根据相关隐私保护法规(《个人信息保护法》《中华人民共和国民法典》《中华人民共和国基本医疗卫生与健康促进法》《医疗卫生机构信息公开管理办法》),系统必须严格记录对患者隐私数据的访问、修改和共享情况。
通过日志记录,可以确保患者数据的追溯性和合规性,并在发生数据泄露或违规访问时提供法律保护
步骤一: 介绍本项目的日志框架和配置
ECP 项目中以 slf4j-api 和 logback 为基础,并联合 blade-starter-log 包实现的日志功能。接下来介绍 logback的配置, ecp中的日志配置
1、 logback.xml 基础概念以及配置介绍
日志记录器(Logger)日志记录器是日志框架中最核心的组件之一。它负责接收应用程序中的日志消息,并将其传递给相应的附加器。日志记录器通常按照类或包的层次结构进行命名,以方便组织和过滤日志消息。在示例中,我们使用 getLogger() 方法获取一个特定类的日志记录器实例。
public class DemoFeignController extends BladeController {
private static final Logger logger = LoggerFactory.getLogger(DemoFeignController.class);
}
附加器(Appender)附加器负责将日志消息写入到目标位置,如控制台、文件或数据库。每个附加器都有自己的配置和格式。在示例中,我们使用了 ConsoleAppender 和 FileAppender 常见的日志记录器
日志级别:
- ERROR:表示严重的错误事件,可能会导致应用程序无法继续运行。例如,数据库连接失败、关键功能失败等。ERROR级别的日志对于故障排除和错误分析非常有帮助
- WARN:表示潜在的问题或警告事件,可能会影响应用程序的正常运行。例如,配置文件中的警告、资源使用超过阈值等情况。WARN级别的日志对于及时发现潜在问题并采取相应的措施非常重要
- INFO:表示信息性的事件,用于记录应用程序的一般运行情况。如应用程序的启动、配置信息、重要业务操作等。INFO级别的日志对于监控系统状态和了解应用程序的基本运行情况非常有用,同时也不会产生过多的日志记录
- DEBUG:表示调试信息,用于在开发和调试过程中进行详细的日志记录。在生产环境中,通常不建议启用DEBUG级别的日志,因为它会产生大量的日志记录,可能对系统性能产生负面影响
- TRACE:表示更详细的调试信息,通常在最详细的日志记录级别下使用。 生产环境禁止使用
- TRACE < DEBUG < INFO < WARN < ERROR
注意:
(1)如果将日志级别设置为高级别,低级别的日志事件将不会被记录。例如,如果将日志级别设置为 WARN,那么只有 WARN 和 ERROR 级别的日志事件会被记录,而低于 WARN 级别的日志事件将被忽略。
(2) 日志级别通常具有继承性,即子记录器会继承父记录器的日志级别。请确保父记录器的日志级别允许子记录器输出日志。如果父记录器的日志级别设置为较高级别(如ERROR),那么子记录器的低级别日志将被忽略。
(3) blade.yaml 中 logging 在运行时动态修改日志级别
logback-dev.xml 文件位于 ecp-common 包内 ,如何配置和标签解析如图所示:
<?xml version="1.0" encoding="UTF-8"?>
<!--配置根节点:<configuration >是配置文件的根节点,表示Logback的配置开始。
scan属性 默认为true,自动检测配置文件的更改并重新加载配置,实现配置的动态更新,无需重启应用程序。适用于测试和开发环境,生产请设置为false
debug属性用于指定是否打印Logback的调试信息,默认为false。 是用于调试logback 本身
scanPeriod属性用于设置配置文件扫描的间隔时间
-->
<configuration scan="false" debug="false" scanPeriod="30 seconds">
<!-- 自定义参数监听 它可以在Logback启动时加载自定义的参数配置 -->
<contextListener class="org.springblade.core.log.listener.LoggerStartupListener"/>
<springProperty scope="context" name="springAppName" source="spring.application.name"/>
<!-- conversionRule 解析 是用于定义自定义的转换规则,将特定的日志事件属性转换为字符串格式
conversionWord 指定转换规则的占位符
converterClass 将属性转为字符串格式
defaultValue 指定无法应用转换规则的默认值
彩色日志依赖的渲染类
clr该转换规则用于实现彩色日志输出。它将日志级别、时间戳等信息添加彩色修饰符,以增强日志的可读性和可视化效果
clr该转换规则用于实现彩色日志输出。它将日志级别、时间戳等信息添加彩色修饰符,以增强日志的可读性和可视化效果
wex该转换规则用于转换异常堆栈跟踪信息。它将异常堆栈跟踪信息格式化为一行,并将其中的空白字符转换为单个空格
wEx和wex都是用于转换异常堆栈跟踪信息,但在转换过程中保留空白字符的原始格式。换句话说,它不会去除或修改异常堆栈跟踪信息中的空白字符,而是保留原始的格式。
-->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- property 是用于配置变量,
name 是唯一标识符, value 可以使值也可以引用其他属性,
scope 指定属性的作用域,【context 作用于多个配置文件,system 作用于命令行,操作系统环境变量,local 只作用于当前xml】
彩色日志格式
clr、wex和wEx三个转换规则,分别用于彩色显示、异常信息的格式化
clr、wex和wEx三个转换规则,分别用于彩色显示、异常信息的格式化
%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint}: 显示当前时间,使用淡色(faint)进行渲染。
%clr(${LOG_LEVEL_PATTERN:-%5p}): 显示日志级别,使用默认的格式(%5p)进行渲染
%clr(${PID:- }){magenta}: 显示进程ID,使用洋红色(magenta)进行渲染
%clr(===){faint}: 显示分隔符 "===",使用淡色(faint)进行渲染
%clr([%15.15t]){faint}: 显示线程名,使用淡色(faint)进行渲染,并限制长度为最多15个字符
%clr(%-40.40logger{39}){cyan}: 显示日志记录器的名称,使用青色(cyan)进行渲染,并限制长度为最多40个字符
%clr(:){faint}: 显示分隔符 ":",使用淡色(faint)进行渲染
%m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}: 显示日志消息和异常堆栈跟踪信息,如果异常信息存在,则使用 %wEx 转换规则进行渲染
提高可读性和可视化效果
-->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- 控制台输出 appender 件定义日志输出到文件,控制台,发送到远程服务器等等 name 唯一表示符号 class 指定实现的类型 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<!-- 生成日志文件 appender 件定义日志输出到文件,控制台,发送到远程服务器等等 name 唯一表示符号 class 指定实现的类型 -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- rollingPolicy 定义日志文件的滚动策略
FileNamePattern 滚动后的日志文件命名模式
maxFileSize 指定每个文件的大小
maxHistory 指定保留滚动日志文件的最大数量 超过则删除较早的日志文件
totalSizeCap 滚动日志文件的总大小上线 超过则删除较早的日志文件-→
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 日志文件输出的文件名 -->
<FileNamePattern>target/log/info-%d{yyyy-MM-dd}.log</FileNamePattern>
<maxFileSize>10MB</maxFileSize>
<maxHistory>7</maxHistory>
<totalSizeCap>100MB</totalSizeCap>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%n%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%logger{50}] %n%-5level: %msg%n</pattern>
</encoder>
<!-- 打印日志级别 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 生成日志文件 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件输出的文件名 -->
<FileNamePattern>target/log/error-%d{yyyy-MM-dd}.log</FileNamePattern>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%n%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] [%logger{50}] %n%-5level: %msg%n</pattern>
</encoder>
<!-- 打印日志级别 它可以根据指定的条件对日志事件进行过滤,只选择满足条件的日志事件进行处理
class 属性:指定过滤器的实现类名
level 子标签:指定要过滤的日志级别。
onMatch 当日志事件的日志级别匹配ERROR 时,执行接受(ACCEPT)操作
onMismatch 当日志事件的日志级别不匹配ERROR 时,执行接受(DENY)操作
-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 日志输出级别 <root> 根节点配置了根日志记录器的输出级别,默认为INFO。定义整个日志记录行为
它引用了STDOUT和${STDOUT_APPENDER}两个Appender,
表示日志将输出到控制台和其他指定的Appender
日志事件的输出顺序将按照 <appender-ref> 子标签的顺序进行处理
-->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="${INFO_APPENDER}"/>
<appender-ref ref="${ERROR_APPENDER}"/>
</root>
<!-- logger 日志记录器
name :指定日志记录器的名称或命名空间。它用于标识要配置的包或类。可以通过包名或类的全限定名来指定
level : 日志级别
additivity :是否继承父级日志记录器的附加器 默认为true 通过设置 additivity 属性,可以灵活地控制日志记录器的行为,避免重复输出日志,并实现更精细的日志管理。
-->
<logger name="net.sf.ehcache" level="INFO"/>
<logger name="druid.sql" level="INFO"/>
<!-- 减少nacos日志 -->
<logger name="com.alibaba.nacos" level="ERROR"/>
</configuration>
2、ecp中的日志配置
yml文件配置:
#blade配置
blade:
#日志配置
log:
request:
#开启控制台请求日志
enabled: true
#控制台请求日志忽略
skip-url:
- /blade-desk/notice/list
- /blade-chat/weixin/**
#开启错误日志入库
error-log: true
logging:
level:
cn.histo.desk: debug
步骤二:基础使用
基于以上 logback.xml和 balde.yaml 文件配置 介绍日志的使用方式
1、在代码中添加日志记录语句,在需要输出日志的地方,使用相应的日志记录器(如Logger
)来记录日志(以下代码会记录到控制台以及文件中)以下是使用方式和结果
package cn.histo.desk.controller;
import io.swagger.annotations.Api;
import lombok.AllArgsConstructor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.LoggerFactory;
import org.springblade.core.tenant.annotation.NonDS;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.logging.Logger;
/**
* @author Verite
* @date 2023/6/13 11:14
*/
@NonDS
@RestController
@RequestMapping("demoLog")
@AllArgsConstructor
@Api(value = "log ", tags = "feign 调用示例")
public class DemoFeignController extends BladeController {
private static final Logger logger = LoggerFactory.getLogger(DemoFeignController.class);
private final IOrderFeignClient orderFeignClient;
/**
* 默认调用
*/
@GetMapping("/orderIndex")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "默认调用", notes = "index")
@ApiLog("orderIndex")
public R<String> orderIndex(@RequestParam("param") String param) throws InterruptedException {
String res = orderFeignClient.index(param);
// 日志打印 – 创建 logger 使用
logger.info("打印日志:{}", param);
// 日志打印 - @Slf4j 注解使用
log.info("使用注解打印日志:{}", param);
return R.success("order index:" + res);
}
}
控制台展示结果:

文件日志展示结果:

2、记录到数据库中 以下是使用方式 和 结果
package cn.histo.desk.controller;
import cn.histo.desk.feign.IOrderFeignClient;
import cn.histo.desk.feign.IUserFeignClient;
import cn.histo.provider.feign.entity.ProvideNotice;
import cn.histo.provider.feign.feign.IProviderNoticeClient;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.AllArgsConstructor;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.log.annotation.ApiLog;
import org.springblade.core.log.logger.BladeLogger;
import org.springblade.core.tenant.annotation.NonDS;
import org.springblade.core.tool.api.R;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制器
*
* @author Verite
*/
@NonDS
@RestController
@RequestMapping("normalFeign")
@AllArgsConstructor
@Api(value = "feign 远程调用示例", tags = "feign 调用示例")
public class DemoFeignController extends BladeController {
private BladeLogger bladeLogger; // 通用日志使用 记录到 blade_log_usual 表中
private final IOrderFeignClient orderFeignClient;
/**
* 默认调用
*/
@GetMapping("/orderIndex")
@ApiOperationSupport(order = 1)
@ApiOperation(value = "默认调用", notes = "index")
@ApiLog("orderIndex")
public R<String> orderIndex(@RequestParam("param") String param) throws InterruptedException {
String res = orderFeignClient.index(param);
bladeLogger.info("info - orderIndex", param);
bladeLogger.error("error - orderIndex ", param);
if (StringUtil.isBlank(param)){
throw new SecurityException("error - orderIndex param is null");
}
return R.success("order index:" + res);
}
}
xx_log_usual (需要手动记录) 结果示例:

xx_log_error (基于Exception 异常运行)结果示例:
xx_log_api (基于@ApiLog注解运行)结果示例:
页面功能结果:
希望本文对正在使用ECP项目进行微服务开发的开发者提供了日志功能的实用的指导和建议。通过合理配置和使用日志功能,帮助开发人员进行调试和故障排查。
步骤三:注意事项
在本节中,我们将列举一些开发者常见的问题,并提供具体的解决方法。这些问题可能涉及日志的配置、使用过程中的错误、性能优化等方面。我们将通过实际案例和解决方案来帮助开发者更好地应对这些问题。
1、<configuration scan="true" debug="true"> sacn 和 debug 属性开启会极大影响性能,在ecp 工程中 重启会更有效
2、日志级别继承举例说明: 假设有以下日志记录器的层次结构:
- 根日志记录器(Root Logger)的级别为 DEBUG。
- 父级别为 "com.example" 的日志记录器的级别为 INFO。
- 子级别为 "com.example.controller" 的日志记录器没有显式设置级别。
在这种情况下,日志记录器 "com.example.controller" 将继承其父级别 "com.example" 的级别,即 INFO 级别。而根日志记录器的级别是 DEBUG,所以日志记录器 "com.example.controller" 的有效级别也将是 DEBUG。因此,当日志消息被记录时,将根据 DEBUG 级别进行处理。
3、使用 yml 的 logging 可以灵活的配置日志级别,debug 用来调试
4、在编写Logback配置文件时,需要注意以下几点:
- 遵循XML的语法规范,确保每个标签都有正确的闭合。
- 注意标签的嵌套关系,确保每个元素都位于正确的父元素下。
- 标签和属性的命名要符合规范,避免使用特殊字符或空格。
- 属性值使用双引号括起来。例如:${logApi}
- 注意大小写敏感,例如标签名和属性名都是大小写敏感的。
- 可以使用注释(<!--- 注释内容-->)来提供注解和说明。
5、推荐使用 log.info("log - orderIndex {}", param); 方式, 原因是 logback延迟绑定机制
6、 记录日志时请思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
7、 特别对 additivity="false" 说明 类似以下情况请设置为false 否则将会打印2次日志
<logger name="com.example" level="DEBUG" additivity="false">
<appender-ref ref="consoleAppender" />
</logger>
<root level="INFO">
<appender-ref ref="consoleAppender" />
</root>
步骤四:具体场景的使用介绍
在本节中,我们将列举一些开发者常见的场景,并提供建议使用的方法,在看之前请想想该日志是否只有我关注,是否只有在本次调试的时候才关注,是否只要调用此模块的开发者都关注
1、故障排查和调试:在这种情况下,通常建议使用 DEBUG 级别的日志。DEBUG 级别的日志可用于打印详细的调试信息,包括变量的值、函数的执行路径等,以帮助定位和解决问题。在任务执行之前和之后打印日志,并且任务执行之前的log带上当前任务的所有参数,任务执行结束的log尽量带上当前的处理结果。可以帮助我们快速定位问题
2、分支选择和条件判断:对于这种情况,通常建议使用 INFO 级别的日志。INFO 级别的日志适用于打印关键分支的执行情况,以确认程序进入了哪个分支。这样可以提供对代码流程的可见性,帮助验证分支逻辑是否按预期执行。
3、关键流程追踪:在关键流程追踪中,通常建议使用 INFO 级别的日志。INFO 级别的日志可用于记录重要的功能点或流程的开始和结束,提供对整个流程的概览。这有助于追踪和理解应用程序的行为。例如说,了解上周周末用户使用了哪些功能,哪些功能中的任务是否是正常执行,是否需要对该功能进行优化等等。或者是业务状态,订单状态的变更,调用第三方时的调用参数和调用结果。
4、监控和性能分析:对于监控和性能分析,通常建议使用 DEBUG 级别的日志。DEBUG 级别的日志可用于打印详细的性能数据、方法的执行时间等信息。这有助于分析性能瓶颈和进行优化。比如,你在浏览器里输入一个action地址,该地址负责执行批量医嘱确认订单的操作,此时运行后,假设处理中比较耗时,你无法在浏览器看到程序的运行结果,此时,通过分析日志输出信息就可以分析该操作的执行情况,还有记录程序运行时间,判断该接口是否需要限流或者是重构。
5、安全和审计:在安全和审计场景中,通常建议使用 INFO、WARN 或 ERROR 级别的日志,具体取决于事件的重要性和严重程度。INFO 级别的日志可用于记录重要的安全事件或操作,WARN 级别的日志可用于记录潜在的风险或异常情况,而 ERROR 级别的日志可用于记录关键错误或失败事件,通过记录的日志可以查看是否是非法攻击,或者非法调用,以及系统处理过程中的安全隐患等等。比如说pms 修改了免疫组化的配置项,可以给出警告的日志,那么就会很快查找到问题所在。
6、关键设置项:修改全局配置或者业务的相关配置,通常建议使用 WARN 级别的日志。
参考资料
logback官方文档 https://logback.qos.ch/manual/
中文手册 https://www.docs4dev.com/docs/zh/logback/1.3.0-alpha4/reference/introduction.html
日志背景以及基础使用介绍 https://www.liaoxuefeng.com/wiki/1252599548343744/1264739155914176
lockback 日志基本使用介绍 https://blog.csdn.net/weixin_72518095/article/details/125574382
locak 日志实战介绍 https://blog.csdn.net/weixin_72518095/article/details/125707612
阿里日志规约 https://my.oschina.net/hutaishi/blog/1617008
医疗卫生机构信息公开管理办法 https://www.gov.cn/zhengce/zhengceku/2022-01/05/content_5666487.htm
中华人民共和国民法典 https://www.gov.cn/xinwen/2020-06/01/content_5516649.htm
中华人民共和国个人信息保护法 https://www.gov.cn/xinwen/2021-08/20/content_5632486.htm
中华人民共和国基本医疗卫生与健康促进法 http://www.npc.gov.cn/npc/c30834/201912/15b7b1cfda374666a2d4c43d1e15457c.shtml
全部评论