自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

小白也能看得懂!日志審計(jì)插件從入門(mén)到實(shí)戰(zhàn)

開(kāi)發(fā) 前端
本文介紹了一款基于 AOP 切面技術(shù)的日志審計(jì)插件,旨在解決系統(tǒng)操作審計(jì)和異常排查的問(wèn)題。插件能夠自動(dòng)集成并支持實(shí)時(shí)分析功能。

1. 前言

1.1. 背景

測(cè)試同學(xué)火急火燎說(shuō)系統(tǒng)出問(wèn)題了,一點(diǎn)一個(gè)不吱聲??墒俏疫@明明操作得像德芙一樣絲滑,究竟是誰(shuí)想要謀害朕?業(yè)務(wù)說(shuō)數(shù)據(jù)對(duì)不上,數(shù)據(jù)被誰(shuí)給操作了?又是什么時(shí)候操作的?產(chǎn)品同學(xué)反應(yīng)某個(gè)頁(yè)面會(huì)發(fā)生“隨機(jī)性”卡頓,是哪一臺(tái)有“問(wèn)題”的服務(wù)器響應(yīng)了請(qǐng)求?研發(fā)同學(xué)向你求助,能不能找到某個(gè)特殊參數(shù)相關(guān)聯(lián)的請(qǐng)求信息,幫他找出異常所在?你是否也碰到過(guò)上面的問(wèn)題,讓你抓耳撓腮,寢食難安。 那么,是時(shí)候需要一款日志插件,來(lái)幫你解決上述的所有問(wèn)題。

1.2. 概覽

乾數(shù)據(jù)系統(tǒng)作為轉(zhuǎn)轉(zhuǎn)廣告投放的基礎(chǔ)服務(wù),雖然系統(tǒng)并發(fā)量不像 C 端系統(tǒng)動(dòng)輒數(shù)十上百萬(wàn),但是每一次業(yè)務(wù)操作背后,都影響著廣告投放的穩(wěn)定性以及資金結(jié)算的準(zhǔn)確性。 因此,我們基于AOP切面技術(shù),開(kāi)發(fā)了一款日志審計(jì)插件,用于乾數(shù)據(jù)系統(tǒng)的操作審計(jì)以及研發(fā)人員的異常排查工作,業(yè)務(wù)項(xiàng)目通過(guò)引入插件對(duì)應(yīng)的 Maven-GAV 坐標(biāo),即可自動(dòng)集成插件。并且插件通過(guò)集成消息隊(duì)列,還可支持一些特殊的實(shí)時(shí)分析功能。以下是日志插件的基礎(chǔ)架構(gòu)圖。

圖片圖片

2. 實(shí)現(xiàn)

2.1. “好東西”

2.1.1. git-commit-id-maven-plugin 插件

為了在開(kāi)發(fā)過(guò)程中,特別是和研發(fā)小伙伴聯(lián)合調(diào)試的過(guò)程中,更好的定位到問(wèn)題所在,避免插件使用版本不一致帶來(lái)的各種問(wèn)題,可以使用git-commit-id-maven-plugin插件。git-commit-id-maven-plugin 是一個(gè) Maven 插件,在 Maven 構(gòu)建過(guò)程中,插件會(huì)生成一個(gè)名為 git-commit-id.properties 的文件。這個(gè)文件通常包含有關(guān)當(dāng)前構(gòu)建的 Git 提交哈希、分支名稱(chēng)、提交時(shí)間等信息。

<groupId>io.github.git-commit-id</groupId>
                    <artifactId>git-commit-id-maven-plugin</artifactId>
                    <version>${git-commit-id-maven-plugin.version}</version>
                <executions>
                    <execution>
                        <id>get-the-git-infos</id>
                        <goals>
                            <goal>revision</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <validationProperties>
                        <!-- verify that the current repository is not dirty -->
                        <validationProperty>
                            <name>validating git dirty</name>
                            <value>${git.dirty}</value>
                            <shouldMatchTo>false</shouldMatchTo>
                        </validationProperty>
                    </validationProperties>
                    <generateGitPropertiesFile>true</generateGitPropertiesFile>
                    <generateGitPropertiesFilename>${project.build.outputDirectory}/META-INF/scm/${project.groupId}/${project.artifactId}/git.properties</generateGitPropertiesFilename>
                </configuration>

git信息文件git信息文件

private static final Properties GIT_PROPERTIES;

    static {
        try {
            GIT_PROPERTIES = new Properties();
            //讀取插件生成的GIT信息文件
            GIT_PROPERTIES.load(ResourceUtil.getResourceObj("META-INF/scm/com.bj58.zhuanzhuan/qianshuju_log_plugin/git.properties").getStream());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

2.1.2. hibernate-validator

對(duì)于自定義配置,用戶可能有意或無(wú)意會(huì)輸入一些奇奇怪怪的東西,輕則導(dǎo)致項(xiàng)目無(wú)法啟動(dòng),重則產(chǎn)生不可估量的影響。因此,對(duì)于屬性的校驗(yàn),可以引入hibernate-validator框架,然后利用@Validated 配套的校驗(yàn)注解,自定義校驗(yàn)規(guī)則。

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
</dependency>
/**
     * 日志-線程池核心數(shù)大?。J(rèn)CPU*2)
     * 對(duì)于IO密集型,可設(shè)置線程數(shù)為 cpu核心數(shù)*2,并根據(jù)情況可適當(dāng)增加。
     *
     */
    @Min(value = 1, message = "線程池核心數(shù)大小不能小于0!")
    private int corePoolSize = Runtime.getRuntime().availableProcessors() * 2 + 1;

    /**
     * 最大線程池?cái)?shù)量
     */
    @Min(value = 1, message = "線程池最大數(shù)大小不能小于0!")
    private  int maxPoolSize=128;

2.1.3. spring-boot-configuration-processor 插件

如果想給用戶更好的使用體驗(yàn),可以引入spring-boot-configuration-processor插件,該插件在打包的時(shí)候,會(huì)生成 target/classes/META-INF/spring-configuration-metadata.json文件,該文件被 IDEA 讀取到后,在用戶配置屬性的時(shí)候,會(huì)有自動(dòng)提示的效果。下面為摘取的/spring-configuration-metadata.json 文件中的部分內(nèi)容。

[{
"name": "qianshuju.logplugin.core-pool-size",
"type": "java.lang.Integer",
"description": "日志-線程池核心數(shù)大?。J(rèn)CPU*2) 對(duì)于IO密集型,可設(shè)置線程數(shù)為 cpu核心數(shù)*2,并根據(jù)情況可適當(dāng)增加。 @see <a href=\"https:\/\/dashen.zhuanspirit.com\/x\/YYVxCQ\">轉(zhuǎn)轉(zhuǎn)大神-線程池的使用<\/a>",
"sourceType": "com.bj58.zhuanzhuan.qianshuju.logPlugin.config.LogPluginProperties",
"defaultValue": 0
},
{
"name": "qianshuju.logplugin.max-pool-size",
"type": "java.lang.Integer",
"description": "最大線程池?cái)?shù)量",
"sourceType": "com.bj58.zhuanzhuan.qianshuju.logPlugin.config.LogPluginProperties",
"defaultValue": 128
}]

圖片圖片

2.1.4. maven-source-plugin 插件

如果想讓用戶 import 插件包后,能看到源碼,最簡(jiǎn)單的方法就是讓用戶利用 IDEA 的反編譯功能,反編譯出代碼,但是會(huì)丟失很多的注釋信息,因此我們可以使用maven-source-plugin插件,順帶打出一個(gè)源碼包,即在 pom.xml 中加上如下配置:

<plugin>
    <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-source-plugin -->
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-source-plugin</artifactId>
    <version>3.2.1</version>
    <executions>
        <execution>
            <goals>
                <goal>
                    jar-no-fork
                </goal>
            </goals>
        </execution>
    </executions>
</plugin>

2.1.5. 依賴(lài)版本問(wèn)題

Springboot 中有很多版本沖突問(wèn)題,有些高版本的依賴(lài)包改動(dòng)很大,刪代碼,改方法,比比皆是,因此不向下兼容,對(duì)使用者來(lái)說(shuō),最好使用統(tǒng)一的包版本管理,在 pom 中加入如下配置:

<dependencyManagement>
     <dependencies>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-dependencies</artifactId>
            <version>2.7.18</version>
             <scope>import</scope>
             <type>pom</type>
         </dependency>
     </dependencies>
 </dependencyManagement>

如果仍然出現(xiàn)版本沖突問(wèn)題,建議 IDEA 下載Maven Helper插件后,查閱對(duì)應(yīng)版本 spring 官方文檔,以下就是從springboot 2.7.18文檔中,摘錄的部分對(duì)其他依賴(lài)要求的最低版本:

圖片圖片

2.2. 底層工具

因篇幅有限,本篇文章不會(huì)細(xì)致到每一個(gè)代碼細(xì)節(jié),而是挑取核心的重點(diǎn)模塊和易錯(cuò)模塊進(jìn)行闡述。因此針對(duì)本文中涉及到的一些基礎(chǔ)工具類(lèi)實(shí)現(xiàn),將不再贅述,僅僅提供實(shí)現(xiàn)思路。

代碼涉及的工具類(lèi)

作用

建議

WebUtil

獲取當(dāng)前 servlet 請(qǐng)求,解析請(qǐng)求參數(shù),請(qǐng)求 header 等

參考 org.springframework.web.util.WebUtils

UrlUtil

獲取 Url 中的 path 路徑

參考 org.springframework.web.util.UriUtils

LogPluginGitUtil

代碼 git 版本工具

參考 git-commit-id-maven-plugin 插件

LogPluginSpringUtils

Spring 容器工具

參考 org.springframework.beans.factory.config.BeanFactoryPostProcessor

LogPluginNetUtil

獲取服務(wù)網(wǎng)絡(luò)狀態(tài)

參考 org.springframework.boot.web.context.WebServerInitializedEvent

SicUtil

轉(zhuǎn)轉(zhuǎn)信息管理平臺(tái)工具類(lèi),用于獲取工程相關(guān)信息

內(nèi)部框架,暫無(wú)參考

CommonConstant

配置常量,用于插件部分默認(rèn)配置

例如默認(rèn)線程池名稱(chēng)

2.3. 整體概覽

如果你問(wèn)我,本日志插件最核心的地方是什么?我認(rèn)為不是 AOP 切面,也不是線程池,而應(yīng)該是自動(dòng)配置模塊,即 spring.factories 中配置的EnableAutoConfiguration屬性值,因?yàn)樗w了整個(gè)日志插件被容器管理的各個(gè)相關(guān) Bean,每個(gè) Bean 都各司其職,“堅(jiān)守”著自己的崗位,完成著部分功能。因此,要想對(duì)本插件有一個(gè)整體的認(rèn)知,我覺(jué)得有必要好好講一講 AutoConfiguration 所涉及到的那些組件們。

圖片圖片

組件

作用

LogPluginProperties

用戶自定義配置所映射的實(shí)體類(lèi),基于本類(lèi)的配置來(lái)進(jìn)行后續(xù)其他組件的“加工”

ICreatedByService

租戶服務(wù),用戶獲取當(dāng)前請(qǐng)求對(duì)應(yīng)的“租戶”標(biāo)識(shí),即操作者用戶標(biāo)識(shí)

Server

用于獲取當(dāng)前“宿主”服務(wù)的狀態(tài)信息,例如 IP,環(huán)境信息等

ThreadPoolTaskExecutor

插件核心線程池,用于執(zhí)行日志數(shù)據(jù)傳輸任務(wù)

IlogPersistenceService

日志數(shù)據(jù)持久層服務(wù),用于將數(shù)據(jù)傳遞至下游存儲(chǔ)引擎

IDataStreamService

消息隊(duì)列服務(wù),用于將數(shù)據(jù)投遞至下游 MQ 進(jìn)行數(shù)據(jù)分析,本插件目前僅支持轉(zhuǎn)轉(zhuǎn)架構(gòu)部自研組件ZZMQ

LogRelayTask

封裝的日志任務(wù),提交給線程池執(zhí)行

2.4. 具體實(shí)現(xiàn)

2.4.1. 插件屬性配置類(lèi):LogPluginProperties

為了實(shí)現(xiàn)讓用戶能夠根據(jù)自身環(huán)境,自定義做一些配置,我們抽取了LogPluginProperties類(lèi)來(lái)作為用戶的統(tǒng)一配置類(lèi)入口,該配置類(lèi)中包含了插件線程池,Stream 流配置等。最后通過(guò)利用afterPropertiesSet()鉤子,可以對(duì)部分設(shè)置進(jìn)行缺省配置,以及執(zhí)行部分依賴(lài)檢查工作。

LogPluginProperties依賴(lài)LogPluginProperties依賴(lài)

提示:DataStreamType.ZZ_MQ 中的ZZMQ是基于早期的RocketMQ,加入了許多轉(zhuǎn)轉(zhuǎn)自己的特性,獨(dú)立于社區(qū)版本,由架構(gòu)團(tuán)隊(duì)負(fù)責(zé)維護(hù)、開(kāi)發(fā)與運(yùn)維的消息中間件。當(dāng)前因?yàn)槠邢?,只展?ZZMQ 的配置樣例,如需使用 Kafka 或 RabbitMQ,可自行改造。

@Data
@EqualsAndHashCode
@ToString
@ConfigurationProperties("qianshuju.logplugin")
@Validated
@Slf4j
public class LogPluginProperties implements InitializingBean {
    /** 數(shù)據(jù)流類(lèi)型 */
    private String dataStreamType = DataStreamType.ZZ_MQ;
    /** 啟用流 */
    private Boolean enableStream = false;
    /** zzmq屬性 */
    private ZZMQProperties zzmqProperties;

    /**
     * 宿主項(xiàng)目名稱(chēng)(即當(dāng)前項(xiàng)目名,用于區(qū)分日志 )
     */
    private String renter;
    @Override
    public void afterPropertiesSet() throws Exception {
        if (StrUtil.isBlank(renter)) {
            //用戶未主動(dòng)配置項(xiàng)目名稱(chēng),降級(jí)為使用SIC封裝的應(yīng)用名
            renter = SicUtil.getCurrentSicInfo().getAppName();
        }
        checkEnv();        //檢查環(huán)境變量

    }
    @Data
    @EqualsAndHashCode
    @ToString
    public static class ZZMQProperties {
        /** zzmq-topic */
        private String topic = "qianshuju-log";
        /** zzmq-tag */
        private String tag = "";

        /** zzmq.producer.group的名稱(chēng) 必填*/
        private String producerName = "";
    }

    private void checkEnv() {
        if (enableStream) {
            if (ObjectUtil.equal(dataStreamType, DataStreamType.ZZ_MQ)) {
                try {
                    Class.forName("com.alibaba.rocketmq.client.producer.DefaultMQProducer");
                } catch (ClassNotFoundException e) {
                    log.error("checkEnv fail: ", e);
                    throw new RuntimeException("The streaming service has been enabled and the configuration item is ZZMQ, but the corresponding dependency is missing!");
                }
            }
        }
    }
}

2.4.2. 定義統(tǒng)一的日志信息實(shí)體 PluginLogDto

因篇幅有限,僅展示部分關(guān)鍵字段

@Data
@EqualsAndHashCode
@ToString
public class PluginLogDto implements Serializable {
    /**
     * 日志標(biāo)題
     */
    private String title;
    /**
     * 服務(wù)器地址
     */
    private String serverIp;
    /**
     * 服務(wù)器名字
     */
    private String serverName;
    /**
     * 客戶端地址
     */
    private String clientIp;
    /**
     * 請(qǐng)求地址
     */
    private String requestUri;
    /**
     * 請(qǐng)求參數(shù)
     */
    private String requestParam;
    /**
     * 方法名
     */
    private String methodName;

}

2.4.3. 日志持久服務(wù) IlogPersistenceService

為了調(diào)試方便,我們配置了一個(gè)默認(rèn)的日志持久化服務(wù),直接把日志信息打印到控制臺(tái)上。當(dāng)然,用戶可以實(shí)現(xiàn)自己的持久化服務(wù),例如存儲(chǔ)到 ES 當(dāng)中,方便后續(xù)的檢索。

/**控制臺(tái)日志默認(rèn)持久化實(shí)現(xiàn),僅供本地簡(jiǎn)單調(diào)試使用,請(qǐng)勿直接用于生產(chǎn)環(huán)境
 * @author liuyangjun@zhuanzhuan.com
 * * @date 2024/3/28
 */
public class DefaultLogPersistenceServiceImpl implements IlogPersistenceService {
    @Override
    public void saveApiLog(PluginLogDto pluginLogDto) {
        System.out.println(JSON.toJSONString(pluginLogDto));
    }

    @Override
    public void saveErrorLog(PluginLogDto pluginLogDto) {
        System.out.println(JSON.toJSONString(pluginLogDto));
    }
}

2.4.4. 租戶配置 ICreatedByService

插件使用者可以實(shí)現(xiàn)自己的ICreatedByService實(shí)現(xiàn)類(lèi),來(lái)提供給插件獲取當(dāng)前操作用戶的標(biāo)識(shí),例如我們可以從當(dāng)前“安全上下文”中獲取當(dāng)?shù)卿浻脩粜畔ⅰ?/p>

@Component
public class DefaultCreatedByServiceImpl implements ICreatedByService {
    @Override
    public String getCreatedBy() {
        return Optional.ofNullable(UserContext.getLoginUserInfo()).map(UserLoginInfo::getRealName).orElse("null");
    }
}

2.4.5. 日志數(shù)據(jù)流服務(wù) IDataStreamService

通過(guò)日志數(shù)據(jù)流服務(wù),將日志數(shù)據(jù)推送至消息隊(duì)列中,下游的實(shí)時(shí)分析服務(wù)可以做一些分析服務(wù)。利用 Springboot 的@ConditionalOn這一套組件,來(lái)完成對(duì)應(yīng)消息服務(wù)組件的自動(dòng)配置。

當(dāng)前僅支持ZZMQ組件的自動(dòng)配置,也可改造成支持Kafka或者RocketMQ。

@Service
@Slf4j
@ConditionalOnProperty(name = "qianshuju.logplugin.dataStreamType", havingValue = "zzmq")
@ConditionalOnClass(DefaultMQProducer.class)
public class ZZMQDataStreamServiceImpl implements IDataStreamService {
    @Autowired
    private LogPluginProperties logPluginProperties;
    private DefaultMQProducer defaultMqProducer;

    @Override
    public boolean sendToStream(PluginLogDto pluginLogDto) {
        ZZMQProperties zzmqProperties = logPluginProperties.getZzmqProperties();
        Message message = new Message(zzmqProperties.getTopic(), zzmqProperties.getTag(), JsonUtil.silentObject2String(pluginLogDto).getBytes());
        try {
            SendResult send = defaultMqProducer.send(message);
            return ObjectUtil.equal(send.getSendStatus(), SendStatus.SEND_OK);
        } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
            log.error("sendToStream fail: ", e);
            return false;
        }
    }


    @PostConstruct
    public void init() {
        String producerName = logPluginProperties.getZzmqProperties().getProducerName();
        this.defaultMqProducer = SpringUtil.getBean(producerName, DefaultMQProducer.class);
    }
}

通過(guò)如下簡(jiǎn)單的配置,就能夠“激活”我們的日志數(shù)據(jù)流服務(wù)了。

key

value

remark

qianshuju.logplugin.dataStreamType

ZZMQ/kafka/RabbitMQ

啟動(dòng)的流式組件,當(dāng)前僅支持 ZZMQ

qianshuju.logplugin.enableStream

true/false

是否開(kāi)啟流式服務(wù)

2.4.6. 線程池服務(wù)

為了不影響“業(yè)務(wù)”性能,我們將日志數(shù)據(jù)的分發(fā)邏輯,放到了線程池中去執(zhí)行。在此,有兩種推薦的線程池,一種是帶監(jiān)控功能的線程池,例如轉(zhuǎn)轉(zhuǎn)架構(gòu)部提供的MonitoredThreadPoolExecutor,能夠監(jiān)控到日志線程池中的狀態(tài)。

圖片圖片

當(dāng)然,如果你手頭上沒(méi)有這樣的“武器”,那么你也可以使用 Spring 提供的ThreadPoolTaskExecutor線程池,該線程池繼承自 Spring-ExecutorConfigurationSupport,實(shí)現(xiàn)了 destroy in interface DisposableBean接口,能夠保證服務(wù)停止的時(shí)候,解決任務(wù)丟失的問(wèn)題。

@Bean(name = CommonConstant.LOG_PLUGIN_EXECUTOR, autowireCandidate = false)
public ThreadPoolTaskExecutor logPluginExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setCorePoolSize(logPluginProperties.getCorePoolSize());
    threadPoolTaskExecutor.setMaxPoolSize(logPluginProperties.getMaxPoolSize());
    threadPoolTaskExecutor.setKeepAliveSeconds(60);
    threadPoolTaskExecutor.setQueueCapacity(logPluginProperties.getQueueCapacity());
    threadPoolTaskExecutor.setAllowCoreThreadTimeOut(false);
    BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
            .namingPattern(logPluginProperties.getThreadNamingPattern())
            .daemon(false)
            .uncaughtExceptionHandler((t, e) -> {
                log.warn("日志線程執(zhí)行任務(wù)失敗", e);
            })
            .build();
    threadPoolTaskExecutor.setThreadFactory(threadFactory);
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
    //注意,此處有大坑,如果設(shè)置了setWaitForTasksToCompleteOnShutdown為true,即容器需要等待線程池停止,
    // 則必須設(shè)置setAwaitTerminationSeconds具體的秒數(shù)!否則setWaitForTasksToCompleteOnShutdown將不生效!
    threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
//        private long awaitTerminationMillis = 0;
//        默認(rèn)值為0,如果自己不設(shè)置,相當(dāng)于setWaitForTasksToCompleteOnShutdown=true白設(shè)置了
    threadPoolTaskExecutor.setAwaitTerminationSeconds(CommonConstant.LOGPLUGIN_EXECUTOR_AWAIT_TERMINATION_SECONDS);
    threadPoolTaskExecutor.setThreadPriority(Thread.MIN_PRIORITY);
    threadPoolTaskExecutor.setDaemon(false);
    return threadPoolTaskExecutor;
}

2.4.7. 最后的最后:切面!

終于,我們已經(jīng)了解了所有的組件以及它們對(duì)應(yīng)的“職責(zé)”,那么,切面就是最后將把他們組合起來(lái),實(shí)現(xiàn)最終日志邏輯的大 Boss。通過(guò)注解切點(diǎn),我們把相關(guān)的切面邏輯織入進(jìn)去。獲取到注解標(biāo)注的部分信息,再結(jié)合請(qǐng)求參數(shù),方法信息,報(bào)錯(cuò)信息,拼裝成我們最后的日志數(shù)據(jù)。

Aspect結(jié)構(gòu)圖

@Aspect
@Component
@Slf4j
public class LogApiLogAspect {
    @Autowired
    private Server server;
    @Autowired
    private ICreatedByService iCreatedByService;
    @Autowired
    private LogPluginProperties logPluginProperties;
   @Resource(name = CommonConstant.LOG_PLUGIN_EXECUTOR)
    private ThreadPoolTaskExecutor executor;
    @Autowired
    private IlogPersistenceService ilogPersistenceService;
    @Autowired(required = false)
    private IDataStreamService dataStreamService;
    /**
     * 配置織入點(diǎn)
     **/
    @Pointcut("@annotation(com.bj58.zhuanzhuan.qianshuju.logPlugin.annotation.ApiLog)")
    public void logPointCut() {
    }


    @AfterReturning("@annotation(apiLog)")
    public void doAround(JoinPoint point, ApiLog apiLog) {
        handleUsualLog(point);
    }

    @AfterThrowing(value = "logPointCut()", throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Exception exception) {
        handleExceptionLog(joinPoint, exception);
    }

    /**
     * 處理正常日志
     */
    protected void handleUsualLog(JoinPoint point) {
        ApiLog apiLog = null;
        try {
            apiLog = getAnnotationLog(point);
        } catch (Exception ex) {
            log.warn("updateMediumWorkerOrderInfo", ex);
            throw ex;
        }
        if (ObjectUtil.isNull(apiLog)) {
            return;
        }
        String className = point.getTarget().getClass().getSimpleName();
        String methodName = point.getSignature().getName();
        String params = getRequestValue(point);
        try {
            HttpServletRequest request = WebUtil.getRequest();
            LogApi logApi = new LogApi();
            logApi.setTitle(apiLog.value());
            logApi.setClazzName(className);
            logApi.setMethodName(methodName);
            logApi.setRequestParam(params);
            logApi.setCreateBy(iCreatedByService.getCreatedBy());
            logApi.setRequestType(request.getMethod());
            logApi.setServerIp(this.server.getIp() + ":" + LogPluginNetUtil.getPort());
            logApi.setClientIp(IpUtil.getIpAddr(request));
            logApi.setRequestUri(UrlUtil.getPath(request.getRequestURI()));
            logApi.setEnv(LogPluginNetUtil.getEnv());
            logApi.setServerName(logPluginProperties.getRenter());
            executor.execute(new LogRelayTask(logApi, ilogPersistenceService, dataStreamService));
        } catch (Throwable throwable) {
            log.warn("處理正常日志發(fā)生異常", throwable);
        }
    }

    /**
     * 是否存在注解,如果存在就獲取
     */
    private @Nullable ApiLog getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(ApiLog.class);
        }
        return null;
    }
}

2.5. 結(jié)果呈現(xiàn)

最終,我們就實(shí)現(xiàn)了如下圖的效果,通過(guò)檢索引擎,能夠快速根據(jù)指定參數(shù)找到對(duì)應(yīng)的接口,進(jìn)而找到相關(guān)聯(lián)的時(shí)間,服務(wù)器地址,參數(shù),創(chuàng)建人等信息,結(jié)合這些信息,極大提升了我們排查問(wèn)題的效率。

3. 寫(xiě)在最后

3.1. 未來(lái)思考

當(dāng)前,我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的日志審計(jì)插件,然而,要想把插件做得更加完善,道阻且長(zhǎng),我們還有很多地方需要思考。

  • 當(dāng)前注解 ApiLog 的 value 值,即業(yè)務(wù)操作名稱(chēng)為寫(xiě)死的字面值,是否可通過(guò)SpringEl表達(dá)式,配合方法參數(shù),動(dòng)態(tài)生成業(yè)務(wù)操作名稱(chēng)?
  • 當(dāng)前注解只能標(biāo)注在 Controller 上,是否可以做成標(biāo)注在 service 方法上,甚至任意方法上,即實(shí)現(xiàn)類(lèi)似事務(wù)嵌套機(jī)制一樣的日志注解嵌套?利用棧是否可以實(shí)現(xiàn)?
  • 當(dāng)前插件可兼容的 JDK21 的 Springboot 版本為 2.7.18~3 之間,當(dāng) springboot 升級(jí)到 3.X 之后,SpringBoot3.x 移除spring.factories,只支持使用 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 增加自動(dòng)配置,此時(shí)插件該如何兼容?是否需要把工程結(jié)構(gòu)再細(xì)拆分下去?
  • 轉(zhuǎn)轉(zhuǎn)架構(gòu)部提供了很好的鏈路追蹤工具:天網(wǎng),是否可以集成天網(wǎng)鏈路追蹤,關(guān)聯(lián)TraceId,不僅可以通過(guò)天網(wǎng)可視化查詢服務(wù)之間的調(diào)用鏈路,還可以利用 TraceId 查出鏈路相關(guān)聯(lián)的日志信息。

3.2. 總結(jié)

本文介紹了一款基于 AOP 切面技術(shù)的日志審計(jì)插件,旨在解決系統(tǒng)操作審計(jì)和異常排查的問(wèn)題。插件能夠自動(dòng)集成并支持實(shí)時(shí)分析功能。文章首先闡述了插件的背景和重要性,接著詳細(xì)介紹了插件的實(shí)現(xiàn),包括多個(gè)有用的 Maven 插件和框架,如git-commit-id-maven-plugin、hibernate-validator和spring-boot-configuration-processor,以提升開(kāi)發(fā)效率和用戶體驗(yàn)。

插件的核心在于自動(dòng)配置模塊,涵蓋了多個(gè)組件的協(xié)作,如日志信息實(shí)體、日志持久化服務(wù)和數(shù)據(jù)流服務(wù)。通過(guò)線程池處理日志數(shù)據(jù),確保不影響業(yè)務(wù)性能。最后,文章展示了切面邏輯的實(shí)現(xiàn),結(jié)合請(qǐng)求參數(shù)和方法信息,生成最終的日志數(shù)據(jù)。

整體而言,本文不僅提供了日志插件的實(shí)現(xiàn)細(xì)節(jié),還分享了在工程中使用的“好東西”,為開(kāi)發(fā)者在日志管理和異常排查方面提供了實(shí)用的解決方案。

3.3. 參考文檔

  • [美團(tuán)技術(shù)團(tuán)隊(duì)-如何優(yōu)雅地記錄操作日志?]https://tech.meituan.com/2021/09/16/operational-logbook.html
  • [動(dòng)態(tài)代理—攔截器—責(zé)任鏈—AOP 面向切面編程底層原理]https://liuyangjun.blog.csdn.net/article/details/83277344
  • [Spring2.7.18 官方文檔]https://docs.spring.io/spring-boot/docs/2.7.18/reference/pdf/spring-boot-reference.pdf

關(guān)于作者

劉揚(yáng)俊,Java 后端開(kāi)發(fā)工程師,CSDN 百萬(wàn)訪問(wèn)量博主,目前負(fù)責(zé)轉(zhuǎn)轉(zhuǎn)廣告投放相關(guān)業(yè)務(wù)。

責(zé)任編輯:武曉燕 來(lái)源: 轉(zhuǎn)轉(zhuǎn)技術(shù)
相關(guān)推薦

2020-03-17 19:39:50

區(qū)塊鏈區(qū)塊鏈技術(shù)

2020-05-06 09:10:08

機(jī)器學(xué)習(xí)無(wú)監(jiān)督機(jī)器學(xué)習(xí)有監(jiān)督機(jī)器學(xué)習(xí)

2022-08-16 21:01:56

runAsyncreload數(shù)據(jù)

2022-06-06 08:02:21

ahooks架構(gòu)hooks

2021-11-01 15:15:37

Context項(xiàng)目代碼

2022-01-20 08:49:24

OTDR光纖

2015-10-10 11:43:19

數(shù)據(jù)漫畫(huà)人才

2015-12-15 14:08:31

2021-11-18 08:09:40

Python爬蟲(chóng)Python基礎(chǔ)

2018-01-08 14:24:32

程序員段子工程師

2022-09-30 15:46:26

Babel編譯器插件

2017-02-22 15:04:52

2019-12-25 09:02:48

HTTPSHTTP安全

2025-04-14 00:00:00

MCPjson 信息地理編碼

2025-03-11 14:45:31

2021-11-29 14:18:05

Nuxt3靜態(tài)Nuxt2

2024-11-01 05:10:00

2025-04-22 07:52:59

2024-07-30 08:19:14

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)