大家好,我是一安~
导读:在上一篇文章中我们讲解了如何解决Feign
远程调用和异步调用请求头丢失问题,今天继续讲解如何整合logback
日志框架。
简介
Logback
是一个开源的 Java
日志框架,由 log4j
创始人 Ceki Gülcü
设计,现在由 QOS.ch
维护。该框架分为三个模块,分别是:logback-core、logback-classic
和 logback-access
。其中 logback-core
是其它两个模块的基础模块,提供了许多日志库的核心功能,比如 Appender
(输出器)、Encoder
(编码器)和 Layout
(布局)等。logback-classic
模块是 log4j
的一个改进版本,同时也完全实现了 SLF4J API
,因此可以轻松地从其它日志框架(如 log4j
或 java.util.logging
)迁移过来。logback-access
模块用于将请求信息和相应信息集成到日志系统中。Logback
框架简单易用而且性能高,在众多 Java
日志框架中备受好评,并且 Logback
是 SpringBoot
内置的日志处理框架。
正文Logback
在单体项目中如果我们需要记录操作日志一般会通过如下手段实现:
-
建立一个自定义注解,标注业务操作类型。 -
通过 AOP
组装日志实体,完成日志的收集工作。
但是在微服务架构中我们不可能每个服务都写一个自定义注解,再写一个AOP
,如果微服务多的话这样会重复写很多代码,所以这时候我们一般都会建立一个公共的组件,在公共组件中完成日志的收集,后端服务只需要引入这个公共的组件即可。
自定义starter
简单提一下SpringBoot Starter
实现自动化配置的流程,详细的请查看之前文章《深入剖析Spring Boot 的SPI机制,提升程序的可扩展性》
-
spring-boot
启动的时候会找到starter-jar
包中的resources/META-INF/spring.factori
es文件,根据spring.factories
文件中的配置,找到需要自动配置的类xxxAutoConfigure
-
通过 xxxAutoConfigure
上的注解@EnableConfigurationProperties
将当前模块的属性绑定到Environment
上。 -
通过 xxxAutoConfigure
中定义的bean
自动装配到IOC
容器中。
创建logging-starter
添加依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>org.yian</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>logging-starter</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.23</version>
</dependency>
</dependencies>
</project>
添加配置application.yml
:
yian:
logs:
ftst: '0101'
path: /org/yian/logs
添加logback-spring.xml
:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<springProperty scope="context" name="logpath" source="yian.logs.path" defaultValue="/org/yian/logs"/>
<springProperty scope="context" name="ftst" source="yian.logs.ftst" defaultValue="0101"/>
<substitutionProperty name="LOG_HOME" value="${logpath}" />
<substitutionProperty name="FTST" value="${ftst}" />
<substitutionProperty name="LOG_HOME_COMMON" value="${LOG_HOME}/stdout" />
<substitutionProperty name="LOG_HOME_ERROR" value="${LOG_HOME}/error" />
<!--输出操作日志-->
<substitutionProperty name="LOG_HOME_PERFORMANCE" value="${LOG_HOME}/common" />
<!-- console -->
<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
${FTST}|%d{yyyy-MM-dd' 'HH:mm:ss.sss}|%-5level|%thread|${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%logger{36} - %msg%n
</pattern>
</layout>
</appender>
<!-- file common -->
<appender name="fileCommonLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME_COMMON}-%d{yyyy-MM-dd}-%i.txt</fileNamePattern>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>100MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
${FTST}|%d{yyyy-MM-dd' 'HH:mm:ss.sss}|%-5level|%thread|${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<!-- file error -->
<appender name="fileErrorLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME_ERROR}-%d{yyyy-MM-dd}-%i.txt</fileNamePattern>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>100MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
${FTST}|%d{yyyy-MM-dd' 'HH:mm:ss.sss}|%-5level|%thread|${springAppName:-},%X{X-B3-TraceId:-},%X{X-B3-SpanId:-},%logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<!-- file performance -->
<appender name="filePerformanceLog" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!--滚动策略-->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME_PERFORMANCE}-%d{yyyy-MM-dd}-%i.txt</fileNamePattern>
<TimeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<MaxFileSize>100MB</MaxFileSize>
</TimeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<encoder>
<pattern>
%msg%n
</pattern>
</encoder>
</appender>
<!-- 操作日志日志配置输入性能日志文件 -->
<logger name="org.yian.log.SysLogAspect" level="DEBUG" additivity="false">
<appender-ref ref="filePerformanceLog"/>
</logger>
<root level="info">
<appender-ref ref="consoleLog" />
<appender-ref ref="fileCommonLog" />
<appender-ref ref="fileErrorLog" />
</root>
</configuration>
定义properties
:
package org.yian.log;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "yian.logs")
public class SysProperties {
/**
* 操作子系统类型
*/
private String ftst;
/**
* 日志输出路径
*/
private String path;
}
自定义注解:
package org.yian.log;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SysLog {
/**
* 日志描述
*/
String desc();
}
自定义切面:
package org.yian.log;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Aspect
public class SysLogAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private String ftst;
public SysLogAspect(String ftst){
this.ftst = ftst;
}
@Pointcut("@annotation(org.yian.log.SysLog)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
StopWatch sw = new StopWatch();
sw.start();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取请求URL
String requestURL = request.getRequestURL().toString();
//获取操作描述信息
String desc = "";
Method method = null;
//类名
String className = "";
//方法名
String methodName = "";
try{
MethodSignature signature = (MethodSignature) pjp.getSignature();
//获取被拦截的方法
method = signature.getMethod();
className = pjp.getTarget().getClass().getName();
methodName = signature.getName();
//获得该注解
SysLog annotation = method.getAnnotation(SysLog.class);
//获得自定义注解上面的值
desc = annotation.desc();
//执行方法
Object result = pjp.proceed();
sw.stop();
JSONObject ret = (JSONObject) JSON.toJSON(result);
//操作子系统类型ftst|时间|操作码(操作类型)|操作请求url|操作响应码|运行耗时(ms)
log.info(ftst+"|"+dtf.format(LocalDateTime.now())+"|"+desc+"|"+requestURL+"|"+ret.get("code")+"|"+ sw.getTotalTimeMillis());
return result;
}catch (Exception e){
log.error("["+ftst+"]应用中["+className+"]类下["+methodName+"]方法记录日志发生异常:"+e.getMessage());
}
return null;
}
}
编写配置类:
package org.yian.log;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
@EnableConfigurationProperties({
SysProperties.class
})
public class SysLogAutoConfigure {
@Bean
public SysLogAspect controllerLogAspect(SysProperties sysProperties){
String ftst = sysProperties.getFtst();
return new SysLogAspect(ftst);
}
}
引入spring.factories
:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.yian.log.SysLogAutoConfigure
用户模块引入日志采集
<dependency>
<groupId>org.yian</groupId>
<artifactId>logging-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
需要记录日志的方法上加@SysLog
注解:
@SysLog(desc = "查询用户")
@ApiOperation("查询用户")
@GetMapping("/account/getByCode/{accountCode}")
@SentinelResource(value = "getByCode")
public Result<Account> getByCode(@PathVariable(value = "accountCode") String accountCode){
log.info("get account detail,accountCode is :{}",accountCode);
Account account = accountService.selectByCode(accountCode);
return Result.ok(account);
}
测试效果
至此我们已通过自定义日志收集组件实现了日志的采集,大家也可以对自定义注解和切面处理逻辑自行扩展,不仅仅局限于本文所讲。
如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!
单点登录SSO解决方案之SpringSecurity+JWT实现
原文始发于微信公众号(一安未来):SpringCloud Alibaba微服务实战之集成日志框架
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145099.html