如何用Serilog记录SignalR Hub方法的内容长度、执行时间与结果?
实现SignalR的Serilog结构化日志(类似UseSerilogRequestLogging)
要实现和UseSerilogRequestLogging一致的SignalR日志效果,需要在IHubFilter中补充执行时间统计、请求内容长度计算和执行结果/异常记录,以下是完整实现方案:
1. 完善HubLogFilter实现
using System.Diagnostics; using System.Text.Json; using Microsoft.AspNetCore.SignalR; using Serilog; public class HubSerilogLoggingFilter : IHubFilter { private readonly ILogger<HubSerilogLoggingFilter> _logger; private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { WriteIndented = false }; public HubSerilogLoggingFilter(ILogger<HubSerilogLoggingFilter> logger) { _logger = logger; } public async ValueTask<object?> InvokeMethodAsync( HubInvocationContext invocationContext, Func<HubInvocationContext, ValueTask<object?>> next) { var stopwatch = Stopwatch.StartNew(); long? requestContentLength = null; string status = "Success"; object? result = null; Exception? exception = null; // 计算请求内容长度:序列化Payload为JSON后统计字节数 if (invocationContext.InvocationMessage.Payload != null) { var payloadJson = JsonSerializer.Serialize(invocationContext.InvocationMessage.Payload, _jsonOptions); requestContentLength = System.Text.Encoding.UTF8.GetByteCount(payloadJson); } // 获取IP地址与用户ID var httpContext = invocationContext.Context.GetHttpContext(); var remoteIp = httpContext?.Request.Headers["X-Forwarded-For"].FirstOrDefault(); if (string.IsNullOrEmpty(remoteIp)) { remoteIp = httpContext?.Connection.RemoteIpAddress?.ToString(); } var userId = invocationContext.Context.UserIdentifier ?? "NonUser"; try { // 执行Hub方法 result = await next(invocationContext); stopwatch.Stop(); } catch (Exception ex) { stopwatch.Stop(); status = "Failed"; exception = ex; throw; // 保留原有异常流程,不干扰业务逻辑 } finally { // 结构化日志记录核心信息 _logger.Information( "SignalR {HubName}.{HubMethodName} {RemoteIpAddress} {UserId} {Status} in {Elapsed:0.0000} ms | RequestContentLength: {RequestContentLength}", invocationContext.HubType.Name, invocationContext.HubMethodName, remoteIp, userId, status, stopwatch.Elapsed.TotalMilliseconds, requestContentLength ); // 异常场景额外记录错误详情 if (exception != null) { _logger.Error(exception, "SignalR {HubName}.{HubMethodName} threw exception", invocationContext.HubType.Name, invocationContext.HubMethodName); } // 成功场景按需记录返回结果(用Verbose级别避免日志冗余) if (result != null && status == "Success") { _logger.Verbose( "SignalR {HubName}.{HubMethodName} returned: {Result}", invocationContext.HubType.Name, invocationContext.HubMethodName, JsonSerializer.Serialize(result, _jsonOptions) ); } } return result; } }
2. 注册HubFilter到SignalR
在Program.cs/Startup.cs中注册过滤器:
// Program.cs示例 builder.Services.AddSignalR() .AddHubFilter<HubSerilogLoggingFilter>();
关键实现说明
- 执行时间统计:用
Stopwatch精准记录Hub方法从调用到完成的耗时,输出毫秒级精度数据。 - 请求内容长度:通过序列化
InvocationMessage.Payload为JSON字符串,再计算UTF-8字节数,适配SignalR的WebSocket/长连接传输场景。 - 执行结果管控:
- 成功时记录返回结果(可根据业务需求调整日志级别)
- 失败时标记状态并记录异常详情,同时保留原有异常抛出逻辑
- 结构化日志:完全遵循Serilog规范,所有字段使用占位符,便于后续日志检索与分析。
自定义扩展建议
- 若无需记录返回结果,可直接移除Verbose级别的日志代码
- 如需过滤特定Hub或方法,可在
InvokeMethodAsync开头添加判断逻辑(比如跳过健康检查类方法) - 可将JSON序列化逻辑封装为工具类,减少重复代码
内容的提问来源于stack exchange,提问作者Fatorin
相关产品推荐
相关产品推荐

