NLog是.Net中最流行的日誌記錄開源專案(之一),它靈活、免費、開源
官方支援檔案、網路(Tcp、Udp)、資料庫、控制檯等輸出
社群支援Elastic、Seq等日誌平臺輸出
實時日誌需求
在工業物聯網等特定場景下需要實時獲取日誌資訊
工業物聯網領域常用的是mqtt協議
那我們就使用NLog 自定義一個Target,將日誌輸出到MqttServer
Web通過Mqtt(websocket)實時獲取日誌,而不是傳統的通過WebApi輪詢日誌
NLog自定義Target
- 官方文件介紹瞭如何自定義Target,可以獲取到一串日誌訊息,無法獲取結構化訊息
- 需要時用使用自定義Field來完成這部分工作
/// <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}";
}
}
- 重寫Write方法
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實時檢視應用程式的日誌。
- 建立WebApi專案
- 引用NLog.Target.MQTT
- 配置檔案
<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>
- 配置MQTTServer和NLog
// ...
// 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;
});
});
// ...
- Web連線MqttServer
// ...
<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()));
}
});
// ...
- 輸出日誌
// ...
_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();
// ...
-
實時檢視日誌
訪問index.html -
通過Mqtt客戶端訂閱日誌
原始碼
在這裡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群:712105424 |
---|---|