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

如何配置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平台,可通过启动独立终端进程并定向管道输出实现:

  1. 配置Serilog将不同命名空间的日志输出到命名管道:
// 示例:输出ModuleA日志到管道
.WriteTo.File("/tmp/moduleA.log.pipe", outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [ModuleA] {Message:lj}{NewLine}{Exception}")
  1. 启动新终端窗口监听管道(保留颜色):
# 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 03:18:23