以下基于.NET Core 2.1

定义GrayLog日志记录中间件:

中间件代码:

public class GrayLogMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;

//在应用程序的生命周期中,中间件的构造函数只会被调用一次
public GrayLogMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
{
_next = next;
_logger = loggerFactory.CreateLogger("GrayLog");
}

public async Task InvokeAsync(HttpContext context)
{
var additionalFields = new Dictionary<string, object>()
{
["LogId"] = Guid.NewGuid()
};

// 若将该中间件做为第一个请求管道中的第一个中间件进行注册
// 那么在此处就可以进行全局异常处理
try
{
var startTime = DateTime.Now;
await _next(context);
var endTime = DateTime.Now;
additionalFields["Elapsed"] = (endTime - startTime).Milliseconds;
_logger.LogInfo(context, additionalFields);
}
catch (Exception ex)
{
if (context.Response.HasStarted == false)
{
await WriteExceptionInfoIntoResponseAsync(context, ex);
}
_logger.LogException(context, ex, additionalFields);
#if DEBUG
throw;
#endif
}
}

private async Task WriteExceptionInfoIntoResponseAsync(HttpContext context, Exception ex)
{
try
{
var resp = new ApiResponse();
resp = resp.Exception(ex);
var respStr = JsonConvert.SerializeObject(resp);
await context.Response.WriteAsync(respStr, Encoding.UTF8);
}
catch
{
// ignore
}
}

}

日志记录代码:

public static class LogExtension
{
public static void LogInfo(this ILogger logger, HttpContext context, IDictionary<string, object> addtionalFields = null)
{
logger.LogCore(context, LogLevel.Information, addtionalFields: addtionalFields);
}

public static void LogException(this ILogger logger, HttpContext context, Exception ex, IDictionary<string, object> addtionalFields = null)
{
logger.LogCore(context, LogLevel.Error, ex, addtionalFields);
}

private static void LogCore(this ILogger logger, HttpContext context, LogLevel logLevel, Exception ex = null, IDictionary<string, object> addtionalFields = null)
{
try
{
var shortMessage = GetShortMessage(context);
if (addtionalFields == null)
{
addtionalFields = GetAddtionalFields(context);
}
else
{
var temp = GetAddtionalFields(context);
addtionalFields = addtionalFields.Union(temp).ToDictionary(d => d.Key, d => d.Value);
}

// 需要使用Scope才能将additionalFields记录到GrayLog中
using (logger.BeginScope(addtionalFields))
{
logger.Log(logLevel, exception: ex, message: shortMessage);
}
}
catch
{
#if DEBUG
throw;
#endif
// ignore
}
}

/// <summary>
/// 获取请求的短消息
/// <para>
/// 消息格式:HttpMethod RequestUrl HttpStatusCode
/// </para>
/// </summary>
/// <example> GET http://localhost:5000 200</example>
private static string GetShortMessage(HttpContext context)
{
var request = context.Request;

var method = request.Method;
var url = request.GetEncodedUrl();
var statusCode = context.Response.StatusCode;

return $"{method} {url} {statusCode}";
}

/// <summary>
/// 需要写入到日志中的额外字段:请求来源,请求参数
/// </summary>
private static IDictionary<string, object> GetAddtionalFields(HttpContext context)
{
var referer = context.Connection.RemoteIpAddress;
var requestData = GetRequestParameters(context);

return new Dictionary<string, object>()
{
["Referer"] = referer,
["RequestData"] = requestData
};
}

private static string GetRequestParameters(HttpContext context)
{
if (context.Request.ContentLength > )
{
var stream = context.Request.Body;
if (stream.CanRead == false)
{
return null;
}
if (stream.CanSeek == false)
{
// 将HttpRequestStream转换为FileBufferingReadStream
context.Request.EnableBuffering();
stream = context.Request.Body;
}
stream.Position = ;

using (var reader = new StreamReader(stream))
{
var data = reader.ReadToEnd();
return data;
}
}

return null;
}

}
 

Graylog日志配置:

  
 public class Program
{
public static void Main(string[] args) => CreateWebHost().Run();

private static IWebHost CreateWebHost() => CreateWebHostBuilder().Build();

// 这里未使用.NET Core封装的CreateDefaultBuilder方法,因为它封装了过多不需要的东西
private static IWebHostBuilder CreateWebHostBuilder() =>
new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
#if RELEASE
.UseIISIntegration()
#endif
.UseKestrel()
.ConfigureLogging((context, builder) =>
{
ConfigLogger(context, builder);
})
.UseStartup<Startup>();

private static void ConfigLogger(WebHostBuilderContext context, ILoggingBuilder builder)
{
// 使用日志过滤器(log filtering),禁止Kestrel记录访问日志
builder.ClearProviders();
builder.AddFilter("Microsoft", LogLevel.None);
builder.AddFilter("System", LogLevel.Error);

if (context.HostingEnvironment.IsDevelopment())
{
builder.AddDebug();
}

// GrayLog配置(这里使用App.config作为配置文件
builder.AddGelf(option =>
{
option.Host = ConfigurationManager.AppSettings["grayLogHost"];
option.Port = Convert.ToInt32(ConfigurationManager.AppSettings["grayLogPort"]);
option.LogSource = ConfigurationManager.AppSettings["grayLogSource"];
option.Protocol = GelfProtocol.Udp;
});
}
}

注册中间件到请求处理管道:

public static class GrayLogMiddlewareExtension
{
/// <summary>
/// 向请求管道中添加GrayLog记录功能及全局异常处理
/// </summary>
public static IApplicationBuilder UseGrayLog(this IApplicationBuilder builder) =>
builder.UseMiddleware<GrayLogMiddleware>();
}

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseGrayLog()
.UseMvc();
}
}

以上日志记录了如下几个方面:

  1. 日志信息Id

  2. 请求来源

  3. 请求基础信息

    采用类似HTTP请求行格式,即:HttpMethod RequestUrl ResponseStatusCode,如:GET http://localhost 200

  4. 入参

  5. 接口耗时

  6. 若发生异常,则记录异常信息

HttpRequestStream vs FileBufferingReadStream

GET请求参数都体现在Url中了,这里讲述如何获取POST请求的参数。

通常POST请求数据都在请求体中,ASP.NET Core中HttpRequest类型的Body属性是HttpRequestStream类型,该类型源码在Github上可以看到,但在Google和微软关方文档中都没搜索到。反编译Microsoft.AspNetCore.Server.Kestrel.Core.dll只找到了同样继承自ReadOnlyStreamFrameRequestStream

HttpRequestStream类的CanSeek属性返回值为false,不支持多次读取,所以需要先转换为FileBufferingReadStream。转换过程可参考:BufferingHelperHttpRequestRewindExtensions。实现代码如下:

public static class HttpRequestRewindExtensions
{
public static void EnableBuffering(this HttpRequest request, int bufferThreshold, long bufferLimit)
{
BufferingHelper.EnableRewind(request, bufferThreshold, bufferLimit);
}
}


public static class BufferingHelper
{
public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}

var body = request.Body;
if (!body.CanSeek)
{
var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, _getTempDirectory);
request.Body = fileStream;
request.HttpContext.Response.RegisterForDispose(fileStream);
}
return request;
}
}

推荐阅读

Logging in ASP.NET Core

ASP.NET Core Middleware

Stream Class

ASP.NET Core中使用Graylog记录日志的更多相关文章

  1. ASP.NET Core 中使用 GrayLog 记录日志

    使用 UDP 协议发送日志 自定义好的查询 key 存储数据,尽量不要使用 graylog2-server 服务端格式化日志再存储 Ubuntu 安装服务端 sudo apt-get update & ...

  2. ASP.NET Core:ASP.NET Core中使用NLog记录日志

    一.前言 在所有的应用程序中,日志功能是不可或缺的模块,我们可以根据日志信息进行调试.查看产生的错误信息,在ASP.NET Core中我们可以使用log4net或者NLog日志组件来实现记录日志的功能 ...

  3. Asp.Net Core中使用NLog记录日志

    2019/10/28, Asp.Net Core 3.0, NLog 4.6.7, NLog.Web.AspNetCore 4.9.0 摘要:NLog在asp.net网站中的使用,NLog日志写入数据 ...

  4. Asp.Net Core中利用Seq组件展示结构化日志功能

    在一次.Net Core小项目的开发中,掌握的不够深入,对日志记录并没有好好利用,以至于一出现异常问题,都得跑动服务器上查看,那时一度怀疑自己肯定没学好,不然这一块日志不可能需要自己扒服务器日志来查看 ...

  5. 在Asp.Net Core中集成Kafka(中)

    在上一篇中我们主要介绍如何在Asp.Net Core中同步Kafka消息,通过上一篇的操作我们发现上面一篇中介绍的只能够进行简单的首发kafka消息并不能够消息重发.重复消费.乐观锁冲突等问题,这些问 ...

  6. 如何在 ASP.Net Core 中使用 Serilog

    记录日志的一个作用就是方便对应用程序进行跟踪和排错调查,在实际应用上都是引入 日志框架,但如果你的 日志文件 包含非结构化的数据,那么查询起来将是一个噩梦,所以需要在记录日志的时候采用结构化方式. 将 ...

  7. ASP.NET Core 中的那些认证中间件及一些重要知识点

    前言 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系列(一,二,三)奠定一下基础. 有关于 Authentication 的知识太广,所以本篇介绍几个在 A ...

  8. Asp.net Core中使用Session

    前言 2017年就这么悄无声息的开始了,2017年对我来说又是特别重要的一年. 元旦放假在家写了个Asp.net Core验证码登录, 做demo的过程中遇到两个小问题,第一是在Asp.net Cor ...

  9. 在ASP.NET Core中使用百度在线编辑器UEditor

    在ASP.NET Core中使用百度在线编辑器UEditor 0x00 起因 最近需要一个在线编辑器,之前听人说过百度的UEditor不错,去官网下了一个.不过服务端只有ASP.NET版的,如果是为了 ...

随机推荐

  1. Maven 在新版eclipse报错的解决

    转自Stack Overflow Remove all your failed downloads: Linux: find ~/.m2 -name "*.lastUpdated" ...

  2. [转]Linux操作系统tcpdump抓包分析详解

    PS:tcpdump是一个用于截取网络分组,并输出分组内容的工具,简单说就是数据包抓包工具.tcpdump凭借强大的功能和灵活的截取策略,使其成为Linux系统下用于网络分析和问题排查的首选工具. t ...

  3. Akka-Cluster(5)- load-balancing with backoff-supervised stateless computation - 无状态任务集群节点均衡分配

    分布式程序运算是一种水平扩展(scale-out)运算模式,其核心思想是能够充分利用服务器集群中每个服务器节点的计算资源,包括:CPU.内存.硬盘.IO总线等.首先对计算任务进行分割,然后把细分的任务 ...

  4. Retrofit官方文档翻译

    Retrofit官方文档翻译 官方文档网址 http://square.github.io/retrofit/ 介绍 Retrofit 将你的 HTTP API 转换为 Java 接口. public ...

  5. Docker学习笔记-CentOS7镜像

    前言: 环境:centos7.5 64 位 正文: 第一步:下载centos7镜像 docker pull centos 第二步:建立centos7的容器 sudo docker run --priv ...

  6. 全栈开发工程师微信小程序-中(中)

    全栈开发工程师微信小程序-中(中) 开放能力 open-data 用于展示微信开放的数据 type 开放数据类型 open-gid 当 type="groupName" 时生效, ...

  7. QMQ去哪儿网-mq中间件(启动失败)

    简介 去哪儿网近日宣布开源其内部广泛使用的消息中间件 QMQ .QMQ 自 2012 年诞生以来在去哪儿网所有业务场景中广泛的应用,包括跟交易息息相关的订单场景: 也包括报价搜索等高吞吐量场景.目前在 ...

  8. AI - 参考消息(References)

    01 - Machine learning infographic 图片解读机器学习的基本概念.五大流派与九种常见算法 EN:http://usblogs.pwc.com/emerging-techn ...

  9. .NET Core SDK在Windows系统安装后出现Failed to load the hostfxr.dll等问题的解决方法

    这次无论如何也要记录下,原因是今天在一台Windows2008R2的电脑上安装.NET Core SDK后再命令行执行dotnet --info 居然爆出了"Failed to load t ...

  10. sql server 性能调优之 资源等待 LCk

    一.  概述 这次介绍实例级别资源等待LCK类型锁的等待时间,关于LCK锁的介绍可参考 “sql server 锁与事务拨云见日”.下面还是使用sys.dm_os_wait_stats 来查看,并找出 ...