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.Logging8 种 Appender + 8 种过滤器 + 11×7 文件/目录滚动
版本: 1.0.0(GA)| 目标框架: .NET 8.0 / .NET 10.0 | 许可证: MIT
状态:首个正式稳定版(GA)。可从源码构建或引用本地包使用;包名
MiniLog经核查在 nuget.org 可用、无冲突。
设计渊源
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 + Scope(ILog.BeginScope,AsyncLocal,using 嵌套 + 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),遵循:
- 用结构化 API 代替字符串插值:
logger.Log(new LogEntry { ... })或源生成器Log<TBag>(编译期FormatTo零分配直写);避免logger.Info($"User {id}")(每次分配插值字符串,基准 560KB/万条)。 - 渲染物化走 Span 直写:Appender 内部默认
WriteToAndClear(TextWriter)直写(零分配);仅确需字符串时才调GetStringAndClear(基准 96KB/千次)。 - 结构化字段用
%property:经LogEntry.Properties["k"] = v设置,%property{k}渲染;PropertyCount == 0短路零开销。 - 高吞吐降载用采样:配置
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(仓库根目录)。

浙公网安备 33010602011771号