net core日志與異常處理小結(jié)
ILogger簡單使用
asp.net core的webApplicationBuilder中自帶了一個日志組件。無需手動注冊服務(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);打印效果如下

在一個日志輸出中,日志的第一行輸出是日志類別,來自泛型參數(shù)<HomeController>的完全限定類名。第二行是模板字符串輸出,可以看到從info級別開始才有輸出,這是因為在appsetting文件中配置了LogLevel為Information,過濾掉了該等級之下的輸出。
MSDN推薦在方法中多次詳細的打印日志,但打印方法使用Trace或Information。開發(fā)時將日志等級設(shè)置為Warn,避免過多信息顯示。在排查故障時才將日志等級設(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。第三行開始時異常的調(diào)用堆棧e.StackTrace最后一個打印中調(diào)用了e.ToString(),結(jié)果是一樣的。由此可見日志打印時,碰到了非字符串參數(shù),就會調(diào)用其ToString()方法。那么我們可以通過重寫這個方法來改變?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é)果是一個json數(shù)據(jù)

日志配置方式
配置日志文件介質(zhì),有三種方法,都是ILoggingBilder調(diào)用AddConsole。他們差不多,但有微小差別。第一種是配置容器中的日志服務(wù),后面兩種是配置服務(wù)主機上的日志。
主機生成器會初始化默認配置,然后在生成主機時將已配置的 ILoggerFactory 對象添加到主機的 DI 容器
所以效果是一樣的
//第一種,配置應(yīng)用程序級別的日志記錄器
builder.Services.AddLogging(configure =>
{
configure.AddConsole();
});
//第二種,配置應(yīng)用程序主機級別的日志記錄器。和第三種一樣,但不推薦使用這個。
builder.Host.ConfigureLogging(configure =>
{
configure.AddConsole();
});
//第三種,配置應(yīng)用程序主機級別的日志記錄器
builder.Logging.AddConsole();Log等級
在asp.net core的日志默認擴展中,定義LogTrace LogDebug LogInformation LogWarning LogError LogCritical Log 7種常用方法。對應(yīng)的日志有7個級別
// 定義日志嚴重性級別。
public enum LogLevel
{
//包含最詳細消息的日志。這些消息可能包含敏感的應(yīng)用程序數(shù)據(jù)
//默認情況下禁用這些消息,生產(chǎn)環(huán)境中永遠不應(yīng)啟用
Trace = 0,
//用于開發(fā)過程中的交互式調(diào)查。這些日志主要包含調(diào)試有用的信息,沒有長期價值
Debug = 1,
//跟蹤應(yīng)用程序的一般流程。這些日志應(yīng)具有長期價值
Information = 2,
//強調(diào)應(yīng)用程序流程中的異常或意外事件,但不會導(dǎo)致應(yīng)用程序執(zhí)行停止
Warning = 3,
//強調(diào)由于失敗導(dǎo)致當前執(zhí)行流程停止的日志。這些日志應(yīng)指示當前活動中的失敗,而不是應(yīng)用程序范圍的失敗
Error = 4,
//描述不可恢復(fù)的應(yīng)用程序或系統(tǒng)崩潰,或者需要立即關(guān)注的災(zāi)難性失敗
Critical = 5,
//不用于編寫日志消息。指定日志類別不應(yīng)寫入任何消息
None = 6,
}異常
在開發(fā)環(huán)境,builder默認會為應(yīng)用配置這個異常中間件app.UseDeveloperExceptionPage(),這個配置是寫在源碼中的,所以不需要我們顯式配置。如果我們不想顯示這個頁面,那可以配置中間件UseExceptionHandler。這個中間件要配置在UseDeveloperExceptionPage后面,因為中間件調(diào)用是遞歸的,異常發(fā)生在終結(jié)點中間件中,之后就是請求處理管道遞歸跳出階段,所以異常捕獲也在這個階段。調(diào)用順序和配置順序是相反的。
或者根據(jù)環(huán)境app.Environment.IsDevelopment()來判斷使用是否使用UseExceptionHandler。至于UseDeveloperExceptionPage則不用擔心,只要不是顯示配置,開發(fā)環(huán)境中不會進入這個中間件。不知道是由于條件編譯,還是做了環(huán)境判斷。
還還有一種方法是在這兩個中間件后面自定義中間件捕獲異常,短路管道。
我注意到,不管使用UseDeveloperExceptionPage還是UseExceptionHandler中間件,控制臺中都會出現(xiàn)異常日志,這是為什么?這是因為這兩個中間件中都調(diào)用了日志打印。所以我們可以自己實現(xiàn)一個異常處理中間件,這樣就不會打印日志了。
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ù)等信息。我們只知道哪里出錯了,卻不知道是什么原因引起的,怎么復(fù)現(xiàn),這是很不完善的。不知道當初為什么這樣設(shè)計。所以最好是自己重新寫個異常處理中間件,設(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
{
//不包含文件實際內(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。僅僅注冊了一個服務(wù),其他的一切不變。
引入包Serilog.AspNetCore。并不需要引入Serilog.Sinks.File,因為Serilog.AspNetCore已經(jīng)引入了文件介質(zhì)的提供程序

日志管線配置Serilog提供程序
日志系統(tǒng)包括如下幾個概念:日志管線、提供程序、介質(zhì)。嚴格來說介質(zhì)不算。
日志系統(tǒng)又叫日志管線logging pipeline。日志管線中的每個提供程序都會處理一次日志,并輸出到自己的介質(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();- 配置完整的日志管線這里默認的控制臺介質(zhì)得到了兩個提供程序,一個是默認的AddConsole,一個是Serilog的WriteTo.Console。按照管線中配置的順序,這兩個提供程序都會向控制臺打印一次。而Serilog還向管線中添加了一個到文件介質(zhì)的提供程序,所以還會產(chǎn)生一個日志文件。
builder.Services.AddLogging(logbuilder =>
{
logbuilder
//刪除管線上默認的提供程序
.ClearProviders()
//serilog子管線上的提供程序合并進來
.AddSerilog()
.AddConsole();
});- 控制臺介質(zhì)兩個提供程序均處理了日志打印

- 文件介質(zhì)

輸出模板
回想默認日志提供程序和serilog日志提供程序,都會在我們的消息之外附加信息,比如等級、類名。這是怎么配置的?
- 默認日志提供程序的格式是
[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";
})
//添加一個控制臺模板配置
.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)文章
看到本質(zhì)而不是現(xiàn)象--解決ASP.NET CS0016的問題
看到本質(zhì)而不是現(xiàn)象--解決ASP.NET CS0016的問題...2007-01-01
ASP.NET數(shù)據(jù)綁定的記憶碎片實現(xiàn)代碼
ASP.NET數(shù)據(jù)綁定的記憶碎片實現(xiàn)代碼,需要的朋友可以參考下2012-10-10
viewstate和datatable動態(tài)錄入數(shù)據(jù)示例
這篇文章主要介紹了viewstate和datatable動態(tài)錄入數(shù)據(jù)示例,需要的朋友可以參考下2014-02-02
asp.net CheckBoxList各項最小寬度CSS樣式(兼容性good)
ASP.NET中,CheckBoxList里的選擇都是自動寬度的,屬性時沒有設(shè)置各項寬度的設(shè)置,在IE10、遨游4極速模式及兼容模式下均可正確顯示最小寬度,此樣式除了用于CheckBoxList外,也可用于DIV等2013-04-04
Asp.Net Core輕松學(xué)習(xí)系列之配置文件
這篇文章主要給大家介紹了關(guān)于Asp.Net Core輕松學(xué)習(xí)系列之配置文件的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11
.NET開發(fā)人員關(guān)于ML.NET的入門學(xué)習(xí)
隨著谷歌,F(xiàn)acebook發(fā)布他們的工具機器學(xué)習(xí)工具Tensorflow 2和PyTorch,微軟也發(fā)布了ML.NET 1.0??梢哉f2019年是機器學(xué)習(xí)社區(qū)普及化的一年,下面小編向大家簡單介紹一下關(guān)于ML.NET的入門學(xué)習(xí)2019-05-05

