-
Notifications
You must be signed in to change notification settings - Fork 4
/
BusinessRequestLogAspect.kt
150 lines (134 loc) · 5.8 KB
/
BusinessRequestLogAspect.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package com.akagiyui.drive.component
import com.akagiyui.common.delegate.LoggerDelegate
import com.akagiyui.common.utils.compressPackageName
import com.akagiyui.common.utils.ellipsis
import com.akagiyui.common.utils.hasText
import com.akagiyui.common.utils.toStr
import com.akagiyui.drive.entity.User
import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import io.minio.GetObjectResponse
import jakarta.servlet.http.HttpServletRequest
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.http.ResponseEntity
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Component
import org.springframework.web.context.request.RequestAttributes
import org.springframework.web.context.request.RequestContextHolder
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
/**
* 业务请求日志 切面
*
* @author AkagiYui
*/
@Aspect
@Component
class BusinessRequestLogAspect {
private val log by LoggerDelegate()
private val objectMapper = ObjectMapper().apply {
setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) // 可以序列化私有字段
configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) // 序列化空对象时不抛异常
registerKotlinModule() // 添加 Kotlin 支持
}
@Around("execution(@(org.springframework.web.bind.annotation.*Mapping) * *(..))")
@Throws(Throwable::class)
fun process(joinPoint: ProceedingJoinPoint): Any? {
val methodSignature = joinPoint.signature as MethodSignature // 获取方法签名
val methodName = "${joinPoint.target::class.qualifiedName}::${methodSignature.name}" // 方法名
val requestLog = StringBuilder()
val request = httpServletRequest ?: return joinPoint.proceed()
val clientIp: String = request.getHeader("X-Real-IP") ?: request.remoteAddr // 客户端IP
val clientPlatform: String? = request.getHeader("Sec-Ch-Ua-Platform").let {
if (!it.hasText()) {
null
} else {
if (it.startsWith("\"") && it.endsWith("\"")) it.substring(1, it.length - 1) else it
}
} // 客户端平台
val clientInfo = if (clientPlatform.hasText()) "$clientIp[$clientPlatform]" else clientIp
var requestLine = "\n $clientInfo -> "
// 用户信息
val user = SecurityContextHolder.getContext().authentication.principal as? User
requestLine += user?.let { "[${it.username}(${it.id})]" } ?: "[anonymous]"
requestLine += "[${request.method}]" // HTTP请求方法
request.contentType.hasText { requestLine += "($it)" } // 请求类型
requestLine += request.requestURI // 请求路径
requestLog.append(requestLine)
// 请求params
request.parameterMap.also { params ->
val paramsBuffer: String = params
.filterNot { it.key == "password" }
.map { (key, value) -> "$key=${value.joinToString(",")}".ellipsis(100) }
.joinToString(", ")
.ellipsis(1000)
requestLog.append(if (paramsBuffer.isNotBlank()) "($paramsBuffer)" else "")
}
// 获取方法参数
requestLog.append("\n method -> ${methodName.compressPackageName(50)}")
val args = joinPoint.args // 方法参数
val paramsBuffer: String = args
.asSequence()
.filterNotNull()
.map { anyToString(it).ellipsis(100) }
.joinToString(", ") // 将参数连接成字符串
.ellipsis(1000)
requestLog.append("($paramsBuffer)")
// 记录返回值与异常
val startTime = System.currentTimeMillis()
try {
val returnObj = joinPoint.proceed()
val duration = System.currentTimeMillis() - startTime
val resultLog = anyToString(returnObj).ellipsis(500)
requestLog.append("\n result[${duration}ms] <- $resultLog")
return returnObj
} catch (e: Throwable) {
val duration = System.currentTimeMillis() - startTime
requestLog.append("\n error[${duration}ms] <- ${e.message}")
throw e
} finally {
log.debug(requestLog.toString())
}
}
private fun anyToString(any: Any?): String {
if (any == null) {
return "null"
}
if (any::class.isData) {
return any.toString()
}
if (any is ByteArray) {
return any.toStr()
}
if (any is GetObjectResponse) {
return any.toStr()
}
if (any is ResponseEntity<*>) {
return any.toStr()
}
if (any is MultipartFile) {
return "MultipartFile[${any.originalFilename}]"
}
if ((any is Collection<*>) && any.isNotEmpty() && (any.first() is MultipartFile)) {
return "Collection<MultipartFile>[${any.size}]"
}
if (any is SseEmitter) {
return any.toString()
}
if (any is User) {
return "User[${any.id}]"
}
return objectMapper.writeValueAsString(any)
}
private val httpServletRequest: HttpServletRequest?
get() {
val requestAttributes = RequestContextHolder.getRequestAttributes() ?: return null
return requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST) as HttpServletRequest?
}
}