规范并补充日志内容

This commit is contained in:
2026-01-16 14:30:42 +08:00
parent 4e0bb33ce2
commit fd6a82eb4e
28 changed files with 325 additions and 537 deletions

View File

@@ -12,6 +12,7 @@ namespace SHH.CameraService;
public class DeviceConfigHandler : ICommandHandler
{
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
private readonly CameraManager _cameraManager;
/// <summary>
@@ -42,12 +43,15 @@ public class DeviceConfigHandler : ICommandHandler
// 2. 尝试获取现有设备
var device = _cameraManager.GetDevice(dto.Id);
string op = device != null ? "更新" : "新增";
_sysLog.Warning($"[Sync] 即将{op}设备配置 => ID:{dto.Id} Name:{dto.Name} IP:{dto.IpAddress} Port:{dto.Port} Brand:{(DeviceBrand)dto.Brand} Rtsp:{dto.RtspPath}");
_sysLog.Debug($"[Sync] 即将{op}设备配置 => ID:{dto.Id} Name:{dto.Name} IP:{dto.IpAddress} 详情:" + "{@dto}", dto, dto.AutoSubscriptions);
if (device != null)
{
// =========================================================
// 场景 A: 设备已存在 -> 执行智能更新 (Smart Update)
// =========================================================
Console.WriteLine($"[Sync] 更新设备配置: {dto.Id} ({dto.Name})");
// 将全量配置映射为部分更新 DTO
var updateDto = new DeviceUpdateDto
@@ -86,7 +90,6 @@ public class DeviceConfigHandler : ICommandHandler
// =========================================================
// 场景 B: 设备不存在 -> 执行新增 (Add New)
// =========================================================
Console.WriteLine($"[Sync] 新增设备: {dto.Id} ({dto.Name})");
// 构造全新的设备配置
var newConfig = new VideoSourceConfig
@@ -126,7 +129,7 @@ public class DeviceConfigHandler : ICommandHandler
// 情况 1: 收到“启动”指令
if (!device.IsOnline) // 只有没在线时才点火
{
Console.WriteLine($"[Sync] 指令:立即启动设备 {dto.Id}");
_sysLog.Warning($"[Sync] 设备立即启动 => ID:{dto.Id} Name:{dto.Name} IP:{dto.IpAddress} Port:{dto.Port} Brand:{(DeviceBrand)dto.Brand} Rtsp:{dto.RtspPath}");
_ = device.StartAsync();
}
}
@@ -135,7 +138,7 @@ public class DeviceConfigHandler : ICommandHandler
// 情况 2: 收到“停止”指令 (即 ImmediateExecution = false)
if (device.IsOnline) // 只有在线时才熄火
{
Console.WriteLine($"[Sync] 指令:立即停止设备 {dto.Id}");
_sysLog.Warning($"[Sync] 设备立即停止 {dto.Id}");
_ = device.StopAsync();
}
}

View File

@@ -1,7 +1,8 @@
using Grpc.Core;
using Ayay.SerilogLogs;
using Grpc.Core;
using Grpc.Net.Client;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;
using SHH.CameraSdk;
using SHH.Contracts;
using SHH.Contracts.Grpc;
@@ -10,14 +11,15 @@ using System.Collections.Concurrent;
namespace SHH.CameraService;
/// <summary>
/// 设备状态监控工作者 (gRPC 版)
/// 职责:监控相机状态并在状态变更或心跳周期内,通过 gRPC 批量上报至所有配置的端点
/// 设备状态监控工作者 (gRpc 版)
/// 职责:监控相机状态并在状态变更或心跳周期内,通过 gRpc 批量上报至所有配置的端点
/// </summary>
public class DeviceStatusHandler : BackgroundService
{
private static ILogger _gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc);
private readonly CameraManager _manager;
private readonly ServiceConfig _config;
private readonly ILogger<DeviceStatusHandler> _logger;
// 状态存储CameraId -> 状态载荷
private readonly ConcurrentDictionary<string, StatusEventPayload> _stateStore = new();
@@ -27,12 +29,10 @@ public class DeviceStatusHandler : BackgroundService
public DeviceStatusHandler(
CameraManager manager,
ServiceConfig config,
ILogger<DeviceStatusHandler> logger)
ServiceConfig config)
{
_manager = manager;
_config = config;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
@@ -46,7 +46,7 @@ public class DeviceStatusHandler : BackgroundService
// 2. 订阅 SDK 状态变更事件
_manager.OnDeviceStatusChanged += OnSdkStatusChanged;
_logger.LogInformation("[StatusWorker] gRPC 状态上报已启动,配置节点数: {Count}", _config.CommandEndpoints.Count);
_gRpcLog.Information($"[gRpc] 状态上报已启动,配置节点数: {_config.CommandEndpoints.Count}");
// 3. 定时循环 (1秒1次检查)
var timer = new PeriodicTimer(TimeSpan.FromSeconds(1));
@@ -60,7 +60,7 @@ public class DeviceStatusHandler : BackgroundService
catch (OperationCanceledException) { /* 正常退出 */ }
catch (Exception ex)
{
_logger.LogError(ex, "[StatusWorker] 运行异常");
_gRpcLog.Error($"[gRpc] 状态上报运行异常");
}
finally
{
@@ -96,12 +96,12 @@ public class DeviceStatusHandler : BackgroundService
{
long now = Environment.TickCount64;
// 策略: 有变更(Dirty) 或 超过5秒(强制心跳)
bool shouldSend = _isDirty || (now - _lastSendTick > 5000);
// 策略: 有变更(Dirty) 或 超过 2 秒(强制心跳)
bool shouldSend = _isDirty || (now - _lastSendTick > 2000);
if (shouldSend && _config.CommandEndpoints.Any())
{
// 1. 构建 gRPC 请求包
// 1. 构建 gRpc 请求包
var request = new StatusBatchRequest
{
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
@@ -129,13 +129,12 @@ public class DeviceStatusHandler : BackgroundService
using var channel = GrpcChannel.ForAddress(grpcUrl);
var client = new GatewayProvider.GatewayProviderClient(channel);
// 获取 gRPC 内部生成的服务全称
// 获取 gRpc 内部生成的服务全称
// 这就是客户端尝试调用的真实路径:/包名.服务名/方法名
var methodName = "ReportStatusBatch";
var serviceName = client.GetType().DeclaringType?.Name ?? "Unknown";
_logger.LogInformation("[gRPC Debug] 准备调用端点: {Url}", grpcUrl);
_logger.LogInformation("[gRPC Debug] 客户端契约服务名: {Service}", serviceName);
_gRpcLog.Debug("[gRpc] 准备调用端点: {Url}", grpcUrl);
_gRpcLog.Debug("[gRpc] 客户端契约服务名: {Service}", serviceName);
// 执行调用
var response = await client.ReportStatusBatchAsync(request,
@@ -143,7 +142,8 @@ public class DeviceStatusHandler : BackgroundService
if (response.Success)
{
_logger.LogInformation("[gRPC Success] 上报成功");
_gRpcLog.Information("[gRpc] 设备状态上报成功, 共计: {Count} 个, Url: {Url}", request.Items.Count, grpcUrl);
_gRpcLog.Debug("[gRpc] 设备状态上报成功: {Url} Items:{Items}", grpcUrl, request.Items);
_isDirty = false;
_lastSendTick = Environment.TickCount64;
}
@@ -151,17 +151,17 @@ public class DeviceStatusHandler : BackgroundService
catch (RpcException ex)
{
// 这里是关键:打印 RpcException 的详细状态
_logger.LogError("[gRPC Error] StatusCode: {Code}, Detail: {Detail}", ex.StatusCode, ex.Status.Detail);
_gRpcLog.Error("[gRpc] StatusCode: {Code}, Detail: {Detail}", ex.StatusCode, ex.Status.Detail);
// 如果是 Unimplemented通常意味着路径不对
if (ex.StatusCode == StatusCode.Unimplemented)
{
_logger.LogError("[gRPC Fix] 请检查服务端是否注册了名为 'GatewayProvider' 的服务,且其 package 声明与客户端一致。");
_gRpcLog.Error("[gRpc] 请检查服务端是否注册了名为 'GatewayProvider' 的服务,且其 package 声明与客户端一致。");
}
}
catch (Exception ex)
{
_logger.LogError("[gRPC Fatal] 非 RPC 异常: {Msg}", ex.Message);
_gRpcLog.Error("[gRpc] 非 RPC 异常: {Msg}", ex.Message);
}
}
}

View File

@@ -9,9 +9,9 @@ using SHH.Contracts.Grpc; // 引用 Proto 生成的命名空间
namespace SHH.CameraService
{
/// <summary>
/// gRPC 指令接收后台服务
/// gRpc 指令接收后台服务
/// 职责:
/// 1. 维护与 AiVideo 的 gRPC 长连接。
/// 1. 维护与 AiVideo 的 gRpc 长连接。
/// 2. 完成节点逻辑注册。
/// 3. 监听 Server Streaming 指令流并移交给 Dispatcher。
/// </summary>
@@ -33,7 +33,7 @@ namespace SHH.CameraService
var gRpcLog = Log.ForContext("SourceContext", LogModules.gRpc);
// 预留系统启动缓冲时间,确保数据库和 SDK 已就绪
gRpcLog.Information("[gRPC] 指令接收服务启动,等待环境预热...");
gRpcLog.Information("[gRpc] 指令接收服务启动,等待环境预热...");
await Task.Delay(3000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
@@ -48,7 +48,7 @@ namespace SHH.CameraService
var client = new GatewayProvider.GatewayProviderClient(channel);
// --- 第一步:发起节点逻辑注册 (Unary) ---
gRpcLog.Information("[gRPC] 正在发起逻辑注册: {Url}", targetUrl);
gRpcLog.Information("[gRpc] 正在发起逻辑注册: {Url}", targetUrl);
var regResp = await client.RegisterInstanceAsync(new RegisterRequest
{
InstanceId = _config.AppId,
@@ -59,7 +59,7 @@ namespace SHH.CameraService
if (regResp.Success)
{
gRpcLog.Information("[gRPC] 注册成功, 正在建立双向指令通道...");
gRpcLog.Information("[gRpc] 注册成功, 正在建立双向指令通道...");
// --- 第二步:开启 Server Streaming 指令流 ---
using var call = client.OpenCommandChannel(new CommandStreamRequest
@@ -86,14 +86,14 @@ namespace SHH.CameraService
}
catch (RpcException ex)
{
gRpcLog.Debug("[gRPC] 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)
{
gRpcLog.Debug("[gRPC] 非预期链路异常: {Msg}5秒后尝试重连", ex.Message);
gRpcLog.Debug("[gRpc] 非预期链路异常: {Msg}5秒后尝试重连", ex.Message);
await Task.Delay(5000, stoppingToken);
}
}

View File

@@ -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,8 @@ namespace SHH.CameraService
/// </summary>
public class RemoveCameraHandler : ICommandHandler
{
private static ILogger _sysLog = Log.ForContext("SourceContext", LogModules.Core);
private readonly CameraManager _cameraManager;
/// <summary>
@@ -50,7 +54,7 @@ namespace SHH.CameraService
if (deviceId <= 0)
{
Console.WriteLine($"[{ActionName}] 收到无效指令: ID解析失败 ({payload})");
_sysLog.Warning($"[Sync] 收到无效指令, ID解析失败 ({payload})");
return;
}
@@ -58,26 +62,25 @@ namespace SHH.CameraService
var device = _cameraManager.GetDevice(deviceId);
if (device == null)
{
Console.WriteLine($"[{ActionName}] 设备 {deviceId} 已经不在管理池中,无需操作");
_sysLog.Warning($"[Sync] 设备 {deviceId} 已经不在管理池中,无需操作.");
return;
}
// 3. 安全移除
// 这里建议增加审计日志,记录谁触发了删除(如果协议里有用户信息的话)
device.AddAuditLog("收到远程指令:彻底移除设备");
Console.WriteLine($"[{ActionName}] 正在安全移除设备: {deviceId} ({device.Config.Name})");
_sysLog.Debug($"[Sync] 收到远程指令, 正在安全移除设备, ID:{deviceId} Name:{device.Config.Name} .");
// CameraManager 内部会StopAsync -> DisposeAsync -> TryRemove -> SaveChanges
await _cameraManager.RemoveDeviceAsync(deviceId);
Console.WriteLine($"[{ActionName}] 设备 {deviceId} 已彻底清理并从持久化库中移除");
_sysLog.Information($"[Sync] 收到远程指令, 设备, ID:{deviceId} Name:{device.Config.Name}已彻底清理并从持久化库中移除 .");
// 4. (可选) 此处可以调用 CommandDispatcher 发送 Success ACK
}
catch (Exception ex)
{
// 捕获异常,防止影响全局 Socket 轮询
Console.WriteLine($"[{ActionName}] 移除设备 {deviceId} 过程中发生致命错误: {ex.Message}");
_sysLog.Error($"[Sync] 移除设备, ID:{deviceId} 过程中发生致命错误, {ex.Message}.");
}
}
}