微服务框架(八)Spring Boot AOP 日志切面实现

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介:   此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为使用Spring Boot AOP 实现日志切面、分离INFO和ERROR级别日志

  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。
  本文为使用Spring Boot AOP 实现日志切面、分离INFO和ERROR级别日志

本系列文章中所使用的框架版本为Spring Boot 2.0.3-RELEASE,Spring 5.0.7-RELEASE,Dubbo 2.6.2。

通用日志组件

为了便于记录日志,实现了通用的日志组件,通过使用注解@Loggable标记方法即可编制入日志切面中

日至切面及日志输出规则均已集成于Maven Archetype

通用日志组件通过以下配置引用

日志切面

根据定义日志切点(@Loggable)环绕处理逻辑:

  1. 根据注解值获取相应的日志对象
  2. 记录日志信息
@Aspect
@Component
public class LogAspect {
   

    private static final Map<Class<?>, Logger> loggerHolder = new ConcurrentHashMap<Class<?>, Logger>();

    @Pointcut(value = "@annotation(com.linghit.common.log.annotation.Loggable)")
    public void log() {
   
    }

    /**
     * 
     * around:根据日志注解类型为方法调用记录日志 <br/>
     */
    @Around("log()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
   
        AbstractLogBean logBean = null;

        Method method = MethodSignature.class.cast(joinPoint.getSignature())
                .getMethod();
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
   
            if (annotation instanceof Loggable) {
   
                Loggable loggable = Loggable.class.cast(annotation);
                logBean = getLogBean(loggable.value(), joinPoint);
                break;
            }
        }

        Object retVal = null;
        if (null == logBean) {
   
            retVal = joinPoint.proceed(joinPoint.getArgs());
        } else {
   
            logBean.setEventName(StringUtils.isBlank(logBean.getEventName()) ? method
                    .getName() : logBean.getEventName() + "_"
                    + method.getName());
            logBean.setRequest(joinPoint.getArgs());
            try {
   
                retVal = joinPoint.proceed(joinPoint.getArgs());
            } catch (Exception e) {
   
                retVal = Resp.createError(RespCode.BUSINESS_INVALID,
                        "service.fail", "服务失败");

                logBean.setE(e);

            } finally {
   

                logBean.setResponse(retVal);

                Logger logger = getLogger(joinPoint.getTarget().getClass());
                LogUtils.log(logger, logBean);
            }
        }

        return retVal;

    }

    private AbstractLogBean getLogBean(DataAccessType type, JoinPoint joinPoint) {
   
        AbstractLogBean logBean = null;
        switch (type.getValue()) {
   
        case "MySQL":
            logBean = DataAccessLogBean.newDataAccessMysqlLogBean();
            break;
        case "Http":
            logBean = DataAccessLogBean.newDataAccessHttpLogBean();
            break;
        case "Redis":
            logBean = DataAccessLogBean.newDataAccessRedisLogBean();
            break;
        default:
            logBean = new ServiceAccessLogBean(joinPoint.getTarget().getClass()
                    .getSimpleName(), DataAccessType.DUBBO.getValue(),
                    RpcContextUtils.getClientIp(),
                    RpcContextUtils.getLocalAddress());
        }
        return logBean;
    }

    private Logger getLogger(Class<?> clazz) {
   
        if (!loggerHolder.containsKey(clazz)) {
   
            loggerHolder.put(clazz, LoggerFactory.getLogger(clazz));
        }
        return loggerHolder.get(clazz);
    }
}

日志注解

@Target({
    ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Loggable {
   

    /**
     * Name of the logType in which the logging takes place.
     * <p>
     * May be used to determine the target cache (or caches), matching the
     * qualifier value (or the bean name(s)) of (a) specific bean definition.
     */
    DataAccessType value() default DataAccessType.DUBBO;
}

切面扫描及使用

Spring Boot启动类扫描日志切面组件,及配置切面代理为true

@EnableAspectJAutoProxy(proxyTargetClass = true)
@ComponentScan("com.example.service.handler.LogAspect")

使用时只需在相应方法处加入@Loggable注解

@Loggable
public void test{
   }

相关依赖

Spring Boot AOP 起步依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    <version>${springboot.version}</version>
</dependency>

日志分离术

为了便于查看及采集错误日志,下述配置设置INFOERROR日志输出至不同文件

filePattern中需含有%i,每次rollover时,计数器将每次加1,若达到max的值,将删除旧的文件

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" monitorInterval="30"
    name="Log4j2Config">
    <Properties>
        <Property name="PATTERN">[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n
        </Property>
        <Property name="filePatch">./log/${projectName}/</Property>
        <Property name="fileName">${projectName}.log</Property>
        <Property name="errorFileName">${projectName}-error.log</Property>
    </Properties>
    <Appenders>
        <!-- 类型名为Console,名称为必须属性 -->
        <Console name="STDOUT">
            <PatternLayout charset="UTF-8" pattern="${PATTERN}" />
        </Console>

        <RollingFile name="DailyRollingFile" fileName="${filePatch}${fileName}"
            filePattern="${filePatch}${fileName}.%d{yyyy-MM-dd}.%i">
            <PatternLayout charset="UTF-8" pattern="${PATTERN}" />
            <Filters>
                <!--如果是error级别拒绝 -->
                <ThresholdFilter level="error" onMatch="DENY"
                    onMismatch="NEUTRAL" />
                <!--如果是debug\info\warn输出 -->
                <ThresholdFilter level="debug" onMatch="ACCEPT"
                    onMismatch="DENY" />
            </Filters>
            <Policies>
                <!-- 一般与 filePattern联用 以日志的命名精度来确定单位 这里用yyyy-MM-dd来记录 所以1 表示是以天为周期存储文件 -->
                <TimeBasedTriggeringPolicy interval="1"
                    modulate="true" />
                <!-- 日志文件大小 <SizeBasedTriggeringPolicy size="1 MB" /> -->
            </Policies>

            <!-- 最多保留文件数 -->
            <DefaultRolloverStrategy max="7" />
        </RollingFile>

        <RollingFile name="ErrorDailyRollingFile" fileName="${filePatch}${errorFileName}"
            filePattern="${filePatch}${errorFileName}.%d{yyyy-MM-dd}.%i">
            <PatternLayout charset="UTF-8" pattern="${PATTERN}" />
            <Filters>
                <ThresholdFilter level="error" onMatch="ACCEPT"
                    onMismatch="DENY" />
            </Filters>
            <Policies>
                <!-- 一般与 filePattern联用 以日志的命名精度来确定单位 这里用yyyy-MM-dd来记录 所以1 表示是以天为周期存储文件 -->
                <TimeBasedTriggeringPolicy interval="1"
                    modulate="true" />
                <!-- 日志文件大小 <SizeBasedTriggeringPolicy size="1 MB" /> -->
            </Policies>

            <!-- 最多保留文件数 -->
            <DefaultRolloverStrategy max="7" />
        </RollingFile>

        <!-- Socket Apppender配置,通过TCP协议连接Logstash <Socket name="LOGSTASH" host="127.0.0.1" 
            port="9001" protocol="TCP"> <PatternLayout pattern="${PATTERN}" /> </Socket> -->

    </Appenders>

    <Loggers>
        <!-- root loggerConfig设置 -->
        <AsyncRoot level="info" additivity="false">
            <AppenderRef ref="STDOUT" />
            <AppenderRef ref="DailyRollingFile" />
            <AppenderRef ref="ErrorDailyRollingFile" />
        </AsyncRoot>
    </Loggers>

</Configuration>

日志输出效果如下:

log
    project.name
        service.log
        service.log.2018-8-10
        service-error.log
        service-error.log.2018-8-10
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
4天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
86 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
12天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
24天前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
37 4
|
26天前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
86 3
|
26天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
132 1
|
21天前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
31 0
|
1天前
|
Java 中间件
SpringBoot入门(6)- 添加Logback日志
SpringBoot入门(6)- 添加Logback日志
24 5
|
8天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
23 2
|
8天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
53 1
|
9天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
26 1
下一篇
无影云桌面