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
相关产品推荐
相关产品推荐

