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 LimitedTaskScheduler _scheduler = new LimitedTaskScheduler(); private static int _fileSize = * * ; //日志分隔文件大小
#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(, 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 == ? "" : "_" + index.ToString()) + ".txt"); _dictSize[strKey] += Encoding.UTF8.GetByteCount(log);
if (_dictSize[strKey] > _fileSize)
{
_dictIndex[strKey]++;
_dictSize[strKey] = ;
} 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(, ' '), log);
}
#endregion #region 获取初始Index和Size
/// <summary>
/// 获取初始Index和Size
/// </summary>
private static void GetIndexAndSize(string pathFolder, string strNow, out int index, out long size)
{
index = ;
size = ;
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[].Value;
if (!string.IsNullOrWhiteSpace(str))
{
int temp = Convert.ToInt32(str);
if (temp > index)
{
index = temp;
currentFile = file;
}
}
else
{
index = ;
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 + "\r\n" + ex.StackTrace : (log + ":") + ex.Message + "\r\n" + 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 }

依赖的LimitedTaskScheduler.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
{
public class LimitedTaskScheduler : TaskScheduler, IDisposable
{
#region 外部方法
[DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")]
public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize);
#endregion #region 变量属性事件
private BlockingCollection<Task> _tasks = new BlockingCollection<Task>();
List<Thread> _threadList = new List<Thread>();
private int _threadCount = ;
private int _timeOut = Timeout.Infinite;
private Task _tempTask;
#endregion #region 构造函数
public LimitedTaskScheduler(int threadCount = )
{
CreateThreads(threadCount);
}
#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)
{
_tasks.Add(task);
}
#endregion #region 资源释放
/// <summary>
/// 资源释放
/// 如果尚有任务在执行,则会在调用此方法的线程上引发System.Threading.ThreadAbortException,请使用Task.WaitAll等待任务执行完毕后,再调用该方法
/// </summary>
public void Dispose()
{
_timeOut = ; foreach (Thread item in _threadList)
{
item.Abort();
}
_threadList.Clear(); GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -, -);
}
}
#endregion #region 创建线程池
/// <summary>
/// 创建线程池
/// </summary>
private void CreateThreads(int? threadCount = null)
{
if (threadCount != null) _threadCount = threadCount.Value;
_timeOut = Timeout.Infinite; for (int i = ; i < _threadCount; i++)
{
Thread thread = new Thread(new ThreadStart(() =>
{
Task task;
while (_tasks.TryTake(out task, _timeOut))
{
TryExecuteTask(task);
}
}));
thread.IsBackground = true;
thread.Start();
_threadList.Add(thread);
}
}
#endregion #region 全部取消
/// <summary>
/// 全部取消
/// 当前正在执行的任务无法取消,取消的只是后续任务,相当于AbortAll
/// </summary>
public void CancelAll()
{
while (_tasks.TryTake(out _tempTask)) { }
}
#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 = ;
string processId = Process.GetCurrentProcess().Id.ToString().PadLeft(, ' ');
List<Task> taskList = new List<Task>();
string str = " abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcda3.1415bcdabcdabcdabcdabc@#$%^&dabcdabcdabcdabcdabcdabcdabcdabcd";
DateTime dtStart = DateTime.Now;
for (int i = ; i <= n; i++)
{
Task task = LogUtil.Log("ProcessId:【" + processId + "】 测试" + i.ToString().PadLeft(, '') + str);
taskList.Add(task);
task = LogUtil.Debug("ProcessId:【" + processId + "】 测试" + i.ToString().PadLeft(, '') + str);
taskList.Add(task);
}
Task.WaitAll(taskList.ToArray()); double sec = DateTime.Now.Subtract(dtStart).TotalSeconds;
MessageBox.Show(n + "条日志完成,耗时" + sec.ToString("0.000") + "秒");
});
}
}
}

测试结果截图:

C#写文本日志帮助类(支持多线程)的更多相关文章

  1. C#写文本日志帮助类(支持多线程)改进版(不适用于ASP.NET程序)

    由于iis的自动回收机制,不适用于ASP.NET程序 代码: using System; using System.Collections.Concurrent; using System.Confi ...

  2. Android开发调试日志工具类[支持保存到SD卡]

    直接上代码: package com.example.callstatus; import java.io.File; import java.io.FileWriter; import java.i ...

  3. glog另启动线程写文本日志

    glog本身是很高效的,google的大牛肯定知道大规模的写日志用glog的话肯定会影响业务线程的处理,带负荷的磁盘IO谁都桑不起.比方levelDB就是默认异步写,更不用说google的三驾马车都是 ...

  4. Asp.Net写文本日志

    底层代码: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespac ...

  5. C#写日志工具类

    代码: using System; using System.Collections.Generic; using System.IO; using System.Linq; using System ...

  6. 支持异步写入的日志类,支持Framework2.0

    因为工作需要需要在XP上运行一个C#编写的Winform插件,我就用Framework2.0,因为存在接口交互所以想保留交易过程的入参出参. 考虑到插件本身实施的因素,就没有使用Log4.NLog等成 ...

  7. 如何将Unicode文本写到日志文件中

    有时为了定位问题,我们需要结合打印日志来处理.特别是较难复现的,一般都需要查看上下文日志才能找出可能存在的问题.考虑到程序要在不同语言的操作系统上运行,程序界面显示要支持Unicode,打印出来的日志 ...

  8. c#中@标志的作用 C#通过序列化实现深表复制 细说并发编程-TPL 大数据量下DataTable To List效率对比 【转载】C#工具类:实现文件操作File的工具类 异步多线程 Async .net 多线程 Thread ThreadPool Task .Net 反射学习

    c#中@标志的作用   参考微软官方文档-特殊字符@,地址 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/toke ...

  9. Linux是一个基于POSIX和Unix的多用户、多任务、支持多线程和多CPU的性能稳定的操作系统,可免费使用并自由传播。

    Linux是一个基于POSIX和Unix的多用户.多任务.支持多线程和多CPU的性能稳定的操作系统,可免费使用并自由传播. Linux是众多操作系统之一 , 目前流行的服务器和 PC 端操作系统有 L ...

随机推荐

  1. 菜鸟学JS(五)——window.onload与$(document).ready()

    我们继续说JS,我们常常在页面加载完成以后做一些操作,比如一些元素的显示与隐藏.一些动画效果.我们通常有两种方法来完成这个事情,一个就是window.onload事件,另一个就是JQuery的read ...

  2. 在线制作h5

    在线制作h5 官网:http://www.godgiftgame.com 在线制作h5首页预览效果图如下: 一.主要功能区域主要功能区域分布在上中左右三个地方,1.上面区域是功能选择区,包括图片素材. ...

  3. C语言函数可变长度参数剖析

    C语言中的很多函数的入参被定义为可变参数,最典型的 int printf (const char * fmt, ...) 要对其中的可变参数进行处理,就要用到va_list类型和 VA_START, ...

  4. EZGUI下的动态图片的处理

    EZGUI的使用过程中,有时需要使用动态的图片,比如商店里面商品的ICON,好友的头像等,通过使用SimpleSprite可以实现这个功能.   比如一个通过网络显示好友头像: WWW www = n ...

  5. 记一次VNC远程连接Linux问题解决记录(5900端口测试、KDE桌面安装)

    最近几天,到一个项目上安装Linux部署环境.由于服务器在机房,而进机房又比较麻烦,于是选择VNC远程连接Linux就显得自然而然了.以前也用过VNC,而且还经常使用,由于各个项目环境不太一样,这次也 ...

  6. Java与邮件系统交互之使用Socket验证邮箱是否存在

    最近遇到一个需求:需要验证用户填写的邮箱地址是否真实存在,是否可达.和普通的正则表达式不同,他要求尝试链接目标邮箱服务器并请求校验目标邮箱是否存在. 先来了解 DNS之MX记录 对于DNS不了解的,请 ...

  7. javascript方法 call()和apply()的用法

    先上代码: apply()方法示例 /*定义一个人类*/ function Person(name,age) { this.name=name; this.age=age; } /*定义一个学生类*/ ...

  8. WPF使用扩展屏幕

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  9. Selenium WebDriver屏幕截图(C#版)

    Selenium WebDriver屏幕截图(C#版)http://www.automationqa.com/forum.php?mod=viewthread&tid=3595&fro ...

  10. SharePoint 2013中以其他用户身份登录的WebPart(免费下载)

    在SharePoint 2013中微软并没有提供在SharePoint 2010中以其他用户身份登录的菜单,这对一般用户影响不大,但对于系统管理员或测试人员或特定人员(如在OA系统中的文员或秘书,常常 ...