diff --git a/Ayay.SerilogLogs/LogBootstrapper.cs b/Ayay.SerilogLogs/LogBootstrapper.cs index 5e37ce1..3b8c876 100644 --- a/Ayay.SerilogLogs/LogBootstrapper.cs +++ b/Ayay.SerilogLogs/LogBootstrapper.cs @@ -1,7 +1,8 @@ using Serilog; +using Serilog.Enrichers.Span; // Nuget: Serilog.Enrichers.Span using Serilog.Events; using Serilog.Exceptions; // Nuget: Serilog.Exceptions -using Serilog.Enrichers.Span; // Nuget: Serilog.Enrichers.Span +using Serilog.Exceptions.Core; using System; using System.IO; using System.Text; @@ -61,11 +62,19 @@ namespace Ayay.SerilogLogs // 2.3 注入全套元数据 (Enrichers) - 让日志更聪明 builder + // 注入全套元数据 (Enrichers) - 让日志更聪明 .Enrich.FromLogContext() // 允许使用 .ForContext() 注入上下文 .Enrich.WithProperty("AppId", opts.AppId) // 注入应用标识 + .Enrich.WithProperty("PcCode", opts.PcCode) // 注入应用标识 + .Enrich.WithMachineName() // [环境] 区分是哪台工控机 (建议加上) .Enrich.WithThreadId() // 线程ID .Enrich.WithProcessId() // 进程ID (用于识别重启) .Enrich.WithExceptionDetails() // 结构化异常堆栈 + // [异常] 结构化异常拆解 (非常强大) + // 它能把 ex.Data 和 InnerException 自动转成 JSON,而不是单纯的一堆字符串 + .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder() + .WithDefaultDestructurers()) + //.WithDestructurers(new[] { new SqlExceptionDestructurer() })) // 如果有数据库操作,这行很关键 .Enrich.WithSpan(); // 全链路追踪 ID // -------------------------------------------------------- diff --git a/Ayay.SerilogLogs/LogModules.cs b/Ayay.SerilogLogs/LogModules.cs index 8d1d3f1..79e765e 100644 --- a/Ayay.SerilogLogs/LogModules.cs +++ b/Ayay.SerilogLogs/LogModules.cs @@ -7,22 +7,24 @@ public static class LogModules { // --- 核心架构层 --- - public const string Core = "Core"; // 系统主逻辑/启动关闭 - public const string Network = "Network"; // 底层网络通讯 (TCP/UDP) - public const string WebApi = "WebAPI"; // 对外 HTTP 接口 - public const string WebSocket = "WebSocket"; // 实时通讯 - public const string Ping = "Ping"; // 心跳/Ping包 (通常量大且不重要) + public static string Core { get; } = "Core"; // 系统主逻辑/启动关闭 + public static string Network { get; } = "Network"; // 底层网络通讯 (TCP/UDP) + public static string WebApi { get; } = "WebAPI"; // 对外 HTTP 接口 + public static string gRpc { get; } = "gRPC"; // 对外 gRPC 接口 + + public static string WebSocket { get; } = "WebSocket"; // 实时通讯 + public static string Ping { get; } = "Ping"; // 心跳/Ping包 (通常量大且不重要) // --- 业务逻辑层 --- - public const string UserSystem = "UserSystem"; // 用户账户/权限系统 - public const string UserAction = "UserAction"; // 用户操作行为 (审计日志) - public const string DeviceOps = "DeviceOps"; // 设备运行/控制指令 + public static string UserSystem { get; } = "UserSystem"; // 用户账户/权限系统 + public static string UserAction { get; } = "UserAction"; // 用户操作行为 (审计日志) + public static string DeviceOps { get; } = "DeviceOps"; // 设备运行/控制指令 // --- 核心算法层 --- - public const string Algorithm = "Algorithm"; // 算法分析/AI识别 (需要上下文追踪) - public const string Observation = "Observation"; // 观察点/埋点 (用于调试或统计) + public static string Algorithm { get; } = "Algorithm"; // 算法分析/AI识别 (需要上下文追踪) + public static string Observation { get; } = "Observation"; // 观察点/埋点 (用于调试或统计) // --- 第三方集成 --- - public const string Sdk = "SDK"; // 第三方 SDK 调用封装 + public static string HikVisionSdk { get; } = "HikVisionSdk"; // 第三方 SDK 调用封装 } } \ No newline at end of file diff --git a/Ayay.SerilogLogs/LogOptions.cs b/Ayay.SerilogLogs/LogOptions.cs index 1603575..a18c925 100644 --- a/Ayay.SerilogLogs/LogOptions.cs +++ b/Ayay.SerilogLogs/LogOptions.cs @@ -50,6 +50,11 @@ namespace Ayay.SerilogLogs /// public string SeqApiKey { get; set; } + /// + /// 机器码 + /// + public string PcCode { get; set; } + // ========================================== // 4. 输出端级别控制 (Sink Levels) // 用于控制不同媒介的“过滤网”疏密程度 @@ -94,23 +99,24 @@ namespace Ayay.SerilogLogs public Dictionary ModuleLevels { get; set; } = new Dictionary { // --- 系统层 --- - { LogModules.Core, LogEventLevel.Debug }, // 系统主逻辑 - { LogModules.Network, LogEventLevel.Debug }, // 网络通讯:平时只看警告,防止心跳刷屏 - { LogModules.WebApi, LogEventLevel.Debug }, // WebAPI:记录请求响应 + { LogModules.Core, LogEventLevel.Debug }, // 系统主逻辑 + { LogModules.Network, LogEventLevel.Debug }, // 网络通讯:平时只看警告,防止心跳刷屏 + { LogModules.WebApi, LogEventLevel.Debug }, // WebAPI:记录请求响应 + { LogModules.gRpc, LogEventLevel.Debug }, // gRpc:记录请求响应 // --- 业务层 --- - { LogModules.UserSystem, LogEventLevel.Debug }, // 用户系统 - { LogModules.UserAction, LogEventLevel.Debug }, // 用户操作:必须记录,用于审计 - { LogModules.DeviceOps, LogEventLevel.Debug }, // 设备操作:记录关键指令 + { LogModules.UserSystem, LogEventLevel.Debug }, // 用户系统 + { LogModules.UserAction, LogEventLevel.Debug }, // 用户操作:必须记录,用于审计 + { LogModules.DeviceOps, LogEventLevel.Debug }, // 设备操作:记录关键指令 // --- 核心/高频数据 --- - { LogModules.Algorithm, LogEventLevel.Debug }, // 算法:核心业务,开启 Debug 以记录全过程 - { LogModules.Observation, LogEventLevel.Debug }, // 观察点:最详细的埋点 + { LogModules.Algorithm, LogEventLevel.Debug }, // 算法:核心业务,开启 Debug 以记录全过程 + { LogModules.Observation, LogEventLevel.Debug }, // 观察点:最详细的埋点 // --- 降噪区 (垃圾数据屏蔽) --- - { LogModules.WebSocket, LogEventLevel.Debug }, // WS:数据量极大,除非报错否则不记 - { LogModules.Ping, LogEventLevel.Debug }, // Ping:几乎不记,除非完全断连 - { LogModules.Sdk, LogEventLevel.Debug } // SDK:屏蔽第三方的废话日志 + { LogModules.WebSocket, LogEventLevel.Debug }, // WS:数据量极大,除非报错否则不记 + { LogModules.Ping, LogEventLevel.Debug }, // Ping:几乎不记,除非完全断连 + { LogModules.HikVisionSdk, LogEventLevel.Debug } // SDK:屏蔽第三方的废话日志 }; // ========================================== diff --git a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs index 1678ed6..394fccf 100644 --- a/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs +++ b/SHH.CameraSdk/Drivers/HikVision/HikVideoSource.cs @@ -1,4 +1,6 @@ -using OpenCvSharp; +using Ayay.SerilogLogs; +using OpenCvSharp; +using Serilog; using SHH.CameraSdk.HikFeatures; using System.Runtime.ExceptionServices; using System.Security; @@ -17,6 +19,8 @@ public class HikVideoSource : BaseVideoSource, { #region --- 静态资源 (Global Resources) --- + // 日志实例 + private static ILogger _sdkLog = Log.ForContext("SourceContext", LogModules.HikVisionSdk); // 静态路由表 private static readonly ConcurrentDictionary _instances = new(); // 全局异常回调 @@ -103,14 +107,18 @@ public class HikVideoSource : BaseVideoSource, if (currentEpoch != _connectionEpoch) return; if (!HikSdkManager.Initialize()) - throw new CameraException(CameraErrorCode.SdkNotInitialized, "SDK初始化失败", DeviceBrand.HikVision); + { + _sdkLog.Error("HikVision Sdk 初始化失败."); + throw new CameraException(CameraErrorCode.SdkNotInitialized, "HikVision Sdk 初始化失败.", DeviceBrand.HikVision); + } try { HikNativeMethods.NET_DVR_SetExceptionCallBack_V30(0, IntPtr.Zero, _globalExceptionCallback, IntPtr.Zero); // [审计] 记录登录动作 - AddAuditLog($"正在执行物理登录... ({_config.IpAddress})"); + AddAuditLog($"Hik 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}"); + _sdkLog.Information($"Hik 正在执行登录 => ID:{_config.Id} IP:{_config.IpAddress} Port:{_config.Port} Name:{_config.Name}"); var devInfo = new HikNativeMethods.NET_DEVICEINFO_V30(); int newUserId = HikNativeMethods.NET_DVR_Login_V30( diff --git a/SHH.CameraService/Bootstrapper.cs b/SHH.CameraService/Bootstrapper.cs index 5cededc..f3e3e9a 100644 --- a/SHH.CameraService/Bootstrapper.cs +++ b/SHH.CameraService/Bootstrapper.cs @@ -1,10 +1,11 @@ -using System.Net.Sockets; -using System.Net; -using Ayay.SerilogLogs; +using Ayay.SerilogLogs; using Grpc.Net.Client; using Serilog; using SHH.CameraSdk; using SHH.Contracts.Grpc; +using System.Net; +using System.Net.Sockets; +using System.Text.RegularExpressions; namespace SHH.CameraService; @@ -37,8 +38,8 @@ public static class Bootstrapper "--uris", "localhost,9001,command,调试PC;", // 日志中心配置 (格式: IP,Port,Desc) - "--sequris", "172.16.41.241,20026,日志处置中心;", - "--seqkey", "Shine899195994250;", + "--sequris", "58.216.225.5,20026,日志处置中心;", + "--seqkey", "Shine101173874928;", // 端口策略 "--mode", "1", @@ -48,6 +49,7 @@ public static class Bootstrapper var config = ServiceConfig.BuildFromArgs(args); + string pcCode = config.SeqApiKey.Replace("Shine", ""); var ops = new LogOptions { AppId = config.AppId, @@ -56,6 +58,7 @@ public static class Bootstrapper // ★这里改为从 config 读取,如果没配则留空或给个默认值 SeqServerUrl = config.SeqServerUrl, SeqApiKey = config.SeqApiKey, + PcCode = Regex.Replace(pcCode, ".{3}", "$0-").TrimEnd('-'), MaxRetentionDays = 10, FileSizeLimitBytes = 1024L * 1024 * 1024, @@ -234,9 +237,10 @@ public static class Bootstrapper /// /// 向网关注册实例 /// - public static async Task RegisterToGatewayAsync(ServiceConfig config, ILogger logger) + public static async Task RegisterToGatewayAsync(ServiceConfig config) { if (!config.CommandEndpoints.Any()) return; + var gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc); try { @@ -246,7 +250,7 @@ public static class Bootstrapper using var channel = GrpcChannel.ForAddress(targetUrl); var client = new GatewayProvider.GatewayProviderClient(channel); - logger.Information($"[Grpc] 正在执行预注册: {targetUrl}"); + gRpcLog.Information($"[gRPC] 正在执行预注册: {targetUrl}"); var resp = await client.RegisterInstanceAsync(new RegisterRequest { InstanceId = config.AppId, @@ -257,11 +261,11 @@ public static class Bootstrapper ProcessId = Environment.ProcessId, Description = "" }); - logger.Information($"💡[Grpc] 预注册成功: {resp.Message}"); + gRpcLog.Information($"💡[gRPC] 预注册成功: {resp.Message}"); } catch (Exception ex) { - logger.Error($"⚠️ [Grpc] 预注册尝试失败: {ex.Message}"); + gRpcLog.Error($"⚠️ [gRPC] 预注册尝试失败: {ex.Message}"); } } diff --git a/SHH.CameraService/Core/CameraEngineWorker.cs b/SHH.CameraService/Core/CameraEngineWorker.cs index 6d78bc8..8cd18c5 100644 --- a/SHH.CameraService/Core/CameraEngineWorker.cs +++ b/SHH.CameraService/Core/CameraEngineWorker.cs @@ -1,6 +1,11 @@ -using Microsoft.Extensions.Hosting; +using Ayay.SerilogLogs; +using Microsoft.Extensions.Hosting; +using Serilog; using SHH.CameraSdk; +/// +/// 设备管理服务引擎 Worker +/// public class CameraEngineWorker : BackgroundService { private readonly CameraManager _manager; @@ -13,17 +18,18 @@ public class CameraEngineWorker : BackgroundService protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - Console.WriteLine("[Engine] 正在启动核心引擎..."); + var sysLog = Log.ForContext("SourceContext", LogModules.Core); + sysLog.Information("[Engine] 正在启动设备管理服务引擎..."); try { // 1. 理由:启动 SDK 内部加载流程(从本地存储恢复设备) await _manager.StartAsync(); - Console.WriteLine("[Engine] 设备管理服务已启动。"); + sysLog.Warning("[Engine] 设备管理服务引擎已启动..."); } catch (Exception ex) { - Console.WriteLine($"[Engine] 严重启动异常: {ex.Message}"); + sysLog.Error($"[Engine] 设备管理服务引擎启动异常: {ex.Message}"); return; // 理由:核心组件失败,终止后续逻辑 } @@ -31,14 +37,15 @@ public class CameraEngineWorker : BackgroundService while (!stoppingToken.IsCancellationRequested) { // 你可以在这里定期输出一些状态统计 - // Console.WriteLine($"[Engine] 活跃设备数: {_manager.GetActiveCount()}"); + sysLog.Debug($"[Engine] 管理设备数: {_manager.GetAllCameras().Count()}"); await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken); } } public override async Task StopAsync(CancellationToken cancellationToken) { - Console.WriteLine("[Engine] 正在执行优雅停机..."); + var sysLog = Log.ForContext("SourceContext", LogModules.Core); + sysLog.Debug($"[Engine] 正在执行优雅停机: {_manager.GetAllCameras()}"); try { // 理由:这是重构的核心。必须在 SDK 退出前释放所有非托管句柄 diff --git a/SHH.CameraService/Core/CommandDispatcher.cs b/SHH.CameraService/Core/CommandDispatcher.cs index 72a7c2c..917e2e3 100644 --- a/SHH.CameraService/Core/CommandDispatcher.cs +++ b/SHH.CameraService/Core/CommandDispatcher.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Ayay.SerilogLogs; +using Newtonsoft.Json.Linq; +using Serilog; using SHH.Contracts.Grpc; namespace SHH.CameraService; @@ -9,6 +11,7 @@ namespace SHH.CameraService; /// public class CommandDispatcher { + private static ILogger _gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc); private readonly Dictionary _handlers; /// @@ -32,7 +35,8 @@ public class CommandDispatcher if (protoMsg == null) return; string cmdCode = protoMsg.CmdCode; // 例如 "Sync_Camera" - Console.WriteLine($"[Dispatcher] 收到远程指令: {cmdCode}, 请求ID: {protoMsg.RequestId}"); + _gRpcLog.Information($"[gRPC] 响应请求, 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 业务分发."); + _gRpcLog.Debug($"[gRPC] 响应请求, {protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 业务分发 => {protoMsg}"); try { @@ -47,16 +51,16 @@ public class CommandDispatcher // 3. 调用具体业务执行 await handler.ExecuteAsync(token); - Console.WriteLine($"[Dispatcher] 指令 {cmdCode} 执行成功。"); + _gRpcLog.Information($"[gRPC] 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 执行成功."); } else { - Console.WriteLine($"[Dispatcher Warning] 未找到指令处理器: {cmdCode}"); + _gRpcLog.Warning($"[gRPC] 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 未找到指令处理器."); } } catch (Exception ex) { - Console.WriteLine($"[Dispatcher Error] 执行指令 {cmdCode} 异常: {ex.Message}"); + _gRpcLog.Error($"[gRPC] 业务:{protoMsg.CmdCode}, 请求ID:{protoMsg.RequestId}, 执行指令处理异常: {ex.Message}."); } // 注意:关于 ACK (require_ack) diff --git a/SHH.CameraService/GrpcImpls/Handlers/DeviceConfigHandler.cs b/SHH.CameraService/GrpcImpls/Handlers/DeviceConfigHandler.cs index 61622ac..f7b68e8 100644 --- a/SHH.CameraService/GrpcImpls/Handlers/DeviceConfigHandler.cs +++ b/SHH.CameraService/GrpcImpls/Handlers/DeviceConfigHandler.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Ayay.SerilogLogs; +using Newtonsoft.Json.Linq; +using Serilog; using SHH.CameraSdk; using SHH.Contracts; @@ -9,6 +11,7 @@ namespace SHH.CameraService; /// public class DeviceConfigHandler : ICommandHandler { + private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core); private readonly CameraManager _cameraManager; /// diff --git a/SHH.CameraService/GrpcImpls/Handlers/GatewayService.cs b/SHH.CameraService/GrpcImpls/Handlers/GatewayService.cs index 0a54238..525aea4 100644 --- a/SHH.CameraService/GrpcImpls/Handlers/GatewayService.cs +++ b/SHH.CameraService/GrpcImpls/Handlers/GatewayService.cs @@ -1,8 +1,8 @@ -using Grpc.Core; +using Ayay.SerilogLogs; +using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json.Linq; +using Serilog; using SHH.CameraSdk; using SHH.Contracts.Grpc; // 引用 Proto 生成的命名空间 @@ -17,24 +17,23 @@ namespace SHH.CameraService /// public class GatewayService : BackgroundService { - private readonly ILogger _logger; private readonly ServiceConfig _config; private readonly CommandDispatcher _dispatcher; public GatewayService( - ILogger logger, ServiceConfig config, CommandDispatcher dispatcher) { - _logger = logger; _config = config; _dispatcher = dispatcher; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + var gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc); + // 预留系统启动缓冲时间,确保数据库和 SDK 已就绪 - _logger.LogInformation("[gRPC Bus] 指令接收服务启动,等待环境预热..."); + gRpcLog.Information("[gRPC] 指令接收服务启动,等待环境预热..."); await Task.Delay(3000, stoppingToken); while (!stoppingToken.IsCancellationRequested) @@ -49,7 +48,7 @@ namespace SHH.CameraService var client = new GatewayProvider.GatewayProviderClient(channel); // --- 第一步:发起节点逻辑注册 (Unary) --- - _logger.LogInformation("[gRPC Bus] 正在发起逻辑注册: {Url}", targetUrl); + gRpcLog.Information("[gRPC] 正在发起逻辑注册: {Url}", targetUrl); var regResp = await client.RegisterInstanceAsync(new RegisterRequest { InstanceId = _config.AppId, @@ -60,7 +59,7 @@ namespace SHH.CameraService if (regResp.Success) { - _logger.LogInformation("[gRPC Bus] 注册成功。正在建立双向指令通道..."); + gRpcLog.Information("[gRPC] 注册成功, 正在建立双向指令通道..."); // --- 第二步:开启 Server Streaming 指令流 --- using var call = client.OpenCommandChannel(new CommandStreamRequest @@ -87,13 +86,14 @@ namespace SHH.CameraService } catch (RpcException ex) { - _logger.LogError("[gRPC Bus] RPC 异常 (Status: {Code}): {Msg}", ex.StatusCode, ex.Message); + gRpcLog.Debug("[gRPC] RPC 异常 (Status: {Code}): {Msg}", ex.StatusCode, ex.Message); + // 链路异常,进入重连等待阶段 await Task.Delay(5000, stoppingToken); } catch (Exception ex) { - _logger.LogError("[gRPC Bus] 非预期链路异常: {Msg},5秒后尝试重连", ex.Message); + gRpcLog.Debug("[gRPC] 非预期链路异常: {Msg},5秒后尝试重连", ex.Message); await Task.Delay(5000, stoppingToken); } } diff --git a/SHH.CameraService/Program.cs b/SHH.CameraService/Program.cs index 03b07ab..8c129fb 100644 --- a/SHH.CameraService/Program.cs +++ b/SHH.CameraService/Program.cs @@ -46,7 +46,7 @@ public class Program config.UpdateActualPort(activePort); // 回填端口 // 具体的 gRPC 链接逻辑封装在 Bootstrapper 中,保持 Main 清爽但逻辑可见 - await Bootstrapper.RegisterToGatewayAsync(config, sysLog); + await Bootstrapper.RegisterToGatewayAsync(config); // ============================================================= // 3. 构建 Web 主机环境 diff --git a/SHH.CameraService/Utils/ServiceCollectionExtensions.cs b/SHH.CameraService/Utils/ServiceCollectionExtensions.cs index 2d19ba0..a5e662d 100644 --- a/SHH.CameraService/Utils/ServiceCollectionExtensions.cs +++ b/SHH.CameraService/Utils/ServiceCollectionExtensions.cs @@ -85,6 +85,8 @@ public static class ServiceCollectionExtensions } logger.Information("📋 加载视频流目标: {Count} 个", netTargets.Count); + if (netTargets.Count > 0) + logger.Debug("🔍 视频流目标详情: {@Targets}", netTargets); services.AddSingleton>(netTargets); services.AddHostedService();