【ASP.NET Core】使用SignalR推送服务器日志

这篇具有很好参考价值的文章主要介绍了【ASP.NET Core】使用SignalR推送服务器日志。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一个多月前接手了一个产线机器人项目,上位机以读写寄存器的方式控制机器人,服务器就是用 ASP.NET Core 写的 Web API。由于前一位开发者写的代码质量问题,导致上位机需要16秒才能启动。经过我近一个月的改造,除了保留业务逻辑代码,其他的基本重写。如今上位机的启动时间在网络状态良好的条件下可以秒启动。原上位机启动慢的原因:

1、启动时使用同步方式访问 Web API,在网络较弱时需要等待很长时间。我改为导步请求,并且不等待请求结果,直接显示窗口;如果前面的请求失败,在窗口显示后再次发出异步请求,并且不等待。如果再失败才提示用户。

2、原项目在 Main 方式处就连接PLC,而产线的PLC压根就没插电源。我改为在连接机器人之后才连接,同样是异步不等待。如果连不上直接忽略。

3、原项目是一个窗口一个项目,然后把这些窗口生成 .dll,放到一个目录下,主程序启动时从目录下扫描 .dll,通过反射动态实例化窗口。这根本不需要的,一个上位机不可能有几百个窗口吧,何必呢。我改为使用服务容器的方式管理窗口,主界面通过依赖注入自动获取子窗口列表,再添加到主界面上。每个子窗口实现 IPage 接口用于识别,接口里面定义标题和页面索引即可。

4、干掉 Log4Net,使用官方的 Logging 库。

5、通信用的 JSON 数据全改用 System.Text.Json,而不是某 Newton,修改后速度快了一个次元。

由于 Web API 程序是运行在服务器的 IIS 中的,上一位开发者没有实现日志功能(仅仅用 ASP.NET Core 应用程序默认开启的控制台等日志功能),问题是日志没有保存。

我原来的计划是把日志写到系统中,这样就能保存下来,用“事件查看器”就能欣赏。后来想想这方案不行,工厂那伙人肯定找不到日志在哪。写数据库里面?想想似乎没这个必要。简单粗暴,直接自定义一个 ILogger,把日志输出到文件中,然后加一个 Web API 读取文件,上位机那里就可以调用,返回日志内容。

后经过现场调试发现,其实也不需要这样。时间长了,会存下很多日志文件,就算用日期标识文件名也是很乱。实际上他们并不要求保存日志,只是在运作过程中实时监控机器人(应该叫机械臂)的工作状态而已。如果不出问题,他们甚至连日志都不看。上面用文件实现的日志方式,主要缺点是不能实时推到上位机。就算他们不看,那我现场调试也方便我自己。

于是,我又想到了另一方案:用 SignalR 实时向上位机推送日志。

----------------------------------------------------------------------------------------------------------------------------------------

上面都是大话,现在开始主题。

原理是这样的:上位机作为 SignalR 客户端,发起连接后,不用主动调用服务器上的方法,而是等服务器调用回调方法。

 第一步,咱们要自定义一个 ILogger。

public class KingkingLogger : ILogger
{
    private readonly string cateName;
    
    public KingkingLogger(string cate)
    {
        cateName = cate;
    }

    public IDisposable? BeginScope<TState>(TState state) where TState : notnull
    {
        return default;
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return logLevel != LogLevel.None;
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    {
        if(IsEnabled(logLevel) == false)
        {
            return;
        }
        // 获取格式化后的文本
        string fstr = formatter(state, exception);
        // 显示消息类型
        string head = logLevel switch
        {
            LogLevel.Information => "消息",
            LogLevel.Warning => "警告",
            LogLevel.Error => "错误",
            _ => "未知"
        };
        // 加个日期
        string currdate = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
        // 连接字符串
        fstr = $"[{head}:{cateName}][{currdate}]{fstr}";
        // 触发事件
        TransferLog?.Invoke(fstr);
    }

    // 静态属性
    public static Action<string>? TransferLog {  get; set; }
}

我暂时想不到叫啥名字,就暂且叫它 Kingking 日志记录器吧,我在项目中的类是叫 WTFLogger 的,什么内涵你懂的,反正现在这项目只有我一个人在写,取这个名字也无所谓。这个类不复杂,我解释一下你就明白了。

1、字符串 cateName 是类别名称。就是记录日志时它属于哪个名录下的,比如我们常见的 Microsoft.Hosting.Lifetime、Microsoft.Hosting.Lifetime 等这些就是。在 Logging 库中有两种方式指定:一是用字符串,二是用 ILogger<T> ,这个类型T将作为日志类别的名称。这里我采用的是字符串方式,所以不使用 ILogger<T>。

2、BeginScope 方法的用处是当你要把 logger 用在 using 语句块时才会实现。正因为用在 using 块中,所以它要求是实现 IDisposable 接口。这个实现 IDisposable 的类一般不用公开。这方法会接收一个泛型参数 TState state。这个看你的需要了,运行库内部调用经常会用字典类型传递一些额外数据。这个 TState 你可以自定义。此处我不需要把 logger 用在 using 语句块中,所以直接返回 default(或null)。

3、IsEnabled 方法的功能是分析一下 logLevel 参数指定的日志级别当前是否要输出日志。如果需要输出日志,返回 true;不想输出日志返回 false。后面实现的 Log 方法中也会用到它,如果返回 false,那就不必去处理怎么输出日志了。

4、Log 方法是核心。在此方法中你尽情发挥吧,你想怎样输出日志就在这里完成。比如你要用 Debug 类输出,那就调用 Debug 类的成员输出;你用控制台输出就调用 Console 类的成员。我这里是要把日志传给 SignalR Hub 对象,让其传回给客户端,故要调用静态的 TransferLog 属性。此属性是委托类型,可以与方法绑定,因为咱们不能在这里调用 Hub,Hub 是由 SignalR 组件自动激活的。所以要用委托来间接实现传递。这个和事件的作用一样,只是我不用事件成员罢了。

顺便说一下,我项目中的类是同时把日志写入数据库的(不写文件了,写数据库里好清理),这里老周为了让示例简单,没有加上写入数据的代码。其实也没啥难度的,就是在数据库中加个表,用 EF Core 往表里 INSERT 一条记录。

 文章来源地址https://www.toymoban.com/news/detail-769385.html

第二步,实现 Provider。ILogger 咱们定义好了,但这个 Kingking 日志记录器可不是直接扔进服务容器,而是通过叫 ILoggerProvider 的对象来创建实例。就相当于一个工厂类。

public class KingkingLoggerProvider : ILoggerProvider
{
    public ILogger CreateLogger(string categoryName)
    {
        return new KingkingLogger(categoryName);
    }

    public void Dispose()
    {
        return;
    }
}

代码很简单,没啥玄机。不过,为了调用方便,咱们可以封装一个扩展方法。

public static class CustLoggerExtensions
{
    public static ILoggingBuilder AddKingkingLogger(this ILoggingBuilder builder)
    {
        builder.Services.AddSingleton<ILoggerProvider, KingkingLoggerProvider>();
        return builder;
    }
}

这样就做到了像官方 API 那样,用 AddXXX 的方法添加日志功能,用法如下:

var builder = WebApplication.CreateBuilder(args);
// 配置日志
builder.Services.AddLogging(o =>
{
    // 清空所有日志提供者
    o.ClearProviders();
    // 添加控制台日志输出
    o.AddConsole();
    // 添加咱们自己写的日志记录器
    o.AddKingkingLogger();
});

 

第三步,实现 Hub。Hub 是 SignalR 通信的“中心”类,当访问的 URL 匹配时就会激活咱们的 Hub。自定义 Hub 只要从 Hub 类派生即可。

public class MyHub : Hub
{
    public MyHub() {
        // 这里关联的就是日志记录类中的静态委托
        KingkingLogger.TransferLog = KingkingLogger_TransferLog;
    }

    private void KingkingLogger_TransferLog(string obj)
    {
        // 向所有客户端发日志
        Clients.All.SendAsync("onLogged", obj);
    }

    protected override void Dispose(bool disposing)
    {
        if(disposing)
        {
            // 实例释放时移除关联
            KingkingLogger.TransferLog = null;
        }
        base.Dispose(disposing);
    }
}

逻辑很简单,就是有日志了就推送给客户端。Clients.All 是把消息发给所有连接的客户端。

这里顺便提一下:Hub 是支持依赖注入的,即你可以在 MyHub 的构造函数里注入你要用的组件,如 DBContext 等。这里我用不到其他组件,所以没有注入。

在Web应用程序初始化时要启用 SignalR 相关服务。

var builder = WebApplication.CreateBuilder(args);
……
builder.Services.AddSignalR();
var app = builder.Build();

还要 Map 一下终结点,以绑定请求 Hub 的地址。

var builder = WebApplication.CreateBuilder(args);
……
var app = builder.Build();

……

// 记得这个
app.MapHub<MyHub>("/hub");

app.Run();

这里我设定的地址是 http://localhost/hub。

 

不要以为这样就完事了,当你运行后用客户端一测试,你会发现连毛都接收不到。这是因为 Hub 对象的默认生命周期太短了,仅在用的时候实例化,然后马上 Dispose 了。然后你会想,那我重写 OnConnectedAsync 方法,关联 TransferLog 委托;再重写 OnDisConnectedAsync 方法,把 TransferLog 委托设置为 null。这个也是不行的,原因还是那个—— Hub 对象生命周期太短。

有什么办法让 Hub 长寿一点呢?还真有,直接把 Hub 类型注册进服务器中,并使用单实例。

var builder = WebApplication.CreateBuilder(args);
……
// 把Hub注册为单实例
builder.Services.AddSingleton<MyHub>();
builder.Services.AddSignalR();
var app = builder.Build();

 

第四步,客户端程序。客户端并不是只能用 JS 来写,.NET 团队也做了相关的 Nuget 包。在项目中引用一下。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWindowsForms>true</UseWindowsForms>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.0" />
  </ItemGroup>

</Project>

在主窗口中放一个文本框,两个按钮。文本框显示收到的日志,按钮用来请求连接和断开连接。

using Microsoft.AspNetCore.SignalR.Client;

namespace TestClient;

public partial class Form1 : Form
{
    // 连接对象
    HubConnection hubConn;
    public Form1()
    {
        InitializeComponent();
        // 初始化连接
        var connBuilder = new HubConnectionBuilder()
            .WithUrl("http://localhost:6225/hub")
            .WithAutomaticReconnect();
        hubConn = connBuilder.Build();
        // 关联方法
        hubConn.On<string>("onLogged", OnLogRecv);
    }

    private void OnLogRecv(string msg)
    {
        // 服务器回调,显示收到的日志
        textBox1.Invoke(() =>
        {
            textBox1.AppendText(msg + Environment.NewLine);
        });
    }

    private async void btnConn_Click(object sender, EventArgs e)
    {
        try
        {
            await hubConn.StartAsync();
            lbMessage.Text = "已建立连接";
        }
        catch(Exception ex) {
            lbMessage.Text = ex.Message;
        }
    }

    private async void btnDisconn_Click(object sender, EventArgs e)
    {
        if(hubConn.State == HubConnectionState.Connected)
        {
            await hubConn.StopAsync();
            lbMessage.Text = "已断开连接";
        }
    }
}

注意,在调用 On 方法时,onLogged 要与服务器上指定的一致,否则服务器回调无效

/*---------------- 服务器端 ------------------*/
private void KingkingLogger_TransferLog(string obj)
{
    // 向所有客户端发日志
    Clients.All.SendAsync("onLogged", obj);
}

/*--------------------- 客户端 -------------------*/
hubConn.On<string>("onLogged", OnLogRecv);

 

为了测试能否真的传递了日志,咱们在服务端写几个 Mini-API 来验证。

app.MapGet("/", (ILoggerFactory logFact) =>
{
    ILogger logger = logFact.CreateLogger("MINI Main");
    logger.LogInformation("欢迎来到圆环世界");
    return "Hello Guy";
});
app.MapGet("/start", (ILoggerFactory logFact) =>
{
    ILogger logger = logFact.CreateLogger("MINI Go Go Go");
    logger.LogWarning("游戏开始了,你必须先和QB签订契约");
    return "圆神启动";
});
app.MapGet("/shot", (ILoggerFactory loggerFact) =>
{
    ILogger logger = loggerFact.CreateLogger("MINI Wind");
    logger.LogInformation("干得好,三发入魂");
    return "第一局完胜";
});

 

同时启动服务端和客户端试试吧。为了使测试更真实,我启动了三个客户端。触发日志记录,请调用任意一个 API。

依次点击三个窗口上的“连接”按钮,确认全部都连上。

【ASP.NET Core】使用SignalR推送服务器日志

然后依次调用那几个 mini API 试试。

【ASP.NET Core】使用SignalR推送服务器日志

可以看到,三个客户端都收到日志推送了。

为了演示,没有数据存储,所以如果客户端没有及时连接,会丢失前面的日志。老周的实际项目中是用数据库存起来,用的时候再取出来发给客户端。默认是发最近的 100 条。如果上位机要看全部,就调用一下 Hub 的方法,Hub 的代码会 select 整个日志表再发回。

 

到了这里,关于【ASP.NET Core】使用SignalR推送服务器日志的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • asp.net core 项目从本地部署到远程云服务器

    今天向大家详细介绍一下,如何将一个.net core项目部署到远程云服务器,我的服务器镜像为 window server 2019. 1.安装IIS 首先在服务器上安装IIS(我的是最新的iis10),我已经安装完毕,这个安装操作比较简单,自行谷歌。 安装完成后会看到下面的界面: 2.安装web deploy程序 该程序的功能: 它

    2023年04月22日
    浏览(48)
  • 微信小程序如何使用原生Websocket与Asp.Net Core SignalR 通信

    如题,这可能算是.net 做小程序的服务端时,绕不开的一个问题,老生常谈了。同样的问题,我记得我2018/19年的一个项目的解决方案是: 修改官方的SignalR.js的客户端 :把里面用到浏览器的Websocket改成微信小程序的官方api的。目前网上也有不少这样的方案,已经改好开源了;

    2024年02月08日
    浏览(62)
  • 在 C#和ASP.NET Core中创建 gRPC 客户端和服务器

    gRPC 是一种可以跨语言运行的现代高性能远程过程调用 (RPC) 框架。gRPC 实际上已经成为 RPC 框架的行业标准,Google 内外的组织都在使用它来从微服务到计算的“最后一英里”(移动、网络和物联网)的强大用例。 gRPC是一个高性能的开源的通用RPC框架,由Google公司开发,支持常

    2024年04月23日
    浏览(46)
  • 微信小程序如何使用原生Websocket api与Asp.Net Core SignalR 通信

    如题,这可能算是.net 做小程序的服务端时,绕不开的一个问题,老生常谈了。同样的问题,我记得我2018/19年的一个项目的解决方案是: 修改官方的SignalR.js的客户端 :把里面用到浏览器的Websocket改成微信小程序的官方api的。目前网上也有不少这样的方案,已经改好开源了;

    2024年02月09日
    浏览(73)
  • ASP.NET Core SignalR 入门

    本章将和大家分享使用 SignalR 生成实时应用的基础知识。通过本文您将学习如何:使用ASP.NET Core SignalR + MVC + Vue 2.x + require 最终创建一个正常运行的简易聊天应用。 废话不多说,我们直接来看一个Demo,Demo的目录结构如下所示: 本Demo的Web项目为ASP.NET Core Web 应用程序( 目标框

    2024年02月11日
    浏览(41)
  • 什么是 ASP.NET Core SignalR?

    所有连接了 Internet 的应用程序都由服务器和客户端组成。 客户端依赖于服务器获取数据,而它们获取数据的主要机制是通过发出超文本传输协议 (HTTP) 请求来进行的。 某些客户端应用程序需要经常更改的数据。 ASP.NET Core SignalR 提供了一个 API,用于创建服务器到客户端远程过

    2024年02月15日
    浏览(41)
  • ASP.NET Core应用程序在IIS服务器上的运行环境介绍和下载安装(超详细)

    要想在 IIS 上成功运行 ASP.NET Core 应用程序,我们需要有ASP.NET Core 运行时和ASP.NET Core Module。而在这里我们仅需要下载ASP.NET Core Hosting Bundle 即可,这是为什么呢? 想了解的可以看文章末节的介绍。 ASP.NET 核心运行时使你能够运行现有的 Web/服务器应用程序。 在 Windows 上,我们

    2024年02月11日
    浏览(50)
  • ASP.NET Core实时库SignalR简单应用

    SignalR 是用于构建需要实时用户交互或实时数据更新的Web 应用程序的一个开放源代码.NET 库。不仅仅用在Web应用中,后面会讲到它的应用范围。它简化了简化了构建实时应用程序的过程,包括 ASP.NET Server 库和 JavaScript Client 库,以便管理Client与Server连接并将内容更新推送给Cl

    2024年02月11日
    浏览(76)
  • ASP.NET Core SignalR 系列(四)- 中心筛选器

    本章将和大家分享 ASP.NET Core SignalR 中的中心筛选器。 本文大部分内容摘自微软官网:https://learn.microsoft.com/zh-cn/aspnet/core/signalr/hub-filters?view=aspnetcore-7.0 废话不多说,下面我们直接进入本章主题。 中心筛选器: 在 ASP.NET Core 5.0 或更高版本中可用。 允许在客户端调用中心方法之

    2024年02月16日
    浏览(46)
  • ASP.NET Core+Vue3 实现SignalR通讯

    从ASP.NET Core 3.0版本开始,SignalR的Hub已经集成到了ASP.NET Core框架中。因此,在更高版本的ASP.NET Core中,不再需要单独引用Microsoft.AspNetCore.SignalR包来使用Hub。 在项目创建一个类继承Hub, 首先是写一个CreateConnection方法 ConnectionId是SignalR中标识的客户端连接的唯一标识符, 将userId和

    2024年02月06日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包