DotNet Core middleware and filters for error logging
1. The concept of middleware
The ASP.NET Core process is a pipeline, and the middleware is the component that is assembled into the application pipeline to process requests and responses. Each middleware can:
- Choose whether to pass the request to the next component in the pipeline.
- Business logic can be executed before and after the next component in the pipeline is called.
The middleware is an instance of the public delegate Task RequestDelegate(HttpContext context) , so the essence of the middleware is a method. The parameter of the method is HttpContext, which returns the Task. The incoming HttpContext parameter contains the request and response information, which we can modify in the middleware. The pipeline processing process for middleware is as follows:
We know that middleware is the component that configures the request processing pipeline, so who is responsible for building the pipeline? The role responsible for building the pipeline is ApplicationBuilder. ApplicationBuilder registers middleware through the Use, Run, Map, and MapWhen methods to build a request pipeline. Let’s take a quick look at these methods.
1 Run
Create a new WebAPI project, modify the Configure method in StartUp as follows, the middleware registered with the Run method can be called the terminal middleware, that is, the middleware is not executed after the middleware is executed.
Public void Configure(IApplicationBuilder app, IHostingEnvironment env) { / / The first middleware app.Run ( async (context) => { context.Response.ContentType = " text/plain;charset=utf-8 " ; await context.Response.WriteAsync( "The first middleware output hello~ " ); }); //The second middleware app.Run( async (context) => { Await context.Response.WriteAsync( "The second middleware output hello~ " ); }); }
Running the program, we see that only the first middleware is executed, and the middleware behind it will not execute.
2 Use
The parameter of the Use method is a delegate instance. The first parameter of the delegate is HttpContext, which is the request context to be processed. The second parameter next is the next middleware. We can call the next middleware by next.Invoke(). And can do a logical processing of HttpContext before/after calling the next middleware.
Public void Configure(IApplicationBuilder app, IHostingEnvironment env) { //The first middleware app.Use( async (context, next) => { context.Response.ContentType = " text/plain;charset=utf-8 " ; // Prevent Chinese garbled await context.Response.WriteAsync($ " first middleware output hello~{Environment.NewLine} " ); Await context.Response.WriteAsync($ " Execute before the next middleware execution ===>{Environment.NewLine} " ); await next.Invoke(); await context.Response.WriteAsync($ " After the middleware is executed Execute <==={Environment.NewLine} " ); }); // Second middleware app.Use( async (context,next) => { Await context.Response.WriteAsync($ " Second middleware output hello~{Environment.NewLine} " ); }); }
Note that if we do not call the next.Invoke() method, the pipeline will be shorted and all subsequent middleware will not be executed.
3 Map
In the case of simple business, it is sufficient to use a request processing pipeline to process all requests. When the business is complicated, we may consider handing over requests for different services to different pipelines for processing. A request pipeline branch is created based on a match for a given request path. If the request path begins with a given path, the branch is executed . Look at a chestnut, the request that starts with /userinfo is handled using the user branch pipeline, and the request at the beginning of /product is handled using the product branch pipeline. The code is as follows: Map
Public class Startup { Public Startup(IConfiguration configuration) { Configuration = configuration; } Public IConfiguration Configuration { get ; } // Dependency injection public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } /// <summary> /// Configure the user branch pipeline, handle requests starting with url with /userinfo /// </summary> /// <param name="app"></param> private static void UserinfoConfigure( IApplicationBuilder app) { app.Use( async (context, next) => { Await context.Response.WriteAsync($ " Processing user business, {Environment.NewLine} " ); await next.Invoke(); }); app.Run( async (context) => { await context.Response.WriteAsync( " User Business Processing Complete ~ " ); }); } /// <summary> /// Configure the product branch pipeline, handle requests with urls beginning with /product /// </summary> /// <param name="app"></param> private static void ProductConfigure( IApplicationBuilder app) { app.Use( async (context, next) => { Await context.Response.WriteAsync($ " Processing Product Business " ); await next.Invoke(); }); } / / Configure request processing pipeline public void Configure (IApplicationBuilder app, IHostingEnvironment env) { / / Prevent Chinese garbled app.Use ( async (context, next) => { context.Response.ContentType = " text/plain;charset=utf-8 " ; await next.Invoke(); }); app.Map( " /userinfo " , UserinfoConfigure); app.Map( " /product " , ProductConfigure); app.Run( async context => { Await context.Response.WriteAsync( " Main pipe handles other services " ); }); } }
The running program execution results are as follows:
4 MapWhen
MapWhen and Map thought quite similar, MapWhen based on custom criteria to create the request pipeline branch, and maps the request to a new branch of the pipeline. Look at a chestnut to understand, the next chestnut needs is that the query parameter contains the name of the request to a branch pipe processing, the url containing /userinfo request is handed to the user branch to handle, the code is as follows:
Public class Startup { Public Startup(IConfiguration configuration) { Configuration = configuration; } Public IConfiguration Configuration { get ; } // Dependency injection public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } /// <summary> /// Configure the branch pipe, handle the request with /userinfo in the url /// </summary> /// <param name="app"></param> private static void UserinfoConfigure( IApplicationBuilder app) { app.Use( async (context, next) => { Await context.Response.WriteAsync($ " Processing user business, {Environment.NewLine} " ); await next.Invoke(); }); app.Run( async (context) => { await context.Response.WriteAsync( " User Business Processing Complete ~ " ); }); } /// <summary> /// Configure the branch pipe, handle the request with the name of the query parameter /// </summary> /// <param name="app"></param> private static void HNameConfigure(IApplicationBuilder app ) { app.Use( async (context, next) => { Await context.Response.WriteAsync($ "The query parameter contains name with the value: {context.Request.Query[ " name " ]} " ); await next.Invoke(); }); } / / Configure request processing pipeline public void Configure (IApplicationBuilder app, IHostingEnvironment env) { / / Prevent Chinese garbled app.Use ( async (context, next) => { context.Response.ContentType = " text/plain;charset=utf-8 " ; await next.Invoke(); }); app.MapWhen(context => context.Request.Query.ContainsKey( " name " ), HNameConfigure); app.MapWhen(context => context.Request.Path.Value.ToString().Contains( " /userinfo " ), UserinfoConfigure); app.Run( async context => { Await context.Response.WriteAsync( " Main pipe handles other services " ); }); } }
Here we have a basic understanding of middleware, and then through an exception log middleware to understand how to use middleware in development.
2 Record the error log using middleware
The log component used here is nlog. First create a WebAPI project, add a custom log processing middleware CostomErrorMiddleware, log the log when the program fails, and print the details of the exception on the page in the development environment, non-development environment. Hide the details, the code is as follows:
/// <summary> /// Custom error handling class /// </summary> public class CostomErrorMiddleware { Private readonly RequestDelegate next; private readonly ILogger logger; private IHostingEnvironment environment; /// <summary> /// DI, inject logger and environment variables /// </summary> /// <param name="next"></ Param> /// <param name="logger"></param> /// <param name="environment"></param> public CostomErrorMiddleware(RequestDelegate next, ILogger<CostomErrorMiddleware> logger, IHostingEnvironment environment) { This .next = next; this .logger = logger; this .environment = environment; } /// <summary> /// Implement the Invoke method /// </summary> /// <param name="context"></param> /// <returns></returns> public async Task Invoke(HttpContext Context) { Try { the await next.Invoke (context); } Catch (Exception ex) { Await HandleError(context, ex); } } /// <summary> /// Error message handling /// </summary> /// <param name="context"></param> /// <param name="ex"></param> /// <returns></returns> private async Task HandleError(HttpContext context, Exception ex) { context.Response.StatusCode = 500 ; context.Response.ContentType = " text / JSON; charset = UTF-. 8; " ; String errorMsg $ = " Error message: {ex.Message} {Environment.NewLine} Error Tracking: ex.StackTrace {} " ; // whether Whether the error log logger.LogError(errorMsg) is recorded for the development environment ; //The browser displays detailed error information in the development environment, and other environment hides the error message if (environment.IsDevelopment()) { the await context.Response.WriteAsync (errorMsg); } Else { Await context.Response.WriteAsync( " Sorry, the server has gone wrong " ); } } }
Modify the Configure method in the StartUp class as follows. To inject nlog, you need to install NLog.Web.AspNetCore first , and use app.UseMiddleware<CostomErrorMiddleware>() to register our custom middleware. The code is as follows:
/ / / configuration request pipeline public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory) { / / Add nlog factory.AddNLog (); env.ConfigureNLog( " nlog.config " ); // generic method to add middleware app.UseMiddleware<CostomErrorMiddleware> (); app.UseMvc(); }
Nlog.config:
<?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" internalLogFile="D:\LogDemoOfWebapi\internal-nlog.txt"> <!-- enable asp.net core layout renderers --> <extensions> <add assembly="NLog.Web.AspNetCore"/> </extensions> <targets> <target xsi:type="File" name="errorLog" fileName="D:/logs/AT___${shortdate}.log" layout="----------------日志记录开始----------------${newline}【日志时间】:${longdate} ${newline}【日志级别】:${level:uppercase=true}${newline}【异常相关信息】${newline}${message}${newline}${newline}${newline}" /> </targets> <rules> <logger name="*" minlevel="Error" writeTo="errorLog" /> </rules> </nlog>
View Code
Here the exception handling middleware is registered, modify the ValueController to create an exception to test it, the code is as follows:
[Route( " api/[controller] " )] [ApiController] Public class ValuesController : ControllerBase { Private ILogger<ValuesController> _logger; public ValuesController(ILogger<ValuesController> logger) { _logger = logger; } // GET api/values [HttpGet] public ActionResult<IEnumerable< string >> Get() { Return new string [] { " value1 " , " value2 " }; } // GET api/values/5 [HttpGet( " {id} " )] public ActionResult< string > Get( int id) { Throw new Exception( " An error has occurred.. " ) ; return "value"; } }
Run the program, access /Values/1 in the development environment, the results are as follows, and these error messages will also be written to the error log via nlog:
In a non-development environment, access /values/1, which looks like this:
If we want to use our custom middleware in a form like app.UseMvc(), we need to add an extension method to ApplicationBuilder. First add a static class CostomMiddleware with the following code:
/// <summary> /// Extension method /// </summary> public static class CostomMiddleware { Public static IApplicationBuilder UseCostomError( this IApplicationBuilder app) { Return app.UseMiddleware<CostomErrorMiddleware> (); } }
Then modify the Configure method to:
Public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory) { / / Add nlog factory.AddNLog (); env.ConfigureNLog( " nlog.config " ); // Use the extension method app.UseCostomError(); app.UseMvc(); }
After running the program, the execution effect is the same as before.
3 Use the filter to log the error log
Filters should be familiar to everyone. There is not much change in the use of filters in ASP.NET Core. Here is also a chestnut that uses filters to log error logs. Just look at the code. First create a filter with the following code. :
/// <summary> /// Custom error handling filter /// </summary> public class CustomErrorFilter :Attribute, IExceptionFilter { Private readonly ILogger _logger; private IHostingEnvironment _environment; Public CustomErrorFilter(ILogger<CustomErrorFilter> logger, IHostingEnvironment environment) { _logger = logger; _environment = environment; } Public void OnException(ExceptionContext context) { Exception ex = context.Exception; string errorMsg = $ " Error Message: {ex.Message}{Environment.NewLine} Error Tracking: {ex.StackTrace} " ; ContentResult result = new ContentResult { ContentType = " text/json;charset=utf-8; " , StatusCode = 500 }; / / Record the error log _logger.LogError (errorMsg) regardless of the development environment ; / / browser displays detailed error information in the development environment, other environments hide the error message if (_environment.IsDevelopment ()) { result.Content = $ " Error Message: {ex.Message}{Environment.NewLine} Error Tracking: {ex.StackTrace} " ; } Else { result.Content = " Sorry, the server has gone wrong " ; } context.Result = result; context.ExceptionHandled = true ; } }
Modify the StartUp class, inject nlog, configure the global filter, the code is as follows, where nlog.config is the same as the middleware chestnut:
Public class Startup { Public Startup(IConfiguration configuration) { Configuration = configuration; } Public IConfiguration Configuration { get ; } // Dependency injection public void ConfigureServices(IServiceCollection services) { services.AddMvc( Configure => { configure.Filters.Add <CustomErrorFilter>(); // Global filter without adding feature headers } // Global filter without adding feature headers ).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); // services.AddScoped<CustomErrorFilter>( ); / / local filter, you need to add a feature header in the Controller / Action [ServiceFilter (typeof (CustomErrorFilter))] } / / Configure the pipeline public void Configure (IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory factory) { factory.AddNLog(); env.ConfigureNLog( " nlog.config " ); app.UseMvc(); } }
Then modify the ValuesController, set the error and the chestnut of the middle middleware. When running the code access /values/1, it will be displayed in the development environment as follows, and the error message will be written into the error log:
If you access /values/1 in a production environment, the error details will be written to the error log. The browser displays the following:
This article introduces the basic use of middleware, and uses the middleware and filter to achieve the record of the exception log. If there is something wrong in the text, I hope everyone can point it out, I will correct it in time.
Reference article
[1]https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0
[2] https://docs.microsoft.com/en-us/aspnet/core/fundamentals/middleware/?view=aspnetcore-3.0
Orignal link:https://www.cnblogs.com/wyy1234/p/11373999.html