MiniLog 1.0 GA 发布:零分配、零依赖的 .NET 高性能日志框架

MiniLog

轻量、高性能的 .NET 日志组件库,零外部依赖。API 形态借鉴 log4net / NLog 的派发模型(Logger → Appender → Layout),但在底层采用结构体日志条目零分配渲染源生成器,定位为「现代、低 GC 压力、可替代 MS Logging 的高性能日志框架」。

核心技术亮点:

  • 零分配渲染 —— LogEntry 值语义结构体 + ReusableDelegateWriter + ValueStringBuilder(栈缓冲 / ArrayPool),热路径零 GC

  • 零装箱派发 —— 源生成器经 ILogArgumentBag 泛型参数袋把结构化参数穿过整条派发链,启用路径不装箱

  • ANSI 彩色分级控制台 —— 可选、EnableColors 默认关、跨平台、不依赖 Console 全局颜色状态

  • 源生成器双后端 —— [Log](注入 ILog 字段)+ [LoggerMessage](强类型零装箱),一份声明通吃 MiniLog 与 Microsoft.Extensions.Logging

  • 8 种 Appender + 8 种过滤器 + 11×7 文件/目录滚动

版本: 1.0.0(GA)| 目标框架: .NET 8.0 / .NET 10.0 | 许可证: MIT

状态:首个正式稳定版(GA)。可从源码构建或引用本地包使用;包名 MiniLog 经核查在 nuget.org 可用、无冲突。

源码仓库:https://gitee.com/netcasewqs/MiniLog


设计渊源

MiniLog 并非从零开始设计,其内核吸收了作者在前公司连续运行 10 余年的日志组件经验。该组件在大数据吞吐场景下,内存与 CPU 占用均低于同期主流开源方案。其滚动策略可在目录、文件、时间维度上叠加容量上限灵活组合,运维友好。当错误发生时,仅凭年、月、日、时即可精确定位对应历史日志,排障效率显著提升。MiniLog 将这些经过产线长期验证的设计理念,以零分配、零依赖的现代 .NET 实现重新落地。

一、特性一览

特性 说明
零外部依赖 纯 .NET 实现,不引入任何第三方包
多格式配置 XML / JSON / INI 三种文件 + Fluent Builder;XML 带 XSD、JSON 带 JSON Schema,IDE 智能提示
多输出目标 Console / Debug / Trace / File / AsyncFile / Udp / AdoNet 共 8 种 Appender,任意组合
完善的文件滚动 11 种文件滚动(大小 / 分 / 时 / 天 / 月 / 年及组合)× 7 种目录滚动,支持过期清理
零分配渲染 LogEntry 值语义结构体 + ReusableDelegateWriter + ValueStringBuilder,热路径零 GC
异步批处理 基于 System.Threading.Channels 的有界队列(ProducerConsumerQueue),Drop / Block 背压,优雅关闭
级别模型 Trace / Debug / Info / Warn / Error / Fatal 六级,含 IsXxxEnabled 前置短路
彩色分级控制台 ConsoleAppender 可选 ANSI 着色(默认关闭),跨平台、多线程不串色
结构化日志 原生 EventId + ScopeILog.BeginScopeAsyncLocalusing 嵌套 + await 流转)
源生成器 [Log](注入 ILog 字段)+ [LoggerMessage](强类型零装箱),双后端通吃 MiniLog 与 MEL
MEL 兼容 MiniLog.Extensions.Logging 提供 AddMiniLog(),可作为 MS Logging 的 Provider 接入
过滤器链 8 种 FilterOptions,三态判定 Deny / Neutral / Accept

二、快速开始

2.1 安装

MiniLog 已发布到 NuGet,直接引用包即可。源生成器 MiniLog.Generators(含 [Log] / [LoggerMessage] 生成与布局模式校验分析器)随包自动以 Analyzer 形式引入,无需单独安装。

# 核心库(.NET 8.0 / .NET 10.0 双目标框架)
dotnet add package MiniLog

# 可选:接入 ASP.NET Core / Microsoft.Extensions.Logging 时再装此包
dotnet add package MiniLog.Extensions.Logging

要求:.NET 8.0 或 .NET 10.0 SDK。
包 ID:MiniLog(核心库)、MiniLog.Extensions.Logging(MEL 适配器)。两者均为 MIT、零外部依赖。

尚未发布或本地开发 / 贡献时,可用源码引用或本地生成包:

# 方式 A:项目引用(含源生成器,自动以 Analyzer 引入)
dotnet add reference ../src/MiniLog/MiniLog.csproj

# 方式 B:本地生成 NuGet 包后引用
dotnet pack src/MiniLog/MiniLog.csproj -c Release   # 产物 bin/Release/MiniLog.1.0.0.nupkg

2.2 静态门面(最简,5 行开始记录)

using MiniLog;

// 程序启动时调用一次:默认查找 log.config.xml,缺失时按 xml→json→ini 自动探测同名配置,
// 均无则回退到 Logs/Log.txt
LogManager.Initialize();

var log = LogManager.GetLogger<Program>();   // Logger 名默认取类型简单名 "Program"
log.Info("应用启动");
log.Warn("磁盘剩余空间偏低");

// 程序退出前调用一次,确保异步 Appender 缓冲落盘
LogManager.Shutdown();

一个完整的控制台最小示例(Program.cs):

using MiniLog;

LogManager.Initialize();              // 读取 log.config.xml(或同目录 xml/json/ini)
try
{
    var log = LogManager.GetLogger<Program>();
    log.Info("应用启动");
    log.Error("出错了", new Exception("演示异常"));
}
finally
{
    LogManager.Shutdown();            // 优雅关闭,刷新异步文件缓冲
}

2.3 源生成器 [Log](Lombok 风格,推荐)

[Log]                                   // 编译期注入:private static readonly ILog log;
public partial class OrderService       // 默认字段名 log;默认 Logger 名 = 简单类名 "OrderService"
{
    public void Process(int id) => log.Info("处理订单 " + id);
}

// 覆盖注入的 Logger 名(默认用类的简单名,不含命名空间)
[Log(Name = "MyApp.Order")]
public partial class OrderService2 { /* ... */ }

// 逐类覆盖默认字段名(库默认已是 log,此处演示可改)
[Log(FieldName = "Logger")]
public partial class OrderService3 { /* ... */ }

默认注入字段名为 log(库新建无历史包袱,已从 Log 改为小写);可用 [Log(FieldName="...")] 逐类覆盖。[Log(Name="...")] 可覆盖注入的 Logger 名,[LoggerMessage] 同样支持。

2.4 源生成器 [LoggerMessage](强类型、零装箱)

[LoggerMessage] 标记 partial 方法,生成器为其补全实现:入口先判定级别 IsXxxEnabled,级别关闭时零分配直接返回;热路径参数经结构化参数袋零装箱直写,兼顾性能与强类型。

[Log]                                       // 注入 log 字段;本类 [LoggerMessage] 方法自动复用它
public partial class OrderService
{
    // 字段模式:无 ILog 首参,生成的实现直接使用类的 log 字段
    [LoggerMessage(LogLevel.Info, "处理订单 {Id} 金额 {Amount}")]
    public partial void LogOrderProcessed(int id, decimal amount);

    // 显式 ILog 首参模式:按传入的 ILog 派发(可用于 MEL 的 ILogger)
    [LoggerMessage(LogLevel.Error, "保存失败 {Id}")]
    public partial void LogSaveFailed(ILog log, int id);
}

// 调用处(与手写日志等价,但无装箱、带级别短路)
var svc = new OrderService();
svc.LogOrderProcessed(1001, 29.9m);

级别可省略,由方法名推断(LogInfo/LogWarn/LogError…);也可 [LoggerMessage(LogLevel.Warn, "...")] 显式指定。[LoggerMessage] 既可单独使用(生成器自动注入 log 字段),也可与 [Log] 同用(复用已注入字段)。

2.5 配置文件 + Fluent Builder

var mgr = LogManager.CreateLogManager(b => b
    .UseConsoleAppender()
    .UseFileAppender("file", new FileAppenderOptions
    {
        FileDirectory = "Logs", FileName = "app", Extension = ".log",
        RollingMode = FileRollingMode.DailyAndSize, ExpiredDays = 30,
    })
    .UseRoot(LogLevel.Warn, "console")
    .UseLogger("MyApp.Data", LogLevel.Info, "console", "file"));
LogManager.SetLogManager(mgr);

更多用法(ILog 接口速查、结构化日志、彩色控制台、MEL 集成、源生成器)见 MiniLog 使用手册

2.6 从源码构建与测试

仓库 .sln 解决方案文件,直接按项目构建(目标 .NET 8.0 / .NET 10.0 双 TFM):

dotnet build src/MiniLog/MiniLog.csproj -c Release
dotnet build src/MiniLog.Generators/MiniLog.Generators.csproj -c Release
dotnet test  test/MiniLog.Tests/MiniLog.Tests.csproj -c Release
dotnet test  test/MiniLog.Extensions.Logging.Tests/MiniLog.Extensions.Logging.Tests.csproj -c Release
dotnet pack  src/MiniLog/MiniLog.csproj -c Release   # 产出 NuGet 包

双门禁(提交门槛)MiniLog / MiniLog.Generators 开启 -warnaserror,Debug 与 Release 下 net8.0 / net10.0 必须 0 警告 0 错误;两套测试全绿才算闭环。贡献前请本地跑齐上述命令。


三、与其他开源日志库对比

MiniLog 与 log4net / NLog / Serilog / MS.Extensions.Logging 定位有本质差异:它保留传统派发模型(Logger → Appender → Layout),但在底层用零分配渲染与源生成器实现数量级性能领先。

3.1 能力矩阵(主流 5 库)

维度 MiniLog log4net NLog Serilog MS.Extensions.Logging
外部依赖 无(抽象层)
配置方式 XML / JSON / INI / Builder XML / 代码 XML / JSON / 代码 JSON / 代码 / C# appsettings / 代码
Appender / 输出目标 8 种 20+ 种 80+ Targets Sink 生态(数百) 内置 + 第三方 Provider
零分配渲染 是(热路径) 部分 取决于 Provider
源生成器 内置(双后端) 无(用 MEL LoggerMessage) LoggerMessage(官方)
结构化日志 EventId + Scope(值语义) 支持(Layout) 一等公民 一等公民
彩色控制台 ANSI(可选,默认关) ColoredConsoleAppender ColoredConsole 控制台 Sink 主题 控制台 Provider 支持
成熟度 / 生态 预览期 极高 官方标准

3.2 性能定位

固定消息:MiniLog(26.8µs) << MS(102µs) << Serilog(869µs) << NLog(16.5ms) ≈ log4net(17.6ms)
异步文件:MiniLog(98µs) << NLog(1.6ms) ≈ MS(1.8ms) << log4net(2.4ms) << Serilog(9.8ms)
级别关闭:MiniLog源生成(7.5µs/0B) < MS LoggerMessage(11.5µs/0B) << MS 原生(仍分配 640KB)
  • vs log4net / NLog:传统派发模型但非文件模式快 600×+,零分配,现代 API。

  • vs Serilog:Serilog 结构化日志与 Sink 生态最丰富;MiniLog 在性能与分配上显著占优。

  • vs MS.Extensions.Logging:真实负载下更快且零分配语义打平;MiniLog 通过 MiniLog.Extensions.Logging 直接作为 MS Provider 接入,兼得两者。

完整能力矩阵、性能三段排名与选型建议见 使用手册·对比章节


四、配置

MiniLog 支持 XML / JSON / INI 三种格式 + Fluent Builder。默认查找 AppDomain.CurrentDomain.BaseDirectory/log.config.xml,缺失时按 xml→json→ini 优先级自动探测同目录同名配置,均无则回退到 Logs/Log.txt

最简 XML 示例:

<?xml version="1.0" encoding="utf-8"?>
<log SupportsExpiredFileDetector="true" ExpiredFileCheckInterval="30">
  <Appenders>
    <Appender Type="Console" Name="console" Level="Info" />
    <Appender Type="File" Name="file" Level="Debug">
      <File FileDirectory="Logs" FileName="app" Extension=".log"
            RollingMode="DailyAndSize" MaxFileSize="50" ExpiredDays="30" />
    </Appender>
  </Appenders>
  <Loggers>
    <Logger Name="MyApp.Data" Level="Debug">
      <Appenders><Appender>file</Appender></Appenders>
    </Logger>
  </Loggers>
  <Root Level="Warn">
    <Appenders><Appender>console</Appender></Appenders>
  </Root>
</log>

常用占位符:%timestamp/%thread/%level/%logger/%message/%exception/%newline/%appdomain/%username/%identity/%file/%line/%method/%location/%eventid/%scope/%property/%stacktrace(共 22 个)。

完整配置参考(加载机制、三格式选型、顶层选项、Appender 全属性、File / AdoNet 选项、Logger·Root、滚动模式、三格式完整示例、Fluent Builder、Schema 验证、已知问题)见 MiniLog 使用手册·配置详解CONFIGURATION.md


五、Appenders 输出目标

共 8 种,均线程安全:

类型 实现 说明
None NoneAppender 丢弃所有日志。用于显式关闭通道或零开销基准。
Console ConsoleAppender 输出到 Console.Out;可选 ANSI 彩色分级。
Debug DebugAppender 输出到 System.Diagnostics.Debugger.Log
Trace TraceAppender 输出到 System.Diagnostics.Trace.Listeners
File FileAppender 同步文件追加,内部 lock 保证写入与滚动安全。
AsyncFile AsyncFileAppender 基于 Channels 异步写入,调用方不阻塞;队列满可能丢弃(设计权衡)。
Udp UdpAppender UdpClient 发送渲染文本到远程端点,超长消息截断。
AdoNet AdoNetAppender 参数化 SQL 写关系型数据库;支持零分配直传与异步批量。

选型:低并发用 File,高并发用 AsyncFile。完整 8 种配置与 AdoNet 异步参数见 使用手册·Appenders


六、性能定位

6.1 基准快照(.NET 8.0,BenchmarkDotNet)

固定消息(10K 次循环总耗时):

Mean 分配
MiniLog(无输出短路) 26.83 µs 0 B
MS Logging(未挂 Provider,纯 no-op) 102.52 µs 0 B
Serilog 868.70 µs 1.6 MB
NLog 16,548.81 µs 1.2 MB
log4net 17,561.30 µs 2.0 MB

异步文件(1K 条):

Mean 分配
MiniLog-AsyncFile 97.52 µs 94 KB
NLog 1,611.5 µs 171 KB
MS Logging 1,781.8 µs 241 KB
log4net 2,360.8 µs 350 KB
Serilog 9,796.4 µs 491 KB

源生成器级别关闭(10K 次):

方式 Mean 分配
MiniLog 源生成 7.49 µs 0 B
MS LoggerMessage 11.53 µs 0 B
MiniLog 原生 API 35.84 µs 0 B
MS 原生 LogInformation(params) 232.93 µs 640 KB

横向基准中 MS Logging 未挂载任何 Provider,Log()IsEnabled 即短路为纯 no-op(102µs 是「什么都不做」的代价)。真实项目 MS 必挂 Provider,开销会上升。详细设计与并发/过滤基准见 使用手册·性能设计BENCHMARK_REPORT.md

6.2 零分配黄金路径

要让日志调用达成热路径零堆分配(对应基准:固定消息 42µs/万条零分配、参数袋 Span 物化 8.8µs/零分配、UDP 新 26ns/零分配;并发 40 万消息 16/18 零 GC),遵循:

  1. 用结构化 API 代替字符串插值logger.Log(new LogEntry { ... }) 或源生成器 Log<TBag>(编译期 FormatTo 零分配直写);避免 logger.Info($"User {id}")(每次分配插值字符串,基准 560KB/万条)。
  2. 渲染物化走 Span 直写:Appender 内部默认 WriteToAndClear(TextWriter) 直写(零分配);仅确需字符串时才调 GetStringAndClear(基准 96KB/千次)。
  3. 结构化字段用 %property:经 LogEntry.Properties["k"] = v 设置,%property{k} 渲染;PropertyCount == 0 短路零开销。
  4. 高吞吐降载用采样:配置 SamplingFilterOptions,无锁零分配按比例丢弃。

完整四层基准(热方法 / 端到端 / 并发零分配 / 解析)与零回退论证见 BENCHMARK_REPORT.md


布局模式校验器(编译期 LLG 诊断)

MiniLog 内置一个 Roslyn 分析器(MiniLog.Generators),在编译期校验赋值给 AppenderOptions.Pattern / AdoNetParameter.Layout(含 WithPattern(...) / WithLayout(...) 入参、常量折叠与插值字面段)的布局模式串,把 %token 拼写 / 格式错误拦在构建前,而非运行时解析失败。经 NuGet 包引入时分析器自动随包进入 analyzers/dotnet/cs,消费方编译期即得校验;配置文件(XML / JSON / INI)字面量则由 ConfigurationBinder 运行期探针以 Trace 警告兜底。

诊断 触发条件 示例
LLG0002 未知占位符(附最近邻建议) %levl → 是否指 %level
LLG0003 未闭合的 { %date{yyyy-MM-dd
LLG0004 空选项 {} / { } %property{}
LLG0005 已知令牌带不应有的选项 %level{critical}
LLG0006 %date / %timestamp 选项保留字拼写错误 %date{ISO860}ISO8601

保留字(LLG0006 建议集):ISO8601 / DATE / ABSOLUTE / COMPACT;精确匹配或任意 .NET 格式(如 yyyy-MM-dd)合法、不触发。

// 编译期即报错,而非运行时才发现布局串写错
new AppenderOptions { Pattern = "%levl %logger" };        // ⚠ LLG0002 未知 'levl'(是否指 'level'?)
new AppenderOptions().WithPattern("%date{yyyy-MM-dd");      // ⚠ LLG0003 未闭合
new AppenderOptions { Pattern = "%property{}" };            // ⚠ LLG0004 空选项
new AppenderOptions { Pattern = "%level{x}" };              // ⚠ LLG0005 'level' 不接受选项
new AppenderOptions { Pattern = "%date{ISO860}" };          // ⚠ LLG0006 疑似 'ISO8601'

// 配置文件(经 NuGet 分析的消费方亦生效,IDE 实时波浪线 + 构建警告)
<Appender Pattern="%levl %level" />                        // ⚠ 未知占位符 'levl'(是否指 'level'?)

有意不做(编译期不可判定):非常量动态值(变量、含非 const 孔的插值)、%date{非法格式} 格式合法性、%property{不存在的键} 键存在性 —— 交由运行时解析兜底。


七、文档导航

文档 内容

MiniLog 使用手册(中文) 核心特性 / 安装 / 快速开始 / 核心概念 / 配置详解 / Appenders / 过滤器 / 彩色控制台 / 结构化日志 / 源生成器 / MEL 集成 / 性能设计 / 与其他开源库对比 / log4net 迁移 / 许可证

配置参考 XML / JSON / INI 全部配置项、加载机制、Fluent API、Schema 验证、15 条已知问题

MEL 集成指南 与 Microsoft.Extensions.Logging 集成的权威深度文档:注册 API 全景 / 两种生命周期模型 / 级别·名称·结构化·Scope 映射 / IConfiguration 桥接原理 / 热重载 / 排错

基准报告 BenchmarkDotNet 性能数据

编译期诊断码总表 LLG0001–LLG0015 目录 / 配置文件分析器触发范围 / MEL 路径边界

变更日志 版本变更记录(1.0.0 GA 起,按需补充)


八、许可证

MIT —— 详见 LICENSE(仓库根目录)。

posted @ 2026-09-06 18:38  风云  阅读(76)  评论(0)    收藏  举报