通常在日志写的过程,每次写日志时,都是直接写入文件中。当数据量不大的时候,完成可以这样做,不会有什么问题。但是当数据量大时,每次写数据则会由于IO的瓶颈造成很大的性能问题。
为了解决这个问题,本文设计了一套基于多线程的在内存中追加日志后再写入文件的缓冲机制,从而解决这个问题。经测试,在没有任何特别文件读写优化的情况下,每秒可以实现20万条长度为30字节的日志的硬盘(SSD)写入。
static void Main(string[] args)
{
LogManager.Log(LogType.L101001_UserLogin, "10232");
LogManager.Log(LogType.L101002_UserLogout, "10232", DateTime.Now);
LogManager.Log(LogType.L415001_Debug, "MyLog.Program", true, 135.2);
// Simulates this program running for 5 seconds.
Thread.Sleep(5000);
}
写入的结果
1136515010100110232
11365150101002102322019-05-12 12:59:10
11365150415001MyLog.ProgramTrue135.2
///
/// This class is used to manage logs from the same program.
/// It has a key method AddLog to receive log. This is a
/// multi-threading class to save logs into a file in specified
/// seconds to avoid save logs too frequently.
///
public class LogManager
{
///
/// The file path to store log.
///
public string LogPath { get; private set; } = "system.log";
///
/// The interval (in second) of saving logs into file.
///
public int SaveInterval { get; set; } = 1;
// To contronl the running status of the thread
bool isRunning = false;
// Teporary log list for holding current logs.
List<Log> logs = new List<Log>();
// The thread to perform method process()
Thread thread;
///
/// Construct a LogManager object.
///
public LogManager()
{
}
///
/// Construct a LogManager object.
///
/// The file path to store logs.
/// Writing intervl.
public LogManager(string logPath, int saveInterval = 10)
{
LogPath = logPath;
SaveInterval = saveInterval;
}
///
/// Add a new log.
///
/// The type of this log.
/// The arguments of this log.
public void AddLog(LogType logType, object[] args)
{
AddLog(new Log(logType, args));
}
///
/// Add a new log.
///
/// The log to be added.
public void AddLog(Log log)
{
lock (this)
{
logs.Add(log);
}
}
///
/// main method to process logs.
///
private void process()
{
isRunning = true;
while (isRunning)
{
if (logs.Count > 0)
{
// Obtain all logs and release lock
List<Log> loglist = new List<Log>();
lock (this)
{
loglist.AddRange(logs);
logs.Clear();
}
// append new logs into log file.
using (StreamWriter fw = new StreamWriter(LogPath, true, Encoding.UTF8))
{
foreach (Log log in loglist)
fw.WriteLine(log);
}
System.Console.WriteLine(DateTime.Now + " Saved.");
}
// Sleep SaveInterval seconds for no waisting CPU resource.
Thread.Sleep(SaveInterval);
}
}
///
/// Starts thread of this log manager.
///
public void Start()
{
if (thread != null)
return;
thread = new Thread(process);
thread.IsBackground = true;
thread.Start();
}
///
/// Stops the thread of this log manager.
///
public void Stop()
{
isRunning = false;
}
// used in static
static LogManager logManager;
// a static method to save logs.
public static void Log(LogType logType, params object[] args)
{
if (logManager == null)
{
logManager = new LogManager();
logManager.Start();
}
logManager.AddLog(logType, args);
}
}
public class Log
{
///
/// The occuring time.
///
public DateTime Time { get; set; } = DateTime.Now;
///
/// The type of the log.
///
public LogType LogType { get; set; }
///
/// Arguements of a log.
///
public string[] Arguments { get; set; }
// Used to separate data fields of a log when serializing.
char separator = (char)6;
///
/// Construct a Log object.
///
/// The type of log.
/// Argument list.
public Log(LogType logType, object[] args)
{
LogType = logType;
Arguments = new string[args == null ? 0 : args.Length];
for (int i = 0; i < Arguments.Length; i++)
Arguments[i] = args[i] == null ? "" : args[i].ToString();
}
///
/// Format: yyyyMMddHHmmss|LogType|Arguments
///
///
public override string ToString()
{
StringBuilder sb = new StringBuilder();
sb.Append((int)(Time - new DateTime(2019,1,1)).TotalSeconds);
sb.Append(separator);
sb.Append((int)LogType);
sb.Append(separator);
sb.Append(string.Join(separator.ToString(), Arguments));
return sb.ToString();
}
///
/// Parses a string into a Log object.
///
/// Input string.
///
public Log Parse(string str)
{
return null;
}
}
///
/// Used to represent different types of logs based on Integers.
///
public enum LogType : int
{
L101001_UserLogin = 101001,
L101002_UserLogout = 101002,
L415001_Debug = 415001,
}