11.【.NET 8 实战--孢子记账--从单体到微服务--转向微服务】--微服务基础工 ...

打印 上一主题 下一主题

主题 1003|帖子 1003|积分 3009

网关作为微服务架构的入口,承载着各服务间的请求转发与安全校验,其日志信息尤为关键。通过整合网关日志,可以将分散在不同系统中的访问记录、错误提示和异常信息集中管理,为问题排查提供全景视角。在排查故障时,同一日志能帮助定位请求链路中的瓶颈与错误,缩短故障恢复时间;在性能监控中,通过统计响应时间、请求频次等指标,及时发现系统负载变化及性能瓶颈;在安全审计方面,集中日志记录能追踪异常举动、识别恶意攻击,并为后续调查提供证据。日志整合的目的是构建一个集中化、智能化的日志平台,实现数据共享、及时监控与预警机制,终极提拔系统稳定性和安全性,保障业务连续运行。日志整合促协作高效,速解故障,极大改善用户体验显。
一、日志框架选择与技能方案

1.1 常用日志框架比力

Serilog、NLog 和 Log4net 是 .NET 平台上广泛应用的日志框架,各自都有独特的优势和适用场景。
Serilog 的特点在于其对结构化日志的原生支持,它采用消息模板记录日志,可以自动提取关键数据并实现日志聚合和过滤,这对分布式系统和微服务架构下的日志分析非常有利。Serilog 的配置方式重要采用链式 API,代码风格轻便直观,而且拥有丰富的插件,可以或许将日志输出到文件、数据库、Elasticsearch、Seq 等多种介质,使其非常得当必要精致日志数据分析和检索的现代化应用。
相比之下,NLog 以高性能和机动性著称,支持异步日志记录,能在高并发情况下稳定工作。它重要通过 XML 配置文件实现具体的日志级别、格式和输出目的的设置,虽然这种配置方式上手大概稍显繁琐,但其机动性和可扩展性使其非常得当企业级大规模日志管理,内置支持多种目的,如文件、数据库、邮件等,而且允许开发者自定义扩展。
Log4net 则是 log4j 在 .NET 情况下的成熟移植版,其历史悠久且在传统项目中应用广泛,具有很高的稳定性。虽然 Log4net 通过 XML 配置文件可以完成根本的日志设置,但在结构化日志的直接支持和扩展性方面相对较弱。
选择哪个框架必要联合具体项目需求来考量,如果项目对日志结构化要求较高,Serilog 是理想选择;若重点考虑高性能和机动配置,NLog 更能满足要求;而对于历史项目或对传统日志功能需求较高的系统,Log4net 则能提供稳定可靠的日志记录本领。
1.2 Ocelot 与日志框架的集成方式

Ocelot 本身构建于 ASP.NET Core 之上,因此它使用了 ASP.NET Core 的日志抽象(Microsoft.Extensions.Logging),这使得与各类日志框架的集成变得非常方便。Ocelot 的集成方式重要表如今以下几个方面:
起首在 ASP.NET Core 应用的启动阶段,通过配置 logging providers,开发者可以设置全局日志记录规则。由于 Ocelot 内部组件也依赖于 ILogger 接口,因此一旦配置好日志提供步调,Ocelot 就会相沿这些设置,将其内部运行、请求转发、异常捕获等日志信息输出到指定的目的。
其次除了基础日志记录之外,我们还可以通过自定义中间件或委托处理步调来扩展日志功能。在 Ocelot 的请求处理管道中,可以插入自定义组件,用于捕获请求的进入、响应输出及错误处理,并将这些信息记录下来。这样一来,不但可以同一日志格式,还能在日志中加入诸如请求跟踪 ID、用户身份、调用链信息等有助于故障排查和性能监控的上下文数据。
借助各日志框架的扩展功能,我们可以为 Ocelot 的日志设置丰富的输出目的和格式,好比将日志存储到 Elasticsearch、Seq、Splunk 等集中式日志平台,以便实实际时监控和数据分析。这种集成方式不但满足了基础的日志记录需求,还支持后续对日志进行高级处理和自动化预警。
1.3 集中式日志解决方案介绍

随着微服务架构和分布式系统的遍及,日志数据来源日益分散且数量庞大,单个服务的日志已难以满足故障排查、性能监控和安全审计的需求。通过集中式日志系统,所有日志数据可以或许及时汇聚到一个中央平台,这不但便于对历史数据进行归档和检索,更可以通过同一的查询和分析工具对全局日志进行聚合分析,从而迅速识别系统异常、瓶颈和安全隐患。
目前市面上较为成熟的集中式日志解决方案重要有 ELK Stack、EFK Stack 以及 Splunk 和 Graylog 等。以 ELK Stack 为例,Logstash 负责日志的网络、解析和传输,Elasticsearch 提供高效的日志数据存储与检索本领,Kibana 则通过直观的图表和仪表盘展实际时数据变化,为运维职员提供全面的监控视图。Splunk 则以其强盛的数据索引和分析本领著称,适用于大规模企业级日志管理,而 Graylog 在用户体验和扩展性方面也有独到之处。
集中式日志解决方案不但提拔了系统监控和故障排查的效率,还增强了安全审计的本领,为企业在面对复杂多变的应用情况时提供了可靠的数据支撑和决策依据。通过集中管理,开发和运维团队可以或许更加迅速地响应系统异常,优化性能配置,终极确保系统的稳定运行和业务的持续发展。
二、日志整合实现步调(以Serilog为例)

2.1 情况搭建与前置配置

起首在网关服务中引入Serilog包,在下令行中输入如下下令:
  1. dotnet add package Serilog
  2. dotnet add package Serilog.AspNetCore
  3. dotnet add package Serilog.Sinks.Console
复制代码
Serilog包就不用多说了,如今来讲讲Serilog.AspNetCore和Serilog.Sinks.Console 包。
Serilog.AspNetCore提供了与 ASP.NET Core 框架的集成,可以或许在 ASP.NET Core 应用步调中更方便地使用 Serilog 进行日志记录,而且包括一些特定于 ASP.NET Core 的功能,比方请求和响应日志记录等。Serilog.Sinks.Console 可以将日志消息输出到控制台,通常用在开发和调试过程中。通过配置 Serilog,可以将日志消息格式化并输出到控制台。
2.2 整合

在网关服务的Program类中添加对Serilog的配置,代码如下:
  1. using Ocelot.DependencyInjection;
  2. using Ocelot.Middleware;
  3. using Serilog;
  4. namespace Gateway
  5. {
  6.     public class Program
  7.     {
  8.         public static void Main(string[] args)
  9.         {
  10.             // more code
  11.             Log.Logger = new LoggerConfiguration()
  12.                 .MinimumLevel.Debug()
  13.                 .WriteTo.Console()
  14.                 .WriteTo.File("logs/ocelot-.txt", rollingInterval: RollingInterval.Day)
  15.                 .CreateLogger();
  16.             builder.Host.UseSerilog();
  17.             // more code
  18.         }
  19.     }
  20. }
复制代码
在代码中,起首创建了一个 Serilog 日志配置对象,通过调用 LoggerConfiguration()  构造器初始化日志配置,并使用 MinimumLevel.Debug() 将日志记录的最低级别设置为 Debug,也就是说 Debug 级别及以上的所有日志信息都会被捕获到。接着,通过 WriteTo.Console() 指定将日志输出到控制台,并使用 WriteTo.File("logs/ocelot-.txt", rollingInterval: RollingInterval.Day) 将日志记录到文件中,其中日志文件会按照每天进行滚动生成新的日志文件。最后,调用 CreateLogger() 方法根据之前的所有配置创建出具体的 Logger 实例,并将该实例赋值给 Serilog 的全局静态属性 Log.Logger。随后调用 builder.Host.UseSerilog() 将 Serilog 集成到应用步调的构建过程中,从而让整个应用在运行时都使用 Serilog 进行日志记录,而不是使用默认的日志系统。
2.3 捕获请求、响应及异常日志的实现方法

在上一小结,我们将Serilog添加到了网关服务中,但是这只是更换了Asp.Net Core 自带的日志组件,对请求、响应以及异常的日志记录没有做任那边理,下面我们就对这三个部分进行处理,要求输出的日志格式是:日志级别+日期时间+内容,其中包含请求参数、响应的数据、异常信息等。代码如下:
  1. using Serilog;
  2. namespace Gateway;
  3. /// <summary>
  4. /// 日志中间件
  5. /// </summary>
  6. public class RequestResponseLoggingMiddleware
  7. {
  8.     private readonly RequestDelegate _next;
  9.    
  10.     /// <summary>
  11.     /// 构造函数
  12.     /// </summary>
  13.     /// <param name="next"></param>
  14.     public RequestResponseLoggingMiddleware(RequestDelegate next)
  15.     {
  16.         _next = next;
  17.     }
  18.    
  19.     /// <summary>
  20.     /// 执行
  21.     /// </summary>
  22.     /// <param name="context"></param>
  23.     public async Task Invoke(HttpContext context)
  24.     {
  25.         // 记录请求
  26.         context.Request.EnableBuffering();
  27.         using var requestBodyStream = new MemoryStream();
  28.         await context.Request.Body.CopyToAsync(requestBodyStream);
  29.         requestBodyStream.Seek(0, SeekOrigin.Begin);
  30.         var requestBodyText = new StreamReader(requestBodyStream).ReadToEnd();
  31.         Log.Information($"收到请求: Method=`{context.Request.Method}`, Path=`{context.Request.Path},` Body=`{requestBodyText}`");
  32.         requestBodyStream.Seek(0, SeekOrigin.Begin);
  33.         context.Request.Body = requestBodyStream;
  34.         // 替换响应流
  35.         var originalResponseBodyStream = context.Response.Body;
  36.         using var responseBodyStream = new MemoryStream();
  37.         context.Response.Body = responseBodyStream;
  38.         try
  39.         {
  40.             await _next(context);
  41.             // 记录响应
  42.             context.Response.Body.Seek(0, SeekOrigin.Begin);
  43.             var responseBodyText = new StreamReader(context.Response.Body).ReadToEnd();
  44.             Log.Information($"输出响应: StatusCode=`{context.Response.StatusCode}`, Body=`{responseBodyText}`");
  45.             // 在写回原始响应流之前,移除 Content-Length 头(如果存在)
  46.             if (context.Response.Headers.ContainsKey("Content-Length"))
  47.             {
  48.                 context.Response.Headers.Remove("Content-Length");
  49.             }
  50.             // 重置响应流的位置并写回原始流
  51.             context.Response.Body.Seek(0, SeekOrigin.Begin);
  52.             await responseBodyStream.CopyToAsync(originalResponseBodyStream);
  53.         }
  54.         catch (Exception ex)
  55.         {
  56.             // 记录异常
  57.             Log.Error(ex, "执行请求时发生了未处理的异常。");
  58.             throw;
  59.         }
  60.         finally
  61.         {
  62.             context.Response.Body = originalResponseBodyStream;
  63.         }
  64.     }
  65. }
复制代码
在 Invoke 方法中,起首调用 context.Request.EnableBuffering() 以允许对 HTTP 请求体进行多次读取,接着创建一个 MemoryStream 实例并将请求体复制到该流中,这样可以在不影响后续中间件读取请求体的条件下获取请求的内容。复制完成后,通过重置流的指针到起始位置并使用 StreamReader 将请求体内容读取为字符串,随后使用 Log.Information 方法记录下请求方法、路径及请求体;接着,将流的指针再次重置并将 MemoryStream 赋值回 context.Request.Body 保证后续中间件可以或许正常访问请求数据。
对响应进行处理时,起首将原始的 context.Response.Body 生存到 originalResponseBodyStream 中,然后将 context.Response.Body 更换为一个新的 MemoryStream,以便捕获下游中间件写入响应流中的数据。当调用 await _next(context) 实验下一个中间件后,通过重置响应流指针读取响应内容,同样将响应状态码和响应体日志记录下来,同时在写回原始响应流之前检查并移除 Content-Length 响应头,以防止由于数据实际写入字节数与 Content-Length 不同等而引发错误;最后,通过将 MemoryStream 中捕获的响应复制回原始响应流完成响应写入。在 try-catch 块中,如果下游中间件抛出异常,则使用 Log.Error 记录异常信息,并在 finally 块中将 context.Response.Body 恢复成原始响应流,保证中的流状态不会影响其他处理流程。
三、总结

网关作为微服务架构的入口,负责请求转发和安全校验,其日志记录对于故障排查、性能监控和安全审计至关紧张。同一日志可以全面呈现请求链路信息,缩短故障恢复时间,同时通过统计响应时间和请求频次及时发现性能瓶颈。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

河曲智叟

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表