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 _mutexDebug = new Mutex(false, "LogUtil.Mutex.Debug.252F8025254D4DAA8EFB7FFE177F13E0"); private static Mutex _mutexInfo = new Mutex(false, "LogUtil.Mutex.Info.180740C3B1C44D428683D35F84F97E22"); private static Mutex _mutexError = new Mutex(false, "LogUtil.Mutex.Error.81273C1400774A3B8310C2EC1C3AFFFF"); 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++; size = 0; } _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 { _mutexDebug.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 { _mutexDebug.ReleaseMutex(); } }, CancellationToken.None, TaskCreationOptions.None, _scheduler); } #endregion #region 写错误日志 public static Task Error(Exception ex, string log = null) { return Error(string.IsNullOrEmpty(log) ? ex.Message + " " + ex.StackTrace : (log + ":") + ex.Message + " " + ex.StackTrace); } /// <summary> /// 写错误日志 /// </summary> public static Task Error(string log) { return Task.Factory.StartNew(() => { try { _mutexError.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 { _mutexError.ReleaseMutex(); } }, CancellationToken.None, TaskCreationOptions.None, _scheduler); } #endregion #region 写操作日志 /// <summary> /// 写操作日志 /// </summary> public static Task Log(string log) { return Task.Factory.StartNew(() => { try { _mutexInfo.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 { _mutexInfo.ReleaseMutex(); } }, CancellationToken.None, TaskCreationOptions.None, _scheduler); } #endregion } #region 日志类型 /// <summary> /// 日志类型 /// </summary> public enum LogType { Debug, Info, Error } #endregion }
依赖的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 } }
依赖的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 } }
测试代码:
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") + "秒"); }); } } }
测试结果截图: