From a73a986f8b26f4a8855ec99267dcef31ec92cb16 Mon Sep 17 00:00:00 2001 From: xRain Date: Sun, 16 Aug 2026 22:46:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20RequestLog=20=E4=B8=AD=E9=97=B4?= =?UTF-8?q?=E4=BB=B6=E8=A1=A5=E9=BD=90=EF=BC=88=E5=AF=B9=E9=BD=90=20C#?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 请求体 JSON 字段级截断(仅超长字符串字段,保留结构) - 下游异常捕获记录后重抛(对齐 C# ExceptionDispatchInfo) - 响应体受 soulsoft body 只读限制,记录 Content-Length 替代 --- src/middlewares/SimApiRequestLogMiddleware.cj | 71 +++++++++++++++---- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/src/middlewares/SimApiRequestLogMiddleware.cj b/src/middlewares/SimApiRequestLogMiddleware.cj index 47e4431..2feb55f 100644 --- a/src/middlewares/SimApiRequestLogMiddleware.cj +++ b/src/middlewares/SimApiRequestLogMiddleware.cj @@ -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" } }