65.9K
CodeProject 正在变化。 阅读更多。
Home

Elasticsearch, Kibana 和 Docker 使用 .NET Standard 2

starIconstarIconstarIconstarIconstarIcon

5.00/5 (28投票s)

2017年12月3日

CPOL

6分钟阅读

viewsIcon

44500

现代 .NET 应用程序中的高级日志记录。一个周日上午的POC(概念验证)。

引言

在本文中,您将学习如何设置开发环境,以便出于日志记录目的使用 Elasticsearch 和 Kibana。在此过程中,您将使用 Docker,非常基础的用法,并且您还将了解到在我们的 Windows 经典桌面应用程序中使用 .NET Standard 库是多么容易。

背景

我将演示在我们的系统中集成 Elasticsearch 和 Kibana 是非常容易的。为此,我将设置并配置一个 Docker 网络和两个 Docker 镜像,一个用于 Elasticsearch,另一个用于 Kibana。这个基础设施将由一个 Windows 经典桌面控制台应用程序使用,该应用程序将执行一些随机日志记录。继续我上一篇文章中关于.NET Core 中的正确日志记录的内容,我将配置三个日志输出:控制台,以及使用 Serilog,我将配置并使用一个滚动文件接收器和一个 Elasticsearch 接收器。这些接收器将用于 .NET Standard 2 库中,但该库将被控制台应用程序引用,并使用最新的 Microsoft Dependency Injection Extensions

设置基础设施

首先,我们需要在我们的机器上安装并配置好 Docker,我使用的是 W10 和 Docker for Windows。(如果您还没有,可以在这里找到)。我们只需要运行三个命令就可以用 Docker 完成所有设置。(这很棒,不是吗?)

  • docker network create elk-logging-poc --driver=bridge
  • docker run -p 5601:5601 --name kibana -d --network elk-logging-poc kibana
  • docker run -p 9200:9200 -p 9300:9300 --name elasticsearch -d --network elk-logging-poc elasticsearch

第一个命令创建一个用于此 POC 的网络,第二个命令运行 Kibana,如果本地不存在则从 Docker Hub 拉取镜像,第三个命令对 Elasticsearch 执行相同的操作。

初始状态

  1. 创建网络

  2. 安装并运行 Kibana

此时,Kibana 已安装,但如果我们访问 localhost:5601,可以看到 Kibana 缺少 Elasticsearch。

安装 Elasticsearch

现在 Kibana 已准备好进行配置。

设置解决方案

好的,现在是时候设置我们的解决方案了,正如我所说,我将使用一个 Windows 经典桌面控制台应用程序和一个 .NET Standard 库。所以,我将创建一个新的解决方案和 WCD 控制台项目。

提示:虽然这是一个 POC,但我始终尝试使用健壮的命名空间,我始终牢记 POC 可能会变成原型,然后变成产品。这些事情确实会发生。

现在,让我们创建 .NET Standard 库。

结果将是这样的:

控制台文件

为了节省您的时间,您需要将 packages.config 中列出的 NuGet 包手动安装到控制台应用程序中,手动操作更方便。

packages.config

<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Elasticsearch.Net" 
  version="5.5.0" targetFramework="net462" />
  <package id="Microsoft.DotNet.InternalAbstractions" 
  version="1.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Configuration" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Configuration.Abstractions" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Configuration.Binder" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Configuration.FileExtensions" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Configuration.Json" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyInjection" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyInjection.Abstractions" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.DependencyModel" 
  version="1.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.FileProviders.Abstractions" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.FileProviders.Physical" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.FileSystemGlobbing" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Logging" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Logging.Abstractions" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Logging.Console" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Logging.Debug" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Options" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Options.ConfigurationExtensions" 
  version="2.0.0" targetFramework="net462" />
  <package id="Microsoft.Extensions.Primitives" 
  version="2.0.0" targetFramework="net462" />
  <package id="Newtonsoft.Json" 
  version="10.0.1" targetFramework="net462" />
  <package id="Serilog" 
  version="2.5.0" targetFramework="net462" />
  <package id="Serilog.Extensions.Logging" 
  version="2.0.2" targetFramework="net462" />
  <package id="Serilog.Settings.Configuration" 
  version="2.4.0" targetFramework="net462" />
  <package id="Serilog.Sinks.Elasticsearch" 
  version="5.4.0" targetFramework="net462" />
  <package id="Serilog.Sinks.File" 
  version="3.2.0" targetFramework="net462" />
  <package id="Serilog.Sinks.PeriodicBatching" 
  version="2.1.0" targetFramework="net462" />
  <package id="Serilog.Sinks.RollingFile" 
  version="3.3.0" targetFramework="net462" />
  <package id="System.Linq" 
  version="4.1.0" targetFramework="net462" />
  <package id="System.Resources.ResourceManager" 
  version="4.0.1" targetFramework="net462" />
  <package id="System.Runtime" 
  version="4.1.0" targetFramework="net462" />
  <package id="System.Runtime.CompilerServices.Unsafe" 
  version="4.4.0" targetFramework="net462" />
</packages

appsettings.json

{
  "Logging": {
    "IncludeScopes": true,
 
    "Debug": {
      "LogLevel": {
        "Default": "Critical"
      }
    },
    "Console": {
      "LogLevel": {
        "Microsoft.AspNetCore.Mvc.Razor.Internal": "Warning",
        "Microsoft.AspNetCore.Mvc.Razor.Razor": "Debug",
        "Microsoft.AspNetCore.Mvc.Razor": "Error",
        "Default": "Critical"
      }
    },
    "LogLevel": {
      "Default": "Critical"
    }
  },
  "Serilog": {
    "WriteTo": [
      {
        "Name": "Elasticsearch",
        "Args": {
          "nodeUris": "https://:9200;http://remotehost:9200/",
          "indexFormat": "elk-poc-index-{0:yyyy.MM}",
          "templateName": "myCustomTemplate",
          "typeName": "myCustomLogEventType",
          "pipelineName": "myCustomPipelineName",
          "batchPostingLimit": 50,
          "period": 2000,
          "inlineFields": true,
          "minimumLogEventLevel": "Trace",
          "bufferBaseFilename": "C:/Logs/docker-elk-serilog-web-buffer",
          "bufferFileSizeLimitBytes": 5242880,
          "bufferLogShippingInterval": 5000,
          "connectionGlobalHeaders": 
          "Authorization=Bearer SOME-TOKEN;OtherHeader=OTHER-HEADER-VALUE"
        }
      }
    ],
    "LogFile": "C:/Logs/ElasticSearchPoc.log",
    "MinimumLevel": "Information"
  }
}

Program.cs

using ElasticSearchPoc.Domain.LogProducer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Serilog;
using System;
 
namespace ElasticSearchPoc.Presentation.WCD.Console
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create service collection
            var serviceCollection = new ServiceCollection();
            ConfigureServices(serviceCollection);
 
            // Create service provider
            var serviceProvider = serviceCollection.BuildServiceProvider();
 
            // Run app (Every execution should create a new RunId)
            serviceProvider.GetService<BasicLogProducer>().Run();
        }
 
        private static void ConfigureServices(IServiceCollection serviceCollection)
        { 
            // Build configuration
            var configuration = new ConfigurationBuilder()
                .SetBasePath(AppContext.BaseDirectory)
                .AddJsonFile("appsettings.json", false)
                .Build();
 
            // Add console logging
            serviceCollection.AddSingleton(new LoggerFactory()
                .AddConsole(configuration.GetSection("Logging"))
                .AddSerilog()
                .AddDebug());
            serviceCollection.AddLogging();
 
            // Add Serilog logging           
            Log.Logger = new LoggerConfiguration()
                .WriteTo.RollingFile(configuration["Serilog:LogFile"])
                .ReadFrom.Configuration(configuration)
                .CreateLogger();
 
            // Add access to generic IConfigurationRoot
            serviceCollection.AddSingleton(configuration);
 
            // Add the App
            serviceCollection.AddTransient<BasicLogProducer>();
        }
    }
}

我想对这些文件添加一些评论,请注意 appsettings.json 中的不同日志级别以及 Elasticsearch 选项,设置是此 POC 的关键。此外,我想强调的是,我们的程序源代码是干净的并且遵循 SOLID 原则,我必须公开感谢 .NET Foundation 及其贡献者提供的这些极其有用的库和扩展。

让我们看一下 .NET Standard 库中的文件,LogProducer

LogProducer 文件

正如您所见,它唯一的依赖是 Microsoft.Extensions.Logging...

BasicLogProducer.cs

using Microsoft.Extensions.Logging;
using System;
using System.Threading;
 
namespace ElasticSearchPoc.Domain.LogProducer
{
    public class BasicLogProducer
    {
        private readonly ILogger<BasicLogProducer> _logger;
 
        public BasicLogProducer(ILogger<BasicLogProducer> logger)
        {
            _logger = logger;
        }
 
        public void Run()
        {
            var runDate = DateTime.Now;
            while (true)
            {
                // Let's randomize our logs...
                Array values = Enum.GetValues(typeof(LogLevel));
                Random random = new Random();
                LogLevel randomLogLevel = (LogLevel)values.GetValue(random.Next(values.Length));
 
                switch (randomLogLevel)
                {
                    case LogLevel.Trace:
                        _logger.LogTrace($"RunDate: {runDate}; 
                        Message Id: {Guid.NewGuid()}; LogLevel: Trace; 
                        LogLevelValue: {randomLogLevel.ToString("D")}");
                        break;
                    case LogLevel.Debug:
                        _logger.LogDebug($"RunDate: {runDate}; 
                        Message Id: {Guid.NewGuid()}; LogLevel: Debug; 
                        LogLevelValue: {randomLogLevel.ToString("D")}");
                        break;
                    case LogLevel.Information:
                        _logger.LogInformation($"RunDate: {runDate}; 
                        Message Id: {Guid.NewGuid()}; LogLevel: Information; 
                        LogLevelValue: {randomLogLevel.ToString("D")}");
                        break;
                    case LogLevel.Warning:
                        _logger.LogWarning($"RunDate: {runDate}; 
                        Message Id: {Guid.NewGuid()}; LogLevel: Warning; 
                        LogLevelValue: {randomLogLevel.ToString("D")}");
                        break;
                    case LogLevel.Error:
                        _logger.LogError($"RunDate: {runDate}; 
                        Message Id: {Guid.NewGuid()}; LogLevel: Error; 
                        LogLevelValue: {randomLogLevel.ToString("D")}");
                        break;
                    case LogLevel.Critical:
                        _logger.LogCritical($"RunDate: {runDate}; 
                        Message Id: {Guid.NewGuid()}; LogLevel: Critical; 
                        LogLevelValue: {randomLogLevel.ToString("D")}");
                        break;
                    case LogLevel.None:
                    default:
                        break;
                }
 
                Thread.Sleep(100); 
            }
        } 
    }
}

虽然我总是尽量编写自解释的源代码,但这个类值得简要解释。它使用了 DI(依赖注入),ILogger<T> 是被创建的,因为宿主应用程序注册了 ILoggingFactorySerilog,所以该日志记录器将拥有我们在主应用程序中配置的所有内容,在本例中是我们的三个接收器:控制台、文件和 Elasticsearch。Run 方法获取初始运行日期并开始生成具有随机日志级别的日志。它非常基础,但能完成它的工作。

配置、运行和测试

我不会提供太多细节。我猜通过一些截图,您会喜欢自己尝试一下。我们首先要做的是在 Kibana 中配置主索引模式,正如您所见,这是我们 appsettings.json 文件中的一个配置参数。我使用了 "indexFormat": "elk-poc-index-{0:yyyy.MM}",所以我们需要将 Kibana 索引配置为 "elk-poc-index-*"。

好了,让我们启动应用程序看看会发生什么……

控制台只记录 Critical 级别,根据配置参数。

我们还记录到文件,如配置文件和我们的 Main 方法中所述,我将通过几张截图演示如何使用我所知的最佳 Tail 工具,Tail Blazer(如果您一直读到这里并且不知道 TailBlazer,那么您现在就必须去尝试一下,因为您会爱上它)。

  1. 纯文本输出

  2. 点击右上角的齿轮图标,我们可以添加一些高亮。

  3. 在主窗口中,我们也可以添加一些过滤器。

我将输入 Fatal,只查看包含 Fatal 关键字的条目。

那么,Elasticsearch 和 Kibana 呢? 好了,在我截取这些截图的时候,控制台应用程序一直在生成日志,在控制台(仅 Critical),在本地文件系统中的文件里(如我们通过 TailBlazer 看到的 Information 级别),并且程序一直在向 Elasticsearch 接收器发送日志(Trace 级别),所以,如果我们进入 Kibana 并选择左侧菜单中的 Timelion 菜单项,我们应该能看到一个显示接收到的日志数量的图表,类似于这样。

图表有意义,因为代码每秒发送大约 10 条日志,因为有一个 Thread.Sleep(100),对吧?让我们稍微加速应用程序,将延迟设置为仅 10 毫秒。如果我再次运行它,时间线看起来是这样的。

如果我取消延迟并强迫我的机器生成尽可能多的日志会怎样?

嗯,文件日志增长得非常快,正如预期的那样(蓝色表示最近创建,请查看时间戳中的毫秒)。

CPU 达到 100%(也符合预期)。

Kibana 每秒接收到 772 条日志的高峰。考虑到所有东西都在同一台机器上运行,而且我正在调试和监视日志文件,这不算太糟,我们可能还可以进一步提升。

好了,今天就到这里吧,我仍然不知道如何正确地可视化 Kibana 中的数据,但 POC 在此结束,因为它正如预期那样记录了。

关注点

  • .NET 标准库是可能的最佳解决方案,因为它们是当今最可重用的选择。
  • 使用 Docker 配置 Elasticsearch 和 Kibana 只需三个步骤。
  • 现代 .NET 应用程序的日志记录有许多可能性,我们已经学会了如何
    • 正确地记录到控制台
    • 正确地记录到日志文件以及如何使用 TailBlazer 实时可视化它们
    • 如何配置 Serilog 接收器以记录到 Elasticsearch

历史

在家里的一个周日 POC,我可能不会再添加任何东西,除非进行一些拼写更正或根据要求进行一些澄清。

© . All rights reserved.