Skip to content

Commit d38f045

Browse files
author
ozakboy
committed
批次處理優化
減少了檔案的開啟/關閉次數 減少了檔案系統的操作次數 更好的記憶體使用(批次寫入) 降低了系統呼叫的開銷
1 parent 3b1d785 commit d38f045

File tree

6 files changed

+223
-139
lines changed

6 files changed

+223
-139
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ try {
9292
LOG.CustomName_Log("API", "外部服務呼叫");
9393
```
9494

95-
## 日誌檔案管理
9695
## 日誌檔案管理
9796

9897
### 預設目錄結構

ozakboy.NLOG/ozakboy.NLOG.Test/Program.cs

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,31 @@
44
using System.Xml.Linq;
55
Console.WriteLine("Hello, World!");
66

7-
7+
LOG.Configure(configure =>
8+
{
9+
configure.KeepDays = -1;
10+
configure.SetFileSizeInMB(10);
11+
configure.EnableAsyncLogging = true;
12+
configure.EnableConsoleOutput = false;
13+
configure.ConfigureAsync(asyncConfig =>
14+
{
15+
asyncConfig.FlushIntervalMs = 5000;
16+
});
17+
});
18+
19+
var i = 6;
20+
for (int i2 = 0; i2 < i; i2++)
21+
{
22+
var g = i2;
23+
_ = Task.Run(async () =>
24+
{
25+
for (int o = 0; o < 1000000; o++)
26+
{
27+
LOG.Debug_Log($"i2:{g}_LOG_TEST_for_o:{o}");
28+
Thread.Sleep(10 * g);
29+
}
30+
});
31+
}
832

933

1034

@@ -119,7 +143,4 @@
119143
LOG.Error_Log(ex);
120144
}
121145

122-
for (int i = 0; i < 1000000; i++)
123-
{
124-
LOG.CustomName_Log("BigFile", "LOG_TEST");
125-
}
146+
Thread.Sleep(-1);

ozakboy.NLOG/ozakboy.NLOG/Core/AsyncLogHandler.cs

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -55,36 +55,6 @@ private static LogConfiguration.IAsyncLogOptions CurrentAsyncOptions
5555
/// </summary>
5656
private static DateTime _lastFlushTime = DateTime.Now;
5757

58-
/// <summary>
59-
/// 日誌項目類,表示單個待處理的日誌條目
60-
/// </summary>
61-
private class LogItem
62-
{
63-
/// <summary>
64-
/// 日誌級別
65-
/// </summary>
66-
public LogLevel Level { get; set; }
67-
68-
/// <summary>
69-
/// 日誌名稱
70-
/// </summary>
71-
public string Name { get; set; }
72-
73-
/// <summary>
74-
/// 日誌消息
75-
/// </summary>
76-
public string Message { get; set; }
77-
78-
/// <summary>
79-
/// 日誌參數
80-
/// </summary>
81-
public object[] Args { get; set; }
82-
/// <summary>
83-
/// 是否需要立即寫入
84-
/// </summary>
85-
public bool RequireImmediateFlush { get; set; }
86-
}
87-
8858
/// <summary>
8959
/// 初始化異步日誌處理器
9060
/// 確保處理線程只被創建一次
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace ozakboy.NLOG.Core
6+
{
7+
/// <summary>
8+
/// 日誌項目類別,用於在不同組件間傳遞日誌資訊
9+
/// </summary>
10+
internal class LogItem
11+
{
12+
/// <summary>
13+
/// 日誌級別
14+
/// </summary>
15+
public LogLevel Level { get; set; }
16+
17+
/// <summary>
18+
/// 日誌名稱
19+
/// </summary>
20+
public string Name { get; set; }
21+
22+
/// <summary>
23+
/// 日誌訊息
24+
/// </summary>
25+
public string Message { get; set; }
26+
27+
/// <summary>
28+
/// 日誌參數
29+
/// </summary>
30+
public object[] Args { get; set; }
31+
32+
/// <summary>
33+
/// 是否需要立即寫入
34+
/// </summary>
35+
public bool RequireImmediateFlush { get; set; }
36+
}
37+
}
Lines changed: 112 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using System.Text;
@@ -7,113 +8,150 @@ namespace ozakboy.NLOG.Core
78
{
89
/// <summary>
910
/// 日誌檔案處理類別,負責日誌檔案的建立、寫入和管理
11+
/// 支援單次寫入和批次寫入
1012
/// </summary>
1113
static class LogText
1214
{
1315
private static object lockMe = new object();
1416

1517
/// <summary>
16-
/// 建立或是新增LOG紀錄
18+
/// 建立或是新增單條LOG紀錄
1719
/// </summary>
18-
/// <param name="level"></param>
19-
/// <param name="name"></param>
20-
/// <param name="Message"></param>
21-
/// <param name="arg"></param>
22-
internal static void Add_LogText(LogLevel level, string name, string Message, object[] arg)
20+
internal static void Add_LogText(LogLevel level, string name, string message, object[] args)
21+
{
22+
var logItem = new LogItem
23+
{
24+
Level = level,
25+
Name = name,
26+
Message = message,
27+
Args = args
28+
};
29+
30+
Add_BatchLogText(new[] { logItem });
31+
}
32+
33+
34+
/// <summary>
35+
/// 批次寫入多條日誌
36+
/// </summary>
37+
internal static void Add_BatchLogText(IEnumerable<LogItem> logItems)
2338
{
2439
try
2540
{
26-
// 使用 lock 避免多執行敘執行時 檔案被佔用問題
2741
lock (lockMe)
2842
{
43+
// 按日誌級別和名稱分組
44+
var groupedLogs = logItems.GroupBy(item => new { item.Level, item.Name });
2945

30-
string LogPath = $"{AppDomain.CurrentDomain.BaseDirectory}\\{LogConfiguration.Current.LogPath}\\{DateTime.Now.ToString("yyyyMMdd")}\\{LogConfiguration.Current.TypeDirectories.GetPathForType(level)}\\";
31-
32-
CheckDirectoryExistCreate(LogPath);
33-
34-
if (string.IsNullOrEmpty(name)) name = level.ToString();
46+
foreach (var group in groupedLogs)
47+
{
48+
var level = group.Key.Level;
49+
var name = string.IsNullOrEmpty(group.Key.Name) ? level.ToString() : group.Key.Name;
3550

36-
var LogFilePath = CheckFileExistCreate(LogPath, name);
51+
// 獲取日誌檔案資訊
52+
var fileInfo = GetLogFileInfo(level, name);
3753

38-
FIleWriteLine(arg, LogFilePath, Message);
54+
// 批次寫入檔案
55+
WriteLogsToFile(fileInfo, group);
56+
}
3957

58+
// 清理過期日誌(只在批次處理完成後執行一次)
4059
Remove_TimeOutLogText();
4160
}
4261
}
4362
catch (Exception ex)
4463
{
45-
Console.WriteLine($"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")}>>LogText Add_LogText Error:{ex.Message}");
64+
Console.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss}>>LogText Add_BatchLogText Error:{ex.Message}");
4665
}
4766
}
4867

68+
4969
/// <summary>
50-
/// 判斷有無資料表 若沒有建立資料表
70+
/// 獲取日誌檔案資訊
5171
/// </summary>
52-
/// <param name="LogPath"></param>
53-
private static void CheckDirectoryExistCreate(string LogPath)
72+
private static LogFileInfo GetLogFileInfo(LogLevel level, string name)
5473
{
55-
if (!Directory.Exists(LogPath))
74+
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
75+
var logPath = Path.Combine(baseDir, LogConfiguration.Current.LogPath,
76+
DateTime.Now.ToString("yyyyMMdd"),
77+
LogConfiguration.Current.TypeDirectories.GetPathForType(level));
78+
79+
var fileInfo = new LogFileInfo
5680
{
57-
Directory.CreateDirectory(LogPath);
81+
DirectoryPath = logPath
82+
};
83+
84+
// 確保目錄存在
85+
if (!Directory.Exists(logPath))
86+
{
87+
Directory.CreateDirectory(logPath);
5888
}
89+
90+
// 確定檔案路徑和是否需要新檔案
91+
DetermineLogFile(fileInfo, name);
92+
93+
return fileInfo;
5994
}
6095

6196
/// <summary>
62-
/// 判斷有無檔案或檔案過大,若沒有或檔案過大則建立新檔案
97+
/// 確定日誌檔案路徑和狀態
6398
/// </summary>
64-
/// <param name="_LogPath">檔案路徑</param>
65-
/// <param name="_FileName">檔案名稱</param>
66-
private static string CheckFileExistCreate(string _LogPath, string _FileName)
99+
private static void DetermineLogFile(LogFileInfo fileInfo, string name)
67100
{
68-
var LogFIleName = $"{_FileName}_Log.txt";
69-
var SearchFIleName = $"{_FileName}*";
70-
FileExistCreate(_LogPath + LogFIleName);
71-
72-
DirectoryInfo di = new DirectoryInfo(_LogPath);
73-
var Files = di.GetFiles(SearchFIleName).OrderBy(x => x.LastWriteTimeUtc).ToArray();
74-
var NowWriteFile = Files[Files.Length - 1];
75-
if (NowWriteFile.Length > LogConfiguration.Current.MaxFileSize)
101+
var logFileName = $"{name}_Log.txt";
102+
var searchPattern = $"{name}*";
103+
var di = new DirectoryInfo(fileInfo.DirectoryPath);
104+
var files = di.GetFiles(searchPattern).OrderBy(x => x.LastWriteTimeUtc).ToArray();
105+
106+
if (files.Length == 0)
76107
{
77-
var FileNameSplits = NowWriteFile.Name.Replace("_" + _FileName, "").Split('_');
78-
if (!FileNameSplits[1].Contains("part"))
79-
{
80-
LogFIleName = $"{_FileName}_part{1}_Log.txt";
81-
}
82-
else
108+
fileInfo.FilePath = Path.Combine(fileInfo.DirectoryPath, logFileName);
109+
fileInfo.RequiresNewFile = true;
110+
return;
111+
}
112+
113+
var currentFile = files.Last();
114+
if (currentFile.Length > LogConfiguration.Current.MaxFileSize)
115+
{
116+
var splits = currentFile.Name.Replace("_" + name, "").Split('_');
117+
int nextPart = 1;
118+
119+
if (splits.Length > 1 && splits[1].StartsWith("part"))
83120
{
84-
var Part = Convert.ToInt32(FileNameSplits[1].Replace("part", ""));
85-
LogFIleName = $"{_FileName}_part{Part + 1}_Log.txt";
121+
nextPart = int.Parse(splits[1].Replace("part", "")) + 1;
86122
}
87-
FileExistCreate(_LogPath + LogFIleName);
123+
124+
logFileName = $"{name}_part{nextPart}_Log.txt";
125+
fileInfo.RequiresNewFile = true;
88126
}
89127
else
90128
{
91-
LogFIleName = NowWriteFile.Name;
129+
logFileName = currentFile.Name;
130+
fileInfo.RequiresNewFile = false;
92131
}
93-
return _LogPath + LogFIleName;
132+
133+
fileInfo.FilePath = Path.Combine(fileInfo.DirectoryPath, logFileName);
94134
}
95135

136+
96137
/// <summary>
97-
/// 判斷有無檔案,若沒有則建立新檔案
138+
/// 批次寫入日誌到檔案
98139
/// </summary>
99-
/// <param name="_LogFilePath"></param>
100-
private static void FileExistCreate(string _LogFilePath)
140+
private static void WriteLogsToFile(LogFileInfo fileInfo, IEnumerable<LogItem> logs)
101141
{
102-
if (!File.Exists(_LogFilePath))
142+
// 如果需要新檔案,先創建
143+
if (fileInfo.RequiresNewFile)
103144
{
104-
using (FileStream fileStream = new FileStream(_LogFilePath, FileMode.Create))
105-
{
106-
fileStream.Close();
107-
}
145+
using (File.Create(fileInfo.FilePath)) { }
108146
}
109-
}
110147

111-
private static void FIleWriteLine(object[] arg, string _filePath, string _Message)
112-
{
113-
using (StreamWriter sw = new StreamWriter(_filePath, true, Encoding.UTF8))
148+
// 批次寫入所有日誌
149+
using (var writer = new StreamWriter(fileInfo.FilePath, true, Encoding.UTF8))
114150
{
115-
sw.WriteLine(_Message, arg);
116-
sw.Close();
151+
foreach (var log in logs)
152+
{
153+
writer.WriteLine(string.Format(log.Message, log.Args));
154+
}
117155
}
118156
}
119157

@@ -124,7 +162,7 @@ private static void Remove_TimeOutLogText()
124162
{
125163
try
126164
{
127-
string logBasePath = $"{AppDomain.CurrentDomain.BaseDirectory}\\{LogConfiguration.Current.LogPath}\\";
165+
string logBasePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogConfiguration.Current.LogPath);
128166
// 確保目錄存在
129167
if (!Directory.Exists(logBasePath))
130168
return;
@@ -150,5 +188,21 @@ private static void Remove_TimeOutLogText()
150188

151189
}
152190
}
191+
192+
193+
#region Class
194+
195+
/// <summary>
196+
/// 日誌檔案資訊類
197+
/// </summary>
198+
private class LogFileInfo
199+
{
200+
public string DirectoryPath { get; set; }
201+
public string FilePath { get; set; }
202+
public bool RequiresNewFile { get; set; }
203+
}
204+
205+
206+
#endregion
153207
}
154208
}

0 commit comments

Comments
 (0)