.NET Core 输出日志

在 .NET Core 中,ILogger 是日志记录的抽象接口,而 ILoggerProvider 负责创建具体的日志记录器实例并决定日志的输出目标(如控制台、文件、数据库等)。

要实现控制台日志,通常有两种方式:使用内置的 ConsoleLoggerProvider(推荐)或实现自定义的 ILoggerProvider。

使用内置 ConsoleLoggerProvider

项目依赖

确保项目中包含以下 NuGet 包:

  • Microsoft.Extensions.Logging
  • Microsoft.Extensions.Logging.Console
  • Microsoft.Extensions.Configuration.Json (用于 JSON 配置)

下面是一个较为完整的代码示例:.NET Core 内置提供程序的使用、配置、过滤、作用域追踪以及自定义格式化。

cs 复制代码
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;

namespace ConsoleLoggingDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 1. 构建配置 (模拟从 appsettings.json 读取)
            var configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .Build();

            // 2. 创建 LoggerFactory 并添加控制台提供程序
            using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
            {
                // 添加控制台日志提供程序
                builder.AddConsole(options =>
                {
                    options.TimestampFormat = "[HH:mm:ss] "; // 设置时间戳格式
                });

                // 3. 应用过滤器 (AddFilter)
                // 全局默认级别为 Information
                builder.SetMinimumLevel(LogLevel.Information);
                
                // 针对特定命名空间过滤:Microsoft 开头的日志只记录 Warning 及以上
                builder.AddFilter("Microsoft", LogLevel.Warning);
                
                // 自定义过滤逻辑:如果类别包含 "Sensitive",仅记录 Error
                builder.AddFilter((category, level) =>
                {
                    if (category.Contains("Sensitive"))
                        return level >= LogLevel.Error;
                    return true;
                });

                // 绑定配置文件中的 Logging 节
                builder.AddConfiguration(configuration.GetSection("Logging"));
            });

            // 4. 创建 Logger 实例
            // 类别名称通常为当前类的全名,便于过滤和分组
            ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
            ILogger sensitiveLogger = loggerFactory.CreateLogger("SensitiveDataProcessor");

            // 5. 定义事件 ID
            EventId eventId = new EventId(1001, "AppStart");

            // 6. 使用 BeginScope 进行日志追踪 (结构化上下文)
            using (logger.BeginScope(new Dictionary<string, object>
            {
                ["RequestId"] = Guid.NewGuid().ToString(),
                ["UserId"] = "User_12345",
                ["Operation"] = "ProcessOrder"
            }))
            {
                logger.Log(eventId, LogLevel.Information, "Application started successfully.");
                logger.LogInformation("Processing order...");

                // 嵌套 Scope
                using (logger.BeginScope(new { Step = "Validation" }))
                {
                    logger.LogInformation("Validating data...");
                }
            }

            // 测试敏感数据过滤
            sensitiveLogger.LogInformation("This should be filtered out.");
            sensitiveLogger.LogError("This sensitive error should appear.");

            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

JSON 配置 (appsettings.json

cs 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    },
    "Console": {
      "LogLevel": {
        "Default": "Debug"
      }
    }
  }
}

ILogger 与 ILoggerProvider 的关系

ILogger:应用程序代码直接使用的接口,用于写入日志。它不关心日志写到哪里,只关心写什么。

ILoggerProvider : 工厂模式的核心,负责根据类别名称创建 ILogger 实例。例如,ConsoleLoggerProvider 会创建 ConsoleLogger,后者知道如何将消息格式化并写入 Console.Out。

ILoggerFactory: 管理所有的 ILoggerProvider。当你调用 factory.CreateLogger<T>() 时,工厂会遍历所有注册的 Provider,为每个 Provider 创建一个对应的 Logger 实例,并将它们组合成一个聚合 Logger。

日志级别 (LogLevel)

在 .NET Core (ASP.NET Core) 中,日志级别由 Microsoft.Extensions.Logging.LogLevel 枚举定义。系统根据严重性从低到高排列,数值越小表示日志越详细,数值越大表示问题越严重。

.NET Core:定义了以下标准级别(数值越小越详细):

Trace (0): 最详细的信息,可能包含敏感数据。

Debug (1):开发调试用。

Information (2):常规业务流跟踪。

Warning (3):非错误异常或意外情况。

Error (4): 导致当前操作失败的错误。

Critical (5):系统级崩溃,需要立即关注。

None (6): 不记录任何日志。

.NET Core 日志级别一览表

日志级别 (LogLevel) 数值 描述与应用场景
‌Trace‌ 0 ‌最详细‌。包含最敏感的 ứng用程序数据,通常仅在开发或诊断复杂问题时短暂启用。可能包含密码等敏感信息,生产环境严禁开启。
‌Debug‌ 1 ‌调试信息‌。用于开发期间的交互式调试,短期有用。例如记录变量值、方法进入/退出等。生产环境通常关闭。
‌Information‌ 2 ‌常规流程 ‌。跟踪应用程序的一般流程。例如:用户登录成功、请求开始/结束、服务启动。这是生产环境的‌推荐默认级别‌。
‌Warning‌ 3 ‌警告‌。表示发生了意外事件,但应用程序仍能继续运行。例如:配置文件缺失使用默认值、API 调用延迟较高。需要关注但不需立即修复。
‌Error‌ 4 ‌错误‌。表示当前活动因故障而停止。例如:数据库连接失败、未处理的异常、业务逻辑验证失败。需要立即调查。
‌Critical‌ 5 ‌严重‌。表示应用程序或系统崩溃、数据丢失或不可恢复的错误。例如:内存溢出、磁盘空间耗尽。需要立即采取紧急措施。
‌None‌ 6 ‌无‌。不写入任何日志消息。用于完全禁用日志记录。

级别过滤机制:日志系统会根据配置的"最低级别"进行过滤。如果配置的最低级别是 Warning,那么只有 Warning、Error 和 Critical 级别的日志会被输出,Trace、Debug 和 Information 将被忽略。

生产环境默认级别:通常设置为 Information,将 Microsoft、System 等命名空间的级别设置为 Warning 或 Error,以避免框架内部的大量调试信息淹没业务日志。

cs 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "MyApp.Namespace": "Debug" 
    }
  }
}

开发环境默认级别:

通常设置为 Debug 或 Information,以便获取更多的执行细节。

避免在生产环境中长期使用 Debug 或 Trace,因为这会导致性能下降和日志文件爆炸式增长。

日志过滤 (AddFilter)

基于类别的过滤

基于类别的过滤: builder.AddFilter("Microsoft", LogLevel.Warning) 会抑制所有以 "Microsoft" 开头的类别中低于 Warning 级别的日志。

cs 复制代码
using Microsoft.Extensions.Logging;
using System;

namespace LoggingFilterDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 创建 LoggerFactory
            using ILoggerFactory factory = LoggerFactory.Create(builder =>
            {
                // 添加控制台提供程序
                builder.AddConsole();

                // ==========================================
                // 1. 基于类别的过滤 (Category-based Filtering)
                // ==========================================
                // 规则:对于所有以 "Microsoft" 开头的类别(通常是框架内部日志),
                // 只记录 Warning 及以上级别的日志。
                // 这意味着 Information, Debug, Trace 级别的微软框架日志将被抑制。
                builder.AddFilter("Microsoft", LogLevel.Warning);

                // 可以为特定子命名空间设置更严格的规则
                // 例如:只记录 EntityFramework 的 Error 级别日志
                builder.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Error);

                // ==========================================
                // 2. 基于委托的过滤 (Delegate-based Filtering)
                // ==========================================
                // 规则:允许执行复杂的逻辑判断。
                // 这里演示:如果是 "MyApp.Sensitive" 类别,只记录 Error 以上;
                // 其他所有类别,记录 Information 以上。
                builder.AddFilter((category, level) =>
                {
                    // 模拟动态配置或运行时状态检查
                    bool isSensitiveModule = category.StartsWith("MyApp.Sensitive");
                    
                    if (isSensitiveModule)
                    {
                        // 敏感模块:仅记录严重错误,减少敏感信息泄露风险
                        return level >= LogLevel.Error;
                    }
                    
                    // 默认业务逻辑:记录 Information 及以上
                    return level >= LogLevel.Information;
                });
                
                // 注意:如果有多个 AddFilter,它们通常会组合生效(取最严格的限制或按注册顺序,
                // 具体行为取决于实现,通常建议针对特定 Category 使用字符串过滤,全局逻辑使用委托)
            });

            // 创建不同类别的 Logger 进行测试
            var microsoftLogger = factory.CreateLogger("Microsoft.AspNetCore.Hosting");
            var sensitiveLogger = factory.CreateLogger("MyApp.Sensitive.DataProcessor");
            var businessLogger = factory.CreateLogger("MyApp.Services.OrderService");

            Console.WriteLine("--- 测试 Microsoft 类别过滤 (应只显示 Warning/Error) ---");
            microsoftLogger.LogInformation("This ASP.NET Core info log should be FILTERED OUT.");
            microsoftLogger.LogWarning("This ASP.NET Core warning log should APPEAR.");
            microsoftLogger.LogError("This ASP.NET Core error log should APPEAR.");

            Console.WriteLine("\n--- 测试 Sensitive 类别委托过滤 (应只显示 Error/Critical) ---");
            sensitiveLogger.LogInformation("Sensitive info: User password check... (FILTERED OUT)");
            sensitiveLogger.LogWarning("Sensitive warning: High latency... (FILTERED OUT)");
            sensitiveLogger.LogError("Sensitive error: Auth failed! (SHOULD APPEAR)");

            Console.WriteLine("\n--- 测试普通业务类别委托过滤 (应显示 Information+) ---");
            businessLogger.LogInformation("Order created successfully. (SHOULD APPEAR)");
            businessLogger.LogDebug("Order details serialized. (FILTERED OUT)");
        }
    }
}
  • 原理:这是最常用且性能较好的过滤方式。.NET Core 的日志类别通常对应于创建 Logger 时传入的名称(如 typeof(Program).FullName)。
  • 匹配规则:采用前缀匹配。
  • builder.AddFilter("Microsoft", LogLevel.Warning) 会匹配 Microsoft、Microsoft.AspNetCore、Microsoft.EntityFrameworkCore 等所有以该字符串开头的类别。
  • 如果存在更具体的规则(如同时定义了 Microsoft 和 Microsoft.AspNetCore),系统通常会应用‌最具体‌(最长匹配)的规则。
  • 作用:有效抑制第三方库或框架产生的大量低级别调试日志,保持主日志文件的整洁。

基于委托的过滤

基于委托的过滤: 允许更复杂的逻辑,如根据动态配置或运行时状态决定。

在配置 ILoggerFactory 时,使用 AddFilter 的重载方法,传入一个 Func<string, LogLevel, bool> 委托。

cs 复制代码
using Microsoft.Extensions.Logging;
using System;

public class Program
{
    public static void Main(string[] args)
    {
        // 创建 LoggerFactory
        using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
        {
            // 添加控制台提供程序
            builder.AddConsole();

            // ==========================================
            // 基于委托的过滤示例
            // ==========================================
            builder.AddFilter((category, level) =>
            {
                // 逻辑1:完全忽略所有来自 "Microsoft" 命名空间的 Debug 级别日志
                if (category.StartsWith("Microsoft") && level == LogLevel.Debug)
                {
                    return false; // 过滤掉,不记录
                }

                // 逻辑2:如果是 "MyApp.Sensitive" 类别,只记录 Error 及以上级别
                if (category.Contains("Sensitive"))
                {
                    return level >= LogLevel.Error;
                }

                // 逻辑3:模拟动态配置(例如从内存变量或配置服务中读取)
                // 假设有一个全局开关 enableVerboseLogging
                // if (!enableVerboseLogging && level == LogLevel.Trace) return false;

                // 默认策略:记录 Information 及以上级别的日志
                return level >= LogLevel.Information;
            });
        });

        // 创建不同类别的 Logger 进行测试
        var microsoftLogger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Hosting");
        var sensitiveLogger = loggerFactory.CreateLogger("MyApp.Sensitive.AuthService");
        var businessLogger = loggerFactory.CreateLogger("MyApp.Services.OrderService");

        Console.WriteLine("--- 测试 Microsoft 类别 (Debug 应被过滤) ---");
        microsoftLogger.LogDebug("This debug log from Microsoft should be FILTERED OUT.");
        microsoftLogger.LogInformation("This info log from Microsoft should APPEAR.");

        Console.WriteLine("\n--- 测试 Sensitive 类别 (Info/Warning 应被过滤) ---");
        sensitiveLogger.LogInformation("Sensitive info: User login attempt... (FILTERED OUT)");
        sensitiveLogger.LogWarning("Sensitive warning: Invalid token... (FILTERED OUT)");
        sensitiveLogger.LogError("Sensitive error: Authentication failed! (SHOULD APPEAR)");

        Console.WriteLine("\n--- 测试普通业务类别 (Default 策略) ---");
        businessLogger.LogDebug("Order details... (FILTERED OUT by default policy)");
        businessLogger.LogInformation("Order created successfully. (SHOULD APPEAR)");
    }
}

结合运行时状态

你可以根据应用程序当前的模式(开发/生产)或特定的功能开关来动态调整过滤规则。

cs 复制代码
// 模拟运行时状态
bool isProduction = true; 
string criticalFeatureFlag = "Enabled";

builder.AddFilter((category, level) =>
{
    // 场景:在生产环境中,除非是 Critical 错误,否则不记录第三方库的日志
    if (isProduction && category.StartsWith("ThirdPartyLib"))
    {
        return level >= LogLevel.Critical;
    }

    // 场景:如果某个关键功能标志未启用,忽略相关模块的所有日志
    if (category.Contains("ExperimentalFeature") && criticalFeatureFlag != "Enabled")
    {
        return false;
    }

    // 其他情况遵循默认级别
    return level >= LogLevel.Information;
});

作用域追踪 (BeginScope)

在 .NET Core 中,BeginScope 是 ILogger 接口提供的一个强大功能,用于创建日志记录的‌**作用域(Scope)**‌。它允许你将特定的上下文信息(如请求 ID、用户 ID、事务 ID 等)附加到该作用域内记录的所有日志中。这对于在复杂的异步或多线程环境中追踪业务流程至关重要。

字符串模板作用域

使用字符串模板创建作用域。作用域内的所有日志都会自动包含这些上下文信息。

cs 复制代码
using Microsoft.Extensions.Logging;
using System;

public class OrderService
{
    private readonly ILogger<OrderService> _logger;

    public OrderService(ILogger<OrderService> logger)
    {
        _logger = logger;
    }

    public void ProcessOrder(int orderId, string userId)
    {
        // 创建作用域,上下文信息为 UserId 和 OrderId
        // 注意:使用命名占位符 {UserId} 而不是字符串拼接,以支持结构化日志
        using (_logger.BeginScope("UserId={UserId}, OrderId={OrderId}", userId, orderId))
        {
            _logger.LogInformation("Starting order processing...");
            
            try
            {
                // 模拟业务逻辑
                ValidateOrder(orderId);
                _logger.LogInformation("Order validated successfully.");
                
                ChargePayment(orderId);
                _logger.LogInformation("Payment charged successfully.");
            }
            catch (Exception ex)
            {
                // 即使发生异常,日志中也会自动携带 UserId 和 OrderId
                _logger.LogError(ex, "Failed to process order.");
            }
        }
        // 离开 using 块后,作用域结束,后续日志不再包含上述上下文
    }

    private void ValidateOrder(int orderId) { /* ... */ }
    private void ChargePayment(int orderId) { /* ... */ }
}

字典对象作用域(推荐用于结构化日志)

为了获得更好的结构化日志支持(例如被 Seq、Application Insights 或 ELK 解析),建议传递一个 Dictionary<string, object> 或匿名对象作为状态。

cs 复制代码
using System.Collections.Generic;

public void ProcessOrderAdvanced(int orderId, string userId)
{
    var scopeState = new Dictionary<string, object>
    {
        ["RequestId"] = Guid.NewGuid().ToString(),
        ["UserId"] = userId,
        ["OrderId"] = orderId,
        ["Source"] = "WebAPI"
    };

    using (_logger.BeginScope(scopeState))
    {
        _logger.LogInformation("Processing order with advanced context.");
        
        // 嵌套作用域示例
        using (_logger.BeginScope(new { Step = "PaymentGateway" }))
        {
            _logger.LogInformation("Calling external payment API...");
        }
    }
}

appsettings.json 中配置

cs 复制代码
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    },
    "Console": {
      "IncludeScopes": true, 
      "TimestampFormat": "[HH:mm:ss] "
    }
  }
}

在代码中配置

cs 复制代码
using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddConsole(options =>
    {
        options.IncludeScopes = true; // 显式启用作用域
    });
    
    // 或者使用简单控制台格式化程序
    builder.AddSimpleConsole(formatter =>
    {
        formatter.IncludeScopes = true;
        formatter.TimestampFormat = "[HH:mm:ss] ";
    });
});

实现自定义控制台格式化程序

如果你需要完全控制控制台输出的格式(例如 JSON 格式、特定颜色、自定义字段顺序),可以实现 ConsoleFormatter。

cs 复制代码
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using System;
using System.IO;

public class CustomJsonConsoleFormatter : ConsoleFormatter
{
    public CustomJsonConsoleFormatter() : base("CustomJson") { }

    public override void Write<TState>(
        in LogEntry<TState> logEntry,
        IExternalScopeProvider scopeProvider,
        TextWriter textWriter)
    {
        string message = logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception);
        if (string.IsNullOrEmpty(message)) return;

        // 简单的 JSON 格式示例
        var timestamp = DateTime.UtcNow.ToString("o");
        var level = logEntry.LogLevel.ToString();
        var category = logEntry.Category;
        
        textWriter.Write($"{{\"Timestamp\":\"{timestamp}\",\"Level\":\"{level}\",\"Category\":\"{category}\",\"Message\":\"{message.Replace("\"", "\\\"")}\"");

        // 添加 Exception
        if (logEntry.Exception != null)
        {
            textWriter.Write($",\"Exception\":\"{logEntry.Exception.Message.Replace("\"", "\\\"")}\"");
        }

        // 添加 Scope 上下文 (简化处理)
        scopeProvider?.ForEachScope((scope, state) =>
        {
            if (scope is IDictionary<string, object> dict)
            {
                foreach (var item in dict)
                {
                    state.Write($",\"{item.Key}\":\"{item.Value}\"");
                }
            }
        }, textWriter);

        textWriter.WriteLine("}");
    }
}

注册自定义格式化程序:

cs 复制代码
builder.AddConsole(options =>
{
    options.FormatterName = "CustomJson";
})
.AddConsoleFormatter<CustomJsonConsoleFormatter, ConsoleFormatterOptions>();
相关推荐
曹牧1 小时前
C#里引用ActiveX控件
开发语言·c#
宇卿.1 小时前
C#委托详解
开发语言·c#
努力,努力再努力2 小时前
零依赖 C# Office 库到底怎么用——四个场景讲透 Wisdom.Office
c#
IT规划师2 小时前
MQTT实战 - 生产环境MQTT丢包终极排查,8年经验总结(六)
物联网·.net
2501_930707785 小时前
使用C#代码在 PDF 文档中创建交互操作
pdf·c#·交互
zimoyin15 小时前
在 C# 里嵌入 JavaScript:用 Jint 做可热更新的自动化脚本(从入门到踩坑)
javascript·c#·自动化
小羊没烦恼!17 小时前
异步编程:线程概述及使用
java·开发语言·mysql·算法·c#
c#上位机19 小时前
C#上位机项目实战——exe项目调试更改dll中源代码
开发语言·c#
淡海水20 小时前
06-02-源码-增量标记算法实现
算法·unity·c#·gc