.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>
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" ] }
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("请求成功"); }
运行一下看看效果