asp.net nlog替代iis日志解决webshell攻击问题

问题起因

网站被webshell攻击代码访问,记录到iis10日志中,请求日志大约长这样 /type.php?template=tag_(){};@unlink(FILE);print_r(xbshell);assert($_POST[1]);{//../rss
阿里云报警告,因为不是被攻击,所以需要筛选一下日志,经过百度搜索iis原来有一个日志过滤的中间件,但是目前已经不允许下载了
https://learn.microsoft.com/zh-cn/iis/extensions/advanced-logging-module/advanced-logging-for-iis-log-filtering
扩展地址
在这里插入图片描述
没办法只能自己实现了

.net framework

asp.net httpmodule

了解到了httpmodule官方解释
在这里插入图片描述
主要是这两个事件即可实现

实现

新建asp.net framework 3.5 web应用程序
安装nlog 5.2.7nlog官网
在这里插入图片描述

添加引用引入system.web
在这里插入图片描述
新建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"
	  throwConfigExceptions="true"
	  >
	<targets>
		<!--csv日志记录,方便查看分析-->
		<target xsi:type="File" name="csvFileLog" fileName="logs/${shortdate}.csv">
			<layout xsi:type="CsvLayout" withHeader="true">
				<column name="time" layout="${longdate}" />
				<column name="level" layout="${level:upperCase=true}"/>
				<column name="method" layout="${event-properties:method}" />
				<column name="rawurl" layout="${event-properties:rawurl}" />
				<column name="query" layout="${event-properties:query}" />
				<column name="port" layout="${event-properties:port}" />
				<column name="ip" layout="${event-properties:ip}" />
				<column name="useragent" layout="${event-properties:useragent}" />
				<column name="referer" layout="${event-properties:referer}" />
				<column name="status" layout="${event-properties:status}" />
				<column name="consuming(ms)" layout="${event-properties:consuming}" />
			</layout>
		</target>
	</targets>

	<rules>
		<logger name="*" minlevel="Info" writeTo="csvFileLog" />
	</rules>
</nlog>

新建GlobalLogingModule .cs

using NLog;
using System;
using System.Web;

namespace AspNetLoggingTest01
{
    public class GlobalLogingModule : IHttpModule
    {
        private Logger Logger = null;
        private DateTime starttime;
        private LogEventInfo theEvent;

        /// <summary>
        /// 销毁的时候
        /// </summary>
        public void Dispose()
        {
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="context"></param>
        public void Init(HttpApplication context)
        {
            Logger = NLog.LogManager.GetCurrentClassLogger();
            context.BeginRequest += new EventHandler(OnBeginRequest);
            context.EndRequest += new EventHandler(OnEndRequest);
        }

        /// <summary>
        /// 请求开始的时候
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnBeginRequest(object sender, EventArgs e)
        {
            HttpContext context = ((HttpApplication)sender).Context;
            theEvent = new LogEventInfo(LogLevel.Info, null, string.Empty);
            theEvent.Properties["method"] = context.Request?.HttpMethod ?? "";
            var requestUrl = context.Request?.Url.AbsolutePath ?? "";
            theEvent.Properties["rawurl"] = requestUrl;
            var queryString = context.Request?.Url?.Query ?? "";
            if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
            {
                queryString = HttpUtility.UrlEncode(queryString);
            }
            theEvent.Properties["query"] = queryString;
            theEvent.Properties["port"] = context.Request.Url?.Port ?? 80;
            theEvent.Properties["ip"] = context.Request?.UserHostAddress ?? "";
            theEvent.Properties["useragent"] = context.Request?.UserAgent ?? "";
            theEvent.Properties["referer"] = context.Request?.UrlReferrer?.AbsoluteUri ?? "";
            starttime = DateTime.Now;
        }

        /// <summary>
        /// 请求结束
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnEndRequest(object sender, EventArgs e)
        {
            if (theEvent != null)
            {
                HttpContext context = ((HttpApplication)sender).Context;
                theEvent.Properties["status"] = context.Response.StatusCode;
                theEvent.Properties["consuming"] = (DateTime.Now - starttime).TotalMilliseconds;
                Logger.Log(theEvent);
                theEvent = null;
            }
        }
    }
}

然后web.cofing中引入

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<!--省略其他配置-->
	<system.webServer>
		<modules>
			<add name="diyLoging" type="AspNetLoggingTest01.GlobalLogingModule,AspNetLoggingTest01"/>
		</modules>
	</system.webServer>
</configuration>

这样就能解决问题了,但是我想提升一下封装为类库方便后续的其他网站使用,请看下面

nuget打包

下载nuget
将nuget.exe放到项目中的csproj中,准备打包,右键nuget.exe属性,选择解除锁定,然后应用,要不然会报nu5133错误
在这里插入图片描述
生成配置文件

nuget spec

AspNetLoggingTest01.nuspec,需要修改内容以及配置
https://learn.microsoft.com/zh-cn/nuget/create-packages/creating-a-package
https://learn.microsoft.com/zh-cn/nuget/quickstart/create-and-publish-a-package-using-visual-studio-net-framework
下面是对于打包参数的解释
https://learn.microsoft.com/zh-cn/nuget/reference/nuspec

<?xml version="1.0" encoding="utf-8"?>
<package>
	<metadata>
		<id>AspNetLoggingTest01</id>
		<version>0.0.1</version>
		<title>自定义全局日志</title>
		<authors>wjl</authors>
		<requireLicenseAcceptance>false</requireLicenseAcceptance>
		<license type="expression">MIT</license>
		<projectUrl>https://www.wujialiang.com/</projectUrl>
		<description>自定义全局日志,替代iis日志</description>
		<releaseNotes>替代iis日志,解决php攻击阿里云警告问题</releaseNotes>
		<copyright>wjl</copyright>
		<tags>日志记录,webshell攻击</tags>
		<!--需要复制到项目中的配置文件-->
		<contentFiles>
			<files include="NLog.config" buildAction="None" copyToOutput="true" flatten="true"/>
		</contentFiles>
		<!--依赖的第三方包-->
		<dependencies>
			<dependency id="NLog" version="5.2.7" />
		</dependencies>
		<!--需要的系统包-->
		<frameworkAssemblies>
			<frameworkAssembly assemblyName="System.Web"  />
		</frameworkAssemblies>
	</metadata>
</package>

打包

nuget pack

在这里插入图片描述
有了nupkg直接上传即可了,外部项目引用需要下载nuget包然后,web.config新增即可

<?xml version="1.0" encoding="utf-8"?>
<configuration>
	<!--省略其他配置-->
	<system.webServer>
		<modules>
			<add name="diyLoging" type="AspNetLoggingTest01.GlobalLogingModule,AspNetLoggingTest01"/>
		</modules>
	</system.webServer>
</configuration>

如果iis是7.0以前的需要配置如下代码

<system.web>
	<httpModules>
		<add name="diyLoging" type="AspNetLoggingTest01.GlobalLogingModule,AspNetLoggingTest01"/>
	</httpModules>
</system.web>

202312051755更新

增加错误日志记录
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"
	  throwConfigExceptions="true"
	  >
	<targets>
		<!--csv日志记录,方便查看分析-->
		<target xsi:type="File" name="csvFileLog" fileName="logs/${shortdate}.csv">
			<layout xsi:type="CsvLayout" withHeader="true">
				<column name="time" layout="${longdate}" />
				<column name="level" layout="${level:upperCase=true}"/>
				<column name="method" layout="${event-properties:method}" />
				<column name="rawurl" layout="${event-properties:rawurl}" />
				<column name="query" layout="${event-properties:query}" />
				<column name="port" layout="${event-properties:port}" />
				<column name="ip" layout="${event-properties:ip}" />
				<column name="useragent" layout="${event-properties:useragent}" />
				<column name="referer" layout="${event-properties:referer}" />
				<column name="status" layout="${event-properties:status}" />
				<column name="consuming(ms)" layout="${event-properties:consuming}" />
				<column name="error" layout="${event-properties:error}" />
			</layout>
		</target>
	</targets>

	<rules>
		<logger name="*" minlevel="Info" writeTo="csvFileLog" />
	</rules>
</nlog>

修改GlobalLogingModule.cs

 public class GlobalLogingModule : IHttpModule
 {
     private Logger Logger = null;
     private DateTime starttime;
     private LogEventInfo theEvent;

     /// <summary>
     /// 销毁的时候
     /// </summary>
     public void Dispose()
     {
     }

     /// <summary>
     /// 初始化
     /// </summary>
     /// <param name="context"></param>
     public void Init(HttpApplication context)
     {
         Logger = NLog.LogManager.GetCurrentClassLogger();
         context.BeginRequest += new EventHandler(OnBeginRequest);
         context.EndRequest += new EventHandler(OnEndRequest);
         context.Error += new EventHandler(OnErrorRequest);
     }

     /// <summary>
     /// 错误日志记录
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     private void OnErrorRequest(object sender, EventArgs e)
     {
         var app = (HttpApplication)sender;
         if (app == null)
         {
             return;
         }
         HttpContext context = app.Context;
         theEvent = new LogEventInfo(LogLevel.Error, null, string.Empty);
         theEvent.Properties["method"] = context.Request?.HttpMethod ?? "";
         var requestUrl = context.Request?.Url.AbsolutePath ?? "";
         theEvent.Properties["rawurl"] = requestUrl;
         var queryString = context.Request?.Url?.Query ?? "";
         if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
         {
             queryString = HttpUtility.UrlEncode(queryString);
         }
         theEvent.Properties["query"] = queryString;
         theEvent.Properties["port"] = context.Request.Url?.Port ?? 80;
         theEvent.Properties["ip"] = context.Request?.UserHostAddress ?? "";
         theEvent.Properties["useragent"] = context.Request?.UserAgent ?? "";
         theEvent.Properties["referer"] = context.Request?.UrlReferrer?.AbsoluteUri ?? "";
         theEvent.Properties["status"] = context.Response.StatusCode;
         theEvent.Properties["consuming"] = (DateTime.Now - starttime).TotalMilliseconds;
         var httpError = app.Server.GetLastError();
         if (httpError != null)
         {
             var errMsg = $"{httpError.Message}\t{httpError.StackTrace}";
             if (httpError.InnerException != null)
             {
                 errMsg = $"{errMsg}\r\n{httpError.InnerException.Message}\t{httpError.InnerException.StackTrace}";
             }
             theEvent.Properties["error"] = errMsg;
             app.Server.ClearError();
         }
         Logger.Log(theEvent);
         theEvent = null;
     }

     /// <summary>
     /// 请求开始的时候
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     private void OnBeginRequest(object sender, EventArgs e)
     {
         HttpContext context = ((HttpApplication)sender).Context;
         theEvent = new LogEventInfo(LogLevel.Info, null, string.Empty);
         theEvent.Properties["method"] = context.Request?.HttpMethod ?? "";
         var requestUrl = context.Request?.Url.AbsolutePath ?? "";
         theEvent.Properties["rawurl"] = requestUrl;
         var queryString = context.Request?.Url?.Query ?? "";
         if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
         {
             queryString = HttpUtility.UrlEncode(queryString);
         }
         theEvent.Properties["query"] = queryString;
         theEvent.Properties["port"] = context.Request.Url?.Port ?? 80;
         theEvent.Properties["ip"] = context.Request?.UserHostAddress ?? "";
         theEvent.Properties["useragent"] = context.Request?.UserAgent ?? "";
         theEvent.Properties["referer"] = context.Request?.UrlReferrer?.AbsoluteUri ?? "";
         starttime = DateTime.Now;
     }

     /// <summary>
     /// 请求结束
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     private void OnEndRequest(object sender, EventArgs e)
     {
         if (theEvent != null)
         {
             HttpContext context = ((HttpApplication)sender).Context;
             theEvent.Properties["status"] = context.Response.StatusCode;
             theEvent.Properties["consuming"] = (DateTime.Now - starttime).TotalMilliseconds;
             Logger.Log(theEvent);
             theEvent = null;
         }
     }
 }

20231207更新

递归记录错误日志

/// <summary>
/// 错误日志记录
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnErrorRequest(object sender, EventArgs e)
{
   var app = (HttpApplication)sender;
   if (app == null)
   {
       return;
   }
   HttpContext context = app.Context;
   theEvent = new LogEventInfo(LogLevel.Error, null, string.Empty);
   theEvent.Properties["method"] = context.Request?.HttpMethod ?? "";
   var requestUrl = context.Request?.Url.AbsolutePath ?? "";
   theEvent.Properties["rawurl"] = requestUrl;
   var queryString = context.Request?.Url?.Query ?? "";
   if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
   {
       queryString = HttpUtility.UrlEncode(queryString);
   }
   theEvent.Properties["query"] = queryString;
   theEvent.Properties["port"] = context.Request.Url?.Port ?? 80;
   theEvent.Properties["ip"] = context.Request?.UserHostAddress ?? "";
   theEvent.Properties["useragent"] = context.Request?.UserAgent ?? "";
   theEvent.Properties["referer"] = context.Request?.UrlReferrer?.AbsoluteUri ?? "";
   theEvent.Properties["status"] = context.Response.StatusCode;
   theEvent.Properties["consuming"] = (DateTime.Now - starttime).TotalMilliseconds;
   var httpError = app.Server.GetLastError();
   if (httpError != null)
   {
       StringBuilder error = new StringBuilder("异常信息:");
       if (httpError != null)
       {
           void ReadException(Exception ex)
           {
               error.Append($"{ex.Message} | {ex.StackTrace} | {ex.InnerException}");
               if (ex.InnerException != null)
               {
                   ReadException(ex.InnerException);
               }
           }
           ReadException(httpError);
           theEvent.Properties["error"] = error.ToString();
       }
       //var errMsg = $"{httpError.Message}\t{httpError.StackTrace}";
       //if (httpError.InnerException != null)
       //{
       //    errMsg = $"{errMsg}\r\n{httpError.InnerException.Message}\t{httpError.InnerException.StackTrace}";
       //}
       //theEvent.Properties["error"] = errMsg;
       app.Server.ClearError();
   }
   Logger.Log(theEvent);
   theEvent = null;
}

在这里插入图片描述
在这里插入图片描述

.net core

纯中间件实现

webapi

RequestLoggingMiddleware.cs

/// <summary>
/// 请求日志记录的中间件
/// </summary>
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly NLog.Logger logger;

    /// <summary>
    /// 构造函数注入
    /// </summary>
    /// <param name="next"></param>
    public RequestLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
        logger = NLog.LogManager.GetCurrentClassLogger();
    }

    /// <summary>
    /// 方法注入
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task Invoke(HttpContext context)
    {
        var theEvent = new LogEventInfo(NLog.LogLevel.Info, null, string.Empty);
        theEvent.Properties["method"] = context.Request?.Method ?? "";
        var requestUrl = context.Request?.Path.ToString() ?? "";
        theEvent.Properties["rawurl"] = requestUrl;
        string queryString = context?.Request?.QueryString.ToString() ?? "";
        if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
        {
            queryString = HttpUtility.UrlEncode(queryString);
        }
        theEvent.Properties["query"] = queryString;
        theEvent.Properties["port"] = context?.Connection?.LocalPort ?? 80;
        theEvent.Properties["ip"] = context?.Connection?.RemoteIpAddress?.ToString() ?? "";
        theEvent.Properties["useragent"] = context?.Request?.Headers[HeaderNames.UserAgent] ?? "";
        theEvent.Properties["referer"] = context?.Request?.Headers[HeaderNames.Referer] ?? "";
        var startTime = DateTime.Now;
        try
        {
            if (context != null)
            {
                await _next(context);
            }
        }
        catch (Exception ex)
        {
            theEvent.Level = NLog.LogLevel.Error;
            StringBuilder error = new StringBuilder("中间件捕获异常信息:");
            void ReadException(Exception ex)
            {
                error.Append($"{ex.Message} | {ex.StackTrace} | {ex.InnerException}");
                if (ex.InnerException != null)
                {
                    ReadException(ex.InnerException);
                }
            }
            ReadException(ex);
            theEvent.Properties["error"] = error.ToString();
            //如果后续没有中间件处理可以使用下面的方式适用于API
            if (context != null)
            {
	            context.Response.StatusCode = 200;
                context.Response.ContentType = "application/json";
                await context.Response.WriteAsync("{\"success\":false}");
            }
        }
        finally
        {
            theEvent.Properties["status"] = context?.Response?.StatusCode ?? 500;
            theEvent.Properties["consuming"] = (DateTime.Now - startTime).TotalMilliseconds;
            logger.Log(theEvent);
        }
    }
}

/// <summary>
/// 请求日志记录中间件扩展
/// </summary>
public static class RequestLoggingMiddlewareExtensions
{
    /// <summary>
    /// 使用请求日志记录中间件
    /// </summary>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IApplicationBuilder UseRequestLoggingMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggingMiddleware>();
    }
}

在这里插入图片描述
这个相当于终结点中间件了,后续的中间件就被阻隔了,适合放在第一个没有终结点中间件的前面
比如下面

 //报告异常,如果UseRequestLoggingMiddleware放在UseDeveloperExceptionPage前面,则被错误异常会被UseDeveloperExceptionPage捕获,UseRequestLoggingMiddleware中间件就不会记录到错误日志
app.UseDeveloperExceptionPage();
app.UseRequestLoggingMiddleware();

UseRequestLoggingMiddleware在前面就会出现下面的情况,根本不会记录到错误日志,只会记录一条500的请求日志
在这里插入图片描述
解决办法如下

//放在第一个
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
    //报告异常
    app.UseDeveloperExceptionPage();
}else{
	//日志请求记录
	app.UseRequestLoggingMiddleware();
}

mvc

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;
using NLog;
using System.DirectoryServices.Protocols;
using System.Text;
using System.Web;

namespace DTcms.Core.Web.Middleware
{
    /// <summary>
    /// 请求日志记录的中间件
    /// </summary>
    public class RequestLoggingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly NLog.Logger logger;

        /// <summary>
        /// 构造函数注入
        /// </summary>
        /// <param name="next"></param>
        public RequestLoggingMiddleware(RequestDelegate next)
        {
            _next = next;
            logger = NLog.LogManager.GetCurrentClassLogger();
        }

        /// <summary>
        /// 方法注入
        /// </summary>
        /// <param name="context"></param>
        /// <returns></returns>
        public async Task Invoke(HttpContext context)
        {
            var theEvent = new LogEventInfo(NLog.LogLevel.Info, null, string.Empty);
            theEvent.Properties["method"] = context.Request?.Method ?? "";
            var requestUrl = context.Request?.Path.ToString() ?? "";
            theEvent.Properties["rawurl"] = requestUrl;
            string queryString = context?.Request?.QueryString.ToString() ?? "";
            if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
            {
                queryString = HttpUtility.UrlEncode(queryString);
            }
            theEvent.Properties["query"] = queryString;
            theEvent.Properties["port"] = context?.Connection?.LocalPort ?? 80;
            theEvent.Properties["ip"] = context?.Connection?.RemoteIpAddress?.ToString() ?? "";
            theEvent.Properties["useragent"] = context?.Request?.Headers[HeaderNames.UserAgent] ?? "";
            theEvent.Properties["referer"] = context?.Request?.Headers[HeaderNames.Referer] ?? "";
            var startTime = DateTime.Now;
            try
            {
                if (context != null)
                {
                    await _next(context);
                }
            }
            catch (Exception ex)
            {
                theEvent.Level = NLog.LogLevel.Error;
                StringBuilder error = new StringBuilder("中间件捕获异常信息:");
                void ReadException(Exception ex)
                {
                    error.Append($"{ex.Message} | {ex.StackTrace} | {ex.InnerException}");
                    if (ex.InnerException != null)
                    {
                        ReadException(ex.InnerException);
                    }
                }
                ReadException(ex);
                theEvent.Properties["error"] = error.ToString();
                //如果后续还有中间件处理异常可以使用这个,比如mvc,一般有外部处理
                throw new Exception(ex.Message, ex.InnerException);
                /*
                //全局异常
                app.UseExceptionHandler("/Error");
                //页面异常
                app.UseStatusCodePagesWithReExecute("/Error/{0}");
                
                [Route("[controller]")]
                //[TempleteFilter]
                [ApiExplorerSettings(IgnoreApi = true)]
                public class ErrorController : Controller
                {
                    /// <summary>
                    /// 全局异常
                    /// </summary>
                    /// <returns></returns>
                    [Route("")]
                    public IActionResult Error()
                    {
                        var result = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
                        ViewBag.ErrorCode = "500";
                        ViewBag.ErrorTitle = "程序过程中发生错误";
                        ViewBag.ErrorPath = result?.Path;
                        ViewBag.ErrorMessage = result.Error.Message;
                        return View("Error");
                    }

                    /// <summary>
                    /// 页面异常
                    /// </summary>
                    [Route("{statusCode}")]
                    public IActionResult Error(int statusCode)
                    {
                        var result = HttpContext.Features.Get<IStatusCodeReExecuteFeature>();
                        switch (statusCode)
                        {
                            case 404:
                                ViewBag.ErrorCode = "404";
                                ViewBag.ErrorTitle = "页面不存在";
                                ViewBag.ErrorPath = result?.OriginalPath;
                                ViewBag.ErrorMessage = "抱歉,您访问的页面不存在";
                                break;
                        }
                        return View("Error");
                    }
                }
                 */
            }
            finally
            {
                theEvent.Properties["status"] = context?.Response?.StatusCode ?? 500;
                theEvent.Properties["consuming"] = (DateTime.Now - startTime).TotalMilliseconds;
                logger.Log(theEvent);
            }
        }
    }

    /// <summary>
    /// 请求日志记录中间件扩展
    /// </summary>
    public static class RequestLoggingMiddlewareExtensions
    {
        /// <summary>
        /// 使用请求日志记录中间件
        /// </summary>
        /// <param name="builder"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseRequestLoggingMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<RequestLoggingMiddleware>();
        }
    }
}

program.cs中代码如下

var app = builder.Build();
app.UseDeveloperExceptionPage();
app.UseRequestLoggingMiddleware();

或者使用状态码展示或者直接跳转

//throw new Exception(ex.Message, ex.InnerException);
if (context != null)
{
    context.Response.StatusCode = 500;
    //下面是直接跳转
    //context.Response.Redirect("/Error");
    //context.Response.Redirect("/Error/500");
}

var app = builder.Build();
app.UseStatusCodePages();
app.UseRequestLoggingMiddleware();

//或者再增加一层中间件,外层处理
//全局异常
app.UseExceptionHandler("/Error");
//页面异常
app.UseStatusCodePagesWithReExecute("/Error/{0}");
app.UseRequestLoggingMiddleware();

在这里插入图片描述

IExceptionFilter+中间件

IExceptionFilter处理的错误到中间件不会捕捉到异常,
IExceptionFilter是在操作引发 Exception后运行的筛选器,他处理过的异常中间件就不获不到了,所以中间件就不用处理异常了,有的方案说只处理controller过程中的问题,不处理的其他的,不放心的话可以添加try catch

/// <summary>
/// 全局异常过滤器
/// </summary>
public class GlobalExceptionFilter : IExceptionFilter
{
    private readonly IWebHostEnvironment _environment;
    private readonly NLog.Logger _nlogLogger;

    /// <summary>
    /// 构造函数注入
    /// </summary>
    /// <param name="environment"></param>
    public GlobalExceptionFilter(IWebHostEnvironment environment)
    {
        _environment = environment;
        _nlogLogger = NLog.LogManager.GetCurrentClassLogger();
    }

    /// <summary>
    /// 发生错误了
    /// </summary>
    /// <param name="context"></param>
    public void OnException(ExceptionContext context)
    {
        var httpContext = context.HttpContext;
        var startTime = DateTime.Now;
        var theEvent = new LogEventInfo(NLog.LogLevel.Error, null, string.Empty);
        theEvent.Properties["method"] = httpContext.Request?.Method ?? "";
        var requestUrl = httpContext.Request?.Path.ToString() ?? "";
        theEvent.Properties["rawurl"] = requestUrl;
        string queryString = httpContext?.Request?.QueryString.ToString() ?? "";
        if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
        {
            queryString = HttpUtility.UrlEncode(queryString);
        }
        theEvent.Properties["query"] = queryString;
        theEvent.Properties["port"] = httpContext?.Connection?.LocalPort ?? 80;
        theEvent.Properties["ip"] = httpContext?.Connection?.RemoteIpAddress?.ToString() ?? "";
        theEvent.Properties["useragent"] = httpContext?.Request?.Headers[HeaderNames.UserAgent] ?? "";
        theEvent.Properties["referer"] = httpContext?.Request?.Headers[HeaderNames.Referer] ?? "";
        theEvent.Properties["status"] = 500;
        theEvent.Properties["consuming"] = (DateTime.Now - startTime).TotalMilliseconds;
        StringBuilder error = new StringBuilder("异常信息:");
        if (context.Exception != null)
        {
            void ReadException(Exception ex)
            {
                error.Append($"{ex.Message} | {ex.StackTrace} | {ex.InnerException}");
                if (ex.InnerException != null)
                {
                    ReadException(ex.InnerException);
                }
            }
            ReadException(context.Exception);
            theEvent.Properties["error"] = error.ToString();
        }
        _nlogLogger.Log(theEvent);
        ResponseMessage apiResponse = new ResponseMessage(ErrorCode.UnknownError, _environment.IsDevelopment() ? error.ToString() : "服务器正忙,请稍后再试.", context.HttpContext);
        HandlerException(context, apiResponse, StatusCodes.Status500InternalServerError);
    }

    /// <summary>
    /// 处理错误请求
    /// </summary>
    /// <param name="context"></param>
    /// <param name="apiResponse"></param>
    /// <param name="statusCode"></param>
    private void HandlerException(ExceptionContext context, ResponseMessage apiResponse, int statusCode)
    {
        context.Result = new JsonResult(apiResponse)
        {
            StatusCode = statusCode,
            ContentType = "application/json",
        };
        context.ExceptionHandled = true;
    }
}

中间件

/// <summary>
/// 请求日志记录的中间件
/// </summary>
public class RequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly NLog.Logger logger;

    /// <summary>
    /// 构造函数注入
    /// </summary>
    /// <param name="next"></param>
    public RequestLoggingMiddleware(RequestDelegate next)
    {
        _next = next;
        logger = NLog.LogManager.GetCurrentClassLogger();
    }

    /// <summary>
    /// 方法注入
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public async Task Invoke(HttpContext context)
    {
        var theEvent = new LogEventInfo(NLog.LogLevel.Info, null, string.Empty);
        theEvent.Properties["method"] = context.Request?.Method ?? "";
        var requestUrl = context.Request?.Path.ToString() ?? "";
        theEvent.Properties["rawurl"] = requestUrl;
        string queryString = context?.Request?.QueryString.ToString() ?? "";
        if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
        {
            queryString = HttpUtility.UrlEncode(queryString);
        }
        theEvent.Properties["query"] = queryString;
        theEvent.Properties["port"] = context?.Connection?.LocalPort ?? 80;
        theEvent.Properties["ip"] = context?.Connection?.RemoteIpAddress?.ToString() ?? "";
        theEvent.Properties["useragent"] = context?.Request?.Headers[HeaderNames.UserAgent] ?? "";
        //referer也可能存在攻击
        theEvent.Properties["referer"] = context?.Request?.Headers[HeaderNames.Referer] ?? "";
        var startTime = DateTime.Now;
        if (context != null)
        {
            await _next(context);
        }
        if (context?.Response?.StatusCode != 500)//因为500的错误都让异常过滤器处理了,所以中间件这里不处理
        {
            var statusCode = context?.Response?.StatusCode ?? 500;
            theEvent.Properties["status"] = statusCode;
            theEvent.Properties["consuming"] = (DateTime.Now - startTime).TotalMilliseconds;
            logger.Log(theEvent);
        }
    }
}

/// <summary>
/// 请求日志记录中间件扩展
/// </summary>
public static class RequestLoggingMiddlewareExtensions
{
    /// <summary>
    /// 使用请求日志记录中间件
    /// </summary>
    /// <param name="builder"></param>
    /// <returns></returns>
    public static IApplicationBuilder UseRequestLoggingMiddleware(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<RequestLoggingMiddleware>();
    }
}

使用

//全局异常配置
builder.Services.AddMvc(setupAction => {
    //全局异常过滤
    setupAction.Filters.Add(typeof(GlobalExceptionFilter));
});

var app = builder.Build();
app.UseStatusCodePages();
app.UseRequestLoggingMiddleware();

这样如果是报错就被过滤器捕获,中间件只需要记录其他日志就行

IExceptionFilter只能捕获controller层错误,所以mvc模式下razor页面的错误就需要中间件捕获,修改中间件代码如下

 /// <summary>
 /// 请求日志记录的中间件
 /// </summary>
 public class RequestLoggingMiddleware
 {
     private readonly RequestDelegate _next;
     private readonly NLog.Logger logger;

     /// <summary>
     /// 构造函数注入
     /// </summary>
     /// <param name="next"></param>
     public RequestLoggingMiddleware(RequestDelegate next)
     {
         _next = next;
         logger = NLog.LogManager.GetCurrentClassLogger();
     }

     /// <summary>
     /// 方法注入
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
     public async Task Invoke(HttpContext context)
     {
         var theEvent = new LogEventInfo(NLog.LogLevel.Info, null, string.Empty);
         theEvent.Properties["method"] = context.Request?.Method ?? "";
         var requestUrl = context.Request?.Path.ToString() ?? "";
         theEvent.Properties["rawurl"] = requestUrl;
         string queryString = context?.Request?.QueryString.ToString() ?? "";
         if (!string.IsNullOrEmpty(requestUrl) && requestUrl.Contains(".php"))
         {
             queryString = HttpUtility.UrlEncode(queryString);
         }
         theEvent.Properties["query"] = queryString;
         theEvent.Properties["port"] = context?.Connection?.LocalPort ?? 80;
         theEvent.Properties["ip"] = context?.Connection?.RemoteIpAddress?.ToString() ?? "";
         theEvent.Properties["useragent"] = context?.Request?.Headers[HeaderNames.UserAgent] ?? "";
         //referer也可能存在攻击
         theEvent.Properties["referer"] = context?.Request?.Headers[HeaderNames.Referer] ?? "";
         var startTime = DateTime.Now;
         var isMiddleError = false;
         try
         {
             if (context != null)
             {
                 await _next(context);
             }
         }
         catch (Exception ex)
         {
             theEvent.Level = NLog.LogLevel.Error;
             StringBuilder error = new StringBuilder("中间件捕获异常信息:");
             void ReadException(Exception ex)
             {
                 error.Append($"{ex.Message} | {ex.StackTrace} | {ex.InnerException}");
                 if (ex.InnerException != null)
                 {
                     ReadException(ex.InnerException);
                 }
             }
             ReadException(ex);
             theEvent.Properties["error"] = error.ToString();
             //如果后续没有中间件处理可以使用下面的方式适用于API
             if (context != null)
             {
                 isMiddleError = true;
                 context.Response.StatusCode = 500;
                 context.Response.ContentType = "application/json";
                 ResponseMessage apiResponse = new ResponseMessage(ErrorCode.UnknownError, "服务器正忙,请稍后再试.", context);
                 await context.Response.WriteAsync(apiResponse.ToString());
             }
         }
         finally
         {
         	//防止和错误过滤器处理重复的问题
             if (isMiddleError || context?.Response?.StatusCode != 500)
             {
                 var statusCode = context?.Response?.StatusCode ?? 500;
                 theEvent.Properties["status"] = statusCode;
                 theEvent.Properties["consuming"] = (DateTime.Now - startTime).TotalMilliseconds;
                 logger.Log(theEvent);
             }
         }
     }
 }

 /// <summary>
 /// 请求日志记录中间件扩展
 /// </summary>
 public static class RequestLoggingMiddlewareExtensions
 {
     /// <summary>
     /// 使用请求日志记录中间件
     /// </summary>
     /// <param name="builder"></param>
     /// <returns></returns>
     public static IApplicationBuilder UseRequestLoggingMiddleware(this IApplicationBuilder builder)
     {
         return builder.UseMiddleware<RequestLoggingMiddleware>();
     }
 }

如果是页面错误可以使用下面的中间件展示

//需要放到最前面
app.UseExceptionHandler("/Error");
app.UseStatusCodePagesWithReExecute("/Error/{0}");

asp.net 的请求处理过程

http://www.360doc.com/content/10/0826/14/2608032_48926077.shtml

.net core中间件和过滤器

https://learn.microsoft.com/zh-cn/aspnet/core/mvc/controllers/filters?view=aspnetcore-8.0
https://www.cnblogs.com/cqpanda/p/16907950.html

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

假装我不帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值