问题起因
网站被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