欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

程序员文章站 2022-12-10 23:51:30
在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;本章将着重介绍如何在 WebApi 程序中对异常进行捕获,然后利用 Nlog 组件进行记录;同时,还将介绍两种不同的 异常捕获方式:管道捕获/服务过滤;通过本练习,将学习到如何捕获异常、处理异... ......

前言

    在业务系统,异常处理是所有开发人员必须面对的问题,在一定程度上,异常处理的能力反映出开发者对业务的驾驭水平;本章将着重介绍如何在 webapi 程序中对异常进行捕获,然后利用 nlog 组件进行记录;同时,还将介绍两种不同的
异常捕获方式:管道捕获/服务过滤;通过本练习,将学习到如何捕获异常、处理异常跳转、记录异常信息。

搭建框架

    首先,创建一个 webapi 项目,选择 asp.net core web 应用程序;

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

  • 进一步选择 api 模板,这里使用的 .netcore 版本为 2.1

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

  • 取消勾选 “启用 docker 支持(e)” 和 “为 https 配置(c)”,点击确定,得到一个完整的 webapi 项目框架,如图

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

  • 直接按 f5 运行项目,一切正常,程序启动后进入默认路由调用,并输出结果

异常路由

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

  • 一切看起来都非常正常和美好,但,祸之福所倚;接下来我们在 接口 get() 中人为的制造一点麻烦。
        [httpget]
        public actionresult<ienumerable<string>> get()
        {
            throw new exception("出错了.....");

            return new string[] { "value1", "value2" };
        }
  • 这是由于项目配置了运行环境变量 aspnetcore_environment=development 后,startup.cs 中配置了开发环境下,使用系统默认页,所以我们才可以看到上面的异常信息

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

  • 如果你把环境变量设置为 aspnetcore_environment=production ,你会发现,在异常发生的时候,你得到了一个空白页。

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

异常处理方式一:服务过滤

    在传统的 asp.net mvc 应用程序中,我们一般都使用服务过滤的方式去捕获和处理异常,这种方式非常常见,而且可用性来说,体验也不错,幸运的是 asp.net core 也完整的支持该方式,接下来创建一个全局异常处理类 customerexceptionfilter

public class customerexceptionfilter : attribute, iexceptionfilter
{
    private readonly ilogger logger = null;
    private readonly ihostingenvironment environment = null;
    public customerexceptionfilter(ilogger<customerexceptionfilter> logger, ihostingenvironment environment)
    {
        this.logger = logger;
        this.environment = environment;
    }

    public void onexception(exceptioncontext context)
    {
        exception exception = context.exception;
        string error = string.empty;

        void readexception(exception ex)
        {
            error += string.format("{0} | {1} | {2}", ex.message, ex.stacktrace, ex.innerexception);
            if (ex.innerexception != null)
            {
                readexception(ex.innerexception);
            }
        }

        readexception(context.exception);
        logger.logerror(error);

        contentresult result = new contentresult
        {
            statuscode = 500,
            contenttype = "text/json;charset=utf-8;"
        };

        if (environment.isdevelopment())
        {
            var json = new { message = exception.message, detail = error };
            result.content = jsonconvert.serializeobject(json);
        }
        else
        {
            result.content = "抱歉,出错了";
        }
        context.result = result;
        context.exceptionhandled = true;
    }
}
  • customerexceptionfilter 继承自 iexceptionfilter 接口,并实现 void onexception(exceptioncontext context) 方法,在 customerexceptionfilter
    构造方法中,定义了两个参数,用于记录异常日志和获取程序运行环境变量
    private readonly ilogger logger = null;
    private readonly ihostingenvironment environment = null;
    public customerexceptionfilter(ilogger<customerexceptionfilter> logger, ihostingenvironment environment)
    {
        this.logger = logger;
        this.environment = environment;
    }
  • 在接下来的 onexception 方法中,利用 environment 进行产品环境的判断,并使用 logger 将日志写入硬盘文件中,为了将日志写入硬盘,
    需要引用 nuget 包 nlog.extensions.logging/nlog.web.aspnetcore ,并在 startup.cs 文件的 configure 方法中添加扩展
public void configure(iapplicationbuilder app, ihostingenvironment env, iloggerfactory factory)
        {
            // 将 nlog
            factory.addconsole(configuration.getsection("logging"))
                   .addnlog()
                   .adddebug();

            var nlogfile = system.io.path.combine(env.contentrootpath, "nlog.config");
            env.configurenlog(nlogfile);

            if (env.isdevelopment())
            {
                app.usedeveloperexceptionpage();
            }

            app.usemvc();
        }
  • 上面的代码读取了配置文件 nlog.config 并设置为 nlog 的配置,该文件定义如下
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/nlog.xsd" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" autoreload="true" internalloglevel="info">

  <!-- load the asp.net core plugin -->
  <extensions>
    <add assembly="nlog.web.aspnetcore"/>
  </extensions>

  <!-- layout: https://github.com/nlog/nlog/wiki/layout%20renderers -->
  <targets>
    <target xsi:type="file" name="errorfile" filename="/data/logs/logfilter/error-${shortdate}.log" layout="${longdate}|${logger}|${uppercase:${level}}|  ${message} ${exception}|${aspnet-request-url}" />
    <target xsi:type="null" name="blackhole" />
  </targets>

  <rules>
    <logger name="microsoft.*" minlevel="error" writeto="blackhole" final="true" />
    <logger name="*" minlevel="error" writeto="errorfile" />
  </rules>
</nlog>
  • 为了在 webapi 控制器中使用 customerexceptionfilter 过滤器,我们还需要在 startup.cs 将 customerexceptionfilter 注入到容器中
        // this method gets called by the runtime. use this method to add services to the container.
        public void configureservices(iservicecollection services)
        {
            // 将异常过滤器注入到容器中
            services.addscoped<customerexceptionfilter>();

            services.addmvc()
                .setcompatibilityversion(compatibilityversion.version_2_1);

        }
  • 最后,在控制器 valuescontroller 中应用该异常过滤器
    [servicefilter(typeof(customerexceptionfilter))]
    [route("api/[controller]"), apicontroller]
    public class valuescontroller : controllerbase
    {
        // get api/values
        [httpget]
        public actionresult<ienumerable<string>> get()
        {
            throw new exception("出错了.....");
            return new string[] { "value1", "value2" };
        }
    }
  • 现在,按 f5 启动程序,如预期所料,报错信息被 customerexceptionfilter 捕获,并转换为 json 格式输出

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

  • 同时,nlog 组件也将日志信息记录到了硬盘中

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

异常处理方式二:中间件捕获

    接下来利用 .netcore 的管道模式,在中间件中对异常进行捕获,首先,创建一个中间件

public class exceptionmiddleware
{
    private readonly requestdelegate next;
    private readonly ilogger logger;
    private ihostingenvironment environment;

    public exceptionmiddleware(requestdelegate next, ilogger<exceptionmiddleware> logger, ihostingenvironment environment)
    {
        this.next = next;
        this.logger = logger;
        this.environment = environment;
    }

    public async task invoke(httpcontext context)
    {
        try
        {
            await next.invoke(context);
            var features = context.features;
        }
        catch (exception e)
        {
            await handleexception(context, e);
        }
    }

    private async task handleexception(httpcontext context, exception e)
    {
        context.response.statuscode = 500;
        context.response.contenttype = "text/json;charset=utf-8;";
        string error = "";

        void readexception(exception ex)
        {
            error += string.format("{0} | {1} | {2}", ex.message, ex.stacktrace, ex.innerexception);
            if (ex.innerexception != null)
            {
                readexception(ex.innerexception);
            }
        }

        readexception(e);
        if (environment.isdevelopment())
        {
            var json = new { message = e.message, detail = error };
            error = jsonconvert.serializeobject(json);
        }
        else
            error = "抱歉,出错了";

        await context.response.writeasync(error);
    }
}
  • 代码比较简单,在管道中使用 try/catch 进行捕获异常,创建 handleexception(httpcontext context, exception e) 处理异常,判断是 development 环境下,输出详细的错误信息,非 development 环境仅提示调用者“抱歉,出错了”,同时使用 nlog 组件将日志写入硬盘;
    同样,在 startup.cs 中将 exceptionmiddleware 加入管道中
        // this method gets called by the runtime. use this method to configure the http request pipeline.
        public void configure(iapplicationbuilder app, ihostingenvironment env, iloggerfactory factory)
        {
            // 将 nlog
            factory.addconsole(configuration.getsection("logging"))
                   .addnlog()
                   .adddebug();

            var nlogfile = system.io.path.combine(env.contentrootpath, "nlog.config");
            env.configurenlog(nlogfile);

            // exceptionmiddleware 加入管道
            app.usemiddleware<exceptionmiddleware>();

            //if (env.isdevelopment())
            //{
            //    app.usedeveloperexceptionpage();
            //}

            app.usemvc();
        }
  • 一切就绪,按 f5 运行程序,网页中输出了期望中的 json 格式错误信息,同时 nlog 组件也将日志写入了硬盘

Asp.NetCore依赖注入和管道方式的异常处理及日志记录

结语

    在本例中,通过依赖注入和管道中间件的方式,演示了两种不同的全局捕获异常处理的过程;值得注意到是,两种方式对于 nlog 的使用,都是一样的,没有任何差别,代码无需改动;实际项目中,也是应当区分不同的业务场景,输出不同的
日志信息,不管是从安全或者是用户体验友好性上面来说,都是非常值得推荐的方式,全局异常捕获处理,完全和业务剥离。