feat: RequestLog 中间件补齐(对齐 C#)
- 请求体 JSON 字段级截断(仅超长字符串字段,保留结构) - 下游异常捕获记录后重抛(对齐 C# ExceptionDispatchInfo) - 响应体受 soulsoft body 只读限制,记录 Content-Length 替代
This commit is contained in:
@@ -8,14 +8,21 @@ package simapi.middlewares
|
||||
import std.collection.*
|
||||
import std.io.*
|
||||
import std.time.*
|
||||
import stdx.encoding.json.*
|
||||
import soulsoft_web_http.*
|
||||
import soulsoft_extensions_logging.*
|
||||
import simapi.communications.*
|
||||
import simapi.configurations.*
|
||||
|
||||
/**
|
||||
* 请求日志中间件:记录请求方法、URL、请求头、请求体、响应状态码与耗时。
|
||||
* 请求日志中间件:记录请求方法、URL、请求头、请求体、响应状态码、耗时与异常。
|
||||
* 对应 C# 的 SimApi.Middlewares.SimApiRequestLogMiddleware。
|
||||
*
|
||||
* 对齐说明:
|
||||
* - 请求体按 JSON 字段级截断(对齐 C#:仅对超长字符串字段截断,保留结构)
|
||||
* - 捕获下游异常并记录,随后重抛(对齐 C# ExceptionDispatchInfo + edi.Throw)
|
||||
* - 响应体:soulsoft HttpResponse.body 只读不可替换(C# 用 MemoryStream 替换捕获),
|
||||
* 此处以 Content-Length 作为替代信息;ShowFullResponse 选项因此暂不生效
|
||||
*/
|
||||
public class SimApiRequestLogMiddleware <: IMiddleware {
|
||||
private let _options: SimApiOptions
|
||||
@@ -51,15 +58,33 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
|
||||
sb.append("*( RequestBody ) =>\n")
|
||||
sb.append(readRequestBody(context))
|
||||
|
||||
// 调用下一级
|
||||
next(context)
|
||||
// 调用下一级,捕获异常以便记录并重抛(对齐 C# ExceptionDispatchInfo)
|
||||
var exception: ?Exception = None
|
||||
try {
|
||||
next(context)
|
||||
} catch (ex: Exception) {
|
||||
exception = Some(ex)
|
||||
}
|
||||
|
||||
// 响应信息
|
||||
let elapsed = MonoTime.now() - start
|
||||
let elapsedMs = elapsed / Duration.millisecond
|
||||
sb.append("*( Response [${context.response.statusCode}] ) => ${elapsedMs}ms\n")
|
||||
sb.append("*( Response [${context.response.statusCode}] ) => ${elapsedMs}ms")
|
||||
// 响应体捕获受 soulsoft 限制(body 只读不可替换),记录 Content-Length 作为替代
|
||||
if (let Some(len) <- context.response.contentLength) {
|
||||
sb.append(" (响应体长度: ${len})")
|
||||
}
|
||||
sb.append("\n")
|
||||
if (let Some(ex) <- exception) {
|
||||
sb.append("Exception: ${ex.toString()}\n")
|
||||
}
|
||||
|
||||
_logger.info(sb.toString())
|
||||
|
||||
// 重抛原异常(对齐 C# edi?.Throw()),由外层 ExceptionMiddleware 处理
|
||||
if (let Some(ex) <- exception) {
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
private func serializeHeaders(context: HttpContext): String {
|
||||
@@ -107,17 +132,39 @@ public class SimApiRequestLogMiddleware <: IMiddleware {
|
||||
}
|
||||
}
|
||||
|
||||
/// 请求体截断:JSON 字段级截断(对齐 C#:仅对超长字符串字段截断),非 JSON 则整串截断
|
||||
private func truncateBody(body: String): String {
|
||||
if (_options.simApiRequestLogOptions.requestStringLogLength <= 0 ||
|
||||
body.size <= _options.simApiRequestLogOptions.requestStringLogLength) {
|
||||
let maxLen = _options.simApiRequestLogOptions.requestStringLogLength
|
||||
if (maxLen <= 0) {
|
||||
return body + "\n"
|
||||
}
|
||||
// 简单按长度截断(不做 JSON 字段级截断,保持实现简洁)
|
||||
let chars = body.toArray()
|
||||
var sb = StringBuilder()
|
||||
for (i in 0.._options.simApiRequestLogOptions.requestStringLogLength) {
|
||||
sb.append(chars[i])
|
||||
try {
|
||||
let jv = JsonValue.fromStr(body)
|
||||
match (jv.kind()) {
|
||||
case JsObject =>
|
||||
let obj = jv.asObject()
|
||||
let newObj = JsonObject()
|
||||
for ((k, v) in obj.getFields()) {
|
||||
match (v.kind()) {
|
||||
case JsString =>
|
||||
let str = v.asString().getValue()
|
||||
if (str.size > maxLen) {
|
||||
newObj.put(k, JsonString(str[0..maxLen] + "...(${str.size})"))
|
||||
} else {
|
||||
newObj.put(k, v)
|
||||
}
|
||||
case _ => newObj.put(k, v)
|
||||
}
|
||||
}
|
||||
return newObj.toString() + "\n"
|
||||
case _ => ()
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
return "${sb.toString()}...(${body.size})\n"
|
||||
// 非 JSON 或解析失败:整串按长度截断
|
||||
if (body.size <= maxLen) {
|
||||
return body + "\n"
|
||||
}
|
||||
return body[0..maxLen] + "...(${body.size})\n"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user