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 块中进行,确保线程安全。如果发生异常,内部异常会被捕获但不会记录,避免日志记录本身抛出的异常导致程序崩溃。
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
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);}}
}
public partial class MainViewModel:IRougamo<ExceptionLogAttribute>{}
MainViewModel
类实现了接口 IRougamo<ExceptionLogAttribute>
。这意味着在这个类中,所有被 ExceptionLogAttribute
特性标记的方法或类,都会在抛出异常时自动调用 ExceptionLogAttribute
的 OnException
方法