📘 Snet.Log 概述
Snet.Log 基于 Serilog 为 Snet 框架提供结构化日志记录。它提供 6 个日志级别(Verbose、Debug、Info、Warn、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.Warn("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.", 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() |
正常操作事件(启动、关闭、里程碑) |
| Warn | Warn() / WarnAsync() |
潜在问题,降级但仍可用状态 |
| Error | Error() / ErrorAsync() |
操作失败,异常,数据丢失 |
| Fatal | Fatal() / FatalAsync() |
应用程序崩溃事件,不可恢复状态 |
3. LogModel 配置
LogModel 类控制每个应用程序实例的日志行为:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
LogOut |
bool |
true |
启用/禁用所有日志输出 |
ConsoleOut |
bool |
true |
启用/禁用控制台接收器 |
FileOut |
bool |
true |
启用/禁用文件接收器 |
LogLevel |
int |
0 |
最低日志级别(0=Verbose 至 5=Fatal) |
LogPath |
string |
"Logs" |
日志文件输出目录 |
4. BeginOperate / EndOperate 集成
每个 Snet 模块调用 LogHelper.BeginOperate() / LogHelper.EndOperate() 将其日志配置注册到 CoreUnify 系统中。这使得可以在没有全局状态冲突的情况下实现按模块的日志设置。
5. 通过 CoreUnify 实现按实例配置
对于不同组件需要不同日志配置的高级场景,请使用 CoreUnify:
// 为特定模块实例设置日志配置
await CoreUnify.LogOperateSetAsync(moduleKey, logModel);
// 获取当前日志配置
var config = await CoreUnify.LogOperateGetAsync(moduleKey);
📚 API 参考
LogHelper 方法
所有方法接受一个 string message 参数。Error 和 Fatal 方法额外接受一个可选的 Exception 参数。
同步方法
| 方法 | 签名 | 描述 |
|---|---|---|
Info |
void Info(string message) |
Info 级别日志记录 |
Error |
void Error(string message, Exception? ex = null) |
Error 级别日志记录 |
Warn |
void Warn(string message) |
Warn 级别日志记录 |
Debug |
void Debug(string message) |
Debug 级别日志记录 |
Verbose |
void Verbose(string message) |
Verbose 级别日志记录 |
Fatal |
void Fatal(string message, Exception? ex = null) |
Fatal 级别日志记录 |
异步方法
| 方法 | 签名 | 描述 |
|---|---|---|
InfoAsync |
Task InfoAsync(string message) |
异步 Info 级别日志记录 |
ErrorAsync |
Task ErrorAsync(string message, Exception? ex = null) |
异步 Error 级别日志记录 |
WarnAsync |
Task WarnAsync(string message) |
异步 Warn 级别日志记录 |
DebugAsync |
Task DebugAsync(string message) |
异步 Debug 级别日志记录 |
VerboseAsync |
Task VerboseAsync(string message) |
异步 Verbose 级别日志记录 |
FatalAsync |
Task FatalAsync(string message, Exception? ex = null) |
异步 Fatal 级别日志记录 |
CoreUnify 日志操作
| 方法 | 签名 | 描述 |
|---|---|---|
LogOperateSetAsync |
Task LogOperateSetAsync(string key, LogModel model) |
为模块实例应用日志配置 |
LogOperateGetAsync |
Task<LogModel?> LogOperateGetAsync(string key) |
获取模块实例的日志配置 |
💻 代码示例
基本日志记录
using Snet.Log;
LogHelper.Info("=== Snet DAQ Service Starting ===");
LogHelper.Debug($"Configuration path: {configPath}");
LogHelper.Info($"Loaded {deviceCount} device definitions.");
LogHelper.Warn("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.", ex);
带异常的结构化日志
try
{
var data = await modbus.ReadHoldingRegistersAsync(deviceId, startAddress, count);
LogHelper.Info($"Read {data.Length} registers from device {deviceId}");
}
catch (TimeoutException ex)
{
LogHelper.Error($"Device {deviceId} timeout: {ex.Message}", ex);
}
catch (Exception ex)
{
LogHelper.Fatal($"Unexpected error communicating with device {deviceId}", ex);
// 启动恢复或关闭流程
}
在 DAQ 驱动上下文中记录日志
public class ModbusTcpDriver : CommunicationAbstract
{
private readonly string _driverKey;
public override async Task<OperateResult> OnAsync(BasicsAbstract basics)
{
// 配置实例特定的日志
var logModel = new LogModel
{
LogOut = true,
ConsoleOut = true,
FileOut = true,
LogLevel = 0, // 驱动开发阶段使用 Verbose 级别
LogPath = Path.Combine("Logs", "Drivers", _driverKey)
};
await CoreUnify.LogOperateSetAsync(_driverKey, logModel);
LogHelper.Info($"[{_driverKey}] Driver initializing...");
var result = await base.OnAsync(basics);
LogHelper.Info($"[{_driverKey}] Driver init result: {result.Status}");
return result;
}
public override async Task<OperateResult<byte[]>> ReadAsync(string address, ushort length)
{
LogHelper.Debug($"[{_driverKey}] READ request: address={address}, length={length}");
var result = await base.ReadAsync(address, length);
if (!result.Status)
LogHelper.Error($"[{_driverKey}] READ failed: {result.Message}");
else
LogHelper.Verbose($"[{_driverKey}] READ success: {result.ResultData!.Length} bytes");
return result;
}
public override async Task<OperateResult> WriteAsync(string address, byte[] data)
{
LogHelper.Debug($"[{_driverKey}] WRITE request: address={address}, length={data.Length}");
var result = await base.WriteAsync(address, data);
if (!result.Status)
LogHelper.Error($"[{_driverKey}] WRITE failed: {result.Message}");
return result;
}
}
后台服务的异步日志
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.", ex);
}
await Task.Delay(1000, stoppingToken);
}
await LogHelper.InfoAsync("Data acquisition service stopped.");
}
private async Task AcquireDataCycleAsync() { /* ... */ }
}
❓ 常见问题
1. 如何更改最低日志级别?
在 LogModel 中设置 LogLevel 属性,然后通过 CoreUnify.LogOperateSetAsync() 应用:
LogLevel 值 |
显示的最低级别 |
|---|---|
0 |
Verbose(全部) |
1 |
Debug 及以上 |
2 |
Info 及以上 |
3 |
Warn 及以上 |
4 |
Error 及以上 |
5 |
仅 Fatal |
var model = new LogModel { LogLevel = 3 }; // 仅显示 Warn、Error、Fatal
await CoreUnify.LogOperateSetAsync("MyModule", model);
2. 如何添加自定义 Serilog 接收器?
LogHelper 通过 Serilog 内部配置接收器。要添加自定义接收器(如 Seq、Elasticsearch 或数据库),请在 LogHelper 初始化之前自定义 Serilog 管道的 Serilog.LoggerConfiguration。有关高级接收器配置的指导,请联系 Snet 框架团队。
3. 日志文件存储在哪里?
默认情况下,日志文件写入应用程序工作目录下的 Logs/ 目录,文件名遵循 snet-{yyyyMMdd}.log 模式。LogModel 上的 LogPath 属性控制输出目录:
var model = new LogModel { LogPath = @"D:\AppLogs\Snet" };
4. 应该使用同步还是异步日志方法?
在异步上下文中(如 ASP.NET Core、后台服务和异步事件处理器),应优先使用异步方法(InfoAsync、ErrorAsync 等)。同步方法适用于控制台应用程序、同步代码路径和无法使用 async 的构造函数。两种变体以相同的格式写入相同的接收器。
5. Snet.Log 是线程安全的吗?
是的。Serilog 的 Log.Logger 是完全线程安全的。LogHelper 使用静态单例包装它,使所有日志调用在任何线程或任务中都是安全的。
6. 日志记录会影响性能吗?
通过 Serilog 的结构化日志设计为低开销。文件接收器使用异步缓冲写入器。对于生产环境中的高频 debug/verbose 日志记录,请提高最低 LogLevel 以避免过多的 I/O。仅在开发或排查问题期间使用 Verbose 和 Debug 级别。
📅 版本历史
| 日期 | 版本 | 变更 |
|---|---|---|
| 2026-07-23 | — | 当前版本;Serilog 4.4.0,.NET 8/10 支持 |
