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

相关推荐
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇26】C#面向对象动态多态——接口(Interface)、接口里氏替换原则、密封方法(`sealed` )
java·开发语言·unity·c#·游戏引擎·里氏替换原则
Java Fans4 小时前
C# 中串口读取问题及解决方案
开发语言·c#
盛派网络小助手5 小时前
微信 SDK 更新 Sample,NCF 文档和模板更新,更多更新日志,欢迎解锁
开发语言·人工智能·后端·架构·c#
码农君莫笑5 小时前
信管通低代码信息管理系统应用平台
linux·数据库·windows·低代码·c#·.net·visual studio
鲤籽鲲6 小时前
C# Random 随机数 全面解析
android·java·c#
fkdw8 小时前
C# Newtonsoft.Json 反序列化派生类数据丢失问题
c#·json
浅尝辄止;11 小时前
C# 异步编程
c#