1. 日志目录和文件管理
- 日志目录:日志文件存储在
./Exceptions
目录下。 - 日志文件命名:日志文件的命名格式为
yyyy_MM_dd.log
,表示当天的日期。如果当天的日志文件大小超过maxFileSizeBytes
(3KB),则会创建新的日志文件,文件名格式为yyyy_MM_dd_P{cnt}.log
,其中cnt
是日志文件的编号。 - 日志文件编码:日志文件使用
UTF-8
编码。
2. 异常日志记录
WriteExceptionLog(Exception ex)
方法:
该方法用于记录异常信息。首先检查日志目录是否存在,如果不存在则创建。
获取当前日期的日志文件列表,并选择最新的日志文件(按文件名顺序)。
如果日志文件存在且大小超过maxFileSizeBytes
,则创建一个新的日志文件,文件名中包含_P{cnt}
,其中cnt
是文件的编号。
将异常信息追加到日志文件中,使用GetLogEntry(ex)
方法生成异常信息的日志条目。
最后调用CleanupOldLogFiles()
方法清理超过maxLogFileAgeDays
(1天)的旧日志文件。
3. 日志条目生成
GetLogEntry(Exception ex, int depth = 0)
方法:
-- 该方法递归地生成异常信息的日志条目。
-- 每层异常信息使用depth
参数控制缩进,便于阅读。
-- 日志条目包括异常时间、异常信息、异常对象和调用堆栈。
-- 如果异常有嵌套的内部异常(InnerException
),则递归调用GetLogEntry
方法生成内部异常的日志条目。
4. 旧日志文件清理
CleanupOldLogFiles()
方法:
-- 该方法用于清理超过maxLogFileAgeDays(
1天)的旧日志文件。
--获取日志目录中所有.log
文件,检查文件的最后修改时间,如果超过maxLogFileAgeDays
,则删除该文件。
5. 异常处理
- 异常处理:在
WriteExceptionLog(Exception ex)
方法中,所有的操作都在 lock 块中进行,确保线程安全。如果发生异常,内部异常会被捕获但不会记录,避免日志记录本身抛出的异常导致程序崩溃。
csharp
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Shapes;
namespace DataParser.Helpers;
public class LogHelper
{
private static readonly object objException = new object();
private static readonly string logDirectory = "./Exceptions";
private static string curfileName = $"{DateTime.Now:yyyy_MM_dd}.log";
private static readonly int maxLogFileAgeDays = 1;
private static readonly long maxFileSizeBytes = 3*1024;
private static readonly Encoding encoding =Encoding.UTF8;
static int cnt= 0;
public static void WriteExceptionLog(Exception ex)
{
try
{
lock (objException)
{
if (!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
var files = Directory.GetFiles(logDirectory, "*.log")
.Select(x=>System.IO.Path.GetFileName(x))
.Where(x => x.Contains($"{DateTime.Now:yyyy_MM_dd}"));
if(files.Count()>0)
{
var tmp = files.OrderBy(x => x.Length);
curfileName = tmp.Last();
if (curfileName.Contains("_P"))
{
Match match = Regex.Match(curfileName, @"_P(\d+)");
if (match.Success)
{
string str = match.Groups[1].Value;
int.TryParse(str, out cnt);
}
}
}
else
{
curfileName = $"{DateTime.Now:yyyy_MM_dd}.log";
}
string fileName = System.IO.Path.Combine(logDirectory, curfileName);
string logEntry = GetLogEntry(ex);
if (File.Exists(fileName) && (new FileInfo(fileName).Length > maxFileSizeBytes))
{
cnt++;
fileName = System.IO.Path.Combine(logDirectory, $"{DateTime.Now:yyyy_MM_dd}_P{cnt}.log");
}
else if(!fileName.Contains("_P"))
{
cnt = 0;
}
File.AppendAllText(fileName, logEntry, encoding);
CleanupOldLogFiles();
}
}
catch (Exception innerEx)
{
}
}
private static string GetLogEntry(Exception ex, int depth = 0)
{
string indent = new string(' ', depth * 4);
string logEntry =
$"{indent}【异常时间】{DateTime.Now}{Environment.NewLine}" +
$"{indent}【异常信息】{ex.Message}{Environment.NewLine}" +
$"{indent}【异常对象】{ex.Source}{Environment.NewLine}" +
$"{indent}【调用堆栈】{Environment.NewLine} {ex.StackTrace?.Trim() ?? "N/A"}{Environment.NewLine}{Environment.NewLine}{Environment.NewLine}";
if (ex.InnerException != null)
{
logEntry += GetLogEntry(ex.InnerException, depth + 1);
}
return logEntry;
}
private static void CleanupOldLogFiles()
{
var files = Directory.GetFiles(logDirectory, "*.log")
.Select(f => new FileInfo(f))
.Where(f => (DateTime.Now - f.LastWriteTime).TotalDays > maxLogFileAgeDays);
foreach (var file in files)
{
File.Delete(file.FullName);
}
}
}
Rougamo 实现AOP
导包Rougamo.Fody
csharp
using DataParser.Helpers;
using Rougamo;
using Rougamo.Context;
namespace DataParser
{
public class ExceptionLogAttribute : MoAttribute
{
public override void OnException(MethodContext context)
{
LogHelper.WriteExceptionLog(context.Exception);
context.HandledException(this, null);
}
}
}
csharp
public partial class MainViewModel:IRougamo<ExceptionLogAttribute>
{}
MainViewModel
类实现了接口 IRougamo<ExceptionLogAttribute>
。这意味着在这个类中,所有被 ExceptionLogAttribute
特性标记的方法或类,都会在抛出异常时自动调用 ExceptionLogAttribute
的 OnException
方法