public class RequestLogContextMiddleware
private readonly RequestDelegate _next;
public RequestLogContextMiddleware(RequestDelegate next)
_next = next;
public Task Invoke(HttpContext context)
using (LogContext.PushProperty("CorrelationId", context.GetCorrelationId()))
return _next.Invoke(context);
日志消息应提供事件的简短描述。我们通常看到开发人员创建过于冗长的消息作为在事件中包含额外数据的手段,例如:
_logger.Information("Storing payment state in Couchbase for Payment ID {PaymentId} and current state {State}", paymentId, state);
相反,您可以使用ForContext
(或本文底部的属性包丰富器)仍然包含数据但具有更简洁的消息:
_logger
.ForContext("PaymentId", paymentId)
.ForContext("State", state)
.Information("Storing payment state in Couchbase");
消息模板推荐
Fluent风格指南
好的 Serilog 事件使用属性名称作为消息中的内容来提高可读性并使事件更紧凑,例如:
_logger.Information("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs);
句子与片段
日志事件消息是片段,而不是句子;为了与使用 Serilog 的其他库保持一致,请尽可能避免尾随句点/句号。
模板与消息
Serilog 事件具有关联的消息模板,而不是消息。在内部,Serilog 解析和缓存每个模板(最多固定大小限制)。将日志方法的字符串参数视为消息,如下例所示,会降低性能并消耗缓存内存。例如,避免:
Log.Information("The time is " + DateTime.Now);
而是使用消息属性:
Log.Information("The time is {Now}", DateTime.Now);
除了在日志消息中使用字符串连接/插值的性能开销之外,它还意味着无法计算一致的事件类型(请参阅事件类型丰富器),从而无法找到特定类型的所有日志。
日志和诊断上下文
Serilog 支持两种可用于增强日志的上下文感知功能。
日志上下文
LogContext
可用于动态地添加和移除来自周围“执行上下文”性能; 例如,在事务期间写入的所有消息都可能带有该事务的 id,等等。
在RequestLogContextMiddleware
上面的介绍演示了如何推动CorrelationId
请求到LogContext
在请求的开始。这可确保该请求中的所有日志都包含该属性。
更多信息可以在Serilog wiki上找到。
诊断上下文
日志记录的一个挑战是上下文并不总是预先知道。例如,在处理 HTTP 请求的过程中,随着我们通过 HTTP 管道(例如了解用户的身份)获得额外的上下文。虽然LogContext
我们所有人都会在附加信息可用时创建新上下文,但此信息仅在 _subsequent_log 条目中可用。这通常会导致日志数量增加,只是为了捕获有关整个请求或操作的所有信息。
诊断上下文提供了一个执行上下文(类似于LogContext
),其优点是可以在其整个生命周期中进行丰富。请求日志中间件然后使用它来丰富最终的“日志完成事件”。这允许我们将许多不同的日志操作折叠为一个日志条目,其中包含来自请求管道中许多点的信息,例如:
在这里您可以看到,不仅有中间件发出的 HTTP 属性,还有应用程序数据,例如AcquirerId
、MerchantName
和ResponseCode
。这些数据点来自请求中的不同点,但通过IDiagnosticContext
接口推送到诊断上下文中:
public class HomeController : Controller
readonly IDiagnosticContext _diagnosticContext;
public HomeController(IDiagnosticContext diagnosticContext)
_diagnosticContext = diagnosticContext ?? throw new ArgumentNullException(nameof(diagnosticContext));
public IActionResult Index()
// The request completion event will carry this property
_diagnosticContext.Set("CatalogLoadTime", 1423);
return View();
在非 HTTP 应用程序中使用诊断上下文
诊断上下文不限于在 ASP.NET Core 中使用。它也可以以与请求日志中间件非常相似的方式在非 HTTP 应用程序中使用。例如,我们使用它在 SQS 使用者中生成完成日志事件。
Serilog 可以使用 Fluent API 或通过 Microsoft 配置系统进行配置。我们建议使用配置系统,因为可以在不发布应用程序新版本的情况下更改日志配置。
为此,添加Serilog.Settings.Configuration包并按如下方式配置 Serilog:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((hostContext, loggerConfiguration) =>
loggerConfiguration.ReadFrom.Configuration(hostContext.Configuration);
.ConfigureAppConfiguration((hostingContext, config) =>
config
.AddEnvironmentVariables(prefix: "FLOW_")
.AddAwsSecrets();
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.UseStartup<Startup>();
您现在可以通过任何支持的配置提供程序配置 Serilog。通常我们appsettings.json
用于全局设置并通过生产中的环境变量配置实际接收器(因为我们不想在本地运行时使用我们的远程日志服务):
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.Datadog.Logs"
"MinimumLevel": {
"Default": "Information"
"WriteTo": [
"Name": "Console"
"Enrich": [
"FromLogContext",
"WithMachineName"
"Properties": {
"ApplicationName": "Flow API"
生产环境下使用日志
在生产中部署应用程序时,请确保相应地配置日志记录:
- 控制台日志记录应限于
Error
. 在 .NET 中,写入控制台是一个阻塞调用,会对性能产生重大影响。 - 应为
Information
及以上配置全局日志记录。
据了解,在新项目发布期间,您可能需要更多信息来建立对解决方案的信心或诊断任何预期的初期问题。与其Information
为此升级您的日志条目,不如考虑Debug
在有限的时间内启用级别。
从开发人员那里听到的一个常见问题是他们如何在运行时动态切换日志级别。虽然这是可能的,但也可以使用蓝/绿部署来实现。使用降低的日志级别配置和部署非活动环境,然后通过加权目标组切换部分或全部流量。
当日志变得不仅仅是日志
日志可以提供对应用程序的大量洞察,并且在许多情况下足以处理日常支持请求或故障排除。然而,在某些情况下,日志不是工作的正确工具,有许多警告信号:
- 您发现自己向非技术用户开放应用程序日志
- 日志用于生成应用程序指标
- 更多信息被“塞进”日志以满足常见的支持请求或报告要求
在这些情况下,您可能需要为您的产品考虑专用工具。许多团队开发了类似“Inspector”的应用程序,将关键系统和业务数据聚合在一起,以处理可以提供给非技术利益相关者的 BAU 请求。此外,您可能会发现需要将应用程序中的数据推送到报告和分析工具中。
日志的有效性取决于您记录的内容和未记录的内容。
其他工具和实用程序
在本地使用 Seq
Seq是由 Serilog 的作者创建的免费(供本地使用)日志记录工具。它提供高级搜索和过滤功能以及对结构化日志数据的完全访问。虽然我们的日志记录要求现在超出了 Seq 所能提供的范围,但它仍然是本地测试的一个很好的选择。
我们通常在 docker 中启动 Seq 作为单独的 docker-compose 文件 ( docker-compose-logging.hml
) 的一部分:
version: "3.5"
services:
image: datalust/seq:latest
container_name: seq
ports:
- '5341:80'
environment:
- ACCEPT_EULA=Y
networks:
- gateway-network
networks:
gateway-network:
name: gateway-network
并配置我们的appsettings.Development.json
文件以使用 Seq 接收器:
"Serilog": {
"Using": [
"Serilog.Sinks.Console",
"Serilog.Sinks.Seq"
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning"
"WriteTo": [
"Name": "Console"
"Name": "Seq",
"Args": {
"serverUrl": "http://localhost:5341",
"apiKey": "none"
事件类型收集器
通常我们需要唯一标识相同类型的日志。一些接收器(例如Seq)通过散列消息模板来自动执行此操作。为了在其他接收器中复制相同的行为,我们创建了以下使用Murmerhash 算法的收集器:
internal class EventTypeEnricher : ILogEventEnricher
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
if (logEvent is null)
throw new ArgumentNullException(nameof(logEvent));
if (propertyFactory is null)
throw new ArgumentNullException(nameof(propertyFactory));
Murmur32 murmur = MurmurHash.Create32();
byte[] bytes = Encoding.UTF8.GetBytes(logEvent.MessageTemplate.Text);
byte[] hash = murmur.ComputeHash(bytes);
string hexadecimalHash = BitConverter.ToString(hash).Replace("-", "");
LogEventProperty eventId = propertyFactory.CreateProperty("EventType", hexadecimalHash);
logEvent.AddPropertyIfAbsent(eventId);
属性包收集器
如果您想向日志事件添加多个属性,请使用PropertyBagEnricher
:
public class PropertyBagEnricher : ILogEventEnricher
private readonly Dictionary<string, Tuple<object, bool>> _properties;
/// <summary>
/// Creates a new <see cref="PropertyBagEnricher" /> instance.
/// </summary>
public PropertyBagEnricher()
_properties = new Dictionary<string, Tuple<object, bool>>(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Enriches the <paramref name="logEvent" /> using the values from the property bag.
/// </summary>
/// <param name="logEvent">The log event to enrich.</param>
/// <param name="propertyFactory">The factory used to create the property.</param>
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
foreach (KeyValuePair<string, Tuple<object, bool>> prop in _properties)
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(prop.Key, prop.Value.Item1, prop.Value.Item2));
/// <summary>
/// Add a property that will be added to all log events enriched by this enricher.
/// </summary>
/// <param name="key">The property key.</param>
/// <param name="value">The property value.</param>
/// <param name="destructureObject">
/// Whether to destructure the value. See https://github.com/serilog/serilog/wiki/Structured-Data
/// </param>
/// <returns>The enricher instance, for chaining Add operations together.</returns>
public PropertyBagEnricher Add(string key, object value, bool destructureObject = false)
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
if (!_properties.ContainsKey(key)) _properties.Add(key, Tuple.Create(value, destructureObject));
return this;
_logger
.ForContext(
new PropertyBagEnricher()
.Add("ResponseCode", response?.ResponseCode)
.Add("EnrollmentStatus", response?.Enrolled)
.Warning("Malfunction when processing 3DS enrollment verification");
收集请求日志
Serilog 请求日志记录中间件允许提供一个函数,该函数可用于将来自 HTTP 请求的附加信息添加到完成日志事件。我们使用它来记录ClientIP
,UserAgent
和Resource
属性:
public static class LogEnricher
/// <summary>
/// Enriches the HTTP request log with additional data via the Diagnostic Context
/// </summary>
/// <param name="diagnosticContext">The Serilog diagnostic context</param>
/// <param name="httpContext">The current HTTP Context</param>
public static void EnrichFromRequest(IDiagnosticContext diagnosticContext, HttpContext httpContext)
diagnosticContext.Set("ClientIP", httpContext.Connection.RemoteIpAddress.ToString());
diagnosticContext.Set("UserAgent", httpContext.Request.Headers["User-Agent"].FirstOrDefault());
diagnosticContext.Set("Resource", httpContext.GetMetricsCurrentResourceName());
app.UseSerilogRequestLogging(opts
=> opts.EnrichDiagnosticContext = LogEnricher.EnrichFromRequest);
© 2021 Ben Foster https://benfoster.io/
SerilogDemo.Wpf
该存储库包含一个使用Serilog进行日志记录的示例WPF应用程序。 创建它的目的是在WPF应用程序的上下文中探索使用Serilog的潜在问题。
此应用程序使用Autofac将记录器注入MainWindow类,该类提供提供SourceContext值以记录事件。 添加了其他值以充实日志语句,但是在不同线程上进行充实时会出现问题。
问题#1-将LogContext.PushProperty与async / await一起使用
遇到的一个问题是,方法完成后,异步调用返回后丢失的值将被推送到LogContext属性中。 从文档中可以很明显地看出,您可以将值推送到上下文中,即使该线程被async / await中断,它们也将持久存在。 但是,如果在等待任务后推送值,则该值将不会持久化回到调用线程。
例如,如果我们查看中的代码,我们会看到它模拟了异步登录过程,在
pom.xml的war报错解决方法
显示错误:web.xml is missing and is set to true
Dynamic Web Module 3.0 工程时代不需要web.xml文件注册相关内容的,所以工程默认不生成web.xml
【解决方法一:】
failOnMissingWebXml 设为false,顾名思义是对丢失web.xml这个检测机制进行忽略
在pom.xml加上
<build>
<plugins>
<plugin>
Serilog.Settings.Configuration
一个Serilog设置提供程序,可从来源读取,包括.NET Core的appsettings.json文件。
默认情况下,从“ Serilog部分读取配置。
" Serilog " : {
" Using " : [ " Serilog.Sinks.Console " , " Serilog.Sinks.File " ],
" MinimumLevel " : " Debug " ,
" WriteTo " : [
{ " Name " : " Console " },
{ " Name " : " File " , " Args " : { " path " : " Logs/log.txt " } }
" Enrich " : [ " Fr
C#中的简单异步记录器C#中的简单异步记录器介绍背景使用代码数据封装编写日志条目未来的工作兴趣点历史许可证关于作者
C#中的简单异步记录器
本文翻译自CodeProject上面的一篇博客A Simple Asynchronous Logger in C#
作者:Toby Patke
2020年5月24日
Ms-PL
6分钟阅读
Clearcove.Logging是一个非常简单的日志记录库,旨在使用直接许可条款来满足大多数日志记录需求。
Download SimpleLogger.zip - 7.2 KB
Serilog简介
Serilog是.net中的诊断日志库,可以在所有的.net平台上面运行。Serilog支持结构化日志记录,对复杂、分布式、异步应用程序的支持非常出色。Serilog可以通过插件的方式把日志写入到各种终端,控制台、文本、Sqlserver、ElasticSearch,Serilog支持终端的列表:https://github.com/serilog/serilog/wiki/...
上文说到Nlog日志框架,感觉它功能已经很强大,今天给大家介绍一个很不错的日志框架Serilog,根据我的了解,感觉它最大的优势是,结构化日志,它输出的日志是Json的格式,如果你使用的是Mongodb进行存储日志,那就是完美的结合,MongoDB也是文档式数据库,存储的格式很像JSON,也可以它是一个JSON文件,查询数据库快。不扯远了,还是讲讲Serilog的使用吧!
一、什么是Serilog?
Serilog 是 ASP.NET Core 的一个插件,可以简化日志记录。Serilog 有各种.
这还自定义Serilog应用基本配置应用功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入
Serilog应用
记录在asp.netcore webApi项目中使用Serilog记录日志。
基本配置应用
添加Serilog包引用