SpringCloud Alibaba微服务实战之集成日志框架

SpringCloud Alibaba微服务实战之集成日志框架

大家好,我是一安~

导读:在上一篇文章中我们讲解了如何解决Feign远程调用和异步调用请求头丢失问题,今天继续讲解如何整合logback日志框架

简介

Logback 是一个开源的 Java 日志框架,由 log4j 创始人 Ceki Gülcü 设计,现在由 QOS.ch 维护。该框架分为三个模块,分别是:logback-core、logback-classiclogback-access。其中 logback-core 是其它两个模块的基础模块,提供了许多日志库的核心功能,比如 Appender(输出器)、Encoder(编码器)和 Layout(布局)等。logback-classic 模块是 log4j 的一个改进版本,同时也完全实现了 SLF4J API,因此可以轻松地从其它日志框架(如 log4jjava.util.logging)迁移过来。logback-access 模块用于将请求信息和相应信息集成到日志系统中。Logback 框架简单易用而且性能高,在众多 Java 日志框架中备受好评,并且 LogbackSpringBoot 内置的日志处理框架。

正文Logback

在单体项目中如果我们需要记录操作日志一般会通过如下手段实现:

  • 建立一个自定义注解,标注业务操作类型。
  • 通过AOP组装日志实体,完成日志的收集工作。

但是在微服务架构中我们不可能每个服务都写一个自定义注解,再写一个AOP,如果微服务多的话这样会重复写很多代码,所以这时候我们一般都会建立一个公共的组件,在公共组件中完成日志的收集,后端服务只需要引入这个公共的组件即可。

自定义starter

简单提一下SpringBoot Starter实现自动化配置的流程,详细的请查看之前文章《深入剖析Spring Boot 的SPI机制,提升程序的可扩展性

  • spring-boot启动的时候会找到starter-jar包中的resources/META-INF/spring.factories文件,根据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);
  }

测试效果

控制台输出:SpringCloud Alibaba微服务实战之集成日志框架运行日志输出:SpringCloud Alibaba微服务实战之集成日志框架操作日志输出:SpringCloud Alibaba微服务实战之集成日志框架

至此我们已通过自定义日志收集组件实现了日志的采集,大家也可以对自定义注解和切面处理逻辑自行扩展,不仅仅局限于本文所讲。


如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!

SpringCloud Alibaba微服务实战之集成日志框架

一个依赖轻松搞定SpringBoot接口盗刷


单点登录SSO解决方案之SpringSecurity+JWT实现


分布式session共享问题

SpringCloud Alibaba微服务实战之集成日志框架

原文始发于微信公众号(一安未来):SpringCloud Alibaba微服务实战之集成日志框架

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145099.html

(0)
青莲明月的头像青莲明月

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!