Files
zszq-trs/YLErpDAL/Modules/SwapModule/SwapCalcTrace.cs
T
hjhan 97029897c4 feat(swap): SwapCalcTrace 支持运行时按请求开关 + 响应头可见(无需重启)
- SwapCalcTrace 改为双模式:保留全局静态开关(单元测试用 IsEnabled/Dump),
  新增请求级 AsyncLocal 缓冲(EnableForRequest/DumpForRequest/ClearForRequest),
  由中间件按请求开关,不跨请求串扰。
- 新增 SwapCalcTraceMiddleware:开启方式三选一——URL ?swaptrace=1 /
  请求头 X-SwapCalc-Trace:1 / 标记文件 App_Data/swapcalctrace.on(无需重启);
  Response.OnStarting 时把逐日计息过程写入响应头 X-SwapCalc-Trace(超长落
  logs/swapcalctrace-YYYYMMDD.log 并在头给路径),同时写服务器日志。
  开发者在浏览器“网络”面板即可查看,零前端改码、零行为影响、性能可忽略。
- Program.cs 在 ExceptionMiddleware 后注册该中间件。
- 删除 CalcDailyCompoundInterestByEod 遗留垃圾日志 LogFactory.GetLogger("test").Error("lksafhasdhfjas")。
- CalcDailySimpleInterest 两处追踪守卫改为 IsEnabled||IsRequestEnabled。
- 既有 PrepaidPrincipalCloseTraceTest / ClosingChainTraceTest 2/2 仍全绿。

回答用户两点:① dynomicPrincipal 公式字段含义;② 原 Trace 需重启才能开,
现改为前端 otcdebug 式(?swaptrace=1) 即可在响应头看到逐步计息过程。
2026-08-08 15:57:00 +08:00

63 lines
2.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
namespace YLErp.Modules.SwapModule
{
/// <summary>
/// 计息计算过程追踪器。默认完全关闭,零运行时成本;开启后逐日记录:
/// 计息起点、日终归档 ValueDate 地板、计息基数 dynomicPrincipal、当日利率、当日利息、累计利息。
/// 便于定位"算出来一个数却不对"的根因,也便于把一次真实平仓的逐步过程打印出来与 Excel 对账。
///
/// 两种开启方式(互不冲突):
/// 1) 全局(单元测试):SwapCalcTrace.IsEnabled = true; SwapCalcTrace.Reset(); ...; SwapCalcTrace.Dump();
/// 2) 请求(运行时排障,无需重启):经 SwapCalcTraceMiddleware,在 URL 追加 ?swaptrace=1
/// 或请求头 X-SwapCalc-Trace: 1 即可,追踪文本会出现在响应头 X-SwapCalc-Trace(超长落 logs/ 并在头部给路径)。
/// </summary>
public static class SwapCalcTrace
{
// ---- 全局模式(单元测试,保持 ThreadStatic 兼容既有测试) ----
public static bool IsEnabled { get; set; } = false;
[ThreadStatic]
private static List<string> _globalLines;
private static List<string> GlobalLines => _globalLines ??= new List<string>();
// ---- 请求模式(运行时排障,按请求异步流隔离,不会跨请求串扰) ----
private static readonly AsyncLocal<StringBuilder> _reqBuf = new AsyncLocal<StringBuilder>();
private static readonly AsyncLocal<bool> _reqOn = new AsyncLocal<bool>();
public static void EnableForRequest(bool on = true) => _reqOn.Value = on;
public static bool IsRequestEnabled => _reqOn.Value;
public static void ClearForRequest() => _reqBuf.Value = null;
public static void Reset()
{
GlobalLines.Clear();
_reqBuf.Value = null;
}
public static void Header(string title) => Record($"== {title} ==");
public static void Line(string text) => Record(text);
/// <summary>记录某一计息日的明细。</summary>
public static void Day(int idx, DateTime date, decimal rate, decimal basePrincipal, decimal dayInterest, decimal accumulated)
=> Record($" [{idx}] {date:yyyy-MM-dd} rate={rate:P6} base={basePrincipal:F4} day={dayInterest:F6} acc={accumulated:F6}");
private static void Record(string s)
{
if (IsEnabled) GlobalLines.Add(s);
if (_reqOn.Value)
{
_reqBuf.Value ??= new StringBuilder();
_reqBuf.Value.AppendLine(s);
}
}
public static string Dump() => string.Join(Environment.NewLine, GlobalLines);
public static string DumpForRequest() => _reqBuf.Value?.ToString() ?? "";
}
}