c# 实现一个简单的异常日志记录(异常迭代+分片+定时清理)+AOP Rougamo全局注入

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 方法

相关推荐
椒颜皮皮虾྅9 小时前
OpenVINO C# API 3.3.1:支持OpenVINO 2026.3及GenAI增强
人工智能·c#·openvino
半亩码田12 小时前
C#转Python第3.1篇:Python 的 class 没有访问修饰符?面向对象的另一条路
开发语言·python·c#
唐青枫19 小时前
别再手写 args[0]:C#.NET CommandLineParser 命令行工具实战详解
c#·.net
LccKyI19 小时前
C#学习day06(方法/函数及其关键字,附思维导图)
经验分享·学习·c#
.Peter20 小时前
.NET/WPF 程序在部分 Windows 电脑无法保存用户环境变量:使用 DPAPI 实现安全兼容
windows·信息安全·c#·.net·wpf·环境变量·dpapi
农村小镇哥21 小时前
怎么把HTM格式转化成WORD
ui·c#·word
农村小镇哥21 小时前
C#读取CSV文件的方法
服务器·数据库·c#
FuckPatience1 天前
C# 报错参数无效
c#
北域码匠2 天前
PID 控制算法完整详解(C# 原生实现,无第三方库)
c#·pid控制·工控开发·闭环控制·自动控制算法·数字pid·增量式pid
离陌在学C#2 天前
C# 重载与重写:深入理解面向对象编程的核心概念
java·c#