CodeWF.Log 设计
本文是 CodeWF.Log 的唯一设计基线,合并原“核心设计”和“Microsoft.Extensions.Logging 适配设计”。README 负责快速接入;本文负责事件模型、Pipeline、MEL、Avalonia、通知、Demo 与发布验收的完整约束。
设计摘要
CodeWF 使用一个完整、不可变的 CodeWFLogEvent 驱动所有输出:
- File 使用独立
OutputTemplate,适合保留诊断消息、结构化属性、Scope、Activity 和异常。 - Console、可选的 Avalonia
LogView和通知管线共享同一个LineTemplate;默认组合式 DesktopWindow 为避免重复,仅呈现结构化级别、时间和UserMessage/Message,完整模板结果保留给 InApp 和自定义桌面模板。 Message是诊断消息;UserMessage是可选的用户友好消息。模板{UserMessage}为空白时回退到{Message}。RequestNotification是调用方显式设置的通知请求,默认false;它与日志级别分别表达“是否打扰用户”和“严重程度”。- 通知资格固定为
Mode != None && RequestNotification && Level >= MinimumLevel。 - 组件不裁剪事件模型;调用方把详细字段加入
LineTemplate即表示允许其出现在 Console、LogView、InApp 或自定义通知模板中。默认 DesktopWindow 只显示用户正文。
ILogger<T> / Logger.*
│
├─ 调用线程:格式化 Message,快照 State/Scope/Activity/Exception
▼
有界 Channel<LogCommand>
│
▼
LoggerHost 单消费者(分配 Sequence)
├─ FileLogSink:File.OutputTemplate
├─ ConsoleLogSink:共享 LineTemplate
└─ LogEventFeed:同一完整 CodeWFLogEvent
├─ LogView(可选):共享 LineTemplate + 独立级别范围
└─ LogNotifications(可独立使用):共享模板结果 + 显式通知资格 + 组合式桌面呈现Logger.*ToFile 使用同一队列但只路由到 File。Sequence 对应单消费者实际处理顺序。慢 Feed 订阅者在独立调度中串行接收,不阻塞日志 Pipeline。
1. 定位
- 状态:设计基线,当前实现持续对齐中;实现状态见第 17 节
- 目标版本:CodeWF.Log 全新版本
- 支持运行时:.NET 8、.NET 10 和 .NET 11;三个 NuGet 包统一提供
net8.0、net10.0和net11.0资产 - 核心定位:CodeWF 是
Microsoft.Extensions.Logging生态中的一个 Provider,业务代码优先使用标准ILogger<T>。 - 设计原则:按 .NET 规范设计,约定大于配置,
AddCodeWF()零配置可用。 - 包名:
CodeWF.Log.Core、CodeWF.Log.Extensions.Logging、CodeWF.Log.Avalonia。 - AOT 约束:
CodeWF.Log.Core和当前 Avalonia 包延续已有 AOT 支持;新增CodeWF.Log.Extensions.Logging不得破坏三个 NuGet 包的 trim/AOT 友好基线。
本文中的 Microsoft.Extensions.Logging 简称 MEL。
2. 核心结论
- 新业务代码的主入口是 MEL 标准
ILogger<T>,不设计替代 MEL 的新业务日志 API。 - CodeWF 的接入形态是
builder.Logging.AddCodeWF(),由 MELLoggerFactory负责 Provider 编排、级别过滤、Category 过滤和多 Provider 并行。 CodeWF.Log.Core可以直接引用Microsoft.Extensions.Logging,把LogLevel、EventId等 MEL 类型作为核心公共契约。- Core 不再定义
LogType,不保留旧枚举值,也不为旧枚举提供Trace = -1、Info、Warn、Fatal等兼容别名;静态Logger.Info/Warn/Fatal方法作为迁移门面继续保留。 - 全局采集过滤交给 MEL 的
Logging:LogLevel和 Provider 专属过滤;CodeWF 只做 Sink 级输出过滤。 - CodeWF 的差异化能力是统一结构化日志事件流
LogEventFeed、文件输出、AvaloniaLogView和通知。 - 普通
ILogger日志与LogUser*日志都生成同一种CodeWFLogEvent;LogUser*只额外提供可选的UserMessage,LogUserNotification(...)再显式设置通知请求。 - 用户可见日志通过
ILogger扩展方法表达,例如LogUserError(...)。 - Sink 失败、队列丢弃、Formatter 失败等内部问题进入 Self Diagnostics,不默认反向破坏业务流程。
- 配置遵循 .NET Options、
appsettings.json、Provider Alias、DI 生命周期和 Host 生命周期约定,不引入自定义配置 DSL。 CodeWF.Log.Core保留旧式Logger.*静态门面作为迁移兼容层;CodeWF.Log.Avalonia保留原<log:LogView />默认用法。- MEL Provider 使用 DI 容器内的实例级 pipeline 和
LogEventFeed,不借用进程级静态LoggerHost;旧式静态Logger.*使用独立的 legacy pipeline。 - File 使用独立
OutputTemplate;Console、LogView 和 LogNotifications 共享同一个LineTemplateController,并对同一种CodeWFLogEvent使用相同占位符语义。默认组合式 DesktopWindow 例外地只呈现结构化级别、时间和用户正文,避免重复字段;完整模板结果继续提供给自定义模板。 - 删除
UserLogEntry和公开的UserLogPayload,不保留废弃类型或兼容别名;本次升级按 breaking change 处理。
3. 包职责
3.1 CodeWF.Log.Core
职责:
- 定义统一的核心事件模型、属性模型和 Scope 模型。
- 实现日志 Pipeline、后台队列、Flush、Shutdown 和 Self Diagnostics。
- 实现 FileSink、ConsoleSink 和
LogEventFeed。 - pipeline 必须可以按实例创建和释放,不能只存在于进程级静态
Logger中。 - 提供默认文本 Formatter 和 Sink 抽象。
- 保留
Logger.Info/Warn/Error/Fatal、Logger.*ToFile、Logger.FlushAsync、Logger.ShutdownAsync等静态门面,内部转发到默认 pipeline。 - 保留
Logger.Initialize(...)作为非 Host/旧项目迁移入口;新项目优先使用AddCodeWF()。
依赖:
- 直接引用
Microsoft.Extensions.Logging。 - 可以使用
LogLevel、EventId、ILogger、ILoggerFactory等 MEL 标准类型。 - 不依赖 Avalonia。
- 不依赖 Serilog、NLog、log4net。
- 必须保持 trim/AOT 友好。
3.2 CodeWF.Log.Extensions.Logging
职责:
- 实现
CodeWFLoggerProvider和CodeWFLogger。 - 提供
ILoggingBuilder.AddCodeWF(...)。 - 通过 DI 持有独立的 pipeline 和
LogEventFeed,Provider Dispose 只释放自身拥有的实例。 - 支持
[ProviderAlias("CodeWF")]、MEL 配置绑定和 Provider 专属过滤。 - 捕获 Category、EventId、消息模板、结构化 State、Scope、Activity 和 Exception。
- 提供
ILogger用户日志扩展方法。 - 必须保持 trim/AOT 友好。
3.3 CodeWF.Log.Avalonia
职责:
- 提供
LogView。 - 提供
LogNotifications。 - LogView 和 LogNotifications 都消费包含完整
CodeWFLogEvent的LogEventFeed。 LogContext.Source在 AvaloniaApplication上提供全局LogEventFeed,LogView.Source只作为控件级覆盖;两者均未指定时回退到 legacy 全局事件源。- 可以按共享
LineTemplate读取 Exception、Scope、Properties、MessageTemplate 和 Activity 等完整事件字段。 - 延续当前 Avalonia 包的 AOT 支持。
- 保留
<log:LogView />默认消费全局事件源的用法;需要多日志源时再显式设置Source。
4. 默认约定
最小接入:
builder.Logging.AddCodeWF();默认行为:
| 能力 | 默认约定 |
|---|---|
| 全局级别过滤 | 使用 MEL Logging:LogLevel |
| Category 过滤 | 使用 MEL Category 规则 |
| Provider 专属过滤 | 使用 Logging:CodeWF:LogLevel |
| 文件日志 | 启用 |
| 文件目录 | Host 场景为 IHostEnvironment.ContentRootPath/logs;非 Host 场景为 AppContext.BaseDirectory/logs |
| 文件保留 | 单文件默认 1000 MB,滚动保留最近 30 天日志文件 |
| 控制台输出 | 默认关闭,避免和 AddConsole() 重复 |
| 行输出模板 | Console、LogView 和通知管线共享 LineTemplate;默认 DesktopWindow 使用组合式字段呈现 |
| LogEventFeed | 启用;发布全部通过采集过滤的 CodeWFLogEvent,RecentCapacity 默认 2000 |
| LogView | MinimumLevel=Information,MaximumLevel=Critical |
| LogNotifications | Mode=None,MinimumLevel=Error,Duration 默认 10 秒 |
| Scope 捕获 | 启用 |
| Activity 捕获 | 启用 |
| Activity Tags/Baggage 捕获 | 默认关闭;涉及容量和敏感信息时显式开启 |
| 结构化属性捕获 | 启用 |
| 消息模板捕获 | 启用 |
| Flush/Shutdown | 跟随 Host / LoggerFactory 生命周期;DI Provider 和 legacy 静态 Logger 互不影响 |
| CodeWF pipeline 配置刷新 | 除共享 LineTemplate 和文件 OutputTemplate 外启动时固定;修改 File、Console、Queue、EventFeed 等结构配置后需重启应用 |
控制台日志遵循 .NET 习惯:应用需要控制台输出时,可以继续使用 Microsoft Console Provider;只有希望 CodeWF 接管控制台格式时,才显式启用 CodeWF ConsoleSink。
文件日志只通过 File.OutputTemplate 配置。Console、LogView 和通知管线使用同一个 LineTemplate Controller。CodeWF 不提供 IncludeEventId、IncludeScopes、IncludeProperties、ShowCategoryName、ShowEventId、ShowTraceId 等字段开关;模板中出现哪个占位符就进入格式化结果,未出现的字段不输出。
LineTemplate 默认只显示 Timestamp、Level 和 UserMessage;需要 Category、EventId、TraceId、Properties 或 Exception 时由调用方加入相应占位符。UserMessage 为空白时回退到 Message。不提供 LogView 或 LogNotifications 的局部模板覆盖。默认 DesktopWindow 已单独显示 Level 和 Timestamp,因此正文直接使用 UserMessage/Message;DesktopContentTemplate 仍可读取完整格式化 Content。
5. 级别模型
全新版本统一使用 MEL 标准级别:
Microsoft.Extensions.Logging.LogLevelMEL LogLevel |
UI 默认显示 |
|---|---|
Trace |
跟踪 |
Debug |
调试 |
Information |
消息 |
Warning |
警告 |
Error |
错误 |
Critical |
严重错误 |
None |
不记录 |
CodeWF 内部、配置、Provider 和 Avalonia 控件的公开级别属性都使用 LogLevel。UI 的中文文字、颜色、图标只是显示层映射,不改变核心级别类型。
迁移旧项目时,本地配置模型中的日志级别属性也改为 LogLevel,配置值使用 Information、Warning、Critical 等 MEL 名称。
6. 过滤模型
全局采集过滤完全交给 MEL:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"MyCompany.Device": "Trace"
}
}
}MEL Provider 不再配置第二套全局采集级别,也不提供 CodeWFLoggerOptions.MinimumLevel。全局、Category 和 Provider 专属过滤统一使用 Logging:LogLevel 与 Logging:CodeWF:LogLevel;File 和 Console 只保留 Sink 级 MinimumLevel。旧式静态 API 仍保留 LoggerOptions.MinimumLevel,它服务于非 Host 场景的 Pipeline 过滤。
Provider 专属过滤使用标准 MEL 约定:
{
"Logging": {
"CodeWF": {
"LogLevel": {
"Default": "Trace",
"Microsoft": "Warning"
}
}
}
}CodeWF Sink 可以有自己的最低输出级别,但它只决定事件是否进入某个输出端,不负责全局采集。
7. 核心事件模型
CodeWFLogEvent 是 File、Console、LogView、LogNotifications 和事件 Feed 的唯一事实源,不再创建用户日志投影类型。
public sealed record CodeWFLogEvent
{
public required long Sequence { get; init; }
public required DateTimeOffset Timestamp { get; init; }
public required LogLevel Level { get; init; }
public required string CategoryName { get; init; }
public EventId EventId { get; init; }
public string? MessageTemplate { get; init; }
public required string Message { get; init; }
public string? UserMessage { get; init; }
public LogExceptionInfo? Exception { get; init; }
public IReadOnlyList<LogProperty> Properties { get; init; } = [];
public IReadOnlyList<LogScope> Scopes { get; init; } = [];
public string? TraceId { get; init; }
public string? SpanId { get; init; }
public string? ParentId { get; init; }
public string? TraceState { get; init; }
public ActivityTraceFlags TraceFlags { get; init; }
public IReadOnlyList<LogProperty> ActivityTags { get; init; } = [];
public IReadOnlyList<LogProperty> ActivityBaggage { get; init; } = [];
}
public sealed record LogScope(
string? Text,
IReadOnlyList<LogProperty> Properties);
public sealed record LogProperty(string Name, LogValue Value);
public sealed record LogExceptionInfo
{
public required string TypeName { get; init; }
public required string Message { get; init; }
public required string Text { get; init; }
public string? StackTrace { get; init; }
public string? Source { get; init; }
public int HResult { get; init; }
public IReadOnlyList<LogExceptionInfo> InnerExceptions { get; init; } = [];
}
public abstract record LogValue;
public sealed record ScalarLogValue(object? Value) : LogValue;
public sealed record SequenceLogValue(IReadOnlyList<LogValue> Values) : LogValue;
public sealed record StructureLogValue(string? TypeName, IReadOnlyList<LogProperty> Properties) : LogValue;设计要点:
Message是 MEL formatter 生成的诊断消息。MessageTemplate来自{OriginalFormat}。Properties保留结构化值,不提前压成字符串。Scopes同时保留文本和结构化属性。Message永远是 MEL formatter 生成的诊断消息;UserMessage是 CodeWF 特有的可选用户友好消息,两者是同一事件上的不同字段。{Message}始终读取Message;{UserMessage}优先读取非空白的UserMessage,没有显式用户消息时回退到Message。该回退只属于模板格式化语义,不新增DisplayMessage字段。- File、Console、LogView 和 LogNotifications 面对同一个事件及相同字段值;通道之间不再通过裁剪事件模型制造字段差异。
- Provider 在日志调用线程把原始
Exception转换成不可变LogExceptionInfo;事件、队列和 Feed 不长期持有原始异常对象及其可能引用的业务对象图。 LogExceptionInfo.Text保存原始Exception.ToString()的有界快照,TypeName、Message、StackTrace、Source、HResult和InnerExceptions支持结构化查看;AggregateException 的多个内部异常必须保留顺序。- 默认不捕获
Exception.Data。异常快照默认最大深度 8、最多 32 个内部异常、单字段最多 32 KiB、单个事件的异常快照总计最多 64 KiB;这些限制允许显式配置,截断和快照失败进入 Self Diagnostics。 Sequence由 pipeline 的单消费者按实际处理顺序分配,生产线程不能在入队前自行分配,避免并发入队造成序号与输出顺序不一致。- 属性快照必须有最大深度、最大属性数、最大集合元素数和最大字符串长度,并检测循环引用;快照失败应降级并进入 Self Diagnostics,不能把异常抛回业务线程。
8. MEL Provider 行为
CodeWFLoggerProvider:
- 实现
ILoggerProvider和ISupportExternalScope。 - 标注
[ProviderAlias("CodeWF")]。 - 按 Category 创建并缓存
CodeWFLogger。 - 使用 DI 容器内共享的实例级
CodeWFLogPipeline,不调用静态Logger.TryInitialize(),也不写入静态LoggerHost。 - Dispose 时释放自身资源,并按拥有关系触发本实例 pipeline 的 Flush/Shutdown。
- 同一进程中的多个 Host、多个
LoggerFactory和测试宿主互不抢占配置与生命周期。
CodeWFLogger:
IsEnabled(LogLevel.None)返回false。- 对已禁用级别尽早返回,减少分配。
- 在
Log<TState>()中调用 MEL formatter 一次,得到Message。 - 即使
Message为空,只要存在 Exception、EventId、Properties 或 UserMessage,也允许记录。 - 从 State 提取
{OriginalFormat}和结构化属性。 - 支持
LoggerMessage/ 源生成日志产生的 State 形状,不依赖FormattedLogValues等具体实现类型。 - State、Scope 和属性必须在调用线程转换为不可变快照,不把可变对象引用交给后台队列。
- State、Scope、枚举器或
ToString()发生异常时降级记录,并通过独立 Self Diagnostics 报告,不默认向业务调用方抛出。 - 从
IExternalScopeProvider捕获 Scope 快照。 - 从
Activity.Current捕获 TraceId、SpanId、ParentId、TraceState。 - TraceFlags 默认捕获;Activity Tags 和 Baggage 支持显式配置捕获,默认关闭,并受属性数量、字符串长度和嵌套深度限制。Baggage 可能包含账号、租户或 Token,启用前必须评估 LogView 和通知展示风险。
- 构造不可变
CodeWFLogEvent并写入 pipeline。 - 不把 Exception 堆栈拼接到
Message。
9. Message 与 UserMessage 语义
普通 MEL 日志生成标准事件:
logger.LogError(ex, "Failed to parse task file {TaskPath}", taskPath);默认结果:
- FileSink:按
File.OutputTemplate输出。 - ConsoleSink:启用且达到 Console 最低级别时,按共享
LineTemplate输出。 - LogEventFeed:发布完整事件,LogView 按自身级别区间展示。
- LogNotifications:普通 MEL 日志的
RequestNotification=false,即使达到MinimumLevel也不通知。
需要单独提供用户友好消息时,使用 CodeWF 的 ILogger 扩展方法:
logger.LogUserError(
exception: ex,
userMessage: "任务文件加载失败,请检查文件格式后重新打开。",
messageTemplate: "Failed to parse task file {TaskPath}",
taskPath);默认结果:
- 四个通道仍处理同一个完整事件。
{Message}输出诊断消息;{UserMessage}输出显式用户消息。UserMessage为null、空字符串或纯空白时,{UserMessage}回退到Message。LogUser*不改变事件是否进入 File、Console 或 LogView,只补充UserMessage,且默认不申请通知;只有LogUserNotification(...)同时设置RequestNotification=true。
统一事件模型不再提供结构上的用户安全隔离。默认 LineTemplate 不展示 Exception、Properties、Scopes 等详细字段;调用方把这些占位符加入 LineTemplate 时,代表明确允许它们出现在 Console、LogView 和系统通知中。日志消息和属性仍可能包含路径、账号、Token 等敏感信息,组件不是自动脱敏器。
旧式 Logger.* 静态门面的兼容语义:
Logger.Info/Warn/Error/Fatal(...):生成统一CodeWFLogEvent,进入 legacy pipeline 的 File、Console 和事件 Feed。Logger.Error(message, exception, userMessage):同一事件的Message、Exception和UserMessage分别保存对应值。Logger.Info/Warn/Error/Fatal(...)的requestNotification可选参数默认false;只有显式传true才申请通知。Logger.*ToFile(...):通过 legacy pipeline 的内部路由信息只进入 FileSink;不为此创建另一种公开事件类型。Logger.MinimumLevel不再使用LogType,迁移为 MELLogLevel。
10. LogEventFeed
LogEventFeed 发布 pipeline 已处理的完整 CodeWFLogEvent,供 LogView、LogNotifications 和应用代码订阅:
- Feed 与 File/Console 共享同一个不可变事件实例,不创建
UserLogEntry或其他裁剪副本。 - Feed 包含普通 MEL 日志和具有
UserMessage的日志,不提供ExplicitOnly/FormattedMessage模式。 - Feed 是后台 pipeline 的最终一致输出;调用日志 API 返回后,不保证对应事件已经可以从 Feed 中读取。
- Feed 的 recent buffer 默认保留最近 2000 条,容量满后按
Sequence淘汰最旧事件;完整 Exception、Properties、Scopes 和 Activity 数据会增加内存占用。 - LogView 通过 Feed 的原子“订阅并重放”操作连接 Source:Feed 先建立订阅边界,再按 Sequence 交付 recent buffer 和后续事件,保证切换期间不遗漏、不重复且顺序稳定。
- LogNotifications 永远不重放 recent buffer,只处理通知启用并完成订阅后的新事件;Console 直接消费 pipeline,也不通过 Feed 重放。
- 订阅者回调不得在 pipeline 单消费者线程上执行慢操作;慢订阅者不能阻塞文件写入并最终反向阻塞业务线程。
- pipeline 创建的
LogEventFeed关联同一实例级ILineTemplateController,使 LogView 可从 Source 解析模板状态;模板更新仍只通过 Controller API 完成。
11. Pipeline 与 Sink
public interface ILogPipeline : IAsyncDisposable
{
void Write(CodeWFLogEvent logEvent);
Task FlushAsync(CancellationToken cancellationToken = default);
Task ShutdownAsync(CancellationToken cancellationToken = default);
}Write(...) 是同步入口,不直接执行慢 IO。慢 IO 由后台队列和 Sink 完成。只有显式选择 Wait 策略时,调用线程才允许因队列背压而等待;其他模式不得无限阻塞业务线程。
队列满策略:
public enum LogQueueFullMode
{
Wait,
DropNewest,
DropTraceAndDebug
}命名约定:
Message:MEL formatter 生成的诊断消息。UserMessage:同一CodeWFLogEvent上可选的 CodeWF 用户友好消息。
语义约定:
Wait:等待队列出现空间,保证不主动丢弃;该模式可能放大慢磁盘或慢 Sink 对业务延迟的影响,只允许调用方显式启用。DropNewest:队列满时丢弃当前事件,优先保护业务线程。DropTraceAndDebug:当前事件为 Trace/Debug 时直接丢弃;Information 及以上事件最多等待EnqueueTimeout,超时后丢弃。该模式用于抵御低级别日志突发,同时避免 Sink 长期故障时无限阻塞业务线程。- 默认使用
DropTraceAndDebug,EnqueueTimeout默认 100 ms;Wait模式不使用该超时。 - 所有丢弃行为都通过独立于日志队列的健康状态统计总数和各级别数量;Self Diagnostics 不能再次写入同一日志队列。
Sequence在单消费者取出事件时分配,表示实际处理顺序。FlushAsync是队列屏障:保证调用前已经成功入队的事件完成 Sink Flush;与 Flush 并发且尚未入队的事件不在保证范围内。- Shutdown 开始后拒绝新事件;并发到达但未成功入队的事件允许丢弃,并计入关闭期丢弃统计。
Sink 抽象:
public interface ILogSink : IAsyncDisposable
{
bool IsEnabled(CodeWFLogEvent logEvent);
ValueTask EmitAsync(CodeWFLogEvent logEvent, CancellationToken cancellationToken);
Task FlushAsync(CancellationToken cancellationToken);
}FileSink:
- 默认启用。
- 默认记录 Timestamp、Level、Category、EventId、Message、MessageTemplate、Properties、Scopes、Activity 和完整 Exception。
- 配置
OutputTemplate后按模板输出;模板未包含的字段不输出。 - 相对路径在 Host 场景下相对
IHostEnvironment.ContentRootPath,非 Host 场景下相对AppContext.BaseDirectory。 - 默认单文件上限为 1000 MB,按日期和大小滚动;
RetentionDays默认为 30,自动清理超过 30 天的日志文件。默认不设置目录总容量和文件数量上限,30 天内的文件不会仅因总容量或数量被提前删除。 RetainedFileCountLimit和MaxDirectorySizeBytes默认为空;调用方显式配置后,它们作为额外空间保护,可以提前删除 30 天内最旧的滚动文件。清理失败进入 Self Diagnostics,不中断当前日志写入。- 文件名包含进程标识,多个进程共享目录时不会追加到同一个活动文件;轮转和保留清理只处理当前进程文件,避免跨进程删除或滚动竞争。
- 默认目录不可创建或不可写时,启动阶段应给出明确的 Options/初始化错误;运行期故障则降级到 Self Diagnostics,不反向破坏业务流程。
ConsoleSink:
- 默认关闭。
- 直接消费全部通过 MEL 采集过滤和 Console Sink 级过滤的
CodeWFLogEvent。 - 与 LogView、LogNotifications 使用同一个全局
LineTemplateController 和同一套占位符语义。 - 不单独提供
Console.OutputTemplate;模板包含{Exception}、{Properties}、{Scopes}等字段时,Console 与 Avalonia 输出都能读取同一事件中的对应值。 - 希望 CodeWF 完全接管控制台时,由应用显式
ClearProviders()后只注册 CodeWF;Provider 不擅自移除 Microsoft Console、Serilog 等其他 Provider。
EventFeedSink:
- 默认启用。
- 把全部通过采集过滤的完整
CodeWFLogEvent发布到实例级LogEventFeed。 - 不复制或裁剪事件;recent buffer 保存同一个不可变事件实例。
12. Avalonia
LogView 和 LogNotifications 都消费 LogEventFeed,但两者互不依赖。只需要文件日志和桌面通知的应用可以完全不创建 LogView;MEL/DI 场景仍需在 Application 上设置实例级 Source,且不能关闭 EventFeed。
可选的 LogView 用法:
<log:LogView Source="{Binding LogEvents}"
MinimumLevel="Information"
MaximumLevel="Critical" />设计约定:
LogContext.Source是设置在 AvaloniaApplication上的全局LogEventFeed;普通单 Host 应用只需配置一次,LogView 和 Notification 自动共享。LogView.Source是可选的控件级覆盖,仅用于同一应用需要展示多个日志源的场景。- LogView 的数据源解析顺序为:控件局部
LogView.Source→ Application 全局LogContext.Source→ legacy 全局事件源。 - LogNotifications 的数据源解析顺序为:Application 全局
LogContext.Source→ legacy 全局事件源。 - MEL/DI Avalonia 应用在 Application 初始化时把容器中的实例级
LogEventFeed设置为全局 Source;原<log:LogView />无 Source 用法仍可在 legacy 静态 Logger 场景工作。 MinimumLevel和MaximumLevel使用 MELLogLevel。- LogView 默认
MinimumLevel=Information、MaximumLevel=Critical;保留MaximumLevel,支持多个 LogView 按区间展示。 - 每行字段完全由
LineTemplate决定,不提供ShowCategoryName、ShowEventId、ShowTraceId等字段开关。 LineTemplate支持完整CodeWFLogEvent的字段,包括Timestamp、Level、Message、UserMessage、MessageTemplate、Category、EventId、EventName、Properties、Scopes、Exception、Activity/Trace 字段和NewLine。- 模板共享以 pipeline/Source 为边界:Console 使用所属 pipeline 的 Controller;LogView 使用其最终解析 Source 关联的 Controller;Application 级 LogNotifications 只使用全局 Source 的 Controller,不跟随某个局部 LogView.Source。
- 同一 Source 下的 Console、所有 LogView 和 LogNotifications 读取同一个模板状态,不提供控件级或通知级模板覆盖;Source 未提供模板时使用
{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:zh}] {UserMessage}{NewLine}。默认 DesktopWindow 的组合式正文不重复呈现其中的 Timestamp 和 Level。
MEL/DI Avalonia 应用的全局 Source 示例:
var logEvents = services.GetRequiredService<LogEventFeed>();
LogContext.SetSource(this, logEvents);LogNotifications 使用“调用方显式申请 + 级别阈值”语义,只保留 MinimumLevel,不提供 MaximumLevel:
log:LogNotifications.MinimumLevel="Error"
log:LogNotifications.MaxVisibleCount="3"
log:LogNotifications.QueueCapacity="100"判断规则:
mode != LogNotificationMode.None
&& entry.RequestNotification
&& entry.Level >= minimumLevel关闭通知使用:
log:LogNotifications.Mode="None"不提供 MaximumLevel,不使用特殊日志级别表达“关闭通知”。
LogNotifications 同时检查 RequestNotification 和 MinimumLevel,不把 UserMessage、Category 或 Exception 作为隐式条件。通知管线读取 Source 携带的全局 LineTemplate;InApp 和自定义桌面模板可使用完整结果。默认 DesktopWindow 由标题、图标、级别、用户正文、时间、翻页和按钮组成,正文直接使用 UserMessage,为空时回退 Message。
默认 DesktopWindow 的可见宽度为 360px,圆角 8px,单条、多条和长内容可见高度分别为 284/320/420px。Error 使用圆形感叹号与 #FFF1F0 渐变,Critical 使用三角感叹号与 #FFCCC7 渐变;窗口打开期间追加的日志显示新日志计数。默认图标作为 AvaloniaResource 内嵌,颜色、尺寸、按钮、渐变可通过公开动态资源覆盖,完整内容结构可通过 DesktopContentTemplate 替换。
通知突发处理:
RequestNotification=true和达到MinimumLevel是启用通知时的两个事件资格条件;不再增加UserMessage、Category、Exception 等隐式过滤。- 符合两个条件的事件按
Sequence顺序提交到有界通知展示队列;队列只影响呈现节奏,不影响事件进入LogEventFeed和可选的 LogView。 InApp默认最多同时显示 3 条通知,其余事件排队;最大可见数量允许显式配置。DesktopWindow每个 Avalonia Application 只复用一个桌面工作区右下角窗口,在同一窗口内按顺序滚动展示,不为每个事件创建独立窗口。- 通知展示队列默认容量为 100。容量耗尽时不阻塞 pipeline,不继续创建窗口;累计溢出数量并在现有通知区域提示“另有 N 条日志,请查看日志文件”。通知文案不假定存在 LogView;溢出也不删除
LogEventFeed中的事件,Feed 仍按自身 recent buffer 容量独立保留。 Mode=None时不建立展示积压;重新启用通知后只处理新事件,不补弹关闭期间的历史事件。
13. 配置
最小配置:
builder.Logging.AddCodeWF();{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning"
}
}
}常见配置:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"MyCompany.Device": "Trace"
},
"CodeWF": {
"LineTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {UserMessage}{NewLine}",
"File": {
"Enabled": true,
"DirectoryPath": "logs",
"MaxFileSizeBytes": 1048576000,
"RetentionDays": 30,
"OutputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] ({Category}) {Message} {Properties}{NewLine}{Exception}"
},
"Console": {
"Enabled": false
},
"EventFeed": {
"Enabled": true,
"RecentCapacity": 2000
},
"Capture": {
"Scopes": true,
"Activity": true,
"ActivityTags": false,
"ActivityBaggage": false
},
"Queue": {
"Capacity": 10000,
"FullMode": "DropTraceAndDebug",
"EnqueueTimeout": "00:00:00.100"
}
}
}
}代码配置:
builder.Logging.AddCodeWF(options =>
{
options.LineTemplate =
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {UserMessage}{NewLine}";
options.File.Enabled = true;
options.File.DirectoryPath = "logs";
options.File.MaxFileSizeBytes = 1_000L * 1024 * 1024;
options.File.RetentionDays = 30;
options.File.OutputTemplate =
"{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] ({Category}) {Message} {Properties}{NewLine}{Exception}";
options.Console.Enabled = false;
options.EventFeed.Enabled = true;
options.EventFeed.RecentCapacity = 2_000;
options.Capture.Scopes = true;
options.Capture.Activity = true;
options.Capture.ActivityTags = false;
options.Capture.ActivityBaggage = false;
options.Queue.Capacity = 10_000;
options.Queue.FullMode = LogQueueFullMode.DropTraceAndDebug;
options.Queue.EnqueueTimeout = TimeSpan.FromMilliseconds(100);
});正式 API 同时支持 appsettings.json 和 AddCodeWF(options => ...) 代码配置,并遵循 .NET Options 的正常合并顺序。WebApiDemo 为了集中验证配置绑定,故意只使用 appsettings.json;这不表示产品 API 不支持代码配置。
File.OutputTemplate 与共享 LineTemplate 使用同一套占位符:
| 占位符 | 含义 |
|---|---|
Timestamp |
时间,支持 {Timestamp:yyyy-MM-dd HH:mm:ss.fff} |
Level |
MEL 日志级别,支持 {Level:u3}、{Level:u4}、{Level:zh} |
Category / CategoryName |
日志 Category |
EventId / EventName |
MEL EventId |
Message |
MEL formatter 生成的消息 |
MessageTemplate |
{OriginalFormat} 原始模板 |
UserMessage |
CodeWF 用户消息;为空白时回退到 Message |
Properties |
结构化属性 |
Scopes |
Scope 快照 |
Activity / TraceId / SpanId |
Activity/Trace 信息 |
TraceFlags / ActivityTags / ActivityBaggage |
Activity 采样标记、Tags 和 Baggage;后两项需显式启用捕获 |
Exception |
异常详情 |
NewLine |
当前平台换行 |
模板中未出现的字段不会输出,因此不再设计字段布尔配置。File.OutputTemplate 和共享 LineTemplate 都可以使用完整 CodeWFLogEvent 的全部占位符;两者的区别只是应用到 File 还是 Console、LogView、通知格式化结果。默认组合式 DesktopWindow 直接呈现结构化级别、时间和用户正文。
与 Serilog/NLog/log4net 并行时,如果第三方 Provider 已负责文件和控制台,CodeWF 只保留 LogEventFeed、LogView 和 LogNotifications:
builder.Logging.AddSerilog();
builder.Logging.AddCodeWF(options =>
{
options.File.Enabled = false;
options.Console.Enabled = false;
options.EventFeed.Enabled = true;
});只有明确希望 CodeWF 成为唯一 Provider 时,才清空默认 Provider:
builder.Logging.ClearProviders();
builder.Logging.AddCodeWF(options =>
{
options.Console.Enabled = true;
options.LineTemplate =
"{Timestamp:HH:mm:ss} [{Level:u3}] ({Category}) {UserMessage}{NewLine}";
});实现要求:
- Options 使用普通公开属性,支持标准配置绑定。
- Options 必须有显式校验,目录、容量、时间间隔、级别、模式等非法值应在启动阶段给出清晰错误。
- Host 场景优先使用
IValidateOptions<TOptions>/ValidateOnStart。 - Host 场景通过
IHostEnvironment.ContentRootPath解析相对日志目录;非 Host 场景才使用AppContext.BaseDirectory。 Logging:LogLevel和Logging:CodeWF:LogLevel继续由 MEL 按标准方式动态刷新。- File、Console、EventFeed、Capture 和 Queue 属于 pipeline 启动配置,第一版不做部分热更新;配置文件变化后需重启应用。不能使用
IOptionsMonitor只更新其中少数字段而让其余字段静默保持旧值。 - 共享
LineTemplate和文件OutputTemplate是格式配置例外:模板必须先完成解析和校验,再通过各自的模板状态原子替换;无效模板不得覆盖上一份有效值。LineTemplate 更新后 LogView 重新渲染当前保留日志,已经写出的 Console 行和已经弹出的通知不追溯修改;OutputTemplate 更新只影响后续写入的文件日志。 - 运行时修改通过实例级
ILineTemplateController和IFileOutputTemplateController完成,不直接修改 Options,也不把更新方法塞入LogEventFeed:
public interface ILineTemplateController
{
string Current { get; }
bool TryUpdate(string template, out string? error);
}
public interface IFileOutputTemplateController
{
string? Current { get; }
bool TryUpdate(string? template, out string? error);
}- 每个 DI pipeline 和 legacy pipeline 各自持有两个 Controller;Console、该 pipeline 的 Feed、LogView 和 LogNotifications 共享 LineTemplate Controller,FileSink 独占 OutputTemplate Controller。多 Host 之间互不影响。
- 配置文件和代码 Options 只确定 Controller 的初始模板;第一版不监听配置文件变化做隐式模板热重载。Demo 的预设和手工编辑器通过注入的 Controller 显式更新。
- AOT 场景不得依赖复杂反射绑定;必要时提供代码配置、手写绑定或 source-generated binding 路径。
14. ILogger 用户日志扩展
建议提供:
public static class CodeWFLoggerExtensions
{
public static void LogUser(
this ILogger logger,
LogLevel level,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUser(
this ILogger logger,
LogLevel level,
EventId eventId,
Exception? exception,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUserTrace(
this ILogger logger,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUserDebug(
this ILogger logger,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUserInformation(
this ILogger logger,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUserWarning(
this ILogger logger,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUserError(
this ILogger logger,
Exception? exception,
string userMessage,
string messageTemplate,
params object?[] args);
public static void LogUserCritical(
this ILogger logger,
Exception? exception,
string userMessage,
string messageTemplate,
params object?[] args);
}要求:
- 扩展方法必须走标准
ILogger.Log(...)。 - 扩展方法不能绕过 MEL 过滤。
- 其他 Provider 至少能收到普通诊断日志。
- CodeWF Provider 通过内部 State marker/interface 识别用户消息,并写入同一
CodeWFLogEvent.UserMessage;不创建UserLogPayload或UserLogEntry。 - 用户消息不能作为
CodeWF.UserMessage等普通枚举属性暴露给其他 Provider。 - State 对其他 Provider 枚举时只返回标准诊断参数和
{OriginalFormat};Serilog/NLog/log4net 不应被 CodeWF 私有用户元数据污染。 - 扩展方法必须保留原始
messageTemplate、参数顺序和{OriginalFormat},不能破坏 MEL 标准结构化日志语义。 - 提供 Trace、Debug、Information、Warning、Error、Critical 六个对称的便捷方法,同时保留接受
LogLevel、EventId和 Exception 的通用LogUser(...)。 LogUser*的诊断格式化结果必须与相同模板和参数的标准 MELLogTrace/Debug/Information/Warning/Error/Critical保持一致,包括对齐、格式说明符、转义花括号、集合、Culture 和参数数量异常行为。
15. AOT 与 trimming
三个包的 net8.0、net10.0 和 net11.0 资产都必须 trim/AOT 友好:
- 不使用运行时程序集扫描发现 Sink、Formatter 或扩展方法。
- 不使用动态代码生成、表达式编译、
Reflection.Emit。 - 不依赖按字符串解析类型名来创建对象。
- Options 类型使用公开无参构造和可设置属性。
- Provider、Sink、Formatter 通过显式 API 或 DI 注册。
- 确实需要反射时,必须通过 trim annotation、source generator 或手写绑定消除 AOT 风险。
- CI 按
net8.0、net10.0和net11.0三个 SDK/TFM 基线完成库构建,并使用 .NET 11 SDK 完成测试、打包和 Demo 验证;不使用一个根global.json强制所有目标共享同一 SDK。 - CI 执行 .NET 11 Native AOT 控制台 smoke app,发布后实际运行产物,验证 trim/AOT 失败不会被仅有的发布成功状态掩盖。
16. Demo
Demo 的原则是职责单一、界面低负担:不提供手工模板编辑器,只提供两档模板下拉框和少量组合按钮;高级模板语法由 README 和本文说明。日志消息模板固定,动态值使用结构化参数。
16.1 ConsoleDemo
- 只引用
CodeWF.Log.Core,不引用 Avalonia、CodeWF.Log.Avalonia 或 MEL Provider。 - 使用静态
Logger.Initialize(...),同时启用 Console 和 File,并明确设置EnableEventFeed=false。 - 不创建 LogView,不配置 LogNotifications,也不存在系统弹窗通道。
- 覆盖
Logger.Info/Warn/Error/Fatal、*ToFile、独立 LineTemplate/OutputTemplate、文件滚动、Flush 和 Shutdown。
16.2 LogViewDemo
- 使用
AddCodeWF()、DI 和ILogger<T>,承载主 Avalonia 集成示例。 - 同一个
LogEventFeed驱动三个 LogView:全部级别、仅 Information、Warning 至 Critical。 - 提供 LineTemplate 和 OutputTemplate 两档切换,不要求用户编辑模板文本。
- 组合按钮覆盖六个级别、Message/UserMessage、EventId、结构化属性、Scope、Activity、Exception、显式通知和并发写入。
- LineTemplate 切换立即重绘三个视图;OutputTemplate 只影响后续文件日志。
16.3 FileNotifyDemo
- Avalonia 窗口不包含
LogView,只使用文件输出和 DesktopWindow 通知。 - 保持 EventFeed 开启并在 Application 设置实例级
LogContext.Source,证明通知不依赖 LogView。 - 提供文件模板“简洁/完整上下文”切换。
- 提供 Error 仅日志、Error 显式通知、Warning 请求通知但低于阈值三个按钮,直接验证三项通知条件。
16.4 SerilogDemo
- 同一个
ILogger<T>同时注册 Serilog 与 CodeWF Provider。 - Serilog 负责文件/控制台;CodeWF 禁用 File/Console,只负责 EventFeed、LogView 和通知。
- 提供 CodeWF LineTemplate 两档切换;Serilog 文件格式保持独立。
- 组合按钮覆盖普通日志、Message/UserMessage、EventId、Scope、Activity、显式通知和并发写入。
- Serilog 不接收 CodeWF 私有 UserMessage 属性,只接收标准诊断 State 和
{OriginalFormat}。
16.5 WebApiDemo
- 使用
WebApplication.CreateBuilder(args)和builder.Logging.AddCodeWF()。 - File、Console、EventFeed、Capture 和 Queue 配置只写在
appsettings.json,集中验证配置绑定。 - 展示 MEL 过滤、EventId、结构化参数、Scope、Activity、LoggerMessage 和
/recent-logs。 /recent-logs只用于 Demo;生产接口不得无鉴权暴露完整日志事件。
17. 当前实现状态
本表用于区分当前代码和目标设计,避免把路线图误读为已发布能力。功能完成后应在同一提交中更新状态。
| 能力 | 当前状态 | 说明 |
|---|---|---|
AddCodeWF()、Provider Alias、AOT-safe 配置绑定 |
已实现 | 支持代码配置和 Logging:CodeWF appsettings 结构 |
| Trace 至 Critical、EventId、结构化 State、Scope、Activity、Exception 捕获 | 已实现 | Exception 在调用线程转换为有界不可变 LogExceptionInfo |
统一 CodeWFLogEvent 与完整 LogEventFeed |
已实现 | 普通日志与 LogUser* 使用同一种完整事件 |
File OutputTemplate |
已实现 | 包含 Sink 阈值、滚动、30 天保留、显式容量保护和运行时原子更新 |
Console/LogView/LogNotifications 共享 LineTemplate Controller |
已实现 | 默认 DesktopWindow 组合字段呈现;完整结果供 InApp/自定义模板使用 |
| 实例级 DI pipeline 与 legacy 静态 pipeline 分离 | 已实现 | Provider 拥有生命周期,可选桥接静态 facade |
多 Host、重复启动和并行 LoggerFactory 隔离 |
已实现 | 自动化测试覆盖独立 Feed 与释放 |
| Queue FullMode、超时、丢弃计数、顺序保证 | 已实现 | CodeWFLogHealth 提供总数与各级别统计 |
| Options 启动校验与明确的重启生效语义 | 已实现 | LineTemplate 与文件 OutputTemplate 支持显式运行时原子更新 |
| Host ContentRoot 相对路径解析 | 已实现 | Provider 通过 IHostEnvironment.ContentRootPath 解析 |
LogContext.Source、LogView.Source、共享 LineTemplate |
已实现 | 全局 Source 与控件级覆盖均支持 |
实例级 ILineTemplateController 与原子模板更新 |
已实现 | 无效模板不覆盖当前值,LogView 自动重绘 |
实例级 IFileOutputTemplateController 与原子模板更新 |
已实现 | 无效模板不覆盖当前值,更新只影响后续文件日志 |
LogContext.LogDirectory、LogView.LogDirectory |
已实现 | 打开目录不依赖静态 Logger 初始化,支持全局目录与控件级覆盖 |
| FileNotifyDemo | 已实现 | 无 LogView 的文件输出、文件模板、显式通知和通知阈值对比 |
| LogViewDemo | 已实现 | MEL/DI、多分级 LogView、两类模板和完整事件场景 |
| SerilogDemo | 已实现 | Serilog 文件/控制台,CodeWF LogView/通知,均由 appsettings 配置 |
| 自动化测试、trim、Native AOT smoke | 已实现 | Core/MEL/Avalonia 测试、三目标框架构建和 Native AOT smoke 已接入验证流程 |
18. 当前版本发布门槛
以下为当前版本发布必须满足的验收标准:
- 三个包同时提供
net8.0、net10.0和net11.0资产;CI 分别使用对应 SDK 完成 Release 构建,并使用 .NET 11 SDK 完成 test/pack/smoke。 - MEL Provider 使用实例级 pipeline;多个 Host、多个
LoggerFactory、顺序重启和并行测试互不抢占配置与生命周期。 - legacy
Logger.*继续可用;Provider 可通过BridgeStaticLogger将静态 facade 路由到实例级 Host,但实例生命周期仍由 DI Provider 独立拥有。原<log:LogView />无 Source 用法仍可回退到 legacy Feed。 ILogger<T>的 Trace、Debug、Information、Warning、Error、Critical 均能进入 CodeWF Provider。- MEL 全局过滤、Category 过滤、Provider 专属过滤和 CodeWF Sink 级过滤均生效。
- EventId、
{OriginalFormat}、结构化 State、Scope、Activity 和 Exception 可被捕获。 LoggerMessage/ 源生成日志的 State 可被正确捕获。- 普通
logger.LogError(...)与logger.LogUserError(...)都生成统一CodeWFLogEvent并进入LogEventFeed;后者只额外设置UserMessage。 LogUser*与标准 MEL 日志的诊断消息和结构化属性语义一致,且不向其他 Provider 暴露 CodeWF 私有用户消息元数据。{Message}始终输出诊断消息;{UserMessage}优先输出用户消息并在其为空白时回退到Message。- Avalonia Application 可配置全局
LogEventFeed,LogView 支持局部 Source 覆盖;同一 pipeline/Source 的 Console、所有 LogView 和 LogNotifications 共享一个实例级ILineTemplateController。 - LogView 默认 MinimumLevel=Information、MaximumLevel=Critical;LogNotifications 默认 Mode=None、MinimumLevel=Error、Duration=10 秒,启用后同时要求
RequestNotification=true和达到 MinimumLevel。 - LogView 的 recent buffer 重放与实时订阅具有原子边界,不遗漏、不重复且保持 Sequence 顺序;LogNotifications 不重放历史事件。
CodeWFLogEvent只保存有界不可变LogExceptionInfo,不把原始 Exception 对象引用交给后台队列或 recent buffer;Exception.Data 默认不捕获。- InApp 通知具有最大可见数量;DesktopWindow 复用单窗口,显示会话新增数量并支持前后翻页;通知展示队列有界且溢出数量可见,通知突发不能阻塞 pipeline。
- Provider 与 Serilog/NLog/log4net 至少一种并行时,业务代码无需修改,CodeWF 可只启用 LogEventFeed、LogView 和通知。
- Sequence 与实际单消费者处理顺序一致;LogEventFeed 的最终一致性和完整事件暴露风险在 API 与 Demo 中有明确说明。
- Queue FullMode 和 EnqueueTimeout 生效,丢弃数量通过独立健康状态可观测,慢订阅者不能阻塞 pipeline。
- Host 停止或 LoggerFactory Dispose 时可以 Flush 和 Shutdown;Flush 屏障语义通过并发测试验证。
- Sink、Formatter、State 快照和订阅者失败不会默认抛回业务线程。
- Options 非法值在启动阶段给出清晰错误;除
LineTemplate和文件OutputTemplate的显式原子运行时切换外,pipeline 配置明确为修改后重启生效,不存在部分静默热更新。 - FileSink 具备有界保留策略,并通过进程 ID 文件名支持多个进程共享目录。
- WebApiDemo 只通过
appsettings.json配置 CodeWF。 - LogViewDemo 可切换 LineTemplate 与 OutputTemplate;SerilogDemo 只切换 CodeWF LineTemplate。
- SerilogDemo 验证 Serilog 负责诊断输出、CodeWF 只负责 LogView 和 InApp/DesktopWindow 通知。
- FileNotifyDemo 验证不创建 LogView 时,文件输出和 DesktopWindow 重要通知仍可独立工作。
CodeWF.Log.Core、CodeWF.Log.Extensions.Logging、CodeWF.Log.Avalonia通过 trim/Native AOT smoke test。
19. 后续增强项
以下能力不阻塞第一版,但设计时应保留扩展空间:
- pipeline/sink 配置的原子热重载。
- EventSource、OpenTelemetry Metrics 或公开
CodeWFLogHealth健康指标。 - 可插拔自定义 Sink/Formatter 公共扩展契约。
- 可插拔的属性脱敏与模板输出策略。
20. 实施顺序
实现必须按依赖顺序推进,每个阶段先完成对应自动化验证,再进入下一阶段,避免在 Demo 中掩盖 Core 或 Provider 的模型问题。
20.1 工程基线
- 三个 NuGet 包保留
net8.0、net10.0和net11.0三组资产;Avalonia 和 Web API Demo 保持net11.0/net11.0-windows,用于验证 .NET 11 应用引用对应库资产。 - 不增加强制整个解决方案使用单一 SDK 的根
global.json;库按三个目标框架分别验证,Demo 和测试使用 .NET 11 SDK,中央包版本继续使用 MEL 10.x。 - 在修改公共模型前记录当前 Release 构建和打包基线,保留现有文档改动,不覆盖无关工作区内容。
验收:三个库的 net8.0、net10.0 和 net11.0 资产都能完成 Release 构建;测试、打包和 AOT smoke 能在 .NET 11 SDK 下完成并实际运行产物。
20.2 Core 统一事件模型
- breaking 删除
UserLogEntry、公开UserLogPayload、UserLogFeed和UserLogMode。 - 完成统一不可变
CodeWFLogEvent、可选UserMessage、LogExceptionInfo和有界属性/异常快照。 - 实现完整
LogEventFeed、recent buffer、按 Sequence 淘汰及原子“订阅并重放”。 - 实现实例级
ILineTemplateController、IFileOutputTemplateController、模板解析校验、原子更新和{UserMessage}空白回退语义。 - File 使用
OutputTemplate;Console 直接消费全部事件并使用共享LineTemplate;EventFeedSink 发布同一事件实例。 - 保留 legacy
Logger.*;Logger.*ToFile通过内部路由信息只进入 FileSink,不引入第二种公开事件模型。 - 完成队列满策略、Sequence、Flush 屏障、Self Diagnostics 和文件滚动保留策略。
验收:Formatter、异常快照、Feed 并发重放、队列顺序/丢弃、Flush、文件滚动和清理均有自动化测试。
20.3 MEL Provider
- Provider 改为实例级 pipeline、
LogEventFeed和ILineTemplateController,不直接复用静态LoggerHost。 - 完成标准 MEL State、
{OriginalFormat}、EventId、Scope、Activity、Exception 和LoggerMessage捕获。 - 用内部 State marker/interface 传递
UserMessage,第三方 Provider 只能枚举标准诊断参数。 - 补齐 Trace、Debug、Information、Warning、Error、Critical 六个
LogUser*方法及通用LogUser(...)。 - 完成 Options 绑定、启动校验、ContentRoot 相对路径、MEL 过滤、Provider Dispose、静态桥接和多 Host 隔离。
验收:标准日志与 LogUser* 的诊断格式化一致;多 Provider、多 Host、重复启动/释放和元数据隔离测试通过。
20.4 Avalonia
LogContext.Source和LogView.Source统一使用LogEventFeed;全局、局部和 legacy Source 回退顺序按第 12 节实现。- LogView 默认 Information 至 Critical,使用原子订阅重放,并在
LineTemplate更新后重新渲染当前 recent buffer。 - LogNotifications 同时要求
RequestNotification=true和达到 MinimumLevel,不重放历史事件;实现 None、InApp、DesktopWindow 三种模式。 - InApp 默认最多同时显示 3 条;DesktopWindow 复用一个右下角窗口;展示队列默认容量 100,溢出计数可见且不阻塞 pipeline。
- Console、同一 Source 下的所有 LogView 和 Notification 读取同一个实例级模板 Controller,不提供控件级模板覆盖;默认 DesktopWindow 使用组合式字段呈现,完整模板内容提供给自定义模板。
验收:Source 切换、模板热切换、UI 线程调度、窗口重建、通知模式切换、突发队列和资源释放通过自动化或可重复 smoke test。
20.5 Demo
- 抽取只供 Demo 使用的动态场景生成器,所有按钮使用固定消息模板和动态结构化参数。
- 五个示例统一命名为 ConsoleDemo、FileNotifyDemo、LogViewDemo、SerilogDemo、WebApiDemo。
- LogViewDemo 提供 LineTemplate/OutputTemplate 两档切换;SerilogDemo 只切换 CodeWF LineTemplate。
- WebApiDemo 的 CodeWF 配置只保留在
appsettings.json,接口为/recent-logs。 - SerilogDemo:Serilog 负责文件/控制台,CodeWF 只启用 EventFeed、LogView 和通知。
- FileNotifyDemo:CodeWF 负责文件和 DesktopWindow 通知,窗口不包含 LogView,并提供 Error、Critical、长内容、连续追加和通知条件对比按钮。
验收:所有 Demo 的六级别、普通/用户消息对照、Exception、EventId、Scope、Activity、LoggerMessage 和并发场景均可重复验证。
20.6 测试、AOT 与发布文档
- 新增 Core、MEL Provider 和 Avalonia 相关测试项目;UI 逻辑优先拆成可独立测试的状态与调度组件。
- 增加 Native AOT 控制台 smoke app、Avalonia trim/publish smoke test和 NuGet pack 验证。
- 更新 README、UpdateLog、包说明、配置示例和 breaking migration,删除所有旧
UserLog*投影 API 示例。 - 最后执行完整 Release build、test、publish smoke 和 pack;同一提交同步更新第 17 节实现状态。
验收:满足第 18 节全部发布门槛后才进入发布流程。