日志系统 - Snet Docs

📘 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.LogSnet.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 参数,外加可选的 foldernameexceptionconsoleShow 参数。泛型重载接受任意消息对象并配对一个消息模板。

同步方法

方法 签名 描述
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())。过滤通过按调用选择合适级别的方法实现。要减少噪音,请在生产代码路径中使用更高级别的方法(InfoWarning 等),或禁用控制台输出:

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、后台服务和异步事件处理器),应优先使用异步方法(InfoAsyncErrorAsync 等)。同步方法适用于控制台应用程序同步代码路径和无法使用 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 支持