You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:35:09