net core日志與異常處理小結(jié)
ILogger簡(jiǎn)單使用
asp.net core的webApplicationBuilder中自帶了一個(gè)日志組件。無需手動(dòng)注冊(cè)服務(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級(jí)別開始才有輸出,這是因?yàn)樵赼ppsetting文件中配置了LogLevel為Information,過濾掉了該等級(jí)之下的輸出。
MSDN推薦在方法中多次詳細(xì)的打印日志,但打印方法使用Trace或Information。開發(fā)時(shí)將日志等級(jí)設(shè)置為Warn,避免過多信息顯示。在排查故障時(shí)才將日志等級(jí)設(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ù),就會(huì)調(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ī)生成器會(huì)初始化默認(rèn)配置,然后在生成主機(jī)時(shí)將已配置的 ILoggerFactory 對(duì)象添加到主機(jī)的 DI 容器
所以效果是一樣的
//第一種,配置應(yīng)用程序級(jí)別的日志記錄器
builder.Services.AddLogging(configure =>
{
configure.AddConsole();
});
//第二種,配置應(yīng)用程序主機(jī)級(jí)別的日志記錄器。和第三種一樣,但不推薦使用這個(gè)。
builder.Host.ConfigureLogging(configure =>
{
configure.AddConsole();
});
//第三種,配置應(yīng)用程序主機(jī)級(jí)別的日志記錄器
builder.Logging.AddConsole();Log等級(jí)
在asp.net core的日志默認(rèn)擴(kuò)展中,定義LogTrace LogDebug LogInformation LogWarning LogError LogCritical Log 7種常用方法。對(duì)應(yīng)的日志有7個(gè)級(jí)別
// 定義日志嚴(yán)重性級(jí)別。
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)試有用的信息,沒有長(zhǎng)期價(jià)值
Debug = 1,
//跟蹤應(yīng)用程序的一般流程。這些日志應(yīng)具有長(zhǎng)期價(jià)值
Information = 2,
//強(qiáng)調(diào)應(yīng)用程序流程中的異?;蛞馔馐录粫?huì)導(dǎo)致應(yīng)用程序執(zhí)行停止
Warning = 3,
//強(qiáng)調(diào)由于失敗導(dǎo)致當(dāng)前執(zhí)行流程停止的日志。這些日志應(yīng)指示當(dā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)會(huì)為應(yīng)用配置這個(gè)異常中間件app.UseDeveloperExceptionPage(),這個(gè)配置是寫在源碼中的,所以不需要我們顯式配置。如果我們不想顯示這個(gè)頁面,那可以配置中間件UseExceptionHandler。這個(gè)中間件要配置在UseDeveloperExceptionPage后面,因?yàn)橹虚g件調(diào)用是遞歸的,異常發(fā)生在終結(jié)點(diǎn)中間件中,之后就是請(qǐng)求處理管道遞歸跳出階段,所以異常捕獲也在這個(gè)階段。調(diào)用順序和配置順序是相反的。
或者根據(jù)環(huán)境app.Environment.IsDevelopment()來判斷使用是否使用UseExceptionHandler。至于UseDeveloperExceptionPage則不用擔(dān)心,只要不是顯示配置,開發(fā)環(huán)境中不會(huì)進(jìn)入這個(gè)中間件。不知道是由于條件編譯,還是做了環(huán)境判斷。
還還有一種方法是在這兩個(gè)中間件后面自定義中間件捕獲異常,短路管道。
我注意到,不管使用UseDeveloperExceptionPage還是UseExceptionHandler中間件,控制臺(tái)中都會(huì)出現(xiàn)異常日志,這是為什么?這是因?yàn)檫@兩個(gè)中間件中都調(diào)用了日志打印。所以我們可以自己實(shí)現(xiàn)一個(gè)異常處理中間件,這樣就不會(huì)打印日志了。
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中間件的異常打印的問題在于,沒有打印請(qǐng)求路徑、請(qǐng)求參數(shù)等信息。我們只知道哪里出錯(cuò)了,卻不知道是什么原因引起的,怎么復(fù)現(xiàn),這是很不完善的。不知道當(dāng)初為什么這樣設(shè)計(jì)。所以最好是自己重新寫個(gè)異常處理中間件,設(shè)置響應(yīng)內(nèi)容以及控制日志打印。
自定義異常中間件打印請(qǐng)求和異常日志
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。僅僅注冊(cè)了一個(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è)提供程序都會(huì)處理一次日志,并輸出到自己的介質(zhì)中。
- 配置serilog子管線
Log.Logger = new LoggerConfiguration()
//最小輸出等級(jí)
.MinimumLevel.Warning()
//增加介質(zhì)
.WriteTo.Console()
.WriteTo.File("logs/log-.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
//注冊(cè)serilog服務(wù)
builder.Services.AddSerilog();- 配置完整的日志管線這里默認(rèn)的控制臺(tái)介質(zhì)得到了兩個(gè)提供程序,一個(gè)是默認(rèn)的AddConsole,一個(gè)是Serilog的WriteTo.Console。按照管線中配置的順序,這兩個(gè)提供程序都會(huì)向控制臺(tái)打印一次。而Serilog還向管線中添加了一個(gè)到文件介質(zhì)的提供程序,所以還會(huì)產(chǎn)生一個(gè)日志文件。
builder.Services.AddLogging(logbuilder =>
{
logbuilder
//刪除管線上默認(rèn)的提供程序
.ClearProviders()
//serilog子管線上的提供程序合并進(jìn)來
.AddSerilog()
.AddConsole();
});- 控制臺(tái)介質(zhì)兩個(gè)提供程序均處理了日志打印

- 文件介質(zhì)

輸出模板
回想默認(rèn)日志提供程序和serilog日志提供程序,都會(huì)在我們的消息之外附加信息,比如等級(jí)、類名。這是怎么配置的?
- 默認(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è)控制臺(tái)模板配置
.AddConsoleFormatter<CustomConsoleFormatter, TemplatedConsoleFormatterOptions>(foption =>
{
foption.Template = "[{Timestamp:HH:mm:ss} {Level}] [{EventId}] {Message:lj}{NewLine}{Exception}";
});
});
- Serilog輸出模板相比之下Serilog就簡(jiǎn)單得多,直接指定模板就行
Log.Logger = new LoggerConfiguration()
//最小輸出等級(jí)
.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)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
asp.net使用FCK編輯器中的分頁符實(shí)現(xiàn)長(zhǎng)文章分頁功能
這篇文章主要介紹了asp.net使用FCK編輯器中的分頁符實(shí)現(xiàn)長(zhǎng)文章分頁功能,涉及asp.net字符串及分頁操作的相關(guān)技巧,需要的朋友可以參考下2016-06-06
Asp.Net平臺(tái)下的圖片在線裁剪功能的實(shí)現(xiàn)代碼(源碼打包)
最近項(xiàng)目中有個(gè)圖片在線裁剪功能,本人查找資料,方法如下:前臺(tái)展現(xiàn)用jquery.Jcrop實(shí)現(xiàn),后臺(tái)使用 System.Drawing.Image類來進(jìn)行裁剪2011-10-10
Ajax實(shí)現(xiàn)異步刷新驗(yàn)證用戶名是否已存在的具體方法
由于要做一個(gè)注冊(cè)頁面,看到許多網(wǎng)站上都是使用Ajax異步刷新驗(yàn)證用戶名是否可用的,所以自己也動(dòng)手做一個(gè)小實(shí)例2014-02-02
用類的繼承關(guān)系(重寫父類的方法)實(shí)現(xiàn)簡(jiǎn)易后臺(tái)代碼模板
Asp.net的優(yōu)勢(shì)就在于快速構(gòu)建應(yīng)用,而對(duì)于一些最基礎(chǔ)數(shù)據(jù)的增刪改以及分頁事件或者樣式的設(shè)定可以通過在父類中寫上虛方法來供子類調(diào)用,接下來將為您測(cè)試一下用子類重寫父類的方法實(shí)現(xiàn)在模板的基礎(chǔ)上衍生變化2013-01-01
微信公眾平臺(tái)開發(fā)之認(rèn)證"成為開發(fā)者".Net代碼解析
這篇文章主要為大家詳細(xì)解析了微信公眾平臺(tái)開發(fā)之認(rèn)證"成為開發(fā)者".Net代碼,感興趣的小伙伴們可以參考一下2016-06-06
如何在ASP.NET Core中使用HttpClientFactory
這篇文章主要介紹了如何在ASP.NET Core中使用HttpClientFactory,幫助大家更好的理解和學(xué)習(xí)使用.net技術(shù),感興趣的朋友可以了解下2021-04-04
asp.net訪問網(wǎng)絡(luò)路徑方法(模擬用戶登錄)
這篇文章主要介紹了asp.net訪問網(wǎng)絡(luò)路徑方法,其實(shí)就是模擬用戶登錄,需要的朋友可以參考下2014-08-08

