Skip to main content

iioter

NLog自定义Target之MQTT

NLog是.Net中最流行的日志记录开源项目(之一),它灵活免费开源

官方支持文件网络(Tcp、Udp)、数据库控制台等输出

社区支持ElasticSeq等日志平台输出

实时日志需求

在工业物联网等特定场景下需要实时获取日志信息

工业物联网领域常用的是mqtt协议

那我们就使用NLog 自定义一个Target,将日志输出到MqttServer

Web通过Mqtt(websocket)实时获取日志,而不是传统的通过WebApi轮询日志

Web-Log

NLog自定义Target

  1. 官方文档介绍了如何自定义Target,可以获取到一串日志消息,无法获取结构化消息
  2. 需要时用使用自定义Field来完成这部分工作
Field.cs
/// <summary>
/// Additional field details
/// </summary>
[NLogConfigurationItem]
public class Field
{
/// <summary>
/// Name of additional field
/// </summary>
[RequiredParameter]
public string Name { get; set; }

/// <summary>
/// Value with NLog <see cref="NLog.Layouts.Layout"/> rendering support
/// </summary>
[RequiredParameter]
public Layout Layout { get; set; }

/// <summary>
/// Custom type conversion from default string to other type
/// </summary>
/// <remarks>
/// <see cref="System.Object"/> can be used if the <see cref="Layout"/> renders JSON
/// </remarks>
public Type LayoutType { get; set; } = typeof(string);

/// <inheritdoc />
public override string ToString()
{
return $"Name: {Name}, LayoutType: {LayoutType}, Layout: {Layout}";
}
}
  1. 重写Write方法
MqttTarget.cs
protected override void Write(LogEventInfo logEvent)
{
//default fields
Dictionary<string, object> logDictionary = new()
{
{ "timestamp", logEvent.TimeStamp },
{ "level", logEvent.Level.Name },
{ "message", RenderLogEvent(Layout, logEvent) }
};

//customer fields
//这里都处理为字符串了,有优化空间
foreach (var field in Fields)
{
var renderedField = RenderLogEvent(field.Layout, logEvent);

if (string.IsNullOrWhiteSpace(renderedField))
continue;

logDictionary[field.Name] = renderedField;
}

SendTheMessage2MqttBroker(JsonConvert.SerializeObject(logDictionary));
}

使用

下面将使用Nlog.Target.MQTT,演示通过web实时查看应用程序的日志

  1. 创建WebApi项目
  2. 引用NLog.Target.MQTT

nuget

  1. 配置文件
nlog.config
<extensions>
<add assembly="NLog.Web.AspNetCore"/>
<!--<add assembly="NLog.Targets.MQTT"/>-->
<add assembly="NLog.Targets.MQTT"/>
</extensions>

<!-- the targets to write to -->
<targets>
<!-- MQTT Target -->
<target xsi:type="MQTT" name="mqtt" host="localhost" port="1883" username="UserName" password="Password" topic="log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}|url: ${aspnet-request-url}|action: ${aspnet-mvc-action}|${callsite}" >
<field name="machine" layout="${machinename}" />
<field name="processid" layout="${processid}" />
<field name="threadname" layout="${threadname}" />
<field name="logger" layout="${logger}" />
<field name="callsite" layout="${callsite-linenumber}" />
<field name="url" layout="${aspnet-request-url}" />
<field name="action" layout="${aspnet-mvc-action}" />
<field name="level" layout="${level:uppercase=true}" />
<field name="message" layout="${message}" />
<field name="exception" layout="${exception:format=toString}" />
</target>
</targets>

<!-- rules to map from logger name to target -->
<rules>
<logger name="*" minlevel="Trace" writeTo="mqtt" />
</rules>
  1. 配置MQTTServer和NLog
Program.cs
// ...
// NLog: Setup NLog for Dependency injection
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Host.UseNLog();

//AddHostedMqttServer
builder.Services.AddHostedMqttServer(mqttServer =>
{
mqttServer.WithoutDefaultEndpoint();
})
.AddMqttConnectionHandler()
.AddConnections();

//Config Port
builder.WebHost.UseKestrel(option =>
{
option.ListenAnyIP(1883, l => l.UseMqtt());
option.ListenAnyIP(80);
});
var app = builder.Build();

// ...
//UseStaticFiles html js etc.
app.UseStaticFiles();
app.UseRouting();

//Websocket Mqtt
app.UseEndpoints(endpoints =>
{
//MqttServerWebSocket
endpoints.MapConnectionHandler<MqttConnectionHandler>("/mqtt", options =>
{
options.WebSockets.SubProtocolSelector = MqttSubProtocolSelector.SelectSubProtocol;
});
});
// ...
  1. Web连接MqttServer
index.html
// ...    
<script src="./jquery.min.js"></script>
<script src="./mqtt.min.js"></script>
<script src="./vue.js"></script>
// ...

var client = mqtt.connect('ws://' + window.location.host + '/mqtt', options);
client.on('connect',
function() {
client.subscribe('log',
function(err) {
if (!err) {
console.log("subed!");
} else {
alert("subed error!");
}
});
});
client.on('message',
function(topic, message) {
if (topic === 'log') {
if (app.logs.length > 50)
app.logs.length = 0;
app.logs.unshift($.parseJSON(message.toString()));
}
});
// ...
  1. 输出日志
WeatherForecastController.cs
// ...  
_logger.LogDebug("LogDebug!");
_logger.LogError(new Exception("Exception Message!"), "LogError!");

//new thread output log after 500ms
Thread thread = new Thread(ThreadProc);
thread.Name = "My Thread";
thread.Start();
// ...
  1. 实时查看日志 访问index.html

  2. 通过Mqtt客户端订阅日志 sublog

源码

在这里NLog.Targets.MQTT:https://github.com/iioter/NLog.Targets.MQTT

相关链接

[1] NLog.Targets.MQTT:https://github.com/iioter/NLog.Targets.MQTT

[2] IoTGateway-Doc:http://iotgateway.net/blog/NLog

[3] NLog自定义Target:https://github.com/NLog/NLog/wiki/How-to-write-a-custom-target

交流

公众号:工业物联网网关QQ群:895199932
wxqq

iioter

探索链路追踪在.NET6工业物联网项目的应用

ExploringIoTDistributedTracingNet6

Jaeger

可能遇到的问题

工业物联网项目自上而下一般分为ERP、Mes、SCADA、WCS、边缘网关、设备等

一个生产订单从SAP发送到设备要经过上述多个系统,当某个环节出现问题,可能需要各个团队共同查找问题,最传统的做法是翻阅各个系统的日志文件,这无疑是非常糟糕和低效的。

APM系统既可以帮你查找问题,又可以定位整个系统的瓶颈。

应用性能监控

APM(Application Performance Monitor),用来监控你的软件性能及行为。通常包括:

  • Metrics 指标

    如CPU、内存、磁盘I/O、网络I/O等
  • Logs 日志

    通常程序输出的不同等级日志Debug、Info、Error等
  • Traces 分布式追踪

    包含请求中每个子操作的调用链路、开始和结束时间、传递的参数、对数据库的操作等

OpenTelemetry

OpenTelemetry

OpenTelemetry是谷歌和微软推出的一套平台无关、厂商无关的协议标准,是OpenTracing和OpenCensus的大统一,使得开发人员能够方便的添加或更换底层APM的实现。我们可以使用它的数据收集中间件:

生成、收集数据(Metrics,Logs and traces)

将数据推送到Jaeger(或Zipkin、SkyWalking等后端)

支持.Net、C++、Go、Java、js、Python等11种语言(平台)

OpenTelemetry-logo

可以采集.Net项目的AspNetCore、Http、EFCore、HttpClient、Grpc等诊断数据

OpenTelemetry

官方代码段

// Define some important constants and the activity source
var serviceName = "MyCompany.MyProduct.MyService";
var serviceVersion = "1.0.0";
var builder = WebApplication.CreateBuilder(args);
// Configure important OpenTelemetry settings, the console exporter, and automatic instrumentation
builder.Services.AddOpenTelemetryTracing(b =>
{
b
.AddConsoleExporter()
.AddSource(serviceName)
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService(serviceName: serviceName, serviceVersion: serviceVersion))
.AddHttpClientInstrumentation()
.AddAspNetCoreInstrumentation();
});
var app = builder.Build();
var httpClient = new HttpClient();
app.MapGet("/hello", async () =>
{
var html = await httpClient.GetStringAsync("https://example.com/");
if (string.IsNullOrWhiteSpace(html))
return "Hello, World!";
else
return "Hello, World!";
});

app.Run();

Jaeger

Jaeger是开源的分布式追踪系统,OpenTelemetry可以将收集到的数据导入到这个里面进行存储和查询。

Seq

项目使用seq作为日志平台,轻量且.Net友好,支持sql查询以及图表展示,你也可以使用Nlog+Elasticsearch+Kibana

项目目录

.
├──WebApp //webapp
├──WebApi //webapi
├──IoTGatewayService //模拟网关
├──Device //模拟设备
└──LogService //日志消费服务

项目介绍

项目项目类型作用说明
Device控制台模拟一个Modbus-TCP设备当设定温度变化会输出日志
IoTGatewayServiceGrpc服务模拟数据采集的网关开放设定温度的Grpc接口
使用Modbus协议将温度下发给设备
WebApiWebApi提供设定温度Api接收用户输入的温度
使用Grpc调用网关服务
操作日志写入数据库
将操作日志发送到RabbitMq
WebAppWeb应用用户访问接收用户输入的温度
调用WebApi
展示结果
LogServiceWorkerService后台服务消费RabbitMq日志消息

启动方式

  1. 使用docker-compose运行jaeger、rabbitmq、postgres、seq
   docker-compose up -d
  1. VisualStudio启动多个项目 start-projects
  2. 访问Web,输入设定温度,回车确认 setValue
  3. 访问Jaeger,查看链路追踪 jaeger
  4. 访问Seq,查看日志 seq

采样率

项目中后期可使用过滤器或降低采样率来减小数据收集对系统性能的影响。

源码

在这里ExploringIoTDistributedTracingNet6

相关链接

[1] OpenTelemetry:https://opentelemetry.io/docs/instrumentation/net/

[2] Jaeger:https://www.jaegertracing.io/

[3] Seq:https://datalust.co/seq

[4] 源码:https://github.com/iioter/ExploringIoTDistributedTracingNet6

[5] IoTGateway:https://github.com/iioter/iotgateway

[6] opentelemetry文章:https://mp.weixin.qq.com/s?__biz=MzAwNTMxMzg1MA==&mid=2654082294&idx=7&sn=472afb8235cd1dee322641b8add3e77c&chksm=80d830a3b7afb9b5dbba5cabe072a310820c75acf2097ad138c07b0792eab91ffbf340b43741&token=348599805&lang=zh_CN#rd

交流

公众号:工业物联网网关QQ群:895199932
wxqq

iioter

什么是工业物联网网关

工业物联网网关(IIoTGateway)是一种硬件设备或软件程序,作为本地设备(如PLC、扫码枪、机器人、数控机床、非标上位机等)与云端系统(如物联网平台、SCADA系统、MES系统等)之间的桥梁,在设备和云端之间流动的所有数据都通过IoT网关,如下图所示:

  • 工业物联网网关的角色

数据的流向

  1. 设备云端的通信(数据采集)
  2. 云端设备的通信(反向控制)
  3. 设备设备的通信(M2M)

基于.NET6的开源工业物联网网关

  1. 受益于.NET在工控以及医疗等领域的长期积累,网上的确分散着很多通信相关的dll、源码和项目,但至今没有基于B/S架构的开源项目,这也是本项目的初衷。
  2. 依托.NET6开源、跨平台、高性能的特点,以及WTM 框架的低代码开发方式,快速搭建起网关应用。目前已完成遥测和属性的上传,反向控制正在开发中。
  • Home
  1. 项目地址

开源网关具有以下特点

  • 跨平台运行

  1. 既可以是windows,也可以是linux系统;
  2. 既可以是主机运行,也可以通过docker运行;
  3. 既可以是x86主机,也可以是arm32、arm64嵌入式开发板。
  • Arm网关
  • Docker运行
  • 内置多种驱动

    驱动可以看做公司的一种无形资产。目前提供Modbus完整协议、西门子全系列PLC、三菱QPLC、欧姆龙PLC、ABPLC、MTConnect数控机床等驱动。当然你也可以通过驱动Demo实现自己的驱动,也可以集成业内流行的IoTClient(码农一生)、HSL(胡工)等提供的驱动进行整合。
  • 驱动
  • 计算表达式

    类似于js等脚本语言,C#也可以作为脚本进行动态编译执行,你可以随时修改表达式对数据进行二次计算,不只是倍率的计算哦。
  • 计算表达式
  • MQTT服务

    内置MQTT Server,可以通过订阅数据与你的业务系统进行集成,当然网关也可以作为MQTT客户端,将数据推送到你的MQTT服务中去。
  • MQTT服务
  • MQTT订阅数据
  • OPCUA服务

    内置OPCUA Server,你的工控系统可以很方便的获取数据。注意,项目中OPCUA相关功能仅用作学习及测试,若使用OPCUA协议请联系OPC基金会进行授权,产生一切纠纷与本项目无关
  • OPCUA服务
  • 数字孪生3D可视化

    通过使用threejs搭建了数字孪生的demo,前端通过基于WebSocket的Mqtt协议订阅数据变化,实时获取数据更新,而不是通过WebAPI轮训。
  • 3D可视化Demo
  • 变量更新
  • 在线组态

    无缝集成在线组态项目,快速设计和部署你的可视化应用。
  • 在线组态
  • 组态应用

为什么需要物联网网关

  • 协议转换

    面对不同年代、不同厂家、不同协议的设备多少会让人头疼,IoT网关会帮助你屏蔽协议的差异,转换为常用的物联网协议(如MQTT、HTTP等)输出到平台端,让你有更多时间专注于平台端的业务建设。

  • 保证安全

    所有物联网设备都有被外部影响和攻击的可能,但IoT网关在互联网和设备本身之间增加了另外一层,大幅度减少了连接到互联网的设备的数量,网关变成了第一道防线,进而防止外部各方对物联网设备进行未经授权的控制。

  • 容易扩展

在工业现场的高度分散性的前提下,随着连接的设备数量不断增加,不同协议设备的持续接入,只能通过网关来管理和控制这些复杂的变化,而不是以往牵一发而动全身的痛苦的升级。

  • 数据处理

  1. 数据预处理

    倍率偏移量处理等,这有助于边缘计算的发展,因为他本身就是边缘智能的一种简单应用。

  2. 数据过滤

    设备产生的海量数据中,全部发送到平台端肯定是不堪重负的,一方面传输、处理和存储都是有成本的;另一方面可能只有一部分是需要关注的,如变化的产量,就需要变化才上传,一直推送不变的值是没有意义的。

  3. 数据汇总和聚合

    有些场景下不同设备或相同设备的不同变量是需要聚合计算后发送到平台端的,有些原始数据没有特别大的价值,当然这也可以分担一部分平台端的压力

  4. 数据缓存

    网络通信不是100%可靠的,断线时IoT网关既需要暂存数据,当网络恢复后发送至云端;又需要存储云端的控制策略,让控制流程及时执行。