如何在运行时切换Serilog的WriteTo与AuditTo日志写入方式?
解决方案思路与实现
1. 核心思路:自定义动态切换Sink包装器
Serilog的AuditTo和WriteTo本质对应不同的Sink执行逻辑——前者同步写入、失败抛异常,后者异步批量、静默容错。要实现运行时切换,需要一个中间包装Sink,内部维护两种模式的Sink实例,通过开关动态路由日志写入逻辑。
2. 实现动态切换Sink
public class DynamicSwitchSink : ILogEventSink { private readonly ILogEventSink _auditSink; private readonly ILogEventSink _writeToSink; private bool _useAuditMode; private readonly object _lockObj = new(); public DynamicSwitchSink(ILogEventSink auditSink, ILogEventSink writeToSink, bool initialUseAuditMode = true) { _auditSink = auditSink ?? throw new ArgumentNullException(nameof(auditSink)); _writeToSink = writeToSink ?? throw new ArgumentNullException(nameof(writeToSink)); _useAuditMode = initialUseAuditMode; } // 线程安全的模式切换方法 public void ToggleAuditMode(bool useAuditMode) { lock (_lockObj) { _useAuditMode = useAuditMode; } } public void Emit(LogEvent logEvent) { lock (_lockObj) { if (_useAuditMode) { // 严格遵循AuditTo逻辑:同步写入,失败直接抛出异常 _auditSink.Emit(logEvent); } else { // 遵循WriteTo逻辑:异步批量写入,不抛出异常 _writeToSink.Emit(logEvent); } } } }
3. 实现SQLite的Audit模式Sink
由于官方SQLite Sink不支持AuditTo,需要自定义一个同步写入、失败抛异常的Audit版Sink:
public class SqliteAuditSink : ILogEventSink { private readonly string _connectionString; private readonly IFormatProvider _formatProvider; public SqliteAuditSink(string connectionString, IFormatProvider formatProvider) { _connectionString = connectionString; _formatProvider = formatProvider; EnsureTableExists(); } private void EnsureTableExists() { using var conn = new SQLiteConnection(_connectionString); conn.Open(); conn.Execute(@" CREATE TABLE IF NOT EXISTS AuditLogs ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Timestamp DATETIME NOT NULL, Level TEXT NOT NULL, Message TEXT NOT NULL, MessageTemplate TEXT, Properties TEXT, Exception TEXT )"); } public void Emit(LogEvent logEvent) { // 同步写入,失败直接抛出SQLite异常,符合AuditTo的可靠性要求 using var conn = new SQLiteConnection(_connectionString); conn.Open(); var props = JsonConvert.SerializeObject(logEvent.Properties); conn.Execute(@" INSERT INTO AuditLogs (Timestamp, Level, Message, MessageTemplate, Properties, Exception) VALUES (@Timestamp, @Level, @Message, @MessageTemplate, @Properties, @Exception)", new { Timestamp = logEvent.Timestamp, Level = logEvent.Level.ToString(), Message = logEvent.RenderMessage(_formatProvider), MessageTemplate = logEvent.MessageTemplate.Text, Properties = props, Exception = logEvent.Exception?.ToString() }); } }
4. 集成Serilog并实现运行时切换
初始化Serilog时注册自定义Sink,保留切换开关的引用即可在运行时随时切换模式:
// 初始化两种模式的SQLite Sink var sqliteAuditSink = new SqliteAuditSink("Data Source=audit.db", CultureInfo.InvariantCulture); var sqliteWriteToSink = new AsyncSink( new SqliteSink("Data Source=audit.db", new SqliteSinkOptions()), new AsyncSinkOptions { QueueLimit = 1000, BatchSizeLimit = 100 }); // 创建动态切换Sink var dynamicSink = new DynamicSwitchSink(sqliteAuditSink, sqliteWriteToSink); // 配置Serilog Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() .WriteTo.Sink(dynamicSink) .CreateLogger(); // 运行时切换示例(可通过API、配置中心等触发) // 切换到WriteTo模式(异步容错) dynamicSink.ToggleAuditMode(false); // 切回Audit模式(同步强一致) dynamicSink.ToggleAuditMode(true);
5. 关键注意事项
- 线程安全:切换逻辑和日志写入逻辑加锁,避免并发切换时出现数据不一致
- 异常处理:Audit模式下写入失败会抛出异常,业务代码需按需捕获处理(符合审计数据不丢的设计目标)
- 性能平衡:WriteTo模式用
AsyncSink包装,保证异步批量写入不阻塞业务线程 - 配置持久化:可将当前模式存入配置中心,应用启动时读取初始值,运行时监听配置变化自动切换
内容的提问来源于stack exchange,提问作者kliszaq
相关产品推荐
相关产品推荐

