首页 > 其他分享 >客户端日志和异常处理

客户端日志和异常处理

时间:2023-03-31 09:00:45浏览次数:131  
标签:记录 private static 日志 异常 public 客户端

目录

一. 使用Serilog结构化日志记录日志信息

Serilog包的引用和使用语法都可以在网上找到(https://github.com/serilog/serilog/wiki/),不再赘述,这里仅分享一下自己在项目中的简单使用。
FileLogHelper帮助类对日志记录进行封装:

public static class FileLogHelper
{
    /// <summary>
    /// 日志模板
    /// </summary>
    private static string ErrorTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss} {Message:j} {NewLine} {Exception} {NewLine}";
    private static string InfoTemplate = "{Message:j} {NewLine}";

    /// <summary>
    /// 记录异常信息
    /// </summary>
    private static ILogger ErrorLog;

    /// <summary>
    /// 记录各类日志信息
    /// </summary>
    private static ILogger InfoLog;

    static FileLogHelper()
    {
        InitLog();
    }

    private static void InitLog()
    {
        ErrorLog = new LoggerConfiguration()
        .MinimumLevel.Warning() // 最小日志级别,小于该级别的日志不会记录
        .WriteTo.Map(
        le => "",
        (d, lc) =>
        {
            lc.File($"Log/Error/{d:yyyyMMdd}.txt", // 日志文件路径,文件以日期命名
            rollingInterval: RollingInterval.Day, // 日志文件按天滚动创建,即每天创建新的文件
            outputTemplate: ErrorTemplate, // 指定日志输出模板
            rollOnFileSizeLimit: true, // 滚动文件是否限制大小 
            fileSizeLimitBytes: 52428800 // 滚动文件限制的大小,单位byte, 52428800为50M
            );
        }).CreateLogger();

        InfoLog = new LoggerConfiguration()
        .MinimumLevel.Information()
        .WriteTo.Map(
        le => "",
        (d, lc) =>
        {
            lc.File($"Log/Info/{d:yyyyMMdd}.txt",
            rollingInterval: RollingInterval.Day,
            outputTemplate: InfoTemplate,
            rollOnFileSizeLimit: true,
            fileSizeLimitBytes: 52428800
            );
        }).CreateLogger();
    }

    /// <summary>
    /// 记录异常信息
    /// </summary>
    /// <param name="exception"></param>
    /// <param name="message"></param>
    public static void LogError(Exception exception, string message = "错误")
    {
        ErrorLog.Error(exception, message);
    }

    /// <summary>
    /// 记录各类日志信息(结构化记录)
    /// </summary>
    /// <param name="title"></param>
    /// <param name="message"></param>
    /// <param name="detailMessage"></param>
    /// <param name="paramter"></param>
    public static void LogInfo(string title, string message, string detailMessage, object paramter)
    {
        LogInfoView log = new LogInfoView(title, message, detailMessage, paramter);
        InfoLog.Information("{@log}", log);
    }

    /// <summary>
    /// 结构化日志对象
    /// </summary>
    private class LogInfoView
    {
        public LogInfoView(string title, string message, string detailMessage, object paramter)
        { 
            this.Title = title;
            this.Message = message;
            this.DetailMessage = detailMessage;
            this.Paramter = paramter;
        }

        public string DateTime { get; set; } = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

        public string Title { get; set; }

        public string Message { get; set; }

        public string DetailMessage { get; set; }

        public object Paramter { get; set; }
    }
}

帮助类中创建了两个静态日志对象ErrorLog和InfoLog,方法LogError通过ErrorLog对象记录异常信息,方法LogInfo通过InfoLog对象记录一般日志信息:

  • ErrorLog和InfoLog分别指定了不同的日志文件目录(Log/Error/与Log/Info/),便于区分和查找;
  • InfoLog使用结构化日志,将日志信息包装成LogInfoView对象,在文本中以JSON格式记录。InfoLog使用了与ErrorLog不同的日志模板,仅保留了{Message:j},这样一条记录就是一个JSON数据,整个日志文件中的数据就是JSON Lines格式,方便内容解析。

按上述设置后,产生的日志文件目录如下:
在这里插入图片描述

结构化日志InfoLog产生的日志内容如下,每一行都是json格式:
(用户密码是不应该输出到日志的,这里只是为了演示效果):
在这里插入图片描述

非结构化的ErrorLog产生的日志内容如下:
在这里插入图片描述

二. 捕获全局异常

winform客户端中,如果程序发生异常后没有对其捕获处理,就会造成系统崩溃,影响用户体验。一种保险的方式是添加全局异常捕获事件,所有未处理的异常都会在这里被捕获到,从而避免系统直接闪崩。异常信息的记录使用了上面第一节中的FileLogHelper帮助类。

internal static class Program
{
    /// <summary>
    /// 应用程序的主入口点。
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        CatchApplicationException(); // 捕获全局异常
        Application.Run(new MainForm());
    }

    /// <summary>
    /// 捕获全局异常
    /// </summary>
    private static void CatchApplicationException()
    {
        //捕获UI异常
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
        Application.ThreadException += OnUIThreadExceptionCatched;
        //捕获非UI异常
        AppDomain.CurrentDomain.UnhandledException += OnDomainExceptionCatched;
    }

    /// <summary>
    /// 捕获UI异常
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private static void OnUIThreadExceptionCatched(object sender, ThreadExceptionEventArgs e)
    {
        FileLogHelper.LogError(e.Exception, "未处理的UI异常");
        MessageBox.Show("软件发生UI错误!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
    }

    /// <summary>
    /// 捕获非UI异常
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private static void OnDomainExceptionCatched(object sender, UnhandledExceptionEventArgs e)
    {
        FileLogHelper.LogError((Exception)e.ExceptionObject, "未处理的异常");
        MessageBox.Show("软件发生错误!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
    }
}

三. 使用AOP统一处理异常

使用AOP处理异常的好处就是不用写大量的try-catch,一是减少重复的代码,二是不让try-catch影响核心代码的可读性;缺点就是统一处理异常后,灵活性会降低。要不要使用可以根据自己的具体情况来。

在异常处理方面,使用起来比较方面的AOP组件,这里推荐两个:一个是Postsharp(https://doc.postsharp.net/),另一个是Rougamo(肉夹馍,https://github.com/inversionhourglass/Rougamo)。推荐的原因是,这两个都可以通过特性Attribute标注的方式进行代码织入,都支持应用于方法、类、程序集等不同的粒度,且都支持设置可访问性属性。一些区别在于:Rougamo是完全开源的,可以随意使用;而Postsharp分商业版和社区版,引用ChatGPT上的解释,比较明了:

在这里插入图片描述

社区版虽说功能有限制,但也足够我们使用了。

这里便以PostSharp举例。如何引用Postsharp可以参考官方文档:https://doc.postsharp.net/deploymentconfiguration/deployment,在NuGet中直接搜索安装就可以。

我们创建一个自己的异常处理Attribute,如下:
(参考文档:https://doc.postsharp.net/custompatterns/aspects/tutorials/exception-handling)

/// <summary>
/// 异常处理
/// </summary>
[PSerializable]
public class LogAttribute : OnExceptionAspect
{
    /// <summary>
    /// 异常处理结束后的方法返回值
    /// </summary>
    public object ReturnValue { get; set; }

    /// <summary>
    /// 异常处理方式
    /// FlowBehavior.Return:忽略异常。如果方法有返回值,必须通过设置ReturnValue来设置方法的返回值。
    /// FlowBehavior.Continue:忽略异常,在OnException方法中,与FlowBehavior.Return的作用一样。
    ///                       同样的,如果方法有返回值,必须通过设置ReturnValue来设置方法的返回值。
    /// FlowBehavior.RethrowException:在异常处理程序退出后重新引发原始异常。
    /// FlowBehavior.ThrowException:一旦异常处理程序退出,就会抛出一个新的异常。
    ///                             当应该向用户隐藏原始异常的详细信息时,或者当要显示更有意义的异常时,这很有用。
    ///                             抛出新异常时,必须将新异常对象分配给MethodExecutionArgs的exception成员。
    /// </summary>
    public FlowBehavior FlowBehavior { get; set; } = FlowBehavior.Return;

    /// <summary>
    /// 不记录日志
    /// </summary>
    public bool IgnoreLogRecord { get; set; } = false;

    /// <summary>
    /// 捕获异常
    /// </summary>
    /// <param name="args"></param>
    public override void OnException(MethodExecutionArgs args)
    {
        if (!IgnoreLogRecord)
        {
            // 记录异常日志
            FileLogHelper.LogError(args.Exception);
        }
        
        args.FlowBehavior = FlowBehavior; // 设置异常处理方式
        args.ReturnValue = ReturnValue; // 设置方法返回值
    }
}

自定义的LogAttribute 类中,新加了三个属性:ReturnValue、FlowBehavior、IgnoreLogRecord

  • ReturnValue属性:对于有返回值的方法发生异常时,通过ReturnValue属性,我们可以自定义异常处理结束后的方法返回值;
  • FlowBehavior属性:可以用来设置异常处理方式,上面的代码中我们默认设置为FlowBehavior.Return,即在异常处理程序OnException处理完后忽略异常。
  • IgnoreLogRecord属性:可以用来设置是否记录异常信息,配合上面第一节说的日志记录来使用,默认记录日志。

接下来我们举几个例子来演示如何使用它。

  1. 在有返回值的方法中,如果想忽略异常并指定异常发生后的返回值,可以这样使用:
private void OnLoginFormLoad(object sender, EventArgs e)
{
    m_allRoles = GetLoginRoles();
}

/// <summary>
/// 获取角色集合
/// 发生异常后返回null
/// </summary>
/// <returns></returns>
[Log(ReturnValue = null)]
private List<LoginRole> GetLoginRoles()
{
    using (UDPDbContext dbContext = new UDPDbContext())
    {
        dbContext.Dispose(); // 这里故意释放掉数据库上下文,为的是让下面的代码抛出异常
        return dbContext.LoginRoles.ToList();
    }
}

窗体Load方法中调用GetLoginRoles()方法获取角色集合,GetLoginRoles()方法上我们用LogAttribute进行标记(特性标记可以省去特性类名后面的“Attribute”),并指定ReturnValue = null,即方法发生异常后,返回null。

我们打断点进行观察,异常发生后,进入异常处理程序OnException,在异常处理程序中记录日志(默认记录),设置异常处理方式(默认FlowBehavior.Return忽略异常),最后设置返回值ReturnValue,这个ReturnValue的值就是我们在方法上标记LogAttribute时指定的。
在这里插入图片描述
异常处理程序执行完后,程序继续执行,在OnLoginFormLoad方法中,发现GetLoginRoles()并没有抛出异常,并且返回了null,达到了预期效果。
在这里插入图片描述

  1. 如果担心基础层的日志帮助类FileLogHelper出现异常,这种情况下,一般要吞噬掉异常,不能让日志记录的异常影响到我们的业务逻辑,并且我们不应该再去让异常处理程序OnException去记录日志了,因为此时记录日志的代码已经不能正常工作了。

使用LogAttribute特性,我们可以这样处理:

[Log(AttributeTargetMemberAttributes = MulticastAttributes.Public, IgnoreLogRecord = true)]
public static class FileLogHelper
{
    /*
     * 此处省略日志记录代码,与第一节中的代码保持一模一样
     */
}

LogAttribute特性直接应用于FileLogHelper类上,并通过给AttributeTargetMemberAttributes 属性赋值MulticastAttributes.Public,使其仅对类中的public方法起作用;IgnoreLogRecord 赋值为true,则在异常处理程序OnException中不记录异常。

  1. 还可能遇到一种情况:对整个类的异常统一设置异常处理方式后,对其中的个别方法需要特殊处理。比如类中的大部分方法都不需要记录日志,但是对于其中的某个方法需要记录异常日志。
    举个代码示例,下面的MainForm窗体类中,除了登录方法OnLoginOutButtonClick外,其他的都不需要记录异常日志,那我们可以这样进行特性标注:
[Log(AspectPriority = 1, IgnoreLogRecord = true)]
public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        // ...
    }

    private void OnMainFormLoad(object sender, EventArgs e)
    {
        // ...
    }

    [Log(AspectPriority = 2, IgnoreLogRecord = false)]
    private void OnLoginOutButtonClick(object sender, EventArgs e)
    {
        // ...
    }
}

由于使用了多层异常处理特性,我们需要使用AspectPriority属性来指定优先级。当符合多个异常处理配置的条件时,按优先级高的来处理(AspectPriority值大的)。参考文档:https://doc.postsharp.net/custompatterns/aspects/applying/attribute-multicasting

  1. 异常处理还可以应用于整个程序集,如下,在程序集文件AssemblyInfo.cs中进行配置,AssemblyInfo.cs文件在项目的Properties路径下:
    在这里插入图片描述

以上就是常见的使用场景。

这里再做一点扩展了解:
Postsharp和Rougamo都是通过静态织入的方式实现了AOP,即在编译时将额外的异常处理代码织入到我们的源代码中。
我们拿上面的日志帮助类FileLogHelper为例,使用[Log(AttributeTargetMemberAttributes = MulticastAttributes.Public, IgnoreLogRecord = true)]进行标记后,进行编译,然后将编译后的dll使用反编译工具查看源码:

在这里插入图片描述
可以看出,FileLogHelper类中的Public方法在编译后自动加上了try-catch块。

标签:记录,private,static,日志,异常,public,客户端
From: https://www.cnblogs.com/Kelons/p/17275099.html

相关文章

  • java学习日记20230330-异常
    异常基本概念java语言中,将程序执行中发生的不正常情况称为异常,开发中的语法错误和逻辑错误不是异常;执行中的异常事件可以分为两类error(错误),java虚拟机无法解决的严重问题:如jvm系统内部错误,资源耗尽:StackOverflowError【栈溢出】和OOM(outofmemory)exception:其他因编程错误或......
  • jenkins 彩色日志显示
    目录jenkins彩色日志显示转自jenkins彩色日志显示示例:这里用到ansiColor插件,在Jenkins输出有颜色的日志信息流水线语法的生成ansiColor('xterm'){//someblock}tools.groovypackageorg.devops//格式化输出defPrintMes(value,color){colors=['red'......
  • 使用 Oracle LogMiner 分析重做日志
    概述我们知道oracle的redo和归档日志,记录了数据库的事务的相关信息。在日常的数据库管理过程中,我们有时需要,查看特定时刻特定用户在数据库上执行的操作。这时我们可以通过用logminer分析日志文件获取相关信息。logminer分析归档日志文件所获得的信息并不是原始的操作信息,而是等......
  • ChCore—实验 3:进程与线程、异常处理 部分记录
    思考题1:内核从完成必要的初始化到用户态程序的过程是怎么样的?尝试描述一下调用关系。内核启动到用户程序启动的流程:main├──uart_init├──mm_init├──arch_interrupt_init├──create_root_thread│├──create_root_cap_group│├──__create_......
  • ABP VNext 的日志机制 + SeriLog
    **ABPVNext的日志机制**正用ABPVNext做个系统,由于框架默认带来日志处理机制,开发阶段基本能用,也一直没有去动它,快要上线了,思考了一下正式环境的日志管理流程,由于系统不大,预计访问量可能也不大,但默认的日志管理太简单,不便于后期日常维护。缺点如下:默认的日志只有单个文件,写上几......
  • Mysql 事务隔离机制、锁机制、MVCC多版本并发控制隔离机制、日志机制、
    原子性(Atomicity)当前事务的操作要么同时成功,要么同时失败。原子性由undolog日志来实现。一致性(Consistency):使用事务的最终目的,由其它3个特性以及业务代码正确逻辑来实现。隔离性(lsolation):在事务并发执行时,他们内部的操作不能互相干扰,隔离性由MySQL的各种锁以及MVC......
  • 如何轻松应对偶发异常
    作者:亦盏在之前的文章中,我们已经介绍了如何通过MSE提供的无损上下线和全链路灰度这两个功能来消除变更态的风险,相信您已经能够在变更时得心应手。但是在应用运行过程中突然遇到流量洪峰、黑产刷单、外部依赖故障、慢SQL等偶发异常时,您如何能够继续轻松应对呢?本文将通过介绍MSE......
  • android 检测应用异常 U…
    继承接口UncaughtExceptionHandler,并重写里面的uncaughtException(Threadthread,Throwableex)方法,这样就可以监测应用程序的异常情况,做相应的处理:publicclassmyCustomExceptionHandlerimplementsUncaughtExceptionHandlerprivateUncaughtExceptionHandlerpublicm......
  • docker启动失败后怎么查看日志
    方法一:dockerlogs--since30mcontainer#查看30分钟日志,容器成功失败都可以查看,container表示容器名称或ID方法二:dockerinspect--format'{{.LogPath}}'container#查询容器日志文件目录vi/var/lib/docker/containers/5338f536922596e5503e8715e0a9d5de9f14436177......
  • 视频融合平台EasyCVR设备录像因时间导致播放异常问题的排查与解决
    EasyCVR视频融合平台可提供丰富的视频能力,支持视频直播、录像、回放、检索、云存储、告警上报、语音对讲、集群、电子地图、智能分析以及平台级联等。平台可支持多协议、多类型设备接入,包括国标GB28181、RTMP、RTSP/Onvif、海康SDK、大华SDK、海康Ehome等,近期我们又拓展了更多SDK......