net core日志與異常處理小結(jié)
ILogger簡單使用
asp.net core
的webApplicationBuilder
中自帶了一個(gè)日志組件。無需手動注冊服務(wù)就能直接在控制器中構(gòu)造注入。
public HomeController(ILogger<HomeController> logger) { _logger = logger; }
_logger.LogTrace("trace{path}", HttpContext.Request.Path); _logger.LogDebug("debug{path}", HttpContext.Request.Path); _logger.LogInformation("info{path}", HttpContext.Request.Path); _logger.LogWarning("warn{path}", HttpContext.Request.Path); _logger.LogError("error{path}", HttpContext.Request.Path); _logger.LogCritical("critical{path}", HttpContext.Request.Path); _logger.Log(LogLevel.Information, "Log{path}", HttpContext.Request.Path);
打印效果如下
在一個(gè)日志輸出中,日志的第一行輸出是日志類別,來自泛型參數(shù)<HomeController>的完全限定類名。第二行是模板字符串輸出,可以看到從info級別開始才有輸出,這是因?yàn)樵赼ppsetting文件中配置了LogLevel為Information,過濾掉了該等級之下的輸出。
MSDN推薦在方法中多次詳細(xì)的打印日志,但打印方法使用Trace或Information。開發(fā)時(shí)將日志等級設(shè)置為Warn,避免過多信息顯示。在排查故障時(shí)才將日志等級設(shè)置為Trace
打印異常信息
try { throw new Exception("自定義異常"); } catch (Exception e) { _logger.LogTrace("trace{path}", e); _logger.LogDebug("debug{path}", e); _logger.LogInformation("info{path}", e); _logger.LogWarning("warn{path}", e); _logger.LogError("error{path}", e); _logger.LogCritical("critical{path}", e); _logger.Log(LogLevel.Information, "Log{path}", e.ToString()); }
打印效果如下
日志的第一行是泛型參數(shù),第二行開始是字符串模板。其中有參數(shù)e的異常類型,異常信息e.Message
。第三行開始時(shí)異常的調(diào)用堆棧e.StackTrace
最后一個(gè)打印中調(diào)用了e.ToString()
,結(jié)果是一樣的。由此可見日志打印時(shí),碰到了非字符串參數(shù),就會調(diào)用其ToString()方法。那么我們可以通過重寫這個(gè)方法來改變?nèi)罩据敵鰡幔?/p>
重寫ToString
public class Person { public int Id { get; set; } public string Name { get; set; } public int Age { get; set; } public override string ToString() { return JsonSerializer.Serialize(this); } } _logger.LogError("{path}", new Person() { Id=1,Name="張三",Age=19}); _logger.LogCritical("{path}", new Person() { Id = 2, Name = "王浩", Age = 20 });
這里我在重寫中調(diào)用了序列化方法,打印結(jié)果是一個(gè)json數(shù)據(jù)
日志配置方式
配置日志文件介質(zhì),有三種方法,都是ILoggingBilder
調(diào)用AddConsole
。他們差不多,但有微小差別。第一種是配置容器中的日志服務(wù),后面兩種是配置服務(wù)主機(jī)上的日志。
主機(jī)生成器會初始化默認(rèn)配置,然后在生成主機(jī)時(shí)將已配置的 ILoggerFactory 對象添加到主機(jī)的 DI 容器
所以效果是一樣的
//第一種,配置應(yīng)用程序級別的日志記錄器 builder.Services.AddLogging(configure => { configure.AddConsole(); }); //第二種,配置應(yīng)用程序主機(jī)級別的日志記錄器。和第三種一樣,但不推薦使用這個(gè)。 builder.Host.ConfigureLogging(configure => { configure.AddConsole(); }); //第三種,配置應(yīng)用程序主機(jī)級別的日志記錄器 builder.Logging.AddConsole();
Log等級
在asp.net core的日志默認(rèn)擴(kuò)展中,定義LogTrace
LogDebug
LogInformation
LogWarning
LogError
LogCritical
Log
7種常用方法。對應(yīng)的日志有7個(gè)級別
// 定義日志嚴(yán)重性級別。 public enum LogLevel { //包含最詳細(xì)消息的日志。這些消息可能包含敏感的應(yīng)用程序數(shù)據(jù) //默認(rèn)情況下禁用這些消息,生產(chǎn)環(huán)境中永遠(yuǎn)不應(yīng)啟用 Trace = 0, //用于開發(fā)過程中的交互式調(diào)查。這些日志主要包含調(diào)試有用的信息,沒有長期價(jià)值 Debug = 1, //跟蹤應(yīng)用程序的一般流程。這些日志應(yīng)具有長期價(jià)值 Information = 2, //強(qiáng)調(diào)應(yīng)用程序流程中的異?;蛞馔馐录?,但不會導(dǎo)致應(yīng)用程序執(zhí)行停止 Warning = 3, //強(qiáng)調(diào)由于失敗導(dǎo)致當(dāng)前執(zhí)行流程停止的日志。這些日志應(yīng)指示當(dāng)前活動中的失敗,而不是應(yīng)用程序范圍的失敗 Error = 4, //描述不可恢復(fù)的應(yīng)用程序或系統(tǒng)崩潰,或者需要立即關(guān)注的災(zāi)難性失敗 Critical = 5, //不用于編寫日志消息。指定日志類別不應(yīng)寫入任何消息 None = 6, }
異常
在開發(fā)環(huán)境,builder默認(rèn)會為應(yīng)用配置這個(gè)異常中間件app.UseDeveloperExceptionPage()
,這個(gè)配置是寫在源碼中的,所以不需要我們顯式配置。如果我們不想顯示這個(gè)頁面,那可以配置中間件UseExceptionHandler
。這個(gè)中間件要配置在UseDeveloperExceptionPage后面,因?yàn)橹虚g件調(diào)用是遞歸的,異常發(fā)生在終結(jié)點(diǎn)中間件中,之后就是請求處理管道遞歸跳出階段,所以異常捕獲也在這個(gè)階段。調(diào)用順序和配置順序是相反的。
或者根據(jù)環(huán)境app.Environment.IsDevelopment()
來判斷使用是否使用UseExceptionHandler。至于UseDeveloperExceptionPage則不用擔(dān)心,只要不是顯示配置,開發(fā)環(huán)境中不會進(jìn)入這個(gè)中間件。不知道是由于條件編譯,還是做了環(huán)境判斷。
還還有一種方法是在這兩個(gè)中間件后面自定義中間件捕獲異常,短路管道。
我注意到,不管使用UseDeveloperExceptionPage還是UseExceptionHandler中間件,控制臺中都會出現(xiàn)異常日志,這是為什么?這是因?yàn)檫@兩個(gè)中間件中都調(diào)用了日志打印。所以我們可以自己實(shí)現(xiàn)一個(gè)異常處理中間件,這樣就不會打印日志了。
app.UseDeveloperExceptionPage(); app.UseExceptionHandler("/Home/Error"); app.Use(async (context, next) => { try { await next(context); } catch (Exception e) { await context.Response.WriteAsync("Custom Exception MiddleWare"); } });
碰到異常打印日志是正常操作,所以這不需要我們?nèi)リP(guān)心。但是,在前面的截圖中,UseExceptionHandler中間件的異常打印的問題在于,沒有打印請求路徑、請求參數(shù)等信息。我們只知道哪里出錯(cuò)了,卻不知道是什么原因引起的,怎么復(fù)現(xiàn),這是很不完善的。不知道當(dāng)初為什么這樣設(shè)計(jì)。所以最好是自己重新寫個(gè)異常處理中間件,設(shè)置響應(yīng)內(nèi)容以及控制日志打印。
自定義異常中間件打印請求和異常日志
public class ExceptionMiddleware { private readonly RequestDelegate next; public ExceptionMiddleware(RequestDelegate next) { this.next = next; } public async Task InvokeAsync(HttpContext context) { try { await next(context); } catch (Exception e) { var _logger=context.RequestServices.GetService<ILogger<ExceptionMiddleware>>(); var bodystring=""; if (context.Request.ContentType==null || !context.Request.ContentType.Contains("multipart/form-data")) { using (StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8)) { bodystring = await reader.ReadToEndAsync(); } } else { //不包含文件實(shí)際內(nèi)容 foreach (var formField in context.Request.Form) { bodystring += formField.Key + " " + formField.Value+","; } } _logger.LogError("堆棧:{e}\n\t路徑:{c}\n\t查詢字符串:{p}\n\t內(nèi)容:{f}", e,context.Request.Path,context.Request.QueryString,bodystring); await context.Response.WriteAsync("Custom Exception MiddleWare"); } } }
使用Serilog輸出日志到文件介質(zhì)
由于asp.net core自己沒有提供文件介質(zhì)的提供程序,所以我轉(zhuǎn)到使用Serilog
。僅僅注冊了一個(gè)服務(wù),其他的一切不變。
引入包Serilog.AspNetCore
。并不需要引入Serilog.Sinks.File
,因?yàn)镾erilog.AspNetCore已經(jīng)引入了文件介質(zhì)的提供程序
日志管線配置Serilog提供程序
日志系統(tǒng)包括如下幾個(gè)概念:日志管線、提供程序、介質(zhì)。嚴(yán)格來說介質(zhì)不算。
日志系統(tǒng)又叫日志管線logging pipeline
。日志管線中的每個(gè)提供程序都會處理一次日志,并輸出到自己的介質(zhì)中。
- 配置serilog子管線
Log.Logger = new LoggerConfiguration() //最小輸出等級 .MinimumLevel.Warning() //增加介質(zhì) .WriteTo.Console() .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day) .CreateLogger(); //注冊serilog服務(wù) builder.Services.AddSerilog();
- 配置完整的日志管線這里默認(rèn)的控制臺介質(zhì)得到了兩個(gè)提供程序,一個(gè)是默認(rèn)的AddConsole,一個(gè)是Serilog的WriteTo.Console。按照管線中配置的順序,這兩個(gè)提供程序都會向控制臺打印一次。而Serilog還向管線中添加了一個(gè)到文件介質(zhì)的提供程序,所以還會產(chǎn)生一個(gè)日志文件。
builder.Services.AddLogging(logbuilder => { logbuilder //刪除管線上默認(rèn)的提供程序 .ClearProviders() //serilog子管線上的提供程序合并進(jìn)來 .AddSerilog() .AddConsole(); });
- 控制臺介質(zhì)兩個(gè)提供程序均處理了日志打印
- 文件介質(zhì)
輸出模板
回想默認(rèn)日志提供程序和serilog日志提供程序,都會在我們的消息之外附加信息,比如等級、類名。這是怎么配置的?
- 默認(rèn)日志提供程序的格式是
[LogLevel]: [EventId] [Category] Message
??梢酝ㄟ^繼承ConsoleFormatter
改寫模板。但是很麻煩。而且顏色,字體還不知道怎么控制。
public class CustomConsoleFormatter : ConsoleFormatter { private readonly string _formatTemplate; public CustomConsoleFormatter(IOptions<TemplatedConsoleFormatterOptions> options) : base(name: "Custom") { _formatTemplate = options.Value.Template ?? "[{Timestamp:HH:mm:ss} {Level}] [{EventId}] {Message}{NewLine}{Exception}"; } public override void Write<TState>(in LogEntry<TState> logEntry, IExternalScopeProvider scopeProvider, TextWriter textWriter) { var timestamp = DateTime.UtcNow; var level = logEntry.LogLevel.ToString(); var eventId = logEntry.EventId.Id; var message = logEntry.Formatter(logEntry.State, logEntry.Exception); var exception = logEntry.Exception != null ? logEntry.Exception.ToString() : ""; var newLine = Environment.NewLine; var logOutput = ReplaceTemplateValues(_formatTemplate, timestamp, level, eventId, message, exception, newLine); textWriter.Write(logOutput); } private string ReplaceTemplateValues(string template, DateTime timestamp, string level, int eventId, string message, string exception, string newLine) { return Regex.Replace(template, @"{(\w+)(?::([^}]+))?}", match => { var key = match.Groups[1].Value; var format = match.Groups[2].Value; switch (key) { case "Timestamp": return timestamp.ToString(format); case "Level": return level; case "EventId": return eventId.ToString(); case "Message": return message; case "NewLine": return newLine; case "Exception": return exception; default: return match.Value; } }); } } public class TemplatedConsoleFormatterOptions : ConsoleFormatterOptions { public string? Template { get; set; } } //使用模板 builder.Services.AddLogging(logbuilder => { logbuilder .ClearProviders() //.AddSerilog() .AddConsole(option => { //使用名為Custom的模板配置 option.FormatterName = "Custom"; }) //添加一個(gè)控制臺模板配置 .AddConsoleFormatter<CustomConsoleFormatter, TemplatedConsoleFormatterOptions>(foption => { foption.Template = "[{Timestamp:HH:mm:ss} {Level}] [{EventId}] {Message:lj}{NewLine}{Exception}"; }); });
- Serilog輸出模板相比之下Serilog就簡單得多,直接指定模板就行
Log.Logger = new LoggerConfiguration() //最小輸出等級 .MinimumLevel.Warning() //增加介質(zhì) .WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") .CreateLogger();
Serilog還能向模板中增加字段,使用Enrichers
Log.Logger = new LoggerConfiguraition() .Enrich.FromLogContext() .Enrich.WithProperty("Application", "Demo") .WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{Properties:j}{NewLine}{Exception}") // 設(shè)置 ThreadContext 或 CallContext 的屬性 ThreadContext.Properties["UserId"] = 42; CallContext.LogicalSetData("UserName", "John Doe"); Log.Information("User {UserId} logged in as {UserName}", ThreadContext.Properties["UserId"], CallContext.LogicalGetData("UserName"));
到此這篇關(guān)于net core日志與異常的文章就介紹到這了,更多相關(guān)netcore日志內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net使用FCK編輯器中的分頁符實(shí)現(xiàn)長文章分頁功能
這篇文章主要介紹了asp.net使用FCK編輯器中的分頁符實(shí)現(xiàn)長文章分頁功能,涉及asp.net字符串及分頁操作的相關(guān)技巧,需要的朋友可以參考下2016-06-06Asp.Net平臺下的圖片在線裁剪功能的實(shí)現(xiàn)代碼(源碼打包)
最近項(xiàng)目中有個(gè)圖片在線裁剪功能,本人查找資料,方法如下:前臺展現(xiàn)用jquery.Jcrop實(shí)現(xiàn),后臺使用 System.Drawing.Image類來進(jìn)行裁剪2011-10-10Ajax實(shí)現(xiàn)異步刷新驗(yàn)證用戶名是否已存在的具體方法
由于要做一個(gè)注冊頁面,看到許多網(wǎng)站上都是使用Ajax異步刷新驗(yàn)證用戶名是否可用的,所以自己也動手做一個(gè)小實(shí)例2014-02-02用類的繼承關(guān)系(重寫父類的方法)實(shí)現(xiàn)簡易后臺代碼模板
Asp.net的優(yōu)勢就在于快速構(gòu)建應(yīng)用,而對于一些最基礎(chǔ)數(shù)據(jù)的增刪改以及分頁事件或者樣式的設(shè)定可以通過在父類中寫上虛方法來供子類調(diào)用,接下來將為您測試一下用子類重寫父類的方法實(shí)現(xiàn)在模板的基礎(chǔ)上衍生變化2013-01-01微信公眾平臺開發(fā)之認(rèn)證"成為開發(fā)者".Net代碼解析
這篇文章主要為大家詳細(xì)解析了微信公眾平臺開發(fā)之認(rèn)證"成為開發(fā)者".Net代碼,感興趣的小伙伴們可以參考一下2016-06-06如何在ASP.NET Core中使用HttpClientFactory
這篇文章主要介紹了如何在ASP.NET Core中使用HttpClientFactory,幫助大家更好的理解和學(xué)習(xí)使用.net技術(shù),感興趣的朋友可以了解下2021-04-04asp.net訪問網(wǎng)絡(luò)路徑方法(模擬用戶登錄)
這篇文章主要介紹了asp.net訪問網(wǎng)絡(luò)路徑方法,其實(shí)就是模擬用戶登錄,需要的朋友可以參考下2014-08-08