前言

以前就写过了 Asp.net core 学习笔记 (Logging 日志), 只是有点乱, 这篇作为整理版.

参考:

docs – Logging in .NET Core and ASP.NET Core

Github – Serilog.AspNetCore

Setting up Serilog in .NET 6

ASP.NET Core Build-in Logging

ASP.NET Core 提供了 Logging 的抽象接口, third party 都会依据抽象来做实现. ASP.NET Core 自己也实现了一套简单的 log, 它只能 log to console. 不能 log to file.

所以绝大部分项目都会搭配一个 third party library, 比如 Serilog.

我们先看看 ASP.NET Core build-in 的 log.

Quick Example for Web

dotnet new webapp -o TestLog

自带的模板首页就有 inject logger 了, 在 OnGet 使用它就可以了.

打开 Visual Studio > View > Output, F5 run 就可以看见了

VS Code

Quick Example for Console Application

安装

dotnet add package Microsoft.Extensions.Logging.Console

main.cs

public class Program
{
public static async Task Main()
{
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Example log message");
}
}

Log Category

每个 log 都有 category 的概念, 在 appsetting.json 可以为每个 category 设置 min level

比如某 category 在 production 的时候 min level 是 warning, 在 dev 的时候是 info.

像上面的注入方式, 它的 category name 是 namespace + class "TestLog.Pages.IndexModel"

如果想自定义的话可以用 factory 来创建 logger

在 appsetting.json 声明 min level

level 的顺序是 Trace = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Critical = 5, and None = 6.

Serilog level 的顺序是 Verbose = 0, Debug = 1, Information = 2, Warning = 3, Error = 4, Fatal = 5

None 就是不要 log. Trace 就类似 track 追踪的意思

Serilog 安装

dotnet add package Serilog.AspNetCore

program.cs

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog(); Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting web host");
app.Run(); // 把 app.Run(); 放进来
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}

Serilog 的 config 不是通过 appsetting 设置的, 如果想用 appsetting 来管理可以另外安装一个 DLL, 下面会介绍.

appsetting 里的 log config 是给 ASP.NET Core build-in log 的哦, Serilog 不会读取它来用.

Formatting Output

参考: Github serilog – Formatting Output

Log.Information 只是填写了 message, 但最终的 log 需要具备其它的资料比如时间, 而这些就有 output template 来完成.

下面这个是 ConsoleLogger 的默认模板

parameters definition:

我们可以通过 config 去调整它.

Log.Logger = new LoggerConfiguration()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.CreateLogger();

serilog-sinks-console 和 serilog-sinks-file 都支持 outputTemplate 设定. 但如果我们看源码会发现它们的实现方式不太一样.

console 源码显示, 它有自己实现一套 ITextFormatter

 

而 file 源码显示, 它用了 Serilog.Formatting.Display 的 MessageTemplateTextFormatter

 

Request Log

Serilog 有一个 build-in 的 request log 取代 ASP.NET Core build-in 的, 因为 ASP.NET Core log 太多了.

先关掉 ASP.NET Core 的

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)

然后

app.UseSerilogRequestLogging(options =>
{
options.MessageTemplate = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0} ms";
options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Information;
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
     // 这里可以加一点料, 加了 template 就可以用 {RequestHost} 这样
// diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);
// diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);
};
});

通过这个 middleware 每个请求都会被记入下来. middleware 放在 static file middleware 下面, 不然 static file 也会被 log 就不好了.

想更 customize 一下可以参考这个 Adding Useful Information to ASP.NET Core Web API Serilog Logs

Write to File

安装

dotnet add package Serilog.Sinks.File

设置

Log.Logger = new LoggerConfiguration()
.WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)

write to file 是马上执行的, 这对 IO 性能不太好.

Async Write to File

所以 Serilog 推出了一个 async 版本. 它会先写入 ram, 等一定量之后才写入 file.

dotnet add package Serilog.Sinks.Async

设置

Log.Logger = new LoggerConfiguration()
.WriteTo.Async(a => a.File("log.txt", rollingInterval: RollingInterval.Day, buffered: true), bufferSize: 500)

wrap 起来就可以了, buffered = true, buffer size 默认是 10,000 items 那是多少我也不清楚.

然后在 app 结束前一定要释放哦

try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush(); // 重要
}

Use appsetting.json as Config

安装

dotnet add package Serilog.Settings.Configuration

设置

Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)

appsetting.json

 "Serilog": {
"Using": [ "Serilog.Sinks.File" ], // 这个在 dotnet core project 下是多余的,可以拿掉哦
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.AspNetCore": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "log.txt",
"rollingInterval": "Day",
"outputTemplate": "[{Timestamp:hh:mm:ss tt} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
}
},
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "File",
"Args": {
"path": "log-async.txt",
"rollingInterval": "Day",
"outputTemplate": "[{Timestamp:hh:mm:ss tt} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}",
"buffered": true
}
}
]
}
}
],
"Enrich": [ "FromLogContext" ]
}

Send Email on Error

如果程序不经常出错的话, 每天查看 log 是挺浪费时间的, 这时就可以改用 Email 通知. 当出现 error 时, 把 error log 发送到我们电邮.

Serilog 有一个插件可以完成这个事儿. 它叫 serilog-sinks-email

很遗憾的是, 我在试用的时候遇到了 SSL 的问题. 可能是它太久没有更新了吧. 它底层依赖 MailKit

而设定 SSL 的方式是 useSsl

这个是比较古老的 way 了. 在我之前写的教程中, 调用 MailKit 应该是下面这样的

所以只好放弃这个插件了. 不开 SSL 倒是可以正常发送, 代码如下

dotnet add package Serilog.Sinks.Email

program.cs

Log.Logger = new LoggerConfiguration()
.WriteTo.Email(
connectionInfo: new EmailConnectionInfo
{
EmailSubject = "System Error",
EnableSsl = false,
FromEmail = "dada@hotmail.com",
Port = 587,
ToEmail = "dada@gmail.com",
MailServer = "smtp.office365.com",
NetworkCredentials = new NetworkCredential
{
UserName = "dada@hotmail.com",
Password = "dada"
},
ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true
},
restrictedToMinimumLevel: LogEventLevel.Error
)
.CreateLogger();

相关 issues:

Stack Overflow – Serilog.Sinks.Email - No Indication of Failure?

Github – Serilog Email sink is not sending emails

Stack Overflow – SslHandshakeException: An error occurred while attempting to establish an SSL or TLS connection

Stack Overflow – Serilog Email sink enableSSL false checks

Github – Serilog Email Sink not working with .NET Core 5

Debug Sink

参考: Github – Debugging and Diagnostics

WriteTo File, Console, Email, Database 都好, by right 是不应该出现 error 的. 因为这个已经是 final 了. 但是在开发阶段任何会有 bug 的可能.

比如上面的 send email 就因为 SSL 报错了. 要 debug 它的话需要开启

Serilog.Debugging.SelfLog.Enable(Console.WriteLine);

这样它就会把 send email 时的错误写入 Console 里. then 用 VS Code DEBUG CONSOLE 就可以看见 Exception 了.

自定义 Sink

既然 serilog-sinks-email 满足不到我们的需求, 那就自己写一个吧.

参考: Developing a sink

首先定义 2 个 class

public class MySink : ILogEventSink
{
public void Emit(LogEvent logEvent)
{
var message = logEvent.RenderMessage();
var level = logEvent.Level;
var now = logEvent.Timestamp.UtcDateTime;
var exception = logEvent.Exception;
}
} public static class MySinkExtensions
{
public static LoggerConfiguration MySink(this LoggerSinkConfiguration loggerConfiguration) => loggerConfiguration.Sink(new MySink());
}

然后调用

Log.Logger = new LoggerConfiguration()
.WriteTo.MySink()
.CreateLogger();

当 log 发生时 Emit 就会被调用了. 通过 logEvent 可以拿到 message, level, timespan, exception (如果 throw Exception 的话就有), 等等.

有了这些, 我们可以写入 file, 写入 Database, 发电子邮件等等.

但它有几个点要注意

1. Emit 不支持 async

Github – Asynchronous support throughout Serilog

Github – Changed sinks and loggers to async

C#同步方法中调用异步方法

How to call asynchronous method from synchronous method in C#?

2. 每一次 log 发生都会被调用 (过于频密).

所以, serilog-sinks-email 里面还依赖了 serilog-sinks-periodicbatching, 它的功能是把 log 搜集起来一次触发, 同时也支持了 async 写法.

主要就是替代掉了 ILogEventSink, 然后设定 config

3. 支持 Formatting Output, 可以参考上面的 Console 和 File 是如何支持 Formatting Output 的.

4. Emit 是会被并发调用的

所以参考, ConsoleSink.cs 和 RollingFileSink.cs 的实现. Emit 里面都加了 lock 来确保线程安全.

 

自定义 Sink for Send Email on Error

dotnet add package MailKit
dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.PeriodicBatching

program.cs

using System.Security.Authentication;
using MailKit.Net.Smtp;
using MimeKit;
using Serilog;
using Serilog.Configuration;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Formatting.Display;
using Serilog.Sinks.PeriodicBatching; namespace TestLogMySinkEmail; class MyEmailSink : IBatchedLogEventSink
{
private readonly ITextFormatter _textFormatter;
public MyEmailSink(ITextFormatter textFormatter)
{
_textFormatter = textFormatter;
} public async Task EmitBatchAsync(IEnumerable<LogEvent> logEvents)
{
var logEvent = logEvents.Single();
var outputWriter = new StringWriter();
_textFormatter.Format(logEvent, outputWriter);
var output = outputWriter.ToString(); using var client = new SmtpClient();
await client.ConnectAsync(
host: "smtp.office365.com",
port: 587,
options: MailKit.Security.SecureSocketOptions.StartTls
);
await client.AuthenticateAsync("dada@hotmail.com", "dada");
client.SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13; var message = new MimeMessage();
message.From.Add(new MailboxAddress("dada", "dada@hotmail.com"));
message.To.Add(new MailboxAddress("dada87", "dada87@gmail.com"));
var builder = new BodyBuilder
{
HtmlBody = $@"
<!DOCTYPE html>
<html lang=""en"" xmlns=""http://www.w3.org/1999/xhtml"" xmlns:o=""urn:schemas-microsoft-com:office:office"">
<head>
<meta charset=""UTF-8"">
<meta name=""viewport"" content=""width=device-width,initial-scale=1"">
<meta name=""x-apple-disable-message-reformatting"">
<title></title>
</head>
<body style=""color:red"">
<p>{output}</p>
</body>
</html>",
};
message.Subject = "Website Error Log";
message.Body = builder.ToMessageBody();
await client.SendAsync(message);
await client.DisconnectAsync(quit: true);
} public Task OnEmptyBatchAsync()
{
return Task.CompletedTask;
}
} public static class LoggerSinkConfigurationExtensions
{
public static LoggerConfiguration MyEmailSink(
this LoggerSinkConfiguration loggerSinkConfiguration,
LogEventLevel restrictedToMinimumLevel = LogEventLevel.Error,
string outputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}"
)
{
var formatter = new MessageTemplateTextFormatter(outputTemplate);
var myEmailSink = new MyEmailSink(formatter);
var batchingOptions = new PeriodicBatchingSinkOptions
{
BatchSizeLimit = 100,
Period = TimeSpan.FromSeconds(2),
EagerlyEmitFirstEvent = true,
QueueLimit = 10000,
}; var batchingSink = new PeriodicBatchingSink(myEmailSink, batchingOptions);
return loggerSinkConfiguration.Sink(batchingSink, restrictedToMinimumLevel);
}
} public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog(); Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
.WriteTo.MyEmailSink(LogEventLevel.Error)
.CreateLogger(); builder.Services.AddRazorPages(); var app = builder.Build(); if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.MapRazorPages(); try
{
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
}

index.cshtml.cs

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace TestLogMySinkEmail.Pages;

public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger; public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
} public void OnGet()
{
throw new Exception("Error");
}
}

ASP.NET Core – Logging & Serilog的更多相关文章

  1. ASP.NET Core Logging Solution

    Serilog.Extensions.Logging.File This package makes it a one-liner - loggerFactory.AddFile() - to con ...

  2. ASP.NET Core Logging in Elasticsearch with Kibana

    在微服务化盛行的今天,日志的收集.分析越来越重要.ASP.NET Core 提供了一个统一的,轻量级的Logining系统,并可以很方便的与第三方日志框架集成.我们也可以根据不同的场景进行扩展,因为A ...

  3. asp.net core使用serilog将日志推送到腾讯云日志服务

    为什么是serilog? Serilog是 .NET 中最著名的结构化日志类库. 基于日志事件log events,而不是日志消息log message. 你可以将日志事件格式化为控制台的可读文本或者 ...

  4. Asp.net core logging 日志

    1 基本概念 Dotnet core 一个重要的特征是 Dependency injection ,中文一般是依赖注入,可以简单理解为一个集合,在应用程序启动时,定义各种具体的实现类型并将其放到集合中 ...

  5. asp.net core 使用 Serilog

    安装NuGet包 PM> Install-Package SerilogPM> Install-Package Serilog.AspNetCorePM> Install-Packa ...

  6. Serilog 是 ASP.NET Core 的一个插件,可以简化日志记录

    [翻译] ASP.NET Core 利用 Docker.ElasticSearch.Kibana 来记录日志 原文: Logging with ElasticSearch, Kibana, ASP.N ...

  7. ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

  8. ASP.NET Core 源码学习之 Logging[3]:Logger

    上一章,我们介绍了日志的配置,在熟悉了配置之后,自然是要了解一下在应用程序中如何使用,而本章则从最基本的使用开始,逐步去了解去源码. LoggerFactory 我们可以在构造函数中注入 ILogge ...

  9. ASP.NET Core 源码学习之 Logging[4]:FileProvider

    前面几章介绍了 ASP.NET Core Logging 系统的配置和使用,而对于 Provider ,微软也提供了 Console, Debug, EventSource, TraceSource ...

  10. 【ASP.NET Core 】ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

随机推荐

  1. 在ubuntu16.04下,源码编译安装特定版本的MongoDB PHP扩展

    背景:我的php项目在连接其他mongo库时报:Server at xxx:27017 reports wire version 5, but this version of libmongoc re ...

  2. 2024-07-17:用go语言,给定一个整数数组nums, 我们可以重复执行以下操作: 选择数组中的前两个元素并删除它们, 每次操作得到的分数是被删除元素的和。 在保持所有操作的分数相同的前提下,

    2024-07-17:用go语言,给定一个整数数组nums, 我们可以重复执行以下操作: 选择数组中的前两个元素并删除它们, 每次操作得到的分数是被删除元素的和. 在保持所有操作的分数相同的前提下, ...

  3. Swift开发基础08-高阶函数

    高阶函数是指接受其它函数作为参数,或者返回其它函数的函数.Swift 提供了许多内置的高阶函数,这些函数在处理集合类型数据(如数组.集合等)时尤其有用.常见的高阶函数包括 map.filter.red ...

  4. JavaScript小技巧~将伪数组转成数组的方法

    伪数组:具有数组结构但是五数组相关方法的类数组结构: 方式1:Array.from() 方式2:Array.prototype.slice.call(); 用方式1吧,好记简单

  5. RHCA rh442 008 oom 故障触发器 内存溢出 swap numa tmpfs shm

    OOM out-of-memory killer 进程被莫名其妙杀死 所有内存加swap为活动 zone_normal 没空间 (目前64G时代可以不考虑这个) 溢出就死机 内存溢出前,去杀死进程 因 ...

  6. .NET 窗口/屏幕截图

    图像采集源除了显示控件(上一篇<.NET 控件转图片>有介绍从界面控件转图片),更多的是窗口以及屏幕. 窗口截图最常用的方法是GDI,直接上Demo吧: 1 private void Gd ...

  7. 一文带你了解CAP的全部特性,你学会了吗?

    目录 前言 消息发布 携带消息头 设置消息前缀 原生支持的延迟消息 并行发布消息 事务消息 事务消息发送 事务消息消费 事务补偿 消息处理 序列化 过滤器 消息重试 多线程处理 自动恢复/重连 分布式 ...

  8. 【Mybatis】11 注解的使用

    文档引用:http://www.mybatis.cn/archives/678.html 视频参考:https://www.bilibili.com/video/BV1NE411Q7Nx?p=15 注 ...

  9. 【转载】 Tensorflow Guide: Batch Normalization (tensorflow中的Batch Normalization)

    原文地址: http://ruishu.io/2016/12/27/batchnorm/ ------------------------------------------------------- ...

  10. NVRM: Xid (PCI:0000:b1:00): 13, pid=1375637, Graphics SM Global Exception on (GPC 0, TPC 1, SM 1): Multiple Warp Errors

    显卡服务器中一个显卡崩溃了: May 16 05:38:58 dell kernel: [14244871.006970] NVRM: Xid (PCI:0000:b1:00): 13, pid=13 ...