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

ASP.NET Core日志敏感信息屏蔽实现方案咨询

这个需求太实用了——要在日志到达任何sink/写入器之前统一屏蔽敏感数据,完全不依赖特定日志框架(比如Serilog),或者在Serilog管道内轻量处理,下面给你两种靠谱的实现思路:

方案1:ASP.NET Core原生日志拦截器(适配所有日志提供者)

这个方案基于ASP.NET Core的ILogger抽象层,不管你用Serilog、NLog还是内置的Console日志,都能生效。核心思路是包装原生的日志提供者,在日志消息被发送前统一替换敏感内容。

步骤1:实现自定义日志包装类

创建一个ILogger的包装类,负责拦截并替换敏感字符串:

public class SensitiveDataLogger : ILogger
{
    private readonly ILogger _innerLogger;
    private readonly IEnumerable<string> _sensitiveTerms;

    public SensitiveDataLogger(ILogger innerLogger, IEnumerable<string> sensitiveTerms)
    {
        _innerLogger = innerLogger;
        _sensitiveTerms = sensitiveTerms;
    }

    public IDisposable BeginScope<TState>(TState state) => _innerLogger.BeginScope(state);

    public bool IsEnabled(LogLevel logLevel) => _innerLogger.IsEnabled(logLevel);

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        // 先拿到原始日志消息
        var originalMsg = formatter(state, exception);
        // 批量替换所有敏感字符串
        var maskedMsg = originalMsg;
        foreach (var term in _sensitiveTerms)
        {
            maskedMsg = maskedMsg.Replace(term, "*****", StringComparison.OrdinalIgnoreCase);
        }
        // 用处理后的消息调用原始日志器
        _innerLogger.Log(logLevel, eventId, state, exception, (s, e) => maskedMsg);
    }
}

步骤2:实现对应的日志提供者包装类

用来注入我们的自定义日志器:

public class SensitiveDataLoggerProvider : ILoggerProvider
{
    private readonly ILoggerProvider _innerProvider;
    private readonly IEnumerable<string> _sensitiveTerms;

    public SensitiveDataLoggerProvider(ILoggerProvider innerProvider, IEnumerable<string> sensitiveTerms)
    {
        _innerProvider = innerProvider;
        _sensitiveTerms = sensitiveTerms;
    }

    public ILogger CreateLogger(string categoryName)
    {
        var innerLogger = _innerProvider.CreateLogger(categoryName);
        return new SensitiveDataLogger(innerLogger, _sensitiveTerms);
    }

    public void Dispose() => _innerProvider.Dispose();
}

步骤3:在Program.cs中注册并替换原生提供者

注意要包裹住你原来的日志提供者(比如Serilog的提供者):

var builder = WebApplication.CreateBuilder(args);

// 先配置你原来的日志框架(比如Serilog)
builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration);
    // 保留你原本的Serilog配置
});

// 定义你的敏感字符串列表
var sensitiveStrings = new List<string> { "mySecretPassword", "apiKey-12345", "userToken-XYZ" };

// 替换已注册的日志提供者为我们的包装类
var originalProviders = builder.Logging.Services
    .Where(s => s.ServiceType == typeof(ILoggerProvider))
    .ToList();

foreach (var provider in originalProviders)
{
    builder.Logging.Services.Remove(provider);
    builder.Logging.Services.Add(ServiceDescriptor.Singleton<ILoggerProvider>(sp =>
    {
        var innerProvider = (ILoggerProvider)sp.GetRequiredService(provider.ImplementationType);
        return new SensitiveDataLoggerProvider(innerProvider, sensitiveStrings);
    }));
}

// 其他服务注册...
var app = builder.Build();
方案2:Serilog专属Enricher(轻量高效)

如果你确定只会用Serilog,用它的Enricher机制会更简洁,直接在Serilog的处理管道中完成屏蔽:

步骤1:实现自定义Enricher

public class SensitiveDataMaskingEnricher : ILogEventEnricher
{
    private readonly IEnumerable<string> _sensitiveTerms;

    public SensitiveDataMaskingEnricher(IEnumerable<string> sensitiveTerms)
    {
        _sensitiveTerms = sensitiveTerms;
    }

    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        if (logEvent.MessageTemplate == null) return;

        // 替换消息模板中的敏感内容
        var originalTemplate = logEvent.MessageTemplate.Text;
        var maskedTemplate = originalTemplate;
        foreach (var term in _sensitiveTerms)
        {
            maskedTemplate = maskedTemplate.Replace(term, "*****", StringComparison.OrdinalIgnoreCase);
        }
        logEvent.MessageTemplate = new MessageTemplateParser().Parse(maskedTemplate);

        // 同时处理结构化日志的属性值(避免敏感数据藏在属性里)
        foreach (var prop in logEvent.Properties.ToList())
        {
            if (prop.Value is ScalarValue scalar && scalar.Value is string propValue)
            {
                var maskedValue = propValue;
                foreach (var term in _sensitiveTerms)
                {
                    maskedValue = maskedValue.Replace(term, "*****", StringComparison.OrdinalIgnoreCase);
                }
                logEvent.AddOrUpdateProperty(new LogEventProperty(prop.Key, new ScalarValue(maskedValue)));
            }
        }
    }
}

步骤2:在Serilog配置中添加Enricher

builder.Host.UseSerilog((context, config) =>
{
    var sensitiveStrings = new List<string> { "mySecretPassword", "apiKey-12345", "userToken-XYZ" };
    
    config.ReadFrom.Configuration(context.Configuration)
          .Enrich.With(new SensitiveDataMaskingEnricher(sensitiveStrings));
});
一些实用建议
  • 正则匹配优化:如果需要匹配更复杂的敏感数据(比如手机号、邮箱、身份证号),可以把字符串替换改成正则表达式,比如:
    maskedMsg = Regex.Replace(maskedMsg, @"\b1[3-9]\d{9}\b", "*****", RegexOptions.IgnoreCase);
    
  • 性能优化:如果敏感字符串很多,建议把它们编译成正则表达式并缓存,减少重复替换的开销。
  • 结构化日志覆盖:两种方案都考虑了结构化日志的属性替换,避免敏感数据藏在日志的属性字段里没被屏蔽。

内容的提问来源于stack exchange,提问作者Poul K. Sørensen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:55:22