欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

net core日志與異常處理小結(jié)

 更新時(shí)間:2024年07月29日 09:44:48   作者:ggtc  
asp.net core的webApplicationBuilder中自帶了一個(gè)日志組件,無需手動注冊服務(wù)就能直接在控制器中構(gòu)造注入,這篇文章主要介紹了net core日志與異常處理小結(jié),需要的朋友可以參考下

ILogger簡單使用

asp.net corewebApplicationBuilder中自帶了一個(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ì)的打印日志,但打印方法使用TraceInformation。開發(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)文章

最新評論