ASP.NET Core中使用Graylog記錄日誌

如下基於.NET Core 2.1html

定義GrayLog日誌記錄中間件:

中間件代碼:git

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(); } }

 

以上日誌記錄了以下幾個方面:github

  1. 日誌信息Idweb

  2. 請求來源api

  3. 請求基礎信息app

    採用相似HTTP請求行格式,即:HttpMethod RequestUrl ResponseStatusCode,如:GET http://localhost 200async

  4. 入參ide

  5. 接口耗時函數

  6. 若發生異常,則記錄異常信息ui

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

相關文章
相關標籤/搜索