📘 Snet.Log 概述
Snet.Log 基于 Serilog 为 Snet 框架提供结构化日志记录。它提供 6 个日志级别(Verbose、Debug、Info、Warning、Error、Fatal),双通道输出(控制台和文件),同步与异步方法变体,以及通过 CoreUnify 系统实现的按实例日志配置。
| 属性 | 值 |
|---|---|
| 包 | Snet.Log |
| 命名空间 | Snet.Log |
| 目标框架 | .NET 8.0 / .NET 10.0 |
| 依赖 | Serilog 4.4.0, Serilog.Sinks.Console 6.1.1, Serilog.Sinks.File 7.0.0 |
| 自动包含 | 是,通过 Snet.Core |
| 许可证 | MIT |
▶️ 快速开始
using Snet.Log;
// 基本日志记录
LogHelper.Info("Application started successfully.");
LogHelper.Warning("Disk usage is above 80%.");
LogHelper.Error("Failed to connect to PLC device.");
// 异步日志记录
await LogHelper.InfoAsync("Data acquisition cycle completed.");
await LogHelper.ErrorAsync("Unhandled exception in driver.", exception: ex);
日志输出同时显示在控制台和文件中(默认:logs/ 目录,snet-{Date}.log 模式)。
⚙️ 安装
dotnet add package Snet.Log
自动包含: Snet.Log 是 Snet.Core 的依赖项,因此如果您的项目已引用 Snet.Core,则无需单独安装。
| 依赖 | 版本 | 用途 |
|---|---|---|
| Serilog | 4.4.0 | 核心结构化日志引擎 |
| Serilog.Sinks.Console | 6.1.1 | 控制台输出接收器 |
| Serilog.Sinks.File | 7.0.0 | 滚动文件输出接收器 |
🧠 核心概念
1. LogHelper 静态类
中心化日志 API。所有方法均为静态方法——无需实例化。LogHelper 内部维护一个 Serilog.ILogger 单例实例,在应用程序启动时进行配置。
LogHelper.Info("message");
LogHelper.Error("message", exception);
await LogHelper.DebugAsync("message");
2. 六个日志级别
| 级别 | 方法 | 使用场景 |
|---|---|---|
| Verbose | Verbose() / VerboseAsync() |
详细跟踪,内部状态转储 |
| Debug | Debug() / DebugAsync() |
开发诊断,变量检查 |
| Info | Info() / InfoAsync() |
正常操作事件(启动、关闭、里程碑) |
| Warning | Warning() / WarningAsync() |
潜在问题,降级但仍可用状态 |
| Error | Error() / ErrorAsync() |
操作失败,异常,数据丢失 |
| Fatal | Fatal() / FatalAsync() |
应用程序崩溃事件,不可恢复状态 |
3. LogModel 配置
LogModel 类控制每个应用程序实例的日志行为:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Out |
bool |
true |
启用/禁用所有日志输出 |
ConsoleOut |
bool? |
null |
启用/禁用控制台接收器(null = 使用全局设置) |
FolderName |
string |
"logs" |
日志文件输出文件夹名 |
GrassrootsDefaultFolderName |
string |
"details" |
低级别详细日志的回退文件夹名 |
FileSuffix |
string |
".log" |
日志文件扩展名 |
FileLocation |
string |
AppContext.BaseDirectory |
日志文件的基目录 |
InfoTimeFormat |
string |
"yyyy-MM-dd HH:mm:ss.ffffff" |
时间戳格式 |
HistoryTime |
int |
30 |
日志文件保留天数 |
Notice |
Action<object, LogEventLevel, string?, Exception?> |
null |
每条日志条目调用的回调 |
NoticeAsync |
Func<object, LogEventLevel, string?, Exception?, Task> |
null |
每条日志条目调用的异步回调 |
4. 通过 LogHelper.Set / Get 配置
使用 LogHelper.Set(LogModel) 全局应用 LogModel,使用 LogHelper.Get() 读回:
var model = new LogModel { FolderName = "MyAppLogs", HistoryTime = 60 };
LogHelper.Set(model);
LogModel current = LogHelper.Get();
5. 通过 CoreUnify 实现按实例配置
对于不同组件需要不同日志配置的高级场景,请使用 CoreUnify 方法:
// 为本实例应用日志配置
await CoreUnify.LogOperateSetAsync(logOut: true, consoleOut: null);
// 带按实例通知回调
await CoreUnify.LogOperateSetAsync(noticeAsync: async (source, level, message, ex) =>
{
await db.InsertLogAsync(message);
});
// 或直接传入完整的 LogModel
var model = new LogModel { FolderName = "ServiceLogs" };
await CoreUnify.LogOperateSetAsync(model);
📚 API 参考
LogHelper 方法
所有方法接受一个 string message 参数,外加可选的 foldername、exception 和 consoleShow 参数。泛型重载接受任意消息对象并配对一个消息模板。
同步方法
| 方法 | 签名 | 描述 |
|---|---|---|
Set |
void Set(LogModel logModel) |
应用全局 LogModel 配置 |
Get |
LogModel Get() |
获取当前 LogModel |
Reset |
(bool status, string? message) Reset() |
重置日志配置 |
Verbose |
void Verbose(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true) |
Verbose 级别日志记录 |
Debug |
void Debug(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true) |
Debug 级别日志记录 |
Info |
void Info(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true) |
Info 级别日志记录 |
Warning |
void Warning(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true) |
Warning 级别日志记录 |
Error |
void Error(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true) |
Error 级别日志记录 |
Fatal |
void Fatal(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true) |
Fatal 级别日志记录 |
异步方法
| 方法 | 签名 | 描述 |
|---|---|---|
ResetAsync |
Task<(bool status, string? message)> ResetAsync() |
异步重置 |
VerboseAsync |
Task VerboseAsync(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true, CancellationToken token = default) |
异步 Verbose 级别日志记录 |
DebugAsync |
Task DebugAsync(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true, CancellationToken token = default) |
异步 Debug 级别日志记录 |
InfoAsync |
Task InfoAsync(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true, CancellationToken token = default) |
异步 Info 级别日志记录 |
WarningAsync |
Task WarningAsync(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true, CancellationToken token = default) |
异步 Warning 级别日志记录 |
ErrorAsync |
Task ErrorAsync(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true, CancellationToken token = default) |
异步 Error 级别日志记录 |
FatalAsync |
Task FatalAsync(string message, string? foldername = null, Exception? exception = null, bool consoleShow = true, CancellationToken token = default) |
异步 Fatal 级别日志记录 |
CoreUnify 日志操作
| 方法 | 签名 | 描述 |
|---|---|---|
LogOperateSet |
OperateResult LogOperateSet(bool logOut = true, bool? consoleOut = null) |
为本实例应用日志配置 |
LogOperateSetAsync |
Task<OperateResult> LogOperateSetAsync(bool logOut = true, bool? consoleOut = null, CancellationToken token = default) |
异步变体 |
LogOperateSet |
OperateResult LogOperateSet(bool logOut = true, bool? consoleOut = null, Action<object, LogEventLevel, string?, Exception?>? notice = null) |
带通知回调 |
LogOperateSetAsync |
Task<OperateResult> LogOperateSetAsync(bool logOut = true, bool? consoleOut = null, Action<object, LogEventLevel, string?, Exception?>? notice = null, CancellationToken token = default) |
带回调的异步变体 |
LogOperateSet |
OperateResult LogOperateSet(bool logOut = true, bool? consoleOut = null, Func<object, LogEventLevel, string?, Exception?, Task>? noticeAsync = null) |
带异步通知回调 |
LogOperateSetAsync |
Task<OperateResult> LogOperateSetAsync(bool logOut = true, bool? consoleOut = null, Func<object, LogEventLevel, string?, Exception?, Task>? noticeAsync = null, CancellationToken token = default) |
带异步回调的异步变体 |
LogOperateSetAsync |
Task<OperateResult> LogOperateSetAsync(LogModel logModel, CancellationToken token = default) |
应用完整 LogModel |
LogOperateGetAsync |
Task<OperateResult> LogOperateGetAsync(CancellationToken token = default) |
获取当前实例日志状态 |
💻 代码示例
基本日志记录
using Snet.Log;
LogHelper.Info("=== Snet DAQ Service Starting ===");
LogHelper.Debug($"Configuration path: {configPath}");
LogHelper.Info($"Loaded {deviceCount} device definitions.");
LogHelper.Warning("Sensor SN-00321 has not reported in 60 seconds.");
LogHelper.Error($"Failed to write value to address {addr}: {er.Message}");
LogHelper.Fatal("Unrecoverable error in main loop. Shutting down.", exception: ex);
带异常的结构化日志
try
{
var result = await modbus.ReadAsync(new Address(new AddressDetails("SN", "M100", DataType.Float)));
LogHelper.Info($"Read {result.Message} from device");
}
catch (TimeoutException ex)
{
LogHelper.Error($"Device timeout: {ex.Message}", exception: ex);
}
catch (Exception ex)
{
LogHelper.Fatal($"Unexpected error communicating with device: {ex.Message}", exception: ex);
// 启动恢复或关闭流程
}
后台服务的异步日志
public class DataAcquisitionService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await LogHelper.InfoAsync("Data acquisition service started.");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await AcquireDataCycleAsync();
await LogHelper.DebugAsync("Acquisition cycle completed successfully.");
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
await LogHelper.InfoAsync("Service stopping by cancellation request.");
break;
}
catch (Exception ex)
{
await LogHelper.ErrorAsync("Acquisition cycle failed.", exception: ex);
}
await Task.Delay(1000, stoppingToken);
}
await LogHelper.InfoAsync("Data acquisition service stopped.");
}
private async Task AcquireDataCycleAsync() { /* ... */ }
}
❓ 常见问题
1. 如何更改最低日志级别?
最低级别在 LogCore 中固定为 Verbose(.MinimumLevel.Verbose())。过滤通过按调用选择合适级别的方法实现。要减少噪音,请在生产代码路径中使用更高级别的方法(Info、Warning 等),或禁用控制台输出:
await CoreUnify.LogOperateSetAsync(logOut: true, consoleOut: false);
2. 如何添加自定义 Serilog 接收器?
LogHelper 通过 Serilog 内部配置接收器。要添加自定义接收器(如 Seq、Elasticsearch 或数据库),请在 LogHelper 初始化之前自定义 Serilog 管道的 Serilog.LoggerConfiguration。有关高级接收器配置的指导,请联系 Snet 框架团队。
3. 日志文件存储在哪里?
默认情况下,日志文件写入 FileLocation(默认:AppContext.BaseDirectory)下的 logs/ 目录(可用 FolderName 覆盖),文件名遵循 snet-{yyyyMMdd}.log 模式。HistoryTime 控制保留天数:
var model = new LogModel { FolderName = @"D:\AppLogs\Snet", HistoryTime = 60 };
LogHelper.Set(model);
4. 应该使用同步还是异步日志方法?
在异步上下文中(如 ASP.NET Core、后台服务和异步事件处理器),应优先使用异步方法(InfoAsync、ErrorAsync 等)。同步方法适用于控制台应用程序、同步代码路径和无法使用 async 的构造函数。两种变体以相同的格式写入相同的接收器。
5. Snet.Log 是线程安全的吗?
是的。Serilog 的 Log.Logger 是完全线程安全的。LogHelper 使用静态单例包装它,使所有日志调用在任何线程或任务中都是安全的。
6. 日志记录会影响性能吗?
通过 Serilog 的结构化日志设计为低开销。文件接收器使用异步缓冲写入器。对于生产环境中的高频 debug/verbose 日志记录,请优先使用 Info/Warning 级别方法以避免过多的 I/O,并在生产中禁用控制台接收器:
await CoreUnify.LogOperateSetAsync(logOut: true, consoleOut: false);
📅 版本历史
| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-08-02 | — | 与源码对齐:Warning(非 Warn)、真实 LogModel 属性、真实 CoreUnify 日志方法签名 |
| 2026-07-23 | — | 当前版本;Serilog 4.4.0,.NET 8/10 支持 |
