feat(webmvc): Add method logging and configure Spring Boot AOP

- Added MethodRecording annotation to record method execution information
- Implement MethodRecordingAspect for method recording
- Add LoggerAutoConfiguration Automatic Configuration Class
- Introduce CoroutineLogger coroutine log object - Update dependencies and configure Spring Boot AOP
This commit is contained in:
gewuyou 2025-04-26 23:47:02 +08:00
parent c89f1f1737
commit fc8bea06b3
12 changed files with 249 additions and 14 deletions

View File

@ -36,7 +36,6 @@ allprojects {
ext { ext {
set(ProjectFlags.IS_ROOT_MODULE, false) set(ProjectFlags.IS_ROOT_MODULE, false)
set(ProjectFlags.USE_SPRING_BOOT_BOM, false) set(ProjectFlags.USE_SPRING_BOOT_BOM, false)
set(ProjectFlags.USE_SPRING_BOOT_WEB, false)
} }
afterEvaluate { afterEvaluate {
if (project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) { if (project.getPropertyByBoolean(ProjectFlags.IS_ROOT_MODULE)) {
@ -58,11 +57,6 @@ subprojects {
implementation(platform(libs.springBootDependencies.bom)) implementation(platform(libs.springBootDependencies.bom))
} }
} }
if (project.getPropertyByBoolean(ProjectFlags.USE_SPRING_BOOT_WEB)) {
dependencies {
compileOnly(libs.springBootStarter.web)
}
}
} }
val libs = rootProject.libs val libs = rootProject.libs
apply { apply {
@ -70,7 +64,6 @@ subprojects {
plugin(libs.plugins.maven.publish.get().pluginId) plugin(libs.plugins.maven.publish.get().pluginId)
plugin(libs.plugins.kotlin.jvm.get().pluginId) plugin(libs.plugins.kotlin.jvm.get().pluginId)
plugin(libs.plugins.axionRelease.get().pluginId) plugin(libs.plugins.axionRelease.get().pluginId)
// plugin(libs.plugins.spring.dependency.management.get().pluginId)
//导入仓库配置 //导入仓库配置
from(file("$configDir/repositories.gradle.kts")) from(file("$configDir/repositories.gradle.kts"))
// 导入源代码任务 // 导入源代码任务
@ -91,6 +84,22 @@ subprojects {
password = System.getenv("GITHUB_TOKEN") password = System.getenv("GITHUB_TOKEN")
} }
} }
val host = System.getenv("GEWUYOU_GITEA_HOST")
host?.let {
maven {
isAllowInsecureProtocol = true
name = "Gitea"
url = uri("http://${it}/api/packages/gewuyou/maven")
credentials(HttpHeaderCredentials::class.java) {
name = "Authorization"
value = "token ${System.getenv("GEWUYOU_GITEA_TOKEN")}"
}
authentication {
create("header", HttpHeaderAuthentication::class.java)
}
}
}
} }
publications { publications {
create<MavenPublication>("mavenJava") { create<MavenPublication>("mavenJava") {

View File

@ -1,5 +1,4 @@
object ProjectFlags { object ProjectFlags {
const val IS_ROOT_MODULE = "isRootModule" const val IS_ROOT_MODULE = "isRootModule"
const val USE_SPRING_BOOT_BOM = "useSpringBootBom" const val USE_SPRING_BOOT_BOM = "useSpringBootBom"
const val USE_SPRING_BOOT_WEB = "useSpringBootWeb"
} }

View File

@ -0,0 +1,3 @@
/gradlew text eol=lf
*.bat text eol=crlf
*.jar binary

View File

@ -0,0 +1,40 @@
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
### Kotlin ###
.kotlin

View File

@ -0,0 +1,12 @@
extra {
// 需要SpringBootBom
setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true)
}
dependencies {
implementation(project(Modules.Core.EXTENSION))
implementation(libs.springBootStarter.aop)
implementation(libs.kotlinxCoroutines.reactor)
compileOnly(libs.springBootStarter.web)
}

View File

@ -0,0 +1,14 @@
package com.gewuyou.forgeboot.webmvc.logger.annotation
/**
* 记录方法的注解 记录出参入参方法执行耗时等信息
*
* @since 2025-01-23 14:22:11
* @author gewuyou
*/
// 注解用于函数
@Target(AnnotationTarget.FUNCTION)
// 在运行时可用
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class MethodRecording(val description: String = "",val printArgs: Boolean = true,val printResult: Boolean = true,val printTime: Boolean = true)

View File

@ -0,0 +1,86 @@
package com.gewuyou.forgeboot.webmvc.logger.aspect
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.webmvc.logger.annotation.MethodRecording
import com.gewuyou.forgeboot.webmvc.logger.entities.CoroutineLogger
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
/**
* 方法记录切面
*
* 该切面用于拦截带有@MethodRecording注解的方法记录方法的执行信息包括方法开始执行执行完成执行失败等信息
* 可以通过@MethodRecording注解的属性选择性打印方法的入参返回值和执行耗时等信息
*
* @since 2025-01-23 14:25:04
* @author gewuyou
*/
@Aspect
class MethodRecordingAspect {
/**
* 环绕通知用于在方法执行前后进行操作
*
* @param joinPoint 切入点包含被拦截方法的信息
* @param methodRecording 方法记录注解包含配置信息
* @return 方法的执行结果
* @throws Throwable 方法执行过程中抛出的异常
*/
@Around("@annotation(methodRecording)")
fun methodRecording(joinPoint: ProceedingJoinPoint, methodRecording: MethodRecording): Any? {
// 获取方法名
val methodName = joinPoint.signature.name
// 获取类名
val className = joinPoint.signature.declaringTypeName
// 获取注解的描述信息
val description = methodRecording.description
// 获取方法的入参
val args = joinPoint.args
// 安全地记录日志,防止日志记录失败
CoroutineLogger.safeLog {
// 构建日志字符串,包括方法开始执行的信息、注解的描述信息和入参信息
val logStr = buildString {
append("开始执行方法: $className.$methodName\n")
append("$description\n")
if (methodRecording.printArgs) {
append("入参: ${args.joinToString()}\n")
}
}
log.info(logStr)
}
// 记录方法开始执行的时间
val startTime = System.currentTimeMillis()
// 尝试执行方法
return try {
val result = joinPoint.proceed()
val endTime = System.currentTimeMillis()
val duration = endTime - startTime
// 安全地记录日志,记录方法执行完成的信息、返回值信息和执行耗时信息
CoroutineLogger.safeLog {
val logStr = buildString {
append("方法 $className.$methodName 执行完成\n")
if (methodRecording.printResult) {
append("返回值: $result\n")
}
if (methodRecording.printTime) {
append("执行耗时: ${duration}ms")
}
}
log.info(logStr)
}
result
} catch (ex: Throwable) {
// 如果方法执行失败,安全地记录错误日志
CoroutineLogger.safeLog {
log.error("方法 $className.$methodName 执行失败: ${ex.message}", ex)
}
throw ex
}
}
}

View File

@ -0,0 +1,32 @@
package com.gewuyou.forgeboot.webmvc.logger.config
import com.gewuyou.forgeboot.core.extension.log
import com.gewuyou.forgeboot.webmvc.logger.aspect.MethodRecordingAspect
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* Logger自动配置
*
* 该类用于自动配置Logger通过Spring的自动配置机制自动加载和配置Logger
* 它定义了一个Bean方法用于创建和配置MethodRecordingAspect切面对象
*
* @since 2025-04-26 21:02:56
* @author gewuyou
*/
@Configuration
open class LoggerAutoConfiguration {
/**
* 创建并返回一个MethodRecordingAspect切面对象
*
* 该方法定义了一个Bean用于在Spring容器中创建并配置一个MethodRecordingAspect切面对象
* MethodRecordingAspect切面对象用于记录方法的执行日志以便于监控和调试
*
* @return MethodRecordingAspect切面对象
*/
@Bean
open fun methodRecordingAspect(): MethodRecordingAspect {
log.info("创建方法记录切面!")
return MethodRecordingAspect()
}
}

View File

@ -0,0 +1,34 @@
package com.gewuyou.forgeboot.webmvc.logger.entities
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
/**
* 协程日志对象用于在协程环境中安全地记录日志
*
* @since 2025-04-26 19:20:43
* @author gewuyou
*/
object CoroutineLogger {
// 创建一个协程作用域使用SupervisorJob和IO调度器适合进行IO密集型的日志记录任务
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
/**
* 安全地执行日志记录block异常会被捕获并忽略确保日志记录的稳定性
*
* @param block 日志记录的协程block包含实际的日志记录逻辑
*/
fun safeLog(block: suspend () -> Unit) {
// 在协程作用域中启动一个新的协程来执行日志记录任务
scope.launch {
try {
// 尝试执行日志记录block
block()
} catch (ex: Exception) {
// 吞噬日志错误,确保协程稳定运行,不会因单个日志记录失败而崩溃
}
}
}
}

View File

@ -0,0 +1 @@
com.gewuyou.forgeboot.webmvc.logger.config.LoggerAutoConfiguration

View File

@ -1,8 +1,9 @@
extra { extra {
// 标记为根项目 // 需要SpringBootBom
setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true) setProperty(ProjectFlags.USE_SPRING_BOOT_BOM, true)
setProperty(ProjectFlags.USE_SPRING_BOOT_WEB, true)
} }
dependencies { dependencies {
implementation(project(Modules.Core.EXTENSION)) implementation(project(Modules.Core.EXTENSION))
compileOnly(libs.springBootStarter.web)
} }

View File

@ -6,7 +6,7 @@
kotlin-version = "2.0.0" kotlin-version = "2.0.0"
kotlinxDatetime-version = "0.6.1" kotlinxDatetime-version = "0.6.1"
kotlinxSerializationJSON-version = "1.7.3" kotlinxSerializationJSON-version = "1.7.3"
kotlinxCoroutines-version = "1.9.0" #kotlinxCoroutines-version = "1.9.0"
axion-release-version = "1.18.7" axion-release-version = "1.18.7"
spring-boot-version = "3.4.4" spring-boot-version = "3.4.4"
@ -15,16 +15,20 @@ slf4j-version = "2.0.17"
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-version" } kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin-version" }
kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime-version" } kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime-version" }
kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON-version" } kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON-version" }
kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines-version" } #kotlinxCoroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines-version" }
kotlinxCoroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core" }
# kotlinx
# 响应式协程库
kotlinxCoroutines-reactor = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-reactor" }
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-version" } slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j-version" }
springBootStarter-aop = { group = "org.springframework.boot", name = "spring-boot-starter-aop" }
springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" } springBootStarter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web" }
springBootDependencies-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot-version" } springBootDependencies-bom = { module = "org.springframework.boot:spring-boot-dependencies", version.ref = "spring-boot-version" }
# Libraries can be bundled together for easier import # Libraries can be bundled together for easier import
[bundles] [bundles]
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines"] kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines-core"]
[plugins] [plugins]
# 应用 Java 插件,提供基本的 Java 代码编译和构建能力 # 应用 Java 插件,提供基本的 Java 代码编译和构建能力