CPU:i5-8265U 硬盘:固态硬盘 测试结果:每秒写入文件大约1万到3万条日志,每条日志的字符串长度是140多个字符
支持多线程并发,支持多进程并发,支持按文件大小分隔日志文件
LogUtil.cs代码:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Utils
{
/// <summary>
/// 写日志类
/// </summary>
public class LogUtil
{
#region 字段
private static string _path = null;
private static Mutex _mutex = new Mutex(false, "LogUtil.Mutex.180740C3B1C44D428683D35F84F97E22");
private static ConcurrentDictionary<string, int> _dictIndex = new ConcurrentDictionary<string, int>();
private static ConcurrentDictionary<string, long> _dictSize = new ConcurrentDictionary<string, long>();
private static ConcurrentDictionary<string, FileStream> _dictStream = new ConcurrentDictionary<string, FileStream>();
private static ConcurrentDictionary<string, StreamWriter> _dictWriter = new ConcurrentDictionary<string, StreamWriter>();
private static ConcurrentDictionary<string, string> _dictPathFolders = new ConcurrentDictionary<string, string>();
private static TaskSchedulerEx _scheduler = new TaskSchedulerEx(2, 2);
private static int _fileSize = 10 * 1024 * 1024; //日志分隔文件大小
#endregion
#region 写文件
/// <summary>
/// 写文件
/// </summary>
private static void WriteFile(LogType logType, string log, string path)
{
try
{
FileStream fs = null;
StreamWriter sw = null;
if (!(_dictStream.TryGetValue(logType.ToString() + path, out fs) && _dictWriter.TryGetValue(logType.ToString() + path, out sw)))
{
foreach (string key in _dictWriter.Keys)
{
if (key.StartsWith(logType.ToString()))
{
StreamWriter item;
_dictWriter.TryRemove(key, out item);
item.Close();
}
}
foreach (string key in _dictStream.Keys)
{
if (key.StartsWith(logType.ToString()))
{
FileStream item;
_dictStream.TryRemove(key, out item);
item.Close();
}
}
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite);
sw = new StreamWriter(fs);
_dictWriter.TryAdd(logType.ToString() + path, sw);
_dictStream.TryAdd(logType.ToString() + path, fs);
}
fs.Seek(0, SeekOrigin.End);
sw.WriteLine(log);
sw.Flush();
fs.Flush();
}
catch (Exception ex)
{
string str = ex.Message;
}
}
#endregion
#region 生成日志文件路径
/// <summary>
/// 生成日志文件路径
/// </summary>
private static string CreateLogPath(LogType logType, string log)
{
try
{
if (_path == null)
{
UriBuilder uri = new UriBuilder(Assembly.GetExecutingAssembly().CodeBase);
_path = Path.GetDirectoryName(Uri.UnescapeDataString(uri.Path));
}
string pathFolder = Path.Combine(_path, "Log\\" + logType.ToString() + "\\");
if (!_dictPathFolders.ContainsKey(pathFolder))
{
if (!Directory.Exists(Path.GetDirectoryName(pathFolder)))
{
Directory.CreateDirectory(Path.GetDirectoryName(pathFolder));
}
_dictPathFolders.TryAdd(pathFolder, pathFolder);
}
int currentIndex;
long size;
string strNow = DateTime.Now.ToString("yyyyMMdd");
string strKey = pathFolder + strNow;
if (!(_dictIndex.TryGetValue(strKey, out currentIndex) && _dictSize.TryGetValue(strKey, out size)))
{
_dictIndex.Clear();
_dictSize.Clear();
GetIndexAndSize(pathFolder, strNow, out currentIndex, out size);
if (size >= _fileSize) currentIndex++;
_dictIndex.TryAdd(strKey, currentIndex);
_dictSize.TryAdd(strKey, size);
}
int index = _dictIndex[strKey];
string logPath = Path.Combine(pathFolder, strNow + (index == 1 ? "" : "_" + index.ToString()) + ".txt");
_dictSize[strKey] += Encoding.UTF8.GetByteCount(log);
if (_dictSize[strKey] > _fileSize)
{
_dictIndex[strKey]++;
_dictSize[strKey] = 0;
}
return logPath;
}
catch (Exception ex)
{
string str = ex.Message;
return null;
}
}
#endregion
#region 拼接日志内容
/// <summary>
/// 拼接日志内容
/// </summary>
private static string CreateLogString(LogType logType, string log)
{
return string.Format(@"{0} {1} {2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), ("[" + logType.ToString() + "]").PadRight(7, ' '), log);
}
#endregion
#region 获取初始Index和Size
/// <summary>
/// 获取初始Index和Size
/// </summary>
private static void GetIndexAndSize(string pathFolder, string strNow, out int index, out long size)
{
index = 1;
size = 0;
Regex regex = new Regex(strNow + "_*(\\d*).txt");
string[] fileArr = Directory.GetFiles(pathFolder);
string currentFile = null;
foreach (string file in fileArr)
{
Match match = regex.Match(file);
if (match.Success)
{
string str = match.Groups[1].Value;
if (!string.IsNullOrWhiteSpace(str))
{
int temp = Convert.ToInt32(str);
if (temp > index)
{
index = temp;
currentFile = file;
}
}
else
{
index = 1;
currentFile = file;
}
}
}
if (currentFile != null)
{
FileInfo fileInfo = new FileInfo(currentFile);
size = fileInfo.Length;
}
}
#endregion
#region 写调试日志
/// <summary>
/// 写调试日志
/// </summary>
public static Task Debug(string log)
{
return Task.Factory.StartNew(() =>
{
try
{
_mutex.WaitOne();
log = CreateLogString(LogType.Debug, log);
string path = CreateLogPath(LogType.Debug, log);
WriteFile(LogType.Debug, log, path);
}
catch (Exception ex)
{
string str = ex.Message;
}
finally
{
_mutex.ReleaseMutex();
}
}, CancellationToken.None, TaskCreationOptions.None, _scheduler);
}
#endregion
#region 写错误日志
public static Task Error(Exception ex, string log = null)
{
return Error(string.IsNullOrEmpty(log) ? ex.Message + "\r\n" + ex.StackTrace : (log + ":") + ex.Message + "\r\n" + ex.StackTrace);
}
/// <summary>
/// 写错误日志
/// </summary>
public static Task Error(string log)
{
return Task.Factory.StartNew(() =>
{
try
{
_mutex.WaitOne();
log = CreateLogString(LogType.Error, log);
string path = CreateLogPath(LogType.Error, log);
WriteFile(LogType.Error, log, path);
}
catch (Exception ex)
{
string str = ex.Message;
}
finally
{
_mutex.ReleaseMutex();
}
}, CancellationToken.None, TaskCreationOptions.None, _scheduler);
}
#endregion
#region 写操作日志
/// <summary>
/// 写操作日志
/// </summary>
public static Task Log(string log)
{
return Task.Factory.StartNew(() =>
{
try
{
_mutex.WaitOne();
log = CreateLogString(LogType.Info, log);
string path = CreateLogPath(LogType.Info, log);
WriteFile(LogType.Info, log, path);
}
catch (Exception ex)
{
string str = ex.Message;
}
finally
{
_mutex.ReleaseMutex();
}
}, CancellationToken.None, TaskCreationOptions.None, _scheduler);
}
#endregion
}
#region 日志类型
/// <summary>
/// 日志类型
/// </summary>
public enum LogType
{
Debug,
Info,
Error
}
#endregion
}
View Code
依赖的TaskSchedulerEx.cs代码:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Utils
{
/// <summary>
/// TaskScheduler扩展
/// 每个实例都是独立线程池
/// </summary>
public class TaskSchedulerEx : TaskScheduler, IDisposable
{
#region 外部方法
[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
#endregion
#region 变量属性事件
private ConcurrentQueue<Task> _tasks = new ConcurrentQueue<Task>();
private int _coreThreadCount = 0;
private int _maxThreadCount = 0;
private int _auxiliaryThreadTimeOut = 20000; //辅助线程释放时间
private int _activeThreadCount = 0;
private System.Timers.Timer _timer;
private object _lockCreateTimer = new object();
private bool _run = true;
private AutoResetEvent _evt = new AutoResetEvent(false);
/// <summary>
/// 活跃线程数
/// </summary>
public int ActiveThreadCount
{
get { return _activeThreadCount; }
}
/// <summary>
/// 核心线程数
/// </summary>
public int CoreThreadCount
{
get { return _coreThreadCount; }
}
/// <summary>
/// 最大线程数
/// </summary>
public int MaxThreadCount
{
get { return _maxThreadCount; }
}
#endregion
#region 构造函数
/// <summary>
/// TaskScheduler扩展
/// 每个实例都是独立线程池
/// </summary>
/// <param name="coreThreadCount">核心线程数(大于或等于0,不宜过大)(如果是一次性使用,则设置为0比较合适)</param>
/// <param name="maxThreadCount">最大线程数</param>
public TaskSchedulerEx(int coreThreadCount = 10, int maxThreadCount = 20)
{
_maxThreadCount = maxThreadCount;
CreateCoreThreads(coreThreadCount);
}
#endregion
#region override GetScheduledTasks
protected override IEnumerable<Task> GetScheduledTasks()
{
return _tasks;
}
#endregion
#region override TryExecuteTaskInline
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return false;
}
#endregion
#region override QueueTask
protected override void QueueTask(Task task)
{
CreateTimer();
_tasks.Enqueue(task);
_evt.Set();
}
#endregion
#region 资源释放
/// <summary>
/// 资源释放
/// 队列中尚未执行的任务不再执行
/// </summary>
public void Dispose()
{
_run = false;
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
_timer = null;
}
while (_activeThreadCount > 0)
{
_evt.Set();
}
}
#endregion
#region 创建核心线程池
/// <summary>
/// 创建核心线程池
/// </summary>
private void CreateCoreThreads(int? coreThreadCount = null)
{
if (coreThreadCount != null) _coreThreadCount = coreThreadCount.Value;
for (int i = 0; i < _coreThreadCount; i++)
{
Interlocked.Increment(ref _activeThreadCount);
Thread thread = null;
thread = new Thread(new ThreadStart(() =>
{
Task task;
while (_run)
{
if (_tasks.TryDequeue(out task))
{
TryExecuteTask(task);
}
else
{
_evt.WaitOne();
}
}
Interlocked.Decrement(ref _activeThreadCount);
if (_activeThreadCount == 0)
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
}));
thread.IsBackground = true;
thread.Start();
}
}
#endregion
#region 创建辅助线程
/// <summary>
/// 创建辅助线程
/// </summary>
private void CreateThread()
{
Interlocked.Increment(ref _activeThreadCount);
Thread thread = null;
thread = new Thread(new ThreadStart(() =>
{
Task task;
DateTime dt = DateTime.Now;
while (_run && DateTime.Now.Subtract(dt).TotalMilliseconds < _auxiliaryThreadTimeOut)
{
if (_tasks.TryDequeue(out task))
{
TryExecuteTask(task);
dt = DateTime.Now;
}
else
{
_evt.WaitOne(_auxiliaryThreadTimeOut);
}
}
Interlocked.Decrement(ref _activeThreadCount);
if (_activeThreadCount == _coreThreadCount)
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
}));
thread.IsBackground = true;
thread.Start();
}
#endregion
#region 创建定时器
private void CreateTimer()
{
if (_timer == null) //_timer不为空时,跳过,不走lock,提升性能
{
if (_activeThreadCount >= _coreThreadCount && _activeThreadCount < _maxThreadCount) //活跃线程数达到最大线程数时,跳过,不走lock,提升性能
{
lock (_lockCreateTimer)
{
if (_timer == null)
{
_timer = new System.Timers.Timer();
_timer.Interval = _coreThreadCount == 0 ? 1 : 500;
_timer.Elapsed += (s, e) =>
{
if (_activeThreadCount >= _coreThreadCount && _activeThreadCount < _maxThreadCount)
{
if (_tasks.Count > 0)
{
if (_timer.Interval != 20) _timer.Interval = 20;
CreateThread();
}
else
{
if (_timer.Interval != 500) _timer.Interval = 500;
}
}
else
{
if (_timer != null)
{
_timer.Stop();
_timer.Dispose();
_timer = null;
}
}
};
_timer.Start();
}
}
}
}
}
#endregion
#region 全部取消
/// <summary>
/// 全部取消
/// 取消队列中尚未执行的任务
/// </summary>
public void CancelAll()
{
Task tempTask;
while (_tasks.TryDequeue(out tempTask)) { }
}
#endregion
}
}
View Code
依赖的RunHelper.cs代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Utils
{
/// <summary>
/// 线程工具类
/// </summary>
public static class RunHelper
{
#region 变量属性事件
#endregion
#region 线程中执行
/// <summary>
/// 线程中执行
/// </summary>
public static Task Run(this TaskScheduler scheduler, Action<object> doWork, object arg = null, Action<Exception> errorAction = null)
{
return Task.Factory.StartNew((obj) =>
{
try
{
doWork(obj);
}
catch (Exception ex)
{
if (errorAction != null) errorAction(ex);
LogUtil.Error(ex, "ThreadUtil.Run错误");
}
}, arg, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
#endregion
#region 线程中执行
/// <summary>
/// 线程中执行
/// </summary>
public static Task Run(this TaskScheduler scheduler, Action doWork, Action<Exception> errorAction = null)
{
return Task.Factory.StartNew(() =>
{
try
{
doWork();
}
catch (Exception ex)
{
if (errorAction != null) errorAction(ex);
LogUtil.Error(ex, "ThreadUtil.Run错误");
}
}, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
#endregion
#region 线程中执行
/// <summary>
/// 线程中执行
/// </summary>
public static Task<T> Run<T>(this TaskScheduler scheduler, Func<object, T> doWork, object arg = null, Action<Exception> errorAction = null)
{
return Task.Factory.StartNew<T>((obj) =>
{
try
{
return doWork(obj);
}
catch (Exception ex)
{
if (errorAction != null) errorAction(ex);
LogUtil.Error(ex, "ThreadUtil.Run错误");
return default(T);
}
}, arg, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
#endregion
#region 线程中执行
/// <summary>
/// 线程中执行
/// </summary>
public static Task<T> Run<T>(this TaskScheduler scheduler, Func<T> doWork, Action<Exception> errorAction = null)
{
return Task.Factory.StartNew<T>(() =>
{
try
{
return doWork();
}
catch (Exception ex)
{
if (errorAction != null) errorAction(ex);
LogUtil.Error(ex, "ThreadUtil.Run错误");
return default(T);
}
}, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
#endregion
#region 线程中执行
/// <summary>
/// 线程中执行
/// </summary>
public static async Task<T> RunAsync<T>(this TaskScheduler scheduler, Func<object, T> doWork, object arg = null, Action<Exception> errorAction = null)
{
return await Task.Factory.StartNew<T>((obj) =>
{
try
{
return doWork(obj);
}
catch (Exception ex)
{
if (errorAction != null) errorAction(ex);
LogUtil.Error(ex, "ThreadUtil.Run错误");
return default(T);
}
}, arg, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
#endregion
#region 线程中执行
/// <summary>
/// 线程中执行
/// </summary>
public static async Task<T> RunAsync<T>(this TaskScheduler scheduler, Func<T> doWork, Action<Exception> errorAction = null)
{
return await Task.Factory.StartNew<T>(() =>
{
try
{
return doWork();
}
catch (Exception ex)
{
if (errorAction != null) errorAction(ex);
LogUtil.Error(ex, "ThreadUtil.Run错误");
return default(T);
}
}, CancellationToken.None, TaskCreationOptions.None, scheduler);
}
#endregion
}
}
View Code
测试代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;
namespace LogUtilTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(() =>
{
int n = 100000;
string processId = Process.GetCurrentProcess().Id.ToString().PadLeft(8, ' ');
List<Task> taskList = new List<Task>();
string str = " abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcda3.1415bcdabcdabcdabcdabc@#$%^&dabcdabcdabcdabcdabcdabcdabcdabcd";
DateTime dtStart = DateTime.Now;
for (int i = 1; i <= n; i++)
{
Task task = LogUtil.Log("ProcessId:【" + processId + "】 测试" + i.ToString().PadLeft(8, '0') + str);
taskList.Add(task);
task = LogUtil.Debug("ProcessId:【" + processId + "】 测试" + i.ToString().PadLeft(8, '0') + str);
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray());
double sec = DateTime.Now.Subtract(dtStart).TotalSeconds;
MessageBox.Show(n + "条日志完成,耗时" + sec.ToString("0.000") + "秒");
});
}
}
}
View Code
测试结果截图: