手记

ASP.NET Core中使用Graylog记录日志

以下基于.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 > 0)
        {            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 = 0;            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;
    }
}

原文出处:https://www.cnblogs.com/Cwj-XFH/p/10239746.html  

0人推荐
随时随地看视频
慕课网APP