文章转载自平娃子(QQ:273206491):http://os.pingwazi.cn/resource/riziserver
系统日志是观察系统运行情况的窗口,它可以帮助我们快速定位错误、发现系统性能瓶颈甚至可以分析用户行为等。下面基于.NetCore开发的一套简单的日志服务器,供大家交流学习。
一、为什么要单独实现日志服务器:
1、系统日志操作(写日志)是一个频率非常高的操作,当系统用户量达到一定程度后,对于高频的日志操作很有可能会成为系统的性能瓶颈
2、实现日志处理与系统的解耦
3、有利于系统的扩展(如果日志单台日志服务器到达负载了,再加一台日志服务器就行)
4、利于日志的集中管理
二、采用什么方式实现日志服务器
1、日志的传输采用tcp协议进行传输
2、日志服务器是监听了指定ip和端口的socket服务器
3、服务器的日志操作采用多线程,充分利用服务器资源
三、如何实现日志服务器
1、安装.NetCore连接数据库的驱动程序(我这里使用的是mysql),便于将日志信息保存到数据中
Install-Package MySql.Data -Version 8.0.15
2、安装Log4Net,当日志服务器发生异常的时候记录异常日志到文件中,便于后期进行错误处理
Install-Package log4net -Version 2.0.8
2.1、配置Log4Net
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- This section contains the log4net configuration settings -->
<log4net>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString" value="Log/" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<staticLogFileName value="false" />
<datePattern value="yyyyMMdd'.log'" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="50MB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %message%newline" />
</layout>
</appender>
<!-- Setup the root category, add the appenders and set the default level -->
<root>
<level value="ALL" />
<appender-ref ref="RollingLogFileAppender" />
</root>
</log4net>
</configuration>
2.2、实现LogHelper类,这个类主要用于文件日志的记
using log4net;
using log4net.Appender;
using log4net.Config;
using log4net.Repository;
using System;
using System.IO;
namespace LogServer
{
/// <summary>
/// 日志帮助类
/// </summary>
public class LogHelper
{
private static ILog Logger { get; set; }
static LogHelper()
{
if (Logger == null)
{
//log4net注册
ILoggerRepository loggerRepository = LogManager.CreateRepository("NETCoreRepository");
XmlConfigurator.Configure(loggerRepository, new FileInfo("log4net.config"));
Logger = LogManager.GetLogger(loggerRepository.Name, "InfoLogger");
IAppender[] appenders = loggerRepository.GetAppenders();
///打印日志的保存路径
foreach (var appender in appenders)
{
RollingFileAppender rollingFileAppender = appender as RollingFileAppender;
Console.WriteLine(rollingFileAppender.File);
}
}
}
/// <summary>
/// 调试信息
/// </summary>
/// <param name="msg"></param>
/// <param name="ex"></param>
public static void Debug(string msg, Exception ex = null)
{
if (ex == null)
{
Logger.Debug(msg);
}
else
{
Logger.Debug(msg, ex);
}
}
//错误信息
public static void Error(string msg, Exception ex = null)
{
if (ex == null)
{
Logger.Error(msg);
}
else
{
Logger.Error(msg, ex);
}
}
}
}
3、客户端数据处理类
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace LogServer
{
public class TcpClientManager
{
private TcpListener _TcpListener { get; set; }
private TcpClient _TcpClient { get; set; }
public TcpClientManager(TcpListener tcpListener, TcpClient tcpClient)
{
this._TcpClient = tcpClient;
this._TcpListener = tcpListener;
}
public void DealClientData()
{
StringBuilder sb = new StringBuilder();
try
{
//1、获取客户端数据流
NetworkStream networkStream = this._TcpClient.GetStream();
//2、创建网络流中自定义数据类型(int类型的存储占用4个字节)
byte[] dataLenByte = new byte[4];
byte[] dataTypeByte = new byte[4];
//3、获取自定义数据值
int readCount = networkStream.Read(dataLenByte, 0, dataLenByte.Length);
while (readCount > 0)
{
//3.1、获取自定义数据
networkStream.Read(dataTypeByte, 0, dataTypeByte.Length);
int dataLen = BitConverter.ToInt32(dataLenByte, 0);
int dataType = BitConverter.ToInt32(dataTypeByte, 0);
if (dataLen > 0)
{
//读取数据
byte[] data = new byte[dataLen];
int readedDataLen = 0;
while (dataLen - readedDataLen > 0)
{
int dataBufferReadCount = 0;
if (dataLen - readedDataLen >= 1024)
{
dataBufferReadCount = 1024;
}
else
{
dataBufferReadCount = dataLen - readedDataLen;
}
//读取数据到缓冲区
byte[] dataBuffer = new byte[dataBufferReadCount];
int tempReadCount = networkStream.Read(dataBuffer, 0, dataBuffer.Length);
//拷贝数据到数据容器中
dataBuffer.CopyTo(data, readedDataLen);
readedDataLen += tempReadCount;
}
sb.Append($"({dataType},'{Encoding.UTF8.GetString(data)}'),");
}
//读取当前客户端连接的下一条数据
readCount = networkStream.Read(dataLenByte, 0, dataLenByte.Length);
}
if (!string.IsNullOrEmpty(sb.ToString()))
{
using (MySqlConnection conn =
new MySqlConnection("server=127.0.0.1;database=Log;uid=root;pwd=root;charset=utf8;"))
{
using (MySqlCommand cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = $"insert into apilog(`Type`,`Msg`) values {sb.ToString().TrimEnd(',')}";
cmd.ExecuteNonQuery();
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"保存日志信息发生异常:{ex}");
LogHelper.Error("保存日志信息发生异常", ex);
}
finally
{
//关闭客户端连接
this._TcpClient.Close();
}
}
}
}
4、主程序
using MySql.Data.MySqlClient;
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace LogServer
{
class Program
{
static void Main(string[] args)
{
Thread tcpListenerThread = new Thread(() =>
{
IPEndPoint ipEndPoint=new IPEndPoint(IPAddress.Any,8888);
TcpListener tcpServer = new TcpListener(ipEndPoint);
tcpServer.Start(100);
while (true)
{
Console.WriteLine("日志服务器正在运行中...");
TcpClient tcpClient = tcpServer.AcceptTcpClient();
Console.WriteLine("请求来了!开始处理...");
TcpClientManager tcpClientManager = new TcpClientManager(tcpServer, tcpClient);
Thread tcpClientThread = new Thread(tcpClientManager.DealClientData);
tcpClientThread.IsBackground = true;
tcpClientThread.Start();
}
});
tcpListenerThread.IsBackground = true;
tcpListenerThread.Start();
while (true)
{
Console.WriteLine("日志服务器正在运行中...");
Console.ReadKey();
}
}
}
}
5、写日志测试
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace ApiServer.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values
[HttpGet]
public ActionResult<string> Get(string msg)
{
try
{
using (TcpClient tcpClient = new TcpClient("127.0.0.1", 8888))
{
if (string.IsNullOrEmpty(msg))
{
return "请传正确的请求参数";
}
//发送日志数据
for (int i = 0; i < 100; i++)
{
byte[] sourceData = Encoding.UTF8.GetBytes(msg);
byte[] dataLen = BitConverter.GetBytes(sourceData.Length);
byte[] dataType = BitConverter.GetBytes(1);
byte[] sendData = new byte[sourceData.Length + 8];
dataLen.CopyTo(sendData, 0);
dataType.CopyTo(sendData, 4);
sourceData.CopyTo(sendData, 8);
tcpClient.Client.Send(sendData);
}
//关闭与日志服务器的连接
tcpClient.Close();
}
}
catch (Exception ex)
{
return ex.Message;
}
return "日志保存成功";
}
}
}