zoukankan      html  css  js  c++  java
  • 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;
        }
    }

    推荐阅读

    Logging in ASP.NET Core

    ASP.NET Core Middleware

    Stream Class

  • 相关阅读:
    鸡兔同笼问题多解
    JavaSE复习日记 : 循环语句(for/while/do while)
    JavaSE复习日记 : 八种基本数据类型
    递归函数
    JavaSE复习日记 : 条件判断语句
    访问权限系列一(public/private/protected/default):成员变量
    JavaSE复习日记 : 算是个小前言吧
    转:SQL Server 索引和视图
    转:SQL Server 数据库基础编程
    转:在网页html/aspx中增加一段播放器代码
  • 原文地址:https://www.cnblogs.com/Cwj-XFH/p/10239746.html
Copyright © 2011-2022 走看看