Loading

.Net Core with 微服务 - Seq 日志聚合

上一次我们介绍并演示了如果使用 Consul 做为我们微服务的注册中心,来实现服务的注册与发现。那么本次我们讲会演示如何做日志聚合。日志聚合比较常用的有 ELK 等,但是这次我想要介绍的是一款比较小众的日志聚合工具 - Seq 。

日志聚合#

日志是我们写程序离不开的一个东西。在我们排查问题的时候日志就是我们的救命稻草。我们的每个服务都在不停的生产日志。但是实施微服务后,如果按照传统的写本地文件的日志方案,显然会面临跟修改配置一样麻烦的境地。不同的日志分散在各个服务器、容器内,这种情况下查日志简直是生不如死。
日志聚合组件为我们解决了这个问题。所有的服务通过接口发送日志到聚合服务,再由聚合服务进行统一存储,并且提供统一的查询、分析的能力。
日志聚合组件业界有 ELK、Exceptionless、Seq 等。

Seq#

Seq 是一款使用现代化技术构建的结构化日志存储,查询,分析工具。比起 ELK 这种组合要轻量级许多。只需要一个安装包就具有数据存储,查询,图表分析功能。它对 windows 友好,直接提供了安装包。当然也可以使用 docker 来部署。Seq 对于单个用户是免费的,这对于一些小团队并没有什么问题。Seq 一个比较强大的功能是提供了类似 Sql 语句的数据查询及处理能力,使得用户可以直接写 Select from 来得到自己想要的数据。

seq 的 dashboard 页面。

seq 的查询界面。
seq 网址

使用 docker 安装#

docker run --name seq -e ACCEPT_EULA=Y -p 8900:80 -p 5341:5341 datalust/seq

使用 docker run 运行一个实例。8900 绑定容器的 80 端口,该端口是 web 管理界面的入口。5341 绑定容器的 5341 端口,该端口是日志写入时候真正的端口。

使用 docker-compose 安装#

  seq_server:
    image: datalust/seq
    restart: always
    container_name: seq_server
    hostname: seq_server
    environment:
      - ACCEPT_EULA=Y
    ports:
      - 8900:80
      - 5341:5341

把上面的 docker 命令改写成 docker-compose 命令。
安装好 seq 之后,我们访问一下 http://localhost:8090 如果管理后台出现说明我们安装成功了。

NLog 集成 Seq#

seq 安装成功之后,我们可以开始跟 asp.net core 项目进行集成了。这里采用 Nlog 日志组件进行演示,如何跟 seq 集成。

点击 “API KEYS” 、“ADD API KEY” 弹出新增 API KEY 界面。

我们可以在这个界面为每个服务指定一个 APIKEY 当写入 Seq 的是用来区分服务。填写 title 信息,选择具有的权限,还可以自定义一些属性,这些属性会附加到每个日志记录上。比如我们这里为 member_center 这个 apikey 自定义一个 app = member_center 的属性。

Install-Package NLog.Targets.Seq

nuget 安装 Nlog 的 Seq 扩展。

<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      autoReload="true"
      internalLogLevel="info"
      internalLogFile="logs/internal-nlog.txt">

<extensions>
<add assembly="NLog.Targets.Seq"/>
</extensions>
<!-- the targets to write to -->
<targets>

<span class="hljs-comment">&lt;!-- write to the void aka just remove --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">target</span> <span class="hljs-attr">xsi:type</span>=<span class="hljs-string">"Null"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"blackhole"</span> /&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">target</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"seq"</span> <span class="hljs-attr">xsi:type</span>=<span class="hljs-string">"BufferingWrapper"</span> <span class="hljs-attr">bufferSize</span>=<span class="hljs-string">"1000"</span> <span class="hljs-attr">flushTimeout</span>=<span class="hljs-string">"2000"</span>  &gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">target</span> <span class="hljs-attr">xsi:type</span>=<span class="hljs-string">"Seq"</span> <span class="hljs-attr">serverUrl</span>=<span class="hljs-string">"http://192.168.18.164:5341"</span> <span class="hljs-attr">apiKey</span>=<span class="hljs-string">"vuRYxr8bMXuvKNbwVNUp"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"ThreadId"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"${threadid}"</span> <span class="hljs-attr">as</span>=<span class="hljs-string">"number"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">property</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"stacktrace"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"${onexception:inner=${stacktrace}}"</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">target</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">target</span>&gt;</span>

</targets>

<!-- rules to map from logger name to target -->
<rules>
<!--Skip Microsoft logs and so log only own logs-->
<logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />

<span class="hljs-tag">&lt;<span class="hljs-name">logger</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"*"</span> <span class="hljs-attr">minlevel</span>=<span class="hljs-string">"Trace"</span> <span class="hljs-attr">writeTo</span>=<span class="hljs-string">"seq"</span> /&gt;</span>

</rules>
</nlog>

在项目根目录添加一个 nlog.config 的 xml 配置文件。添加一个 seq 的 target ,在这个 target 上填写 serverUrl(seq的服务地址),apiKey。

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.ConfigureKestrel(options =>
                    {
                        options.ListenAnyIP(6003);
                    });
                    webBuilder.UseStartup<Startup>();
                })
                .UseNLog();

在 Program 文件的 CreateHostBuilder 方法最后追加 UseNLog 调用。

    [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        private readonly ILogger _logger;
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TestController</span>(<span class="hljs-params">ILoggerFactory loggerFactory</span>)</span>
    {
        _logger = loggerFactory.CreateLogger&lt;TestController&gt;();
    }

    [<span class="hljs-meta">HttpGet(<span class="hljs-string">"TestLogSeq"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-built_in">string</span> <span class="hljs-title">TestLogSeq</span>()</span>
    {
        _logger.LogTrace(<span class="hljs-string">"this is a test log for trace level ."</span>);
        _logger.LogDebug(<span class="hljs-string">"this is a test log for debug level ."</span>);
        _logger.LogInformation(<span class="hljs-string">"this is a test log for info level ."</span>);
        _logger.LogWarning(<span class="hljs-string">"this is a test log for warning level ."</span>);
        _logger.LogError(<span class="hljs-keyword">new</span> Exception(<span class="hljs-string">"this is a ex for seq log ."</span>), <span class="hljs-string">"this is a test log for error level ."</span>);

        <span class="hljs-keyword">return</span> <span class="hljs-string">"ok"</span>;
    }
}

添加一个 TestController 编写一个 TestLogSeq 方法,在这里打一些日志。

我们访问一下这个接口,打开 seq 的站点可以看到我们的日志已经写到 seq 里面了。

查询#

我们的服务每天都会产生数以万计的日志,现在把所有服务的日志都聚合在一起,那数量就更加庞大。在海量日志中查找需要的日志,显然变得很困难。
还好 Seq 带有强大的查询功能。我们可以像在数据库里查询那样,使用 Sql 语句来进行查询。
下面演示几个查询例子:

  1. like 查询
@Message like '%init%'

查询日志消息里带有 init 关键字的日志

2. = 查询

@Level = 'Error'

查询日志级别为 Error 的日志。

app = 'hotel_base'

查询所有 hotel_base 服务的日志。

3. 聚合函数

select count(1) from stream group by @Level

按日志级别分组,统计每个级别的数量。

以上简单的演示了几个查询方案,Seq 的查询相当的强大,具体请查考官方文档:the-seq-query-language

总结#

通过以上内容,我们简单的演示了 Seq 日志聚合工具的安装、与.net core 进行集成,查询日志等功能。Seq 虽然比起 ELK 比较冷门,但是最近跟一些朋友交流下来,发现很多朋友都开始使用 Seq 来做为日志聚合工具了。这也说明 Seq 确实有一定的实力,大家不妨一试。

项目地址#

https://github.com/wanghuayu007/myhotel_microservice

相关文章#

NET Core with 微服务 - 什么是微服务
.Net Core with 微服务 - 架构图
.Net Core with 微服务 - Ocelot 网关
.Net Core with 微服务 - Consul 注册中心

posted @ 2022-07-27 15:09  直争朝夕  阅读(112)  评论(0编辑  收藏  举报