You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Delegating Handler中用Serilog和Destructurama.Attributed实现日志脱敏

解决方案

方案一:根据端点反序列化到对应实体类(复用Destructurama特性)

既然你已经在实体类上标记了[LogMasked]和[NotLogged],最直接的方式就是根据请求端点匹配对应的实体类型,将JSON反序列化为该类型后再记录日志,这样Destructurama的特性会自动生效。

实现步骤与代码示例

  1. 建立端点与请求/响应实体的映射关系
public static class EndpointEntityMapping
{
    // 键:完整端点路径(需与路由一致,比如"/api/user/login"),值:对应请求实体类型
    public static readonly Dictionary<string, Type> RequestTypeMap = new Dictionary<string, Type>
    {
        { "/api/user/login", typeof(LoginRequest) },
        // 补充其他端点的映射...
    };

    // 同理维护响应实体的映射
    public static readonly Dictionary<string, Type> ResponseTypeMap = new Dictionary<string, Type>
    {
        { "/api/user/login", typeof(LoginResponse) },
        // 补充其他端点的响应映射...
    };
}
  1. 修改Delegating Handler的日志逻辑,添加反序列化处理
private async void Log(HttpRequestMessage request, HttpResponseMessage response)
{
    var endpoint = string.Join(string.Empty, request.RequestUri.Segments);
    var requestJson = await GetHttpRequestContentAsync(request);
    
    // 处理请求日志:匹配映射反序列化
    if (EndpointEntityMapping.RequestTypeMap.TryGetValue(endpoint, out var requestType))
    {
        var requestObj = JsonConvert.DeserializeObject(requestJson, requestType);
        _logger.Information("Request: {@request}", requestObj); // 脱敏特性自动生效
    }
    else
    {
        // 无映射的端点,可直接记录原始JSON或做通用脱敏
        _logger.Information("Request (unmapped): {request}", requestJson);
    }

    // 处理响应日志逻辑同理
    var responseJson = await response.Content.ReadAsStringAsync();
    if (EndpointEntityMapping.ResponseTypeMap.TryGetValue(endpoint, out var responseType))
    {
        var responseObj = JsonConvert.DeserializeObject(responseJson, responseType);
        _logger.Information("Response: {@response}", responseObj); // 脱敏特性自动生效
    }
    else
    {
        _logger.Information("Response (unmapped): {response}", responseJson);
    }
}

注:示例用Newtonsoft.Json实现反序列化,若项目用System.Text.Json,替换为对应的JsonSerializer.Deserialize方法即可。

方案二:手动解析JObject实现脱敏(无需实体映射)

如果不想维护端点-实体的映射表,或需要处理动态JSON结构,可以直接解析JObject,手动对敏感字段做掩码或忽略处理。

实现步骤与代码示例

  1. 编写通用脱敏工具类
public static class JsonMaskingHelper
{
    // 需要掩码的字段:键为字段名,值为保留的前置字符数
    private static readonly Dictionary<string, int> MaskFields = new Dictionary<string, int>
    {
        { "LoginName", 3 },
        // 补充其他需要掩码的字段...
    };

    // 需要忽略不记录的敏感字段
    private static readonly HashSet<string> IgnoreFields = new HashSet<string>
    {
        { "Password" },
        { "Token" },
        // 补充其他敏感字段...
    };

    public static JToken MaskSensitiveData(JToken token)
    {
        if (token is JObject obj)
        {
            var maskedObj = new JObject();
            foreach (var prop in obj.Properties())
            {
                if (IgnoreFields.Contains(prop.Name))
                    continue; // 跳过不记录的字段

                if (MaskFields.TryGetValue(prop.Name, out var showFirst))
                {
                    // 仅对字符串类型字段做掩码处理
                    if (prop.Value.Type == JTokenType.String)
                    {
                        var value = prop.Value.Value<string>();
                        if (!string.IsNullOrEmpty(value) && value.Length > showFirst)
                        {
                            maskedObj[prop.Name] = value.Substring(0, showFirst) + new string('*', value.Length - showFirst);
                        }
                        else
                        {
                            maskedObj[prop.Name] = prop.Value;
                        }
                    }
                    else
                    {
                        maskedObj[prop.Name] = prop.Value;
                    }
                }
                else
                {
                    // 递归处理嵌套对象
                    maskedObj[prop.Name] = MaskSensitiveData(prop.Value);
                }
            }
            return maskedObj;
        }
        else if (token is JArray array)
        {
            // 递归处理数组内的每个元素
            var maskedArray = new JArray();
            foreach (var item in array)
            {
                maskedArray.Add(MaskSensitiveData(item));
            }
            return maskedArray;
        }
        else
        {
            return token;
        }
    }
}
  1. 修改日志逻辑,使用工具类处理JSON
private async void Log(HttpRequestMessage request, HttpResponseMessage response)
{
    var requestJson = await GetHttpRequestContentAsync(request);
    if (!string.IsNullOrEmpty(requestJson))
    {
        var requestJObj = JToken.Parse(requestJson);
        var maskedRequest = JsonMaskingHelper.MaskSensitiveData(requestJObj);
        _logger.Information("Request: {@request}", maskedRequest);
    }

    var responseJson = await response.Content.ReadAsStringAsync();
    if (!string.IsNullOrEmpty(responseJson))
    {
        var responseJObj = JToken.Parse(responseJson);
        var maskedResponse = JsonMaskingHelper.MaskSensitiveData(responseJObj);
        _logger.Information("Response: {@response}", maskedResponse);
    }
}

方案对比

  • 方案一:复用已有的实体特性,代码简洁无重复规则,适合接口数量固定、结构稳定的项目,但需要维护端点-实体映射表。
  • 方案二:无需依赖实体类,灵活性更高,适合动态JSON或不想维护映射表的场景,但需手动维护脱敏规则,可能与实体特性规则重复。

内容的提问来源于stack exchange,提问作者darrenleeyx

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 02:59:55