100行代码实现了多线程,批量写入,文件分块的日志方法
一,您選擇用什么樣的日志組件
日志組件,不得不提大名鼎鼎的Log4Net。比較常用的還有 Enterprise Library Logging,ServiceStack Logging。當(dāng)然您還可以補(bǔ)充,我就只用過(guò)這幾款。
上邊提到的3款日志組件,都要在.config里加代碼,特別是Log4Net,還要把SQL寫(xiě)在配置里。我就是僅僅只寫(xiě)個(gè)日志,還要配置這么多信息,讓人略有不爽。
所以在很長(zhǎng)一段時(shí)間里,我用下邊這個(gè)方法寫(xiě)日志:
private static void WriteText(string logPath, string logContent){try{if (!File.Exists(logPath)){File.CreateText(logPath).Close();}StreamWriter sw = File.AppendText(logPath);sw.Write(logContent);sw.Close();}catch (Exception ex){}finally{}} View Code這個(gè)方法足夠的簡(jiǎn)單,核心代碼就只有那么5,6行,還包含容錯(cuò)機(jī)制。我就喜歡用這種簡(jiǎn)單的代碼來(lái)處理簡(jiǎn)單的事。
二,多線(xiàn)程下引爆了問(wèn)題
在多線(xiàn)程的情況下,比如100個(gè)線(xiàn)程同時(shí)需要寫(xiě)日志,上邊提到的這個(gè)方法就力不從心了。
一個(gè)線(xiàn)程訪(fǎng)問(wèn)日志資源,另一個(gè)線(xiàn)程再去訪(fǎng)問(wèn)的時(shí)候,就會(huì)出現(xiàn)異常。
方法一:
public static Object _processLock = new Object();private void Button_Click_1(object sender, RoutedEventArgs e){lock (_processLock){}}方法二:
public static Object _processLock = new Object();private void Button_Click_1(object sender, RoutedEventArgs e){Monitor.Enter(_processLock);Monitor.Exit(_processLock);}這樣,你不得不承認(rèn),我已經(jīng)解決了多線(xiàn)程的問(wèn)題。
但是有瓶頸,這些需要寫(xiě)日志的線(xiàn)程,必須要等前一個(gè)釋放了鎖資源,后一個(gè)線(xiàn)程才能訪(fǎng)問(wèn)的情況。
三,重新設(shè)計(jì)日志組件
先看圖,再說(shuō)一下我的思路:
1,不管有多少線(xiàn)程同時(shí)需要寫(xiě)日志,我都用一個(gè)臨時(shí)隊(duì)列來(lái)存放這些日志信息。
2,再啟用一個(gè)Task任務(wù)把隊(duì)列的日志批量存放到.log文件里。
3,附加一個(gè)小功能,每個(gè)日志存儲(chǔ)的大小限制,當(dāng)日志太大了,查看打開(kāi)的時(shí)候比較慢。
四,具體的代碼實(shí)現(xiàn)
1,在多線(xiàn)程的情況下,我們首先把日志壓到Queue隊(duì)列里
static ConcurrentQueue<Tuple<string, string>> logQueue = new ConcurrentQueue<Tuple<string, string>>();在這兒,我為什么選用 ConcurrentQueue??而不是? Queue? 。因?yàn)镃oncurrentQueue??表示線(xiàn)程安全的先進(jìn)先出 (FIFO) 集合。
當(dāng)然你一定要用Queue也是可以的,但是要自己去實(shí)現(xiàn)鎖機(jī)制,何必自找麻煩呢?
2,把日志隊(duì)列里的數(shù)據(jù),批量持久化到.log文件里
這個(gè)問(wèn)題,讓我很頭大。我最開(kāi)始的方法是:
持久化日志方法一:
a,申明一個(gè)Task任務(wù),當(dāng)我Task任務(wù)沒(méi)有實(shí)現(xiàn)化時(shí),先實(shí)例化,然后再進(jìn)行持久化日志寫(xiě)入。
b,當(dāng)我的Task任務(wù),已經(jīng)實(shí)例化了,并且是處于 IsCompleted 狀態(tài),我重新實(shí)例化Task,再進(jìn)行持久化日志的寫(xiě)入。
static Task writeTask = default(Task); public static void WriteLog(String customDirectory, String preFile, String infoData){string logPath = GetLogPath(customDirectory, preFile); string logContent = String.Concat(DateTime.Now, " ", infoData); logQueue.Enqueue(new Tuple<string, string>(logPath, logContent)); if (writeTask == null) { writeTask = new Task((object obj) => { //pause.WaitOne(1000, true); LogRepository(); } , null , TaskCreationOptions.LongRunning); writeTask.Start(); } if (writeTask.IsCompleted) { writeTask = new Task((object obj) => { //pause.WaitOne(1000, true); LogRepository(); } , null , TaskCreationOptions.LongRunning); writeTask.Start(); } } View Code異常信息:
理論是那么的美好,但是現(xiàn)實(shí)是那么殘酷,當(dāng)我跑單元測(cè)試的時(shí)候,一段時(shí)間后總是拋出如下錯(cuò)誤。如果是有那位朋友知道其原因,把這個(gè)問(wèn)題解決就完美了。
但是我不能因?yàn)檫@一個(gè)異常,導(dǎo)致我這個(gè)組件寫(xiě)不下去吧!活人不能被一泡尿給憋死。
追加:完整代碼如下:
public class IOExtention{static ConcurrentQueue<Tuple<string, string>> logQueue = new ConcurrentQueue<Tuple<string, string>>();static volatile Task writeTask = default(Task);static IOExtention(){}public static void WriteLog(String preFile, String infoData){WriteLog(string.Empty, preFile, infoData);}static AutoResetEvent pause = new AutoResetEvent(false);public static void WriteLog(String customDirectory, String preFile, String infoData){string logPath = GetLogPath(customDirectory, preFile);string logContent = String.Concat(DateTime.Now, " ", infoData);logQueue.Enqueue(new Tuple<string, string>(logPath, logContent));if (writeTask == null){writeTask = new Task((object obj) =>{//pause.WaitOne(1000, true); LogRepository();}, null, TaskCreationOptions.LongRunning);writeTask.Start();}if (writeTask.IsCompleted){writeTask = new Task((object obj) =>{//pause.WaitOne(1000, true); LogRepository();}, null, TaskCreationOptions.LongRunning);writeTask.Start();}}public static void LogRepository(){List<string[]> temp = new List<string[]>();foreach (var logItem in logQueue){string logPath = logItem.Item1;string logMergeContent = String.Concat(logItem.Item2, Environment.NewLine, "-----------------------------------------------------------", Environment.NewLine);string[] logArr = temp.FirstOrDefault(d => d[0].Equals(logPath));if (logArr != null){logArr[1] = string.Concat(logArr[1], logMergeContent);}else{logArr = new string[] { logPath, logMergeContent };temp.Add(logArr);}Tuple<string, string> val = default(Tuple<string, string>);logQueue.TryDequeue(out val);}foreach (string[] item in temp){WriteText(item[0], item[1]);}}private static string GetLogPath(String customDirectory, String preFile){string newFilePath = string.Empty;String logDir = string.IsNullOrEmpty(customDirectory) ? Path.Combine(Environment.CurrentDirectory, "logs") : customDirectory;if (!Directory.Exists(logDir)){Directory.CreateDirectory(logDir);}string extension = ".log";string fileNameNotExt = String.Concat(preFile, DateTime.Now.ToString("yyyyMMdd"));String fileName = String.Concat(fileNameNotExt, extension);string fileNamePattern = string.Concat(fileNameNotExt, "(*)", extension);List<string> filePaths = Directory.GetFiles(logDir, fileNamePattern, SearchOption.TopDirectoryOnly).ToList();if (filePaths.Count > 0){int fileMaxLen = filePaths.Max(d => d.Length);string lastFilePath = filePaths.Where(d => d.Length == fileMaxLen).OrderByDescending(d => d).FirstOrDefault();if (new FileInfo(lastFilePath).Length > 1 * 1024 * 1024){string no = new Regex(@"(?is)(?<=\()(.*)(?=\))").Match(Path.GetFileName(lastFilePath)).Value;int tempno = 0;bool parse = int.TryParse(no, out tempno);string formatno = String.Format("({0})", parse ? (tempno + 1) : tempno);string newFileName = String.Concat(fileNameNotExt, formatno, extension);newFilePath = Path.Combine(logDir, newFileName);}else{newFilePath = lastFilePath;}}else{string newFileName = String.Concat(fileNameNotExt, String.Format("({0})", 0), extension);newFilePath = Path.Combine(logDir, newFileName);}return newFilePath;}private static void WriteText(string logPath, string logContent){try{if (!File.Exists(logPath)){File.CreateText(logPath).Close();}StreamWriter sw = File.AppendText(logPath);sw.Write(logContent);sw.Close();}catch (Exception ex){}finally{}}} View Code?
持久化日志方法二:
我采用了另外一種方法,在Task任務(wù)里我用信號(hào)量的方式來(lái)解決了些問(wèn)題,完整代碼如下:
static AutoResetEvent pause = new AutoResetEvent(false);
信號(hào)量法:
public class IOExtention{static ConcurrentQueue<Tuple<string, string>> logQueue = new ConcurrentQueue<Tuple<string, string>>();static Task writeTask = default(Task);static IOExtention(){writeTask = new Task((object obj) =>{while (true){pause.WaitOne(1000, true);List<string[]> temp = new List<string[]>();foreach (var logItem in logQueue){string logPath = logItem.Item1;string logMergeContent = String.Concat(logItem.Item2, Environment.NewLine, "-----------------------------------------------------------", Environment.NewLine);string[] logArr = temp.FirstOrDefault(d => d[0].Equals(logPath));if (logArr != null){logArr[1] = string.Concat(logArr[1], logMergeContent);}else{logArr = new string[] { logPath, logMergeContent };temp.Add(logArr);}Tuple<string, string> val = default(Tuple<string, string>);logQueue.TryDequeue(out val);}foreach (string[] item in temp){WriteText(item[0], item[1]);}}}, null, TaskCreationOptions.LongRunning);writeTask.Start();}public static void WriteLog(String preFile, String infoData){WriteLog(string.Empty, preFile, infoData);}static AutoResetEvent pause = new AutoResetEvent(false);public static void WriteLog(String customDirectory, String preFile, String infoData){string logPath = GetLogPath(customDirectory, preFile);string logContent = String.Concat(DateTime.Now, " ", infoData);logQueue.Enqueue(new Tuple<string, string>(logPath, logContent));}private static string GetLogPath(String customDirectory, String preFile){string newFilePath = string.Empty;String logDir = string.IsNullOrEmpty(customDirectory) ? Path.Combine(Environment.CurrentDirectory, "logs") : customDirectory;if (!Directory.Exists(logDir)){Directory.CreateDirectory(logDir);}string extension = ".log";string fileNameNotExt = String.Concat(preFile, DateTime.Now.ToString("yyyyMMdd"));String fileName = String.Concat(fileNameNotExt, extension);string fileNamePattern = string.Concat(fileNameNotExt, "(*)", extension);List<string> filePaths = Directory.GetFiles(logDir, fileNamePattern, SearchOption.TopDirectoryOnly).ToList();if (filePaths.Count > 0){int fileMaxLen = filePaths.Max(d => d.Length);string lastFilePath = filePaths.Where(d => d.Length == fileMaxLen).OrderByDescending(d => d).FirstOrDefault();if (new FileInfo(lastFilePath).Length > 1 * 1024 * 1024){string no = new Regex(@"(?is)(?<=\()(.*)(?=\))").Match(Path.GetFileName(lastFilePath)).Value;int tempno = 0;bool parse = int.TryParse(no, out tempno);string formatno = String.Format("({0})", parse ? (tempno + 1) : tempno);string newFileName = String.Concat(fileNameNotExt, formatno, extension);newFilePath = Path.Combine(logDir, newFileName);}else{newFilePath = lastFilePath;}}else{string newFileName = String.Concat(fileNameNotExt, String.Format("({0})", 0), extension);newFilePath = Path.Combine(logDir, newFileName);}return newFilePath;}private static void WriteText(string logPath, string logContent){try{if (!File.Exists(logPath)){File.CreateText(logPath).Close();}StreamWriter sw = File.AppendText(logPath);sw.Write(logContent);sw.Close();}catch (Exception ex){}finally{}}} View Code持久化日志方法三:
如果你感覺(jué)寫(xiě)一個(gè)日志類(lèi)還用什么信號(hào)量這些技術(shù),太復(fù)雜了,那也可以用最簡(jiǎn)單的方式,定時(shí)器來(lái)解決。
有同學(xué)一聽(tīng)定時(shí)器,就默默的笑了,但是這兒的坑也很深,首先了解一下這幾個(gè)定時(shí)器的使用場(chǎng)合,再用不遲!
System.Windows.Threading.DispatcherTimer
System.Windows.Forms.Timer
System.Timers.Timer
System.Threading.Timer
定時(shí)器法:
public class IOExtention{static ConcurrentQueue<Tuple<string, string>> logQueue = new ConcurrentQueue<Tuple<string, string>>();static System.Timers.Timer logTimers = new System.Timers.Timer();static IOExtention(){logTimers.Interval = 1000;logTimers.Elapsed += logTimers_Elapsed;logTimers.AutoReset = true;logTimers.Enabled = true;}public static void WriteLog(String preFile, String infoData){WriteLog(string.Empty, preFile, infoData);}public static void WriteLog(String customDirectory, String preFile, String infoData){string logPath = GetLogPath(customDirectory, preFile);string logContent = String.Concat(DateTime.Now, " ", infoData);logQueue.Enqueue(new Tuple<string, string>(logPath, logContent));}private static string GetLogPath(String customDirectory, String preFile){string newFilePath = string.Empty;String logDir = string.IsNullOrEmpty(customDirectory) ? Path.Combine(Environment.CurrentDirectory, "logs") : customDirectory;if (!Directory.Exists(logDir)){Directory.CreateDirectory(logDir);}string extension = ".log";string fileNameNotExt = String.Concat(preFile, DateTime.Now.ToString("yyyyMMdd"));String fileName = String.Concat(fileNameNotExt, extension);string fileNamePattern = string.Concat(fileNameNotExt, "(*)", extension);List<string> filePaths = Directory.GetFiles(logDir, fileNamePattern, SearchOption.TopDirectoryOnly).ToList();if (filePaths.Count > 0){int fileMaxLen = filePaths.Max(d => d.Length);string lastFilePath = filePaths.Where(d => d.Length == fileMaxLen).OrderByDescending(d => d).FirstOrDefault();if (new FileInfo(lastFilePath).Length > 1 * 1024 * 1024){string no = new Regex(@"(?is)(?<=\()(.*)(?=\))").Match(Path.GetFileName(lastFilePath)).Value;int tempno = 0;bool parse = int.TryParse(no, out tempno);string formatno = String.Format("({0})", parse ? (tempno + 1) : tempno);string newFileName = String.Concat(fileNameNotExt, formatno, extension);newFilePath = Path.Combine(logDir, newFileName);}else{newFilePath = lastFilePath;}}else{string newFileName = String.Concat(fileNameNotExt, String.Format("({0})", 0), extension);newFilePath = Path.Combine(logDir, newFileName);}return newFilePath;}static void logTimers_Elapsed(object sender, System.Timers.ElapsedEventArgs e){System.Timers.Timer logTimers = (System.Timers.Timer)sender;logTimers.Enabled = false;List<string[]> temp = new List<string[]>();foreach (var logItem in logQueue){string logPath = logItem.Item1;string logMergeContent = String.Concat(logItem.Item2, Environment.NewLine, "-----------------------------------------------------------", Environment.NewLine);string[] logArr = temp.FirstOrDefault(d => d[0].Equals(logPath));if (logArr != null){logArr[1] = string.Concat(logArr[1], logMergeContent);}else{logArr = new string[] { logPath, logMergeContent };temp.Add(logArr);}Tuple<string, string> val = default(Tuple<string, string>);logQueue.TryDequeue(out val);}foreach (string[] item in temp){WriteText(item[0], item[1]);}logTimers.Enabled = true;}private static void WriteText(string logPath, string logContent){try{if (!File.Exists(logPath)){File.CreateText(logPath).Close();}StreamWriter sw = File.AppendText(logPath);sw.Write(logContent);sw.Close();}catch (Exception ex){}finally{}}} View Code五,結(jié)語(yǔ)
重新設(shè)計(jì)的日志組件,思路還是非常清晰的。只是在持久化日志時(shí)遇上了問(wèn)題了。
持久化日志方法一,其實(shí)是很完美的解決方法,但是在高并發(fā)的時(shí)候,總是拋出異常,找不出原因。
持久化日志方法二,是我目前采用的方法,能夠有效的解決問(wèn)題。
持久化日志方法三,采用定時(shí)器解決,也是可行的。只是代碼看起來(lái)很別扭。
歡迎大家熱烈討論,看有沒(méi)有更好的解決方案。
?
阿里云客戶(hù)端的實(shí)現(xiàn)(支持文件分塊,斷點(diǎn)續(xù)傳,進(jìn)度,速度,倒計(jì)時(shí)顯示)
淘寶刷單軟件(刷單工具)程序?qū)崿F(xiàn)
?
信號(hào)量改進(jìn)版:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks;namespace LogTest {public class IOExtention{static ConcurrentQueue<Tuple<string, string>> logQueue = new ConcurrentQueue<Tuple<string, string>>();static Task writeTask = default(Task);static ManualResetEvent pause = new ManualResetEvent(false);//Mutex mmm = new Mutex();static IOExtention(){writeTask = new Task((object obj) =>{while (true){pause.WaitOne();pause.Reset();List<string[]> temp = new List<string[]>();foreach (var logItem in logQueue){string logPath = logItem.Item1;string logMergeContent = String.Concat(logItem.Item2, Environment.NewLine, "-----------------------------------------------------------", Environment.NewLine);string[] logArr = temp.FirstOrDefault(d => d[0].Equals(logPath));if (logArr != null){logArr[1] = string.Concat(logArr[1], logMergeContent);}else{logArr = new string[] { logPath, logMergeContent };temp.Add(logArr);}Tuple<string, string> val = default(Tuple<string, string>);logQueue.TryDequeue(out val);}foreach (string[] item in temp){WriteText(item[0], item[1]);}}}, null, TaskCreationOptions.LongRunning);writeTask.Start();}public static void WriteLog(String preFile, String infoData){WriteLog(string.Empty, preFile, infoData);}public static void WriteLog(String customDirectory, String preFile, String infoData){string logPath = GetLogPath(customDirectory, preFile);string logContent = String.Concat(DateTime.Now, " ", infoData);logQueue.Enqueue(new Tuple<string, string>(logPath, logContent));pause.Set();}private static string GetLogPath(String customDirectory, String preFile){string newFilePath = string.Empty;String logDir = string.IsNullOrEmpty(customDirectory) ? Path.Combine(Environment.CurrentDirectory, "logs") : customDirectory;if (!Directory.Exists(logDir)){Directory.CreateDirectory(logDir);}string extension = ".log";string fileNameNotExt = String.Concat(preFile, DateTime.Now.ToString("yyyyMMdd"));String fileName = String.Concat(fileNameNotExt, extension);string fileNamePattern = string.Concat(fileNameNotExt, "(*)", extension);List<string> filePaths = Directory.GetFiles(logDir, fileNamePattern, SearchOption.TopDirectoryOnly).ToList();if (filePaths.Count > 0){int fileMaxLen = filePaths.Max(d => d.Length);string lastFilePath = filePaths.Where(d => d.Length == fileMaxLen).OrderByDescending(d => d).FirstOrDefault();if (new FileInfo(lastFilePath).Length > 1 * 1024 * 1024 * 1024){string no = new Regex(@"(?is)(?<=\()(.*)(?=\))").Match(Path.GetFileName(lastFilePath)).Value;int tempno = 0;bool parse = int.TryParse(no, out tempno);string formatno = String.Format("({0})", parse ? (tempno + 1) : tempno);string newFileName = String.Concat(fileNameNotExt, formatno, extension);newFilePath = Path.Combine(logDir, newFileName);}else{newFilePath = lastFilePath;}}else{string newFileName = String.Concat(fileNameNotExt, String.Format("({0})", 0), extension);newFilePath = Path.Combine(logDir, newFileName);}return newFilePath;}private static void WriteText(string logPath, string logContent){try{if (!File.Exists(logPath)){File.CreateText(logPath).Close();}StreamWriter sw = File.AppendText(logPath);sw.Write(logContent);sw.Close();}catch (Exception ex){}finally{}}} } View Code?
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的100行代码实现了多线程,批量写入,文件分块的日志方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: vscode添加注释的方法
- 下一篇: 关于条件宏的易错点