Serilog[1]是 Microsoft .NET 的结构化日志记录库,并已成为Checkout.com 上NET 的首选日志记录库。它支持各种日志记录目的地(称为接收器[2])包从标准控制台和基于文件的接收器到日志服务,如 Datadog。
本指南最初是我们工程手册中的一篇文章,在收到内部积极反馈后,我决定在我的博客上发布它。
1.标准日志属性
2.日志记录基础知识
1.记录一切
2.选择合适的日志记录级别
3.定时操作源
4.上下文
5.HTTP 日志记录
6.日志的职责
3.日志内容的采集
1.Serilog标准采集方法
2.通过全局属性采集日志
4.关联日志
5.消息模板
1.消息模板推荐
6.日志和诊断上下文
1.日志上下文
2.诊断上下文
7.配置
8.生产日志
1.当日志变得不仅仅是日志
9.其他工具和实用程序
1.在本地使用 Seq
2.按日志类型记录日志
3.按属性块记录日志
4.按请求记录日志
标准化日志事件属性使您能够充分利用日志搜索和分析工具。在适用的情况下使用以下属性:
ApplicationName |
生成日志事件的应用程序的名称 |
---|---|
ClientIP |
发出请求的客户端的 IP 地址 |
CorrelationId |
可用于跨多个应用程序边界跟踪请求的 ID |
Elapsed |
操作完成所用的时间(以毫秒为单位) |
EventType |
用于确定消息类型的消息模板的哈希值 |
MachineName |
运行应用程序的机器的名称 |
Outcome |
手术的结果 |
RequestMethod |
HTTP 请求方法,例如POST |
POST``RequestPath |
HTTP 请求路径 |
SourceContext |
日志源自的组件/类的名称 |
StatusCode |
HTTP 响应状态码 |
UserAgent |
HTTP 用户代理 |
Version |
正在运行的应用程序的版本 |
上面的很多属性都来自于 Serilog 自己的扩展,例如Serilog Timings[3](用于计时操作)和Serilog 请求日志记录[4]。
通常,记录所有可以深入了解您的应用程序和用户行为的内容,例如:
•代码中的主要分支点•遇到错误或意外值时•任何 IO 或资源密集型操作•重大领域事件•请求失败和重试•耗时的批处理操作的开始和结束
对您的日志记录要慷慨,但对您的日志记录级别要严格。在几乎所有情况下,您的日志级别都应该是Debug
. 使用Information
的日志事件,将在生产中需要确定运行状态或应用程序的正确性,Warning
或Error
突发事件,如异常。
请注意,该Error
级别应保留用于您打算对其采取行动的事件。如果某些事情成为正常的应用程序行为(例如,请求输入验证失败),您应该降级日志级别以减少日志“噪音”。
将应用程序中的每个资源密集型操作(例如 IO)与指标代码一起记录下来。这在本地运行应用程序以查看应用程序瓶颈或响应时间消耗的情况时非常有用。该Serilog时序库[5]提供了一个方便的方式来做到这一点:
using (_logger.TimeDebug("Sending notification to Slack channel {Channel} with {WebhookUrl}", _slackOptions.Channel, _slackOptions.WebhookUrl))
using (_metrics.TimeIO("http", "slack", "send_message"))
{
}
该SourceContext
属性用于跟踪日志事件的来源,通常是使用记录器的 C# 类。ILogger
使用依赖注入将 Serilog 注入到类中是很常见的。为确保SourceContext
正确设置,请使用ForContext
扩展名:
public TheThing(ILogger logger)
{
_logger = logger?.ForContext<TheThing>() ?? throw new ArgumentNullException(nameof(_logger));
}
使用Serilog 请求日志记录中间件[6]来记录 HTTP 请求。这会自动包含上面列出的许多 HTTP 属性并生成以下日志消息:
HTTP POST /payments responded 201 in 1348.6188 ms
将以下内容添加到您的应用程序启动中以添加中间件:
public void Configure(IApplicationBuilder app)
{
app.UseHealthAndMetricsMiddleware();
app.UseSerilogRequestLogging();
app.UseAuthentication();
app.UseMvc();
}
请注意,在health 和 metrics 中间件之后添加了 Serilog中间件。这是为了避免每次 AWS 负载均衡器命中您的健康检查端点时生成日志。
Serilog 中间件默认记录请求路径。如果您确实需要查看对应用程序中特定端点的所有请求,如果路径包含标识符等动态参数,您可能会遇到挑战。
为了解决这个问题,记录资源名称,在我们的应用程序中,按照惯例,它是Name
赋予相应路由的属性。这是这样检索的:
public static string GetMetricsCurrentResourceName(this HttpContext httpContext)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
Endpoint endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
#if NETCOREAPP3_1
return endpoint?.Metadata.GetMetadata<EndpointNameMetadata>()?.EndpointName;
#else
return endpoint?.Metadata.GetMetadata<IRouteValuesAddressMetadata>()?.RouteName;
#endif
}
过度全面的日志记录不仅会对您的应用程序产生性能影响,而且还会使诊断问题变得更加困难,并增加暴露敏感信息的风险。
Serilog 支持结构化对象输出,允许将复杂对象作为日志中的参数传递。这应该谨慎使用,如果您的主要目标是对相关属性进行分组,您最好初始化一个新的匿名对象,这样您就可以明确哪些信息被推送到日志中。
倾向于使用 Serilog 的诊断上下文功能(下面讨论)将日志折叠为单个日志条目。
将附加信息推送到您的日志中有助于提供有关特定事件的附加上下文。
您可以使用收集器来丰富应用程序生成的所有日志事件。我们建议使用以下 Serilog 浓缩器:
•日志上下文收集器 - 内置于 Serilog,此丰富器可确保添加到日志上下文的[7]任何属性都被推送到日志事件中•环境收集器[8]- 使用机器或当前用户名采集日志
可以使用Enrich.With
Serilog的fluent APILoggerConfiguration
或通过您的appsettings.json
文件(推荐)指定增强器:
{
"Serilog": {
"Using": [
"Serilog.Sinks.Console"
],
"MinimumLevel": {
"Default": "Information"
},
"WriteTo": [
{
"Name": "Console"
}
],
"Enrich": [
"FromLogContext",
"WithMachineName"
],
"Properties": {
"ApplicationName": "Gateway API"
}
}
}
您还可以全局指定属性。上面的片段appsettings.json
演示了我们通常如何设置ApplicationName
属性。在某些情况下,我们需要在启动时计算属性,这可以使用 Fluent API 来完成:
loggerConfiguration.ReadFrom.Configuration(hostContext.Configuration)
.EnrichWithEventType()
.Enrich.WithProperty("Version", ReflectionUtils.GetAssemblyVersion<Program>());
为了关联属于同一请求的日志,甚至跨多个应用程序,请CorrelationId
向日志添加一个属性。
在 HTTP 应用程序中,我们通常从HttpContext.TraceIdentifier
属性映射它。这是使用Cko-Correlation-Id
标头在内部 API 之间传递的。我们使用以下扩展来获取 _current_correlation ID:
public static string GetCorrelationId(this HttpContext httpContext)
{
httpContext.Request.Headers.TryGetValue("Cko-Correlation-Id", out StringValues correlationId);
return correlationId.FirstOrDefault() ?? httpContext.TraceIdentifier;
}
请注意,如果应用程序面向用户,则不应依赖提供的相关 ID 标头。
为了确保将关联 ID 推送到每个日志事件中,我们使用以下使用 Serilog 的中间件LogContext
(本文稍后将详细讨论):
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");
好的 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[9]上找到。
日志记录的一个挑战是上下文并不总是预先知道。例如,在处理 HTTP 请求的过程中,随着我们通过 HTTP 管道(例如了解用户的身份)
获得
额外的上下文。虽然LogContext
我们所有人都会在附加信息可用时创建新上下文,但此信息仅在 _subsequent_log 条目中可用。这通常会导致日志数量增加,只是为了捕获有关整个请求或操作的所有信息。
诊断上下文提供了一个执行上下文(类似于LogContext
),其优点是可以在其整个生命周期中进行丰富。请求日志中间件然后使用它来丰富最终的“日志完成事件”。这允许我们将许多不同的日志操作折叠为一个日志条目,其中包含来自请求管道中许多点的信息,例如:
SEQ 中的丰富属性
在这里您可以看到,不仅有中间件发出的 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();
}
诊断上下文不限于在 ASP.NET Core 中使用。它也可以以与请求日志中间件非常相似的方式在非 HTTP 应用程序中使用。例如,我们使用它在 SQS 使用者中生成完成日志事件。
Serilog 可以使用 Fluent API 或通过 Microsoft 配置系统进行配置。我们建议使用配置系统,因为可以在不发布应用程序新版本的情况下更改日志配置。
为此,添加Serilog.Settings.Configuration[10]包并按如下方式配置 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 中,写入控制台是一个阻塞调用,会对性能产生重大影响[11]。•应为Information
及以上配置全局日志记录。
据了解,在新项目发布期间,您可能需要更多信息来建立对解决方案的信心或诊断任何预期的初期问题。与其Information
为此升级您的日志条目,不如考虑Debug
在有限的时间内启用级别。
从开发人员那里听到的一个常见问题是他们如何在运行时动态切换日志级别。虽然这是可能的,但也可以使用蓝/绿部署来实现。使用降低的日志级别配置和部署非活动环境,然后通过加权目标组切换部分或全部流量。
日志可以提供对应用程序的大量洞察,并且在许多情况下足以处理日常支持请求或故障排除。然而,在某些情况下,日志可能不能确保你的工作正确,有许多警告信号:
•您发现自己向非技术用户开放应用程序日志•日志用于生成应用程序指标•更多信息被“塞进”日志以满足常见的支持请求或报告要求
在这些情况下,您可能需要为您的产品考虑专用工具。许多团队开发了类似“Inspector”的应用程序,将关键系统和业务数据聚合在一起,以处理可以提供给非技术利益相关者的 BAU 请求。此外,您可能会发现需要将应用程序中的数据推送到报告和分析工具中。
日志的有效性取决于您记录的内容和未记录的内容。
Seq[12]是由 Serilog 的作者创建的免费(供本地使用)日志记录工具。它提供高级搜索和过滤功能以及对结构化日志数据的完全访问。虽然我们的日志记录要求现在超出了 Seq 所能提供的范围,但它仍然是本地测试的一个很好的选择。
我们通常在 docker 中启动 Seq 作为单独的 docker-compose 文件 ( docker-compose-logging.hml
) 的一部分:
version: "3.5"
services:
seq:
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)[13]通过散列消息模板来自动执行此操作。为了在其他接收器中复制相同的行为,我们创建了以下使用Murmerhash 算法[14]的收集器[15]:
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);
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/cXVgBWUXKrJoLZ6x8Snwmw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。