如何配置Serilog Console Sink将日志输出到多个控制台窗口?
如何配置Serilog Console Sink将日志输出到不同控制台窗口?
Serilog默认Console Sink仅绑定当前进程的控制台窗口,要按命名空间拆分日志到多窗口并保留颜色,核心是为不同命名空间的日志分配独立控制台实例,结合自定义Sink实现定向输出。
实现方案(Windows平台)
1. 封装Windows控制台API调用
通过P/Invoke调用系统API创建新控制台窗口并切换输出句柄:
using System; using System.Runtime.InteropServices; public static class ConsoleNative { [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr AllocConsole(); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle); public const int STD_OUTPUT_HANDLE = -11; }
2. 自定义多窗口Sink
实现Serilog的ILogEventSink接口,将指定命名空间的日志输出到独立控制台:
using Serilog; using Serilog.Core; using Serilog.Events; using Serilog.Formatting; public class NamedConsoleSink : ILogEventSink { private readonly ITextFormatter _formatter; private readonly IntPtr _originalOutputHandle; private readonly IntPtr _newConsoleHandle; public NamedConsoleSink(ITextFormatter formatter, string consoleTitle) { _formatter = formatter; // 创建新控制台窗口并设置标题 ConsoleNative.AllocConsole(); Console.Title = consoleTitle; // 保存原控制台输出句柄,避免干扰主窗口 _originalOutputHandle = ConsoleNative.GetStdHandle(ConsoleNative.STD_OUTPUT_HANDLE); // 获取新控制台的输出句柄 _newConsoleHandle = ConsoleNative.GetStdHandle(ConsoleNative.STD_OUTPUT_HANDLE); } public void Emit(LogEvent logEvent) { // 切换到新控制台输出 ConsoleNative.SetStdHandle(ConsoleNative.STD_OUTPUT_HANDLE, _newConsoleHandle); // 使用Serilog格式化器输出(保留ANSI颜色) using var writer = Console.Out; _formatter.Format(logEvent, writer); writer.WriteLine(); // 切回原控制台输出 ConsoleNative.SetStdHandle(ConsoleNative.STD_OUTPUT_HANDLE, _originalOutputHandle); } }
3. 配置Serilog按命名空间过滤
通过Conditional筛选不同命名空间的日志,定向到对应的自定义Sink:
using Serilog; using Serilog.Formatting.Display; var loggerConfig = new LoggerConfiguration() // 主控制台输出全量日志(可选) .WriteTo.Console( outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}") // 定向MyApp.ModuleA命名空间日志到专属窗口 .WriteTo.Conditional( evt => evt.Properties.TryGetValue("SourceContext", out var ctx) && ctx.ToString().Contains("MyApp.ModuleA"), wt => wt.Sink(new NamedConsoleSink( new MessageTemplateTextFormatter( "[{Timestamp:HH:mm:ss} {Level:u3}] [ModuleA] {Message:lj}{NewLine}{Exception}", null), "ModuleA 日志控制台"))) // 定向MyApp.ModuleB命名空间日志到专属窗口 .WriteTo.Conditional( evt => evt.Properties.TryGetValue("SourceContext", out var ctx) && ctx.ToString().Contains("MyApp.ModuleB"), wt => wt.Sink(new NamedConsoleSink( new MessageTemplateTextFormatter( "[{Timestamp:HH:mm:ss} {Level:u3}] [ModuleB] {Message:lj}{NewLine}{Exception}", null), "ModuleB 日志控制台"))); Log.Logger = loggerConfig.CreateLogger();
跨平台替代方案(Linux/macOS)
对于非Windows平台,可通过启动独立终端进程并定向管道输出实现:
- 配置Serilog将不同命名空间的日志输出到命名管道:
// 示例:输出ModuleA日志到管道 .WriteTo.File("/tmp/moduleA.log.pipe", outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [ModuleA] {Message:lj}{NewLine}{Exception}")
- 启动新终端窗口监听管道(保留颜色):
# Linux(gnome-terminal) gnome-terminal -- bash -c "cat /tmp/moduleA.log.pipe; exec bash" # macOS(iTerm2) open -a iTerm.app --args /bin/bash -c "cat /tmp/moduleA.log.pipe; exec bash"
注意事项
- 确保
SourceContext正确注入:通过Log.ForContext<YourClass>()或DI容器自动配置,Serilog才能识别日志所属命名空间。 - 自定义Sink方案仅支持Windows,跨平台需用管道+终端监听方案。
- 两种方案均保留Serilog的ANSI颜色格式,满足开发环境的日志可读性需求。
内容的提问来源于stack exchange,提问作者Iaman Swtrse
相关产品推荐
相关产品推荐

