log4日志内容换行_SignalR+Vue+Log4net实时日志推送

(给DotNet加星标,提升.Net技能)

转自:飞天猪皮怪 cnblogs.com/aqgy12138/p/13719851.html

系列

SignalR+Vue

SignalR+Vue 服务端向客户端发送信息

SignalR+Vue+Log4net 实时日志推送

待定......

源码地址:

https://github.com/QQ2287991080/SignalRServerAndVueClientDemo

效果

老规矩先看最后效果

f0fe7ecdb817f265cde9e2dee54e2c61.gif

步骤

1、配置log4net日志

实现日志推送,首先需要配置log4net日志,然后定义一个全局异常捕获器,用于捕获错误写入到日志文件。

a4bfea609b2be4acbfa9408008faaf66.png

先把nuget包安装一下。

然后需要配置log4net的xml信息,右键web项目“添加”->“新建项”

86c330b9dde00a86c0f8f290cf32125a.png

找到Web配置文件->“命名”->"点击添加"

235763edb8a8cc6572ce1a9e94405825.png

然后把xml配置放入到config文件中,配置如下:

xml version="1.0" encoding="utf-8" ?><configuration><log4net><appender name="DebugAppender" type="log4net.Appender.DebugAppender" ><layout type="log4net.Layout.PatternLayout"><conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />layout>appender><appender name="RollingFile" type="log4net.Appender.RollingFileAppender"><file value="../../../logs/system.log" /><appendToFile value="true" /><rollingStyle value="Composite" /><staticLogFileName value="true" /><maxSizeRollBackups value="10" /><maximumFileSize value="1GB" /><layout type="log4net.Layout.PatternLayout"><conversionPattern value="%n时间:%date{yyyy-MM-dd HH:mm:ss},%n线程Id:%thread,%n日志级别:%-5level,%n描述:%message|%newline"/>layout>appender><root><level value="All"/><appender-ref ref="DebugAppender" /><appender-ref ref="RollingFile"  />root>log4net>configuration>

想要更多配置的可以前往官网:http://logging.apache.org/log4net/release/config-examples.html  

如果对生成多个文件夹有兴趣的可以看我另外:Asp.Net Core Log4Net 配置分多个文件记录日志(不同日志级别)

接下来就需要在Startup中配置log4net.

public Startup(IConfiguration configuration){
Configuration = configuration;
Logger = LogManager.CreateRepository(Assembly.GetEntryAssembly(), typeof(log4net.Repository.Hierarchy.Hierarchy));
XmlConfigurator.Configure(Logger, new FileInfo("log4net.config"));// _logger = LogManager.GetLogger(Logger.Name, typeof(Startup));
}public static ILoggerRepository Logger { get; set; }

按照我最开始说的,在配置好日志之后需要配置一个全局错误捕获器,直接上代码。

public class SysExceptionFilter : IAsyncExceptionFilter
{readonly IHubContext _hub;//使用log4
ILog _log = LogManager.GetLogger(Startup.Logger.Name, typeof(SysExceptionFilter));public SysExceptionFilter(IHubContext hub){
_hub = hub;
}public async Task OnExceptionAsync(ExceptionContext context){//错误var ex = context.Exception;//错误信息string message = ex.Message;//请求方法的路由string url = context.HttpContext?.Request.Path;//写入日志文件描述 注意这个地方尽量不要用中文冒号,否则读取日志文件的时候会造成信息确实,当然你可以定义自己的规则string logMessage = $"错误信息=>【{message}】,【请求地址=>{url}】";//写入日志
_log.Error(logMessage);//读取日志var data = ReadHelper.Read();//发送给客户端await _hub.Clients.All.SendAsync("ReceiveLog", data);//返回一个正确的200http码,避免前端错误
context.Result = new JsonResult(new { ErrCode = 0, ErrMsg = message, Data = true });
}
}

代码中的读取日志会在第二节中讲到。

在Startup服务中注册这个过滤器。

public void ConfigureServices(IServiceCollection services){
......
services.AddMvc(option =>
{//添加错误捕获
option.Filters.Add(typeof(SysExceptionFilter));//option.EnableEndpointRouting = false;
});
......
}

按照我这个配置将会在程序目录生成一个logs文件夹,以及一个system.log文件。

6336cebd6816e81214b4e602bc601e31.png

2、读取日志文件

在配置日志文件中已经将日志配置了,再看看生成日志文件内容。

d2a0e42e6afa5b8104cbaff9d29fc4d2.png

跟我在log4net.config中配置的是一样的。

<layout type="log4net.Layout.PatternLayout">

<conversionPattern value="%n时间:%date{yyyy-MM-dd HH:mm:ss},%n线程Id:%thread,%n日志级别:%-5level,%n描述:%message|%newline"/>
layout>

然后需要读取日志文件的,把日志文件的内容转换成前端能够识别的数据。

public class ReadHelper
{
///
/// https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netframework-4.8
/// 这里主要控制控制多个线程读取日志文件
///
static ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();
public static ListRead(string filePath=""){
//日志对象集合
List datas = new List();
filePath = Directory.GetCurrentDirectory() + "\\logs\\system.log";//判断日志文件是否存在if (!File.Exists(filePath))
{return datas;
}
_slimLock.EnterReadLock();try
{//获取日志文件流var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);//读取内容var reader = new StreamReader(fs);var content = reader.ReadToEnd();
reader.Close();
fs.Close();/*
*处理内容,换行符替换掉,然后在log4net配置文件中在每一写入日志结尾的地方加上 |
*这样做的好处是便于在读取日志文件的时候处理日志数据返回给客户端
*由于是在每一行结束的地方加上| 所有根据Split分割之后最后一个数据必然是空的
*所有Where去除一下。
*/var contentList = content.Replace("\r\n", "").Split('|').Where(w => !string.IsNullOrEmpty(w));foreach (var item in contentList)
{//根据逗号分割单个日志数据的内容var info = item.Split(',');//实例化日志对象
SysExceptionData data = new SysExceptionData();
data.CreateTime = Convert.ToDateTime(info[0].Split(':')[1]);
data.Level = info[2].Split(':')[1];
data.Summary = info[3].Split(':')[1];
datas.Add(data);
}
}finally
{//退出
_slimLock.ExitReadLock();
}return datas.OrderByDescending(bo=>bo.CreateTime).ToList();
}
}public class SysExceptionData
{/// /// 时间/// public DateTime CreateTime { get; set; }/// /// 日志级别/// public string Level { get; set; }/// /// 日志描述/// public string Summary { get; set; }
}

这里需要说一下的是为什么要用ReaderWriterLockSlim,其实在写这篇博客之前我刚好看书学到这个东西。

来一段原文描述:

通常一个类型实例的并发读操作是线程安全的,而并发更新操作则不是。诸如文件这样的资源也具有相同的特点。

虽然可以简单的使用一个排它锁来保护对实例的任何形式的访问。

但是如果其读操作很多但是更新操作很少,则使用单一的锁限制并发性就不大合理了。

这种情况出现在业务应用服务器上,它会将常用的数据缓存在静态字段中进行快速检索。

ReaderWriterLockSlim是专门为这种情形设计的,它可以最大限度的保证锁的可用性。

ReaderWriterLockSlim在.net3.5引入的它替代了笨重的ReaderWriterLock类。

虽然两者功能相识,但是后者的执行速度比前置慢数倍。

ReaderWriteLockSlim和ReaderWriterLock都拥有两种基本锁,读和写。

写锁是全局排它锁

读锁可以兼容其他的锁

因此,一个持有写锁的线程将阻塞其他任何试图获取读锁或写锁的京城。但是如果没有任何线程持有写锁的话,那么任意数量的线程都可以获得读锁。

ReaderWriterLockSlim和lock一样也有类似TryEnter之类的方法,来判断是否超时,如果超时就抛出错误(lock返回false)

这是关于ReaderWriterLockSlim官网最新的描述:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.readerwriterlockslim?view=netframework-4.8

对了,我看的是孔雀鸟--《c# 7.0核心技术指南》c#想进阶强烈推荐这本书。

同时这部分代码也有参考老张的Blog.Core的源码,感谢!

接下来调试一下看看读取日志文件处理后的数据,我在TestController加了故意抛出错误的接口。

908c3feb74b746fd1dc1fc8500e21ef2.png

直接在浏览器输入 :http://localhost:13989/api/test/getLog

7a9987bb58602428c74484452ef99d6d.png

成功进入断点

6c5eb59858f78a0d16d96943d62be6bf.png

shift+f9监听data看看数据

5bd3f934a4c4e73376e077ce0232b3df.png

拿到这个数据,在客户端就直接可以用来展示,那么读取日志文件这部分就说完了,然后再说如何发送日志给客户端。

3、实时发送日志数据

在日志过滤器中有这样一段代码,玩过signalr的人都知道SendAsync的第一个字符串其实是集线器中方法(Hub)的名称,但是我们也是可以自定义它的名称的。

//发送给客户端
await _hub.Clients.All.SendAsync("ReceiveLog", data);

signalr强类型中心:

https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-3.1#change-the-name-of-a-hub-method

之前用的Hub不是强类型中心,这次一并给他改造了。

/// 
/// https://docs.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-3.1
/// 强类型中心
///
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
Task ReceiveMessage(object message);
Task ReceiveCaller(object message);
Task ReceiveLog(object data);
}

重构源码之前的方法。

public class ChatHub : Hub
{/// /// 给所有客户端发送消息/// /// 用户/// 消息/// public async Task SendMessage(string user, string message){
await Clients.All.ReceiveMessage(user, message);
}/// /// 向调用客户端发送消息/// /// /// public async Task SendMessageCaller(string message){
await Clients.Caller.ReceiveCaller( message);
}/// /// 客户端连接服务端/// /// public override Task OnConnectedAsync(){
var id = Context.ConnectionId;//_logger.Info($"客户端ConnectionId=>【{id}】已连接服务器!");return base.OnConnectedAsync();
}/// /// 客户端断开连接/// /// /// public override Task OnDisconnectedAsync(Exception exception){
var id = Context.ConnectionId;//_logger.Info($"客户端ConnectionId=>【{id}】已断开服务器连接!");return base.OnDisconnectedAsync(exception);
}public async Task ReceiveLog(object data){
data = ReadHelper.Read();
await Clients.All.ReceiveLog(data);
}
}

ps:这个改动不会影响它在控制器注入,或者其它注入地方的使用。

其实服务端的配置差不多好了,现在需要想的是在客户端,首次进入页面的时候是应该手动给他调用一次发送日志,否则进入页面是没有数据的。

然后我在TestController中加上一个接口手动触发

[HttpGet]public  async TaskGetLogMessage(){var data = ReadHelper.Read();await _hubContext.Clients.All.SendAsync("ReceiveLog", data);return new JsonResult(0);
}

?,接下来需要把注意力集中到客户端上了,

之前的两篇博客我是没有安装element-ui的,这一次我为了展示数据省事,就打算直接用element-table展示数据好了。

element官网:https://element.eleme.cn/#/zh-CN/component/installation

npm i element-ui -S

在mian.js添加配置

import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'

vue 这里我不敢乱讲,这个我也不是很会,所以直接放代码了,我把客户端直接的代码进行了一下改造,加了个菜单,然后之前的内容都放在不同的菜单。

<template>
<div class="home">
<h1>服务端错误日志返回h1>
<button @click="sendErr">执行一个错误button>
<div class="table">
<el-table :data="tableData" border style="width: 100%">
<el-table-column type="index" label="序号" width="100">el-table-column>
<el-table-column prop="createTime" label="日期" width="180">el-table-column>
<el-table-column prop="level" label="级别" width="100">el-table-column>
<el-table-column prop="summary" label="描述" width="300">el-table-column>
el-table>
div>
div>
template>
<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";
import * as signalR from "@aspnet/signalr";
export default {
name: "Home",
components: {
HelloWorld,
},
data() {
return {
message: "", //消息
connection: "", //signalr连接
messages: [], //返回消息
tableData: [],
};
},
methods: {
//发出一个错误
sendErr: function () {
this.$http.get("http://localhost:13989/api/test/getLog").then((resp) => {
//console.log(resp);
});
},
//获取系统日志
getLog: function () {
this.$http
.get("http://localhost:13989/api/test/GetLogMessage")
.then((res) => {
console.log(res);
});
},
getdatalist: function () {
this.$http
.get("http://localhost:13989/api/test/GetLogMessage")
.then((res) => {
// console.log(res);
//this.tableData = res.data;
})
.catch((err) => {
console.log(err);
});
},
},
computed: {},
mounted: function () {
let thisVue = this;
this.connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:13989/chathub", {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
})
.configureLogging(signalR.LogLevel.Information)
.build();
this.connection.start();
//连接日志发送事件
this.connection.on("ReceiveLog", function (message) {
console.log("listening receivelog");
thisVue.tableData = message;
});
//初始化表格数据
thisVue.getdatalist();
},
};
script>
<style scoped>
.table {
margin: 20px;
}
style>

启动看看效果。

这是日志接口展示的客户端页面

e78538fc937fec70b276bc6594a4ce9e.png

之前博客的内容在聊天中。。

35b35ec21e3ee6fbc5d11571d384e30c.png

来个gif看看效果

0ec795ee40538190333662463912c4dc.gif

结语

今天的分享到这里就结束了,内心觉得写一篇博客真不容易,从这个想法的萌芽到写demo去实现大概花了一周,不断地去看资料,研究源码。

俗话说,人不逼自己一下,不知道有多少潜力。

最后希望博客能够帮助到需要的人,后续还想研究下signalr 配置jwt,redis,sqlserver等。

Dome源码地址:https://github.com/QQ2287991080/SignalRServerAndVueClientDemo

学习使我快乐!!!

- EOF -

6b24e7cdb1bcd465ab5c10bc82fc4fb7.png

推荐阅读   点击标题可跳转 新版C#高效率编程指南 .NET Core守护进程配置(Supervisor) C#中居然也有切片语法糖,太厉害了 

看完本文有收获?请转发分享给更多人

关注「DotNet」加星标,提升.Net技能 

84dcecb92906c168b2665b347806345a.png

好文章,我在看❤️

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值