ASP.NET Core Logging Solution

.NET Core Logging

This package makes it a one-liner - loggerFactory.AddFile() - to configure top-quality file logging for ASP.NET Core apps.

  • Text or JSON file output
  • Files roll over on date; capped file size
  • Request ids and event ids included with each message
  • Writes are performed on a background thread
  • Files are periodically flushed to disk (required for Azure App Service log collection)
  • Fast, stable, battle-proven logging code courtesy of Serilog

You can get started quickly with this package, and later migrate to the full Serilog API if you need more sophisticated log file configuration.

Getting started

1. Add the NuGet package as a dependency of your project either with the package manager or directly to the CSPROJ file:

<PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0" />

2. In your Program class, configure logging on the web host builder, and call AddFile() on the provided loggingBuilder.

 Host.CreateDefaultBuilder(args)
        .ConfigureLogging((hostingContext, loggingBuilder) =>
        {
            // loggingBuilder.AddFile("Logs/Logs_{Date}.txt");
            loggingBuilder.AddFile(hostingContext.Configuration.GetSection("Logging"));
        })
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

3. Add a custom excetion filter as a global exception handler:

    public class GlobalExceptionFilter : IExceptionFilter
    {
        private readonly ILogger<GlobalExceptionFilter> _logger;

        public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
        {
            _logger = logger;
        }

        public void OnException(ExceptionContext context)
        {
            var loggingBuilder = new StringBuilder();

            if (context.HttpContext.Request.GetDisplayUrl() != null)
                loggingBuilder.AppendLine($"\tUrl: {context.HttpContext.Request.GetDisplayUrl()}");

            loggingBuilder.AppendLine($"\tIp: {context.HttpContext.Connection.RemoteIpAddress}");

#if DEBUG
            foreach (var key in context.HttpContext.Request.Headers.Keys)
            {
                loggingBuilder.AppendLine($"\t{key}: {context.HttpContext.Request.Headers[key]}");
            }
#endif

            loggingBuilder.AppendLine($"\tError Message: {context.Exception.Message}");
            if (context.Exception.InnerException != null)
            {
                PrintInnerException(context.Exception.InnerException, loggingBuilder);
            }

            loggingBuilder.AppendLine($"\tError HelpLink: {context.Exception.HelpLink}");
            loggingBuilder.AppendLine($"\tError StackTrace: {context.Exception.StackTrace}");

            _logger.LogError(loggingBuilder.ToString());
        }

        public void PrintInnerException(Exception ex, StringBuilder loggingBuilder)
        {
            loggingBuilder.AppendLine($"\tError InnerMessage: {ex.Message}");
            if (ex.InnerException != null)
            {
                PrintInnerException(ex.InnerException, loggingBuilder);
            }
        }
    }

4. In your Startup class, configure the global exception handler on the ConfigureServices method, so we can catch all unhandled exceptions:

public void ConfigureServices(IServiceCollection services)
{
    ...

    services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalExceptionFilter>();
    });
  
    ...
}
public IActionResult Privacy()
{
    throw new Exception("Unhandled exception");
    return View();
}

5. In your Startup class, add the log directory as static on the Configure method, so we can view the log directory:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    ...

    app.UseStaticFiles();

    //add the log directory as static, so we can view the log directory
    app.UseFileServer(new FileServerOptions()
    {
        FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), @"Logs")),
        RequestPath = new PathString("/Log"),
        EnableDirectoryBrowsing = true
    });

    app.UseRouting();

    ...
}

Done! The framework will inject ILogger instances into controllers and other classes:

class HomeController : Controller
{
    readonly ILogger<HomeController> _log;

    public HomeController(ILogger<HomeController> log)
    {
        _log = log;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Hello, world!");
        _logger.LogError(new Exception("Custom exception"), "Custom exception");
    }
}

The events will appear in the log file:

2016-10-18T11:14:11.0881912+10:00 0HKVMUG8EMJO9 [INF] Hello, world! (f83bcf75)

File format

By default, the file will be written in plain text. The fields in the log file are:

Field Description Format Example
Timestamp The time the event occurred. ISO-8601 with offset 2016-10-18T11:14:11.0881912+10:00
Request id Uniquely identifies all messages raised during a single web request. Alphanumeric 0HKVMUG8EMJO9
Level The log level assigned to the event. Three-character code in brackets [INF]
Message The log message associated with the event. Free text Hello, world!
Event id Identifies messages generated from the same format string/message template. 32-bit hexadecimal, in parentheses (f83bcf75)
Exception Exception associated with the event. Exception.ToString() format (not shown) System.DivideByZeroException: Attempt to divide by zero\r\n\ at...

To record events in newline-separated JSON instead, specify isJson: true when configuring the logger:

loggingBuilder.AddFile("Logs/myapp-{Date}.txt", isJson: true);

This will produce a log file with lines like:

{"@t":"2016-06-07T03:44:57.8532799Z","@m":"Hello, world!","@i":"f83bcf75","RequestId":"0HKVMUG8EMJO9"}

The JSON document includes all properties associated with the event, not just those present in the message. This makes JSON formatted logs a better choice for offline analysis in many cases.

Rolling

The filename provided to AddFile() should include the {Date} placeholder, which will be replaced with the date of the events contained in the file. Filenames use the yyyyMMdd date format so that files can be ordered using a lexicographic sort:

log-20160631.txt
log-20160701.txt
log-20160702.txt

To prevent outages due to disk space exhaustion, each file is capped to 1 GB in size. If the file size is exceeded, events will be dropped until the next roll point.

Message templates and event ids

The provider supports the templated log messages used by Microsoft.Extensions.Logging. By writing events with format strings or message templates, the provider can infer which messages came from the same logging statement.

This means that although the text of two messages may be different, their event id fields will match, as shown by the two "view" logging statements below:

2016-10-18T11:14:26.2544709+10:00 0HKVMUG8EMJO9 [INF] Running view at "/Views/Home/About.cshtml". (9707eebe)
2016-10-18T11:14:11.0881912+10:00 0HKVMUG8EMJO9 [INF] Hello, world! (f83bcf75)
2016-10-18T11:14:26.2544709+10:00 0HKVMUG8EMJO9 [INF] Running view at "/Views/Home/Index.cshtml". (9707eebe)

Each log message describing view rendering is tagged with (9707eebe), while the "hello" log message is given (f83bcf75). This makes it easy to search the log for messages describing the same kind of event.

Additional configuration

The AddFile() method exposes some basic options for controlling the connection and log volume.

Parameter Description Example value
pathFormat Filename to write. The filename may include {Date} to specify how the date portion of the filename is calculated. May include environment variables. Logs/log-{Date}.txt
minimumLevel The level below which events will be suppressed (the default is LogLevel.Information). LogLevel.Debug
levelOverrides A dictionary mapping logger name prefixes to minimum logging levels.
isJson If true, the log file will be written in JSON format. true
fileSizeLimitBytes The maximum size, in bytes, to which any single log file will be allowed to grow. For unrestricted growth, passnull. The default is 1 GiB. 1024 * 1024 * 1024
retainedFileCountLimit The maximum number of log files that will be retained, including the current log file. For unlimited retention, pass null. The default is 31. 31
outputTemplate The template used for formatting plain text log output. The default is {Timestamp:o} {RequestId,13} [{Level:u3}] {Message} ({EventId:x8}){NewLine}{Exception} {Timestamp:o} {RequestId,13} [{Level:u3}] {Message} {Properties:j} ({EventId:x8}){NewLine}{Exception}

appsettings.json configuration

The file path and other settings can be read from JSON configuration if desired.

In appsettings.json add a "Logging" property:

{
  "Logging": {
    "PathFormat": "Logs/log-{Date}.txt",
    "LogLevel": {
      "Default": "Debug",
      "Microsoft": "Information"
    }
  }
}

And then pass the configuration section to the AddFile() method:

loggingBuilder.AddFile(Configuration.GetSection("Logging"));

In addition to the properties shown above, the "Logging" configuration supports:

Property Description Example
Json If true, the log file will be written in JSON format. true
FileSizeLimitBytes The maximum size, in bytes, to which any single log file will be allowed to grow. For unrestricted growth, passnull. The default is 1 GiB. 1024 * 1024 * 1024
RetainedFileCountLimit The maximum number of log files that will be retained, including the current log file. For unlimited retention, pass null. The default is 31. 31
OutputTemplate The template used for formatting plain text log output. The default is {Timestamp:o} {RequestId,13} [{Level:u3}] {Message} ({EventId:x8}){NewLine}{Exception} {Timestamp:o} {RequestId,13} [{Level:u3}] {Message} {Properties:j} ({EventId:x8}){NewLine}{Exception}

Using the full Serilog API

This package is opinionated, providing the most common/recommended options supported by Serilog. For more sophisticated configuration, using Serilog directly is recommened. See the instructions in Serilog.AspNetCore to get started.

The following packages are used to provide AddFile():

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侦啸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鸭你,更是在濱河造成了極大的恐慌屈雄,老刑警劉巖慢味,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膘掰,死亡現(xiàn)場離奇詭異,居然都是意外死亡陕壹,警方通過查閱死者的電腦和手機应狱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門共郭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人疾呻,你說我怎么就攤上這事除嘹。” “怎么了岸蜗?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵尉咕,是天一觀的道長。 經(jīng)常有香客問我璃岳,道長年缎,這世上最難降的妖魔是什么悔捶? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮单芜,結(jié)果婚禮上蜕该,老公的妹妹穿的比我還像新娘。我一直安慰自己洲鸠,他們只是感情好堂淡,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扒腕,像睡著了一般绢淀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘾腰,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天皆的,我揣著相機與錄音,去河邊找鬼蹋盆。 笑死费薄,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的怪嫌。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼柳沙,長吁一口氣:“原來是場噩夢啊……” “哼岩灭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赂鲤,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤噪径,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后数初,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體找爱,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年泡孩,在試婚紗的時候發(fā)現(xiàn)自己被綠了车摄。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡仑鸥,死狀恐怖吮播,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眼俊,我是刑警寧澤意狠,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站疮胖,受9級特大地震影響环戈,放射性物質(zhì)發(fā)生泄漏闷板。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一院塞、第九天 我趴在偏房一處隱蔽的房頂上張望遮晚。 院中可真熱鬧,春花似錦迫悠、人聲如沸鹏漆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽艺玲。三九已至,卻和暖如春鞠抑,著一層夾襖步出監(jiān)牢的瞬間饭聚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工搁拙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秒梳,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓箕速,卻偏偏與公主長得像酪碘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盐茎,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353