.net core 微服务之 链路监控、日志中心(ELK+Nlog、ELK+Serilog、Exceptionless)

链路监控概念

什么是链路监控

 

 

链路:在分布式系统中,完成一个功能 ,需要涉及到许多服务协作,连接这些服务的请求组合起来就是链路。

就是用来记录服务之间的请求过程,就是链路监控。

为什么要使用链路监控

如果出现以下问题,就会使用链路监控

1. 客户端请求耗时非常长,需要监控并排查是那个服务导致的

2. 客户端请求异常,需要排查具体服务

3. 需要监控每次客户端请求后,每个服务的调用过程

使用框架SkyWalking

SkyWalking:java 开发 SkyWalking是本土开源的基于字节码注入的调用链分析,以及应用监控分析工具。特点是支持多种插件,UI功能较强,接入端无代码侵入。目前已加入Apache孵化器。全链路追踪,配置极其简单。没有任何代码入侵。

SkyWalking 概念

Skywalking Agent

​ SkyWalking客户端,用来发送链路数据到Collecter

SkyWalking Collecter   

​ 也叫APM, 对于链路数据进行分析处理,然后存储到storage

Skywalking Storage

​ 对于分析的链路结果数据进行存储, 但是这里用到了 Elasticsearch (也可以用MYSQL),简称ES

SkyWalking UI

​ 展示链路数据,方便监控

 

环境搭建

JAVA环境安装

JDK 1.8 安装 ,之前分布式事务已经安装了,这里就直接跳过

Elasticsearch 安装

1. 下载地址:https://www.elastic.co/cn/downloads/elasticsearch

2. 在下载好的文件夹config配置elasticsearch.yml,结尾新增如下配置

network.host: 0.0.0.0
# 配置线程池
thread_pool.bulk.queue_size: 1000

 3. 启动 elasticsearch,进入到bin目录直接双击运行 elasticsearch.bat 。运行之后访问 http://localhost:9200/ (我应该是端口号被占用了,然后运行之后自动变成9201),如果能访问表示启动成功

 

 

 

 

 

SkyWalking 安装

1. 下载地址:http://skywalking.apache.org/downloads/

2. 解压后,在apache-skywalking-apm-bin/config目录下,在application.yml内配置。

grpchost: skywalking grpc地址,后面代码用于连接
grpcport: skywalking grpc端口,后面代码用于连接
clusternodes: ES 连接地址

 

 

 3. bin目录中直接双击startup.bat 并运行,运行成功后输入 http://localhost:8081 访问webapp 的UI界面(相关配置在 webapp的webapp.yml中) ,能访问表示 skywalking 运行成功

 

 

 

链路监控示例

1. 在需要监控的项目中 引用nuget包,我这里用的是ES作为存储,如果想用mysql作为存储,引用相关包即可

SkyAPM.Agent.AspNetCore
System.Data.SqlClient

2. 新建一个skyapm.json 的 json文件新增如下内容,或者不用新建,直接在 appsettings.json 中直接新增如下内容

  "SkyWalking": {
    "ServiceName": "ConsulApiService",    //服务名称,随便起
    "Transport": {
      "gRPC": {
        "Servers": "localhost:11800"  // Skywalking Collector的地址,上面讲到的配置文件中可以配置
      }
    }
  }

3. 在launchSettings.json中配置环境变量

 "environmentVariables": {
        "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore"
      }

这样就已经可以了,是不是很简单,下面我们来随便添加测试代码。我这里为了监控服务之间请求规则,所以在网关中也像上面步骤添加了相应代码,这里省略了一些代码。流程就是

1. 用户访问 http://localhost:5004/api/user/test  接口
2.  /api/user/test 接口 中 调用网关
3.  网关通过consul 轮询算法后转发到 其中一个节点
4.  在consul子节点中对应接口添加相应代码 

这里贴一下代码

 [HttpGet("Test")]
        public async Task<IActionResult> Test()
        {
            Thread.Sleep(1000);
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri("http://localhost:7000");
            await client.GetAsync("/one");
            return Ok("添加成功");
        }

网关配置部分代码

"ReRoutes": [
    {
      "DownstreamPathTemplate": "/ConsulHealthCheck/GetTest", //下游转发接口
      "DownstreamScheme": "http",

      "ServiceName": "consualapi",
      "LoadBalancerOptions": { //负载均衡配置
        "Type": "LeastConnection" //“轮询”算法  可以配置其他的
      },
      "UpstreamPathTemplate": "/one", //上游接口地址
      "UpstreamHttpMethod": [ "Get", "Post" ], //限制网关http请求方式
      "key": "one"
    }
  ],
 [HttpGet("GetTest")]
        public IActionResult GetTest(string url)
        {
            Thread.Sleep(500);
            return Ok($"你正在调用端口号为{Request.HttpContext.Connection.LocalPort}的方法");
        }

好了,浏览器直接运行一下 http://localhost:5004/api/user/test  ,运行完成之后看一下SkyWalking结果

这里两张图片展示的方式不一样,但都可以直观的看到相应的请求过程和花费的时间,和上面演示代码的流程是差不多的。就可以针对花费的时间长的接口做相应的优化

这里也可以看到服务添加成功了

 

日志中心概念

上面的链路监控只能监控到请求的时间,并不能监控到我们请求参数,系统异常等日志信息,所以这里需要重新引入日志中心,用来记录软件系统运行的状态。

日志组成:时间,类,方法信息(输入参数和输出结果) 

日志中心的框架

ELK:对于目前ELK成为了微服务系统和分布式系统的主流,ELK是三个开源软件的缩写,分别表示:Elasticsearch , Logstash, Kibana 。Logstash 是核心地位。

Logstash :日志收集,处理器

Elasticsearch :日志存储器

Kibana:日志可视化分析器(webui)

Logstash安装启动

官网地址:https://www.elastic.co/

Logstash 6.6.0下载地址:https://www.elastic.co/cn/downloads/past-releases/logstash-6-6-0

1. 下载成功并解压之后在/config目录下,创建logstash.conf文件,在其中添加配置信息

input {
    tcp {
        port => "9900"
        type => "microservice-log"
    }
}
    
output {
  elasticsearch { 
        hosts => ["http://localhost:9200"]
        index => "microservice-%{+YYYY.MM.dd}"
		#user => "elastic"
		#password => "changeme"
  }
}	

2. 在bin目录中运行命令,注意这里把整个Logstash包不要放在含有中文字符的文件夹中,否则会启动失败。运行完成之后,浏览器输入:http://localhost:9600,显示结果,启动成功

logstash -f  ../config/logstash.conf

kibana安装启动

官网地址:https://www.elastic.co/

Kibana 6.6.0下载地址:https://www.elastic.co/cn/downloads/past-releases/kibana-6-6-0

1. 解压后,在/config目录下,打开kibana.yml文件,没有如下配置则在其中添加配置信息

server.port: 5601
server.host: "localhost"
elasticsearch.hosts: ["http://localhost:9200"]

2. 在/bin目录下,双击kibana.bat并运行,浏览器输入:http://localhost:5601,显示结果,启动成功

日志中心示例

集成Nlog

1. 这里代码是集成的Nlog,可以集成其他日志框架

NLog.Web.AspNetCore

2. 添加nlog.config 配置文件

<?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="Warn"
      internalLogFile="internal-nlog.txt">

    <extensions>
        <!--enable NLog.Web for ASP.NET Core-->
        <add assembly="NLog.Web.AspNetCore"/>
    </extensions>

    <!-- define various log targets -->
    <!--定义日志文件目录-->
    <variable name="logDirectory" value="${basedir}/logs/${shortdate}"/>
    <variable name="nodeName" value="node1"/>

    <targets async="true">
        <!-- 全部日志target -->
        <target xsi:type="File"
                name="allfile"
                fileName="${logDirectory}/nlog-all/${shortdate}.log"
                layout="#node1#${longdate}#${logger}#${uppercase:${level}}#${callsite}#${callsite-linenumber}#${aspnet-request-url}#${aspnet-request-method}#${aspnet-mvc-controller}#${aspnet-mvc-action}#${message}#${exception:format=ToString}#"
                keepFileOpen="false"
            />

        <!-- 本地文件日志target -->
        <target xsi:type="File"
                name="ownLog-file"
                fileName="${logDirectory}/nlog-${level}/${shortdate}.log"
                layout="#${longdate}#${nodeName}#${logger}#${uppercase:${level}}#${callsite}#${callsite-linenumber}#${aspnet-request-url}#${aspnet-request-method}#${aspnet-mvc-controller}#${aspnet-mvc-action}#${message}#${exception:format=ToString}#"
                keepFileOpen="false"
            />

        <!-- Tcp日志target -->
        <target xsi:type="Network"
                name="ownLog-tcp"
                keepConnection="false"
                address ="tcp://127.0.0.1:9400"
                layout="#${longdate}#${nodeName}#${logger}#${uppercase:${level}}#${callsite}#${callsite-linenumber}#${aspnet-request-url}#${aspnet-request-method}#${aspnet-mvc-controller}#${aspnet-mvc-action}#${message}#${exception:format=ToString}#"
            />
        <!--grok 规则-->
        <!--%#{DATA:request_time}#%{DATA:node_name}#%{DATA:class_name}#%{DATA:log_level}#%{DATA:call_site}#%{DATA:line_number}#%{DATA:request_url}#%{DATA:request_method}#%{DATA:container_name}#%{DATA:action_name}#%{DATA:log_info}#%{DATA:exception_msg}#-->
        <!--空白-->
        <target xsi:type="Null" name="blackhole" />
    </targets>

    <!--日志级别从小到大 Trace -》Debug-》 Info -》Warn-》 Error-》 Fatal-->
    <!--日志规则-->
    <rules>
        <!--全部日志, 包括Microsoft日志-->
        <logger name="*" minlevel="Trace" writeTo="allfile" />

        <!--自定义日志,排除Microsoft日志-->
        <logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />
        <logger name="*" minlevel="Debug" writeTo="ownLog-file" />
        <logger name="*" minlevel="Info" writeTo="ownLog-tcp" />
    </rules>
</nlog>
View Code

3. Startup.cs 中注入服务

 services.AddSingleton(NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger());

4. Program.cs在最后添加配置

 public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
..... .ConfigureWebHostDefaults(webBuilder
=> { webBuilder.ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); }).UseNLog(); });

5. 添加测试代码

 private readonly ILogger<ConsulHealthCheckController> _logger;
        public ConsulHealthCheckController(ILogger<ConsulHealthCheckController> logger)
        {
            _logger = logger;
        }
 [HttpGet("GetTest")]
        public IActionResult GetTest(string url)
        {
            _logger.LogInformation("这是一条测试消息Information");

            _logger.LogError("这是一条测试消息Erro");

            _logger.LogDebug("这是一条测试消息Debug");
            return Ok($"你正在调用端口号为{Request.HttpContext.Connection.LocalPort}的方法");
        }

OK,运行后,添加索引看效果

 

Serilog

单独使用Serilog

1. 引入Nuget

Serilog.AspNetCore

2. 配置Program.cs 中 CreateHostBuilder

  public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog()   //新增
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

3. 配置 Program.cs 中  Main。有两种方式,一种是直接配置,另外一种是把配置项写入appsettings.json,选其一就行了

  3.1  直接配置

 public static void Main(string[] args)
        {
            #region 配置 Serilog 
            #region 方式一:直接配置
            LoggerConfiguration loggerConfiguration = new LoggerConfiguration();
            // 最小的日志输出级别,Serilog 人为写日志最小级别
            loggerConfiguration.MinimumLevel.Debug();
            // 日志调用类命名空间如果以 Microsoft 开头,覆盖日志输出最小级别为 Information
            loggerConfiguration.MinimumLevel.Override("Microsoft", LogEventLevel.Verbose);
            //记录相关上下文信息
            loggerConfiguration.Enrich.FromLogContext();
            // 配置日志输出到控制台
            loggerConfiguration.WriteTo.Console();
            // 配置日志输出到文件路径,日记的生成周期为每天
            loggerConfiguration.WriteTo.File(new RenderedCompactJsonFormatter(), Path.Combine("D:\\selog\\", ".json"), rollingInterval: RollingInterval.Day);
            // 创建 logger
            Log.Logger = loggerConfiguration.CreateLogger();
            #endregion

            #endregion


            CreateHostBuilder(args).Build().Run();
        }
直接配置

       3.2 通过配置文件配置

public static void Main(string[] args)
        {
            #region 配置 Serilog 
            #region 方式二:通过配置文件配置
            IConfiguration configuration = new ConfigurationBuilder()
               .SetBasePath(Directory.GetCurrentDirectory())
               .AddJsonFile("appsettings.json", true, true)
               .AddEnvironmentVariables()
               .Build();

            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(configuration)
                .WriteTo.File(new RenderedCompactJsonFormatter(), Path.Combine("D:\\selog\\", ".json"), rollingInterval: RollingInterval.Day)
                .CreateLogger();
            #endregion

            #endregion


            CreateHostBuilder(args).Build().Run();
        }
配置文件配置
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Information"
      }
    },
    "WriteTo": [
      { "Name": "Console" }
    ],
    "Enrich": [ "FromLogContext" ]
  }
appsettings.json

 

4. 添加测试代码

        private IConfiguration _configuration;
        private readonly ILogger<ConsulHealthCheckController> _logger;
        public ConsulHealthCheckController(IConfiguration configuration, ILogger<ConsulHealthCheckController> logger)
        {
            _configuration = configuration;
            _logger = logger;
        }
        /// <summary>
        /// 测试
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        [HttpGet("GetTest")]
        public IActionResult GetTest(string url)
        {
            _logger.LogInformation("这是一条测试消息Information");
            _logger.LogError("这是一条测试消息Erro");
            _logger.LogDebug("这是一条测试消息Debug");
            return Ok($"你正在调用端口号为{Request.HttpContext.Connection.LocalPort}的方法");
        }

运行一下

Serilog集成ELK

1. 引入nuget 包,有些包用不上,只是方便扩展一些功能

Serilog.AspNetCore
Serilog.Enrichers.Environment
Serilog.Settings.Configuration
Serilog.Sinks.Console
Serilog.Sinks.Http     
Serilog.Sinks.Seq        //用不上,引用尽量方便需要时集成Seq
Serilog.Sinks.Elasticsearch 

2. 配置Program.cs 中 Main

public static void Main(string[] args)
        {
            #region Serilog集成ELK
            IConfiguration configuration = new ConfigurationBuilder()
               .SetBasePath(Directory.GetCurrentDirectory())
               .AddJsonFile("appsettings.json", true, true)
               .AddEnvironmentVariables()
               .Build();
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Verbose()   //最小日志记录等级
                .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)    //指定程序集最小记录级别
                .MinimumLevel.Override("System", LogEventLevel.Warning)
                .ReadFrom.Configuration(configuration)
                .Enrich.FromLogContext()
                .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri(configuration["Serilog:EsUrl"]))
                {
                    MinimumLogEventLevel = LogEventLevel.Information,
                    AutoRegisterTemplate = true,
                    IndexFormat = "project-{0:yyyy.MM.dd}"  //文件名
                })
                .CreateLogger(); ;
            #endregion
            CreateHostBuilder(args).Build().Run();
        }

3. 配置Program.cs 中 CreateHostBuilder

 Host.CreateDefaultBuilder(args)
                .UseSerilog()  //新增

3. 配置appsettings.json

"Serilog": {
    "MinimumLevel": {
      "Default": "Debug", //最小日志记录级别
      "Override": { //系统日志最小记录级别
        "Default": "Warning",
        "System": "Warning",
        "Microsoft": "Warning"
      }
    },
    "WriteTo": [
      { "Name": "Console" }, //输出到控制台
      {
        "Name": "File", //输出文件
        "Args": {
          "path": "D:\\selog\\log.txt",
          "outputTemplate": "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Class:{SourceContext}{NewLine}Message:{Message}{NewLine}{Exception}",
          "rollingInterval": "3" //日志文件生成精度:1:年  2:月 3:日 4:小时
        }
      }
    ],
    "EsUrl": "http://localhost:9201"
  }

4. 添加测试代码

private IConfiguration _configuration;
        private readonly ILogger<ConsulHealthCheckController> _logger;
        public ConsulHealthCheckController(IConfiguration configuration, ILogger<ConsulHealthCheckController> logger)
        {
            _configuration = configuration;
            _logger = logger;
        }
        /// <summary>
        /// 测试日志
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        [HttpGet("GetTest")]
        public IActionResult GetTest(string url)
        {
            _logger.LogError("这是一条测试消息Erro");

            _logger.LogWarning("这是一条测试消息Warning");

            _logger.LogInformation("这是一条测试消息Information");

            _logger.LogDebug("这是一条测试消息Debug");
            return Ok($"你正在调用端口号为{Request.HttpContext.Connection.LocalPort}的方法");
        }

运行请求一下接口,在ELK中创建索引就可以看到相应日志了

 Exceptionless 分布式日志

除了使用ELK,这里还可以使用Exceptionless,是.NET CORE 开发的,使用起来还是很简单,安装过程就不说了,如果用于测试可以直接用官方(https://be.exceptionless.io)提供的控制台测试

1. 在后台创建一个组织和项目名称,选择对应框架生成相应KEY

2. 使用方法上面这个图已经告诉你了,按照它这个设置就可以成功,下面演示的是自己搭建后的Exceptionless,所以唯一却别就是指定服务端地址。引用Nuget

Exceptionless.AspNetCore

3. 在ConfigureServices中注入服务 

ExceptionlessClient.Default.Configuration.ApiKey =你的ApiKey;
ExceptionlessClient.Default.Configuration.ServerUrl = 你的服务端地址;
services.AddExceptionless();

4. 注入管道

 app.UseExceptionless();

这样就完成了,现在添加测试代码

        [HttpGet("TestLog")]
        public async Task<IActionResult> TestLog()
        {
            try
            {
                ExceptionlessClient.Default.CreateLog($"正常记录Error", Exceptionless.Logging.LogLevel.Error).AddTags("测试一下").Submit();
                ExceptionlessClient.Default.CreateLog($"正常记录Info", Exceptionless.Logging.LogLevel.Info).AddTags("测试一下").Submit();
                ExceptionlessClient.Default.CreateLog($"正常记录Warn", Exceptionless.Logging.LogLevel.Warn).AddTags("测试一下").Submit();
                throw new Exception("故意抛出异常");
            }
            catch (Exception ex)
            {
                ex.ToExceptionless().Submit();
            }
            return Ok("请求成功");
        }

运行一下看看效果

posted @ 2023-02-15 16:26  Joni是只狗  阅读(2355)  评论(0编辑  收藏  举报