前言

当一个APM或一个日志中心实际部署在生产环境中时,是有点力不从心的。

比如如下场景分析的问题:

  • 从APM上说,知道某个节点出现异常,或延迟过过高,却不能及时知道日志反馈情况,总不可能去相应的节点上一个一个的翻日志文件吧。

  • 从日志中心上说(特别是Exceptionless,能及时反馈出异常信息),知道某个节点出现异常日志,可不知道引起异常的源头在哪;或者出现延迟过高日志,却不能及时知道节点问题,还是链路问题;就算诸上问题都能应付,那么一行行的、一个个的日志文件和使用图形化的表述形式,谁会更加直观,当然,你说你可以一目十行,甚至百行来分析日志,那我挺佩服你的。

本节内容较多,所以笔者特列举了如下目录。

一:准备

1.SkyWalking和Exceptionless简单回顾

2.新建多个站点(物理节点)

3.附加SkyApm-dotnet程序集到宿主

二:将SkyApm-dotnet的日志输出到Exceptionless

4.SkyApm-dotnet的日志入口

5.继承ILoggerFactory获取全局ILogger对象

6.将Logger写入到Exceptionless

三:运行

7.SkyWalking和Exceptionless的结合分析

SkyWalking和Exceptionless简单回顾

前两篇就《NET Core微服务之路:SkyWalking+SkyApm-dotnet分布式链路追踪系统的分享》和《NET Core微服务之路:简单谈谈对ELK,Splunk,Exceptionless统一日志收集中心的心得体会》简单的介绍了SkyApm-dotnet和三个日志收集中心。为何最终会选择SkyWalking和Exceptionless来作为生产实战,很简单:

1.SkyWalking和Exceptionless的存储和检索都是使用的ElasticSearch,ES的强大之处不用介绍:“you know, for search”

2.SkyWalking作为国人(吴晟)开发的一套开源追踪系统,虽然比不上Pinpoint功能强大,但社区活跃且免费,相信开源的力量,会越来越完善,甚至更好。

3.Exceptionless作为.Net开源社区的新起之秀,目前也十分活跃,原生.Net语言支持,能做到日后无缝扩展。

新建多个站点(物理节点)

传统单体应用(或站点)没必须要做到APM追踪,因为她毫无意义。只有在分布式架构模式下,例如SOA、微服务等架构才有意义,比如说,你在两个地方分别部署了多个应用,当某个地方的应用出现了故障,你总不可能专门跑去一个一个文件的查阅日志吧,假如这个应用部署在火星呢(哈哈,开个玩笑)。

我们就SkyApm-dotnet中的

Sample https://github.com/SkyAPM/SkyAPM-dotnet/tree/master/sample

做一些二次修改和扩展,来模拟一个实际的分布式系统。

先看看这个系统的网络拓扑图:

asp-net-core-*为系统主要节点,而localhost:50000为Exceptionless的日志中心,114.215是数据库,具体每个线条的颜色请查阅SkyWalking手册。

asp-net-core-aspnetcore:我们可以把她理解为请求端,笔者在里面做了一个单请求,和一个并行请求,严格意义上来说,代码中不应该有try catch来进行重试,而是应该使用polly的Retry进行重试和异常处理,可以参考《NET Core微服务之路:弹性和瞬态故障处理库Polly的介绍》,代码参考如下:

[Route("api/[controller]")][ApiController]public class ValuesController : ControllerBase{    [HttpGet]public async Task<string> Get()    {var httpClient = new HttpClient();var values = await httpClient.GetStringAsync("http://localhost:5001/api/values");        ExceptionlessClient.Default.SubmitLog(JsonConvert.SerializeObject(values), LogLevel.Debug);return values;    }

    [HttpGet("getall")]public string GetAll()    {var list = new List<int>();var listValue = new List<string>();for (var i = 1; i <= 50; i++)        {            list.Add(i);        }

        Parallel.ForEach(list, (i, state) =>        {try            {using (var httpClient = new HttpClient())                {                    listValue.Add(httpClient.GetStringAsync($"http://localhost:5001/api/values/{i}/other").Result);                }            }catch (Exception)            {// ignored            }        });        ExceptionlessClient.Default.SubmitLog(JsonConvert.SerializeObject(listValue), LogLevel.Debug);return JsonConvert.SerializeObject(listValue);    }}

asp-net-core-frontend:我们可以把她理解为一个网关,一个中继,或者一个权限验证等等,笔者没做太多处理,就单纯做了一个switch的参数选择桥接,参考代码如下:

[Route("api/[controller]")][ApiController]public class ValuesController : ControllerBase{    [HttpGet]public async Task<string> Get()    {var httpClient = new HttpClient();var values = await httpClient.GetStringAsync("http://localhost:5002/api/values");return values;    }

    [HttpGet("{id:int}/other")]public async Task<string> Get(int id)    {var httpClient = new HttpClient();var values = "";switch (id)        {case 1:                values = await httpClient.GetStringAsync("http://localhost:5002/api/delay/100");break;case 2:                values = await httpClient.GetStringAsync("http://localhost:5002/api/Error");break;case 3:                values = await httpClient.GetStringAsync("http://localhost:5002/api/Values");break;case 4:                values = await httpClient.GetStringAsync("http://localhost:5002/api/Apps");break;case 5:            {var userClient = new User.UserClient(new Channel("127.0.0.1:5050", ChannelCredentials.Insecure));var response = await userClient.GetListAsync(new GetListRequest());if (response.Code == 1000)                {return JsonConvert.SerializeObject(response.Data);                }

break;            }        }

return values;    }}

asp-net-core-backend:我们可以把她理解为一个节点,笔者还创建了一个Grpc的服务节点,不知是因为目前SkyApm-dotnet探针没做Grpc的适配,还是笔者这边配置错误,目前并未实现Grpc的追踪,代码较多,就不一一的贴上来了,做个截图即可,源码在文章最后

附加SkyApm-dotnet程序集到宿主

An IHostingStartup (hosting startup) implementation adds enhancements to an app at startup from an external assembly. For example, an external library can use a hosting startup implementation to provide additional configuration providers or services to an app. IHostingStartup is available in ASP.NET Core 2.0 or later.

通过追加外部程序集来增强宿主功能,例如,可以在外部程序集中提供额外的服务或配置,此项功能支持NET Core 2.0+。

当然,能加载也就能禁用 ,使用ASPNETCORE_PREVENTHOSTINGSTARTUP便可实现。除以上通过set的方式配置环境参数以外,还可以通过代码的方式来指定ASPNETCORE_HOSTINGSTARTUPASSEMBLIES启动扩展程序集。

Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", "SkyAPM.Agent.AspNetCore");

对了,在WebHosting的环境变量定义中,默认提供了如下环境变量,有兴趣的朋友可深入研究。

SkyApm-dotnet的日志入口

在SkyApm-dotnet的配置文件中,默认是开启了本地日志的,像这样

"Logging": {"Level": "Information","FilePath": "logs/skyapm-{Date}.log"},

如果部署了多个SkyApm-dotnet探针到节点,那是不是要在多个节点上来查阅日志呢?答案肯定是拒绝的,如果这样下来,那么我们的日志收集中心就没有任何存在的意义了。所以,为了实现这个功能,找到了SkyApm.Logging.ILoggerFactory的接口,使用再次注入的方式,替换了原来默认的DefaultLoggerFactory(当然,如果有更好的方式,或者已经提供了接口,麻烦大家告知一下),这是默认日志注入的源码:

可以看到,SkyApm-dotnet的日志默认通过ServiceCollection进行注入,我们只需要实现ILoggerFactory便可实现自定义的日志处理方式。

继承ILoggerFactory获取全局ILogger对象

通过F12我们可以定位接口的具体源码定义,可以看到SkyApm.Logging中,定义了一个ILoggerFactory的接口定义,内部需实现一个Ilogger的创建,代码源码截图如下:

我们可以实现这个接口,定义为我们自己实现的处理方式。但是,其实我们可以将源码拷贝过来,因为我们仍然需要将日志保存在本地作为副本,而不是单纯将日志发送到日志中心,所以需要另起一个实现的名字,我这里取名叫SkyApmExtensionsLoggerFactory,源码如下:

namespace SkyApmExceptionless{public class SkyApmExtensionsLoggerFactory : SkyApm.Logging.ILoggerFactory    {private const string OutputTemplate =@"{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{ServiceName}] [{Level}] {SourceContext} : {Message}{NewLine}{Exception}";

private readonly LoggerFactory _loggerFactory;

public SkyApm.Logging.ILogger CreateLogger(Type type)        {return new SkyApmExtensionsLogger(_loggerFactory.CreateLogger(type));        }

public SkyApmExtensionsLoggerFactory(IConfigAccessor configAccessor)        {            _loggerFactory = new LoggerFactory();

var loggingConfig = configAccessor.Get<LoggingConfig>();var instrumentationConfig = configAccessor.Get<InstrumentConfig>();var level = EventLevel(loggingConfig.Level);

            _loggerFactory.AddSerilog(new LoggerConfiguration().MinimumLevel.Verbose().Enrich                .WithProperty("SourceContext", null).Enrich                .WithProperty(nameof(instrumentationConfig.ServiceName),                    instrumentationConfig.ServiceName ?? instrumentationConfig.ApplicationCode).Enrich                .FromLogContext().WriteTo.RollingFile(loggingConfig.FilePath,                    level,                    OutputTemplate,null,1073741824,31,null,false,false,                    TimeSpan.FromMilliseconds(500)).CreateLogger());        }

private static LogEventLevel EventLevel(string level)        {return Enum.TryParse<LogEventLevel>(level, out var logEventLevel)? logEventLevel                : LogEventLevel.Error;        }    }}

从上面的代码加粗的代码中可以看到,通过ILoggerFactory创建了一个SkyApm.Logging.ILogger的实现SkyApmExtensionsLogger,这样,我们便拿到的SkyApm.Logging.ILoggerFactory的ILogger接口,接下来便是将ILogger的具体实现功能写到Exceptionless。

将Logger写入到Exceptionless

先看看SkyApm.Logging.ILogger的接口定义,源码截图如下:

超级简单,跟NLog,Log4net等等日志组件的接口定义大同小异,几乎可以说是一样的,包含Debug, Information, Warning, Error, Trace,接下来该怎么做,就变得十分简单了,不过,在写入这个日志前,先简单了解一下Exceptionless的用法。

1.创建一个日志。源码定义为Source,我觉得叫组比较容易理解,她就像一个分类器,指定她的名称是SkyApmExtensionsLogger,其次,可以提交不同的日志类型,Exceptionless定义了如下几种日志等级,其实有部分我们用不着。

ExceptionlessClient.Default.CreateLog(nameof(SkyApmExtensionsLogger), "Create logging started.", Exceptionless.Logging.LogLevel.Info).Submit();

2.创建一个会话Session。ession会话的作用在Exceptionless算是一个特殊功能的存在了,她可以自动发送会话开始,会话心跳和会话结束事件,使用非常简单,后面会截图介绍这个功能的作用。

ExceptionlessClient.Default.Configuration.UseSessions();

OK,Exceptionless就介绍这么点用法(详细更多用法可参考官网),已经可以满足日志的写入(或收集)了,接下来看看完整的源码:

using System;using Exceptionless;using Microsoft.Extensions.Logging;

namespace SkyApmExceptionless{internal class SkyApmExtensionsLogger : SkyApm.Logging.ILogger    {private readonly ILogger _readLogger;

public SkyApmExtensionsLogger(ILogger readLogger)        {            _readLogger = readLogger;            ExceptionlessClient.Default.CreateLog(nameof(SkyApmExtensionsLogger), "Create logging started.", Exceptionless.Logging.LogLevel.Info).Submit();            ExceptionlessClient.Default.Configuration.UseSessions();            ExceptionlessClient.Default.Configuration.SetUserIdentity("SetUserIdentity", $"{nameof(SkyApmExtensionsLogger)} Groups");        }

public void Debug(string message)        {            _readLogger.LogDebug(message);            ExceptionlessClient.Default                .CreateLog(nameof(SkyApmExtensionsLogger), message, Exceptionless.Logging.LogLevel.Debug).Submit();        }

public void Information(string message)        {            _readLogger.LogInformation(message);            ExceptionlessClient.Default                .CreateLog(nameof(SkyApmExtensionsLogger), message, Exceptionless.Logging.LogLevel.Info).Submit();        }

public void Warning(string message)        {            _readLogger.LogWarning(message);            ExceptionlessClient.Default                .CreateLog(nameof(SkyApmExtensionsLogger), message, Exceptionless.Logging.LogLevel.Warn).Submit();        }

public void Error(string message, Exception exception)        {            _readLogger.LogError(message + Environment.NewLine + exception);            ExceptionlessClient.Default                .CreateLog(nameof(SkyApmExtensionsLogger), message + Environment.NewLine + exception,                    Exceptionless.Logging.LogLevel.Error)                .Submit();        }

public void Trace(string message)        {            _readLogger.LogTrace(message);            ExceptionlessClient.Default                .CreateLog(nameof(SkyApmExtensionsLogger), message, Exceptionless.Logging.LogLevel.Trace).Submit();        }    }}

这样,通过SkyApm-dotnet生成的日志,将自动发送到Exceptionless日志中心去,是不是非常简单。当然,如果作者有更好的建议,欢迎分享和交流。

SkyWalking和Exceptionless的结合分析

通过上面的扩展和部署,我们已经可以开始跑起来玩一玩了,如果有小伙伴跑不通,或者懒得敲代码(哎...),源码在文章结尾,但如何配置环境还请自行搜索,以免浪费篇幅。

万恶的再来两张截图,哈哈,其实是先看看默认状态下SkyWalking和Exceptionless的初始界面。

让我们启动这个项目。嗯,很好,发现日志正在蹭蹭的上涨,再来一张万恶的全屏截图。

我们并没运行任何一个接口,也并没调用任何一个接口,这日志是哪来的呢,对,就是SkyApm-dotnet的日志,我们可以通过Session里面查看到SkyApmExtensionsLogger正在不断的追加日志,这是因为SkyApm-Agent正在运行追踪,这里也清晰的解释了Session事件在这个SkyApmExtensionsLogger中的作用(目前还在不断的追加中)。

再看看SkyWalking,很好,出现了三个服务(节点)

运行一下,代码在上面,万恶的全屏截图再来一张:

我们发现,在ListMode中有报错的情况,这样:

赶紧定位到日志,搜索Api/Error

嗯,这正是刚才刷新两次所产生的错误结果,也是笔者故意抛出的,查看一下详情

确实由于5001上面接受到了远程返回404错误,因为这个接口实际就不存在。

反之,你也可以通过Exceptionless的exception模块或其他日志来反查SkyWalking详情,但是这样的效率不高。

万恶的全屏截图已结束,感谢!

总结

通过APM和日志中心(例如SkyWalking和Exceptionless)进行整合分析的场景越来越被重视和使用,如果还是停留在单个日志分析,或者单个APM分析,那么随着节点数的增加,服务的规模增加,那将无法及时确定问题所在的。还有更多的结合用法,欢迎小伙伴们共同交流。

原文地址:

https://www.cnblogs.com/SteveLee/p/SkyWalking_Exceptionless_Actual_Combat.html

.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

NET Core微服务之路:实战SkyWalking+Exceptionless体验生产下追踪系统相关推荐

  1. NET Core微服务之路:SkyWalking+SkyApm-dotnet分布式链路追踪系统的分享

    对于普通系统或者服务来说,一般通过打日志来进行埋点,然后再通过elk或splunk进行定位及分析问题,更有甚者直接远程服务器,直接操作查看日志,那么,随着业务越来越复杂,企业应用也进入了分布式服务化的 ...

  2. .NET Core微服务之路:文章系列和内容索引汇总 (v0.52)

    原文:.NET Core微服务之路:文章系列和内容索引汇总 (v0.52) 微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架 ...

  3. .NET Core微服务之路:文章系列和内容索引汇总 (v0.53)

    微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑,包含微服务核心组件如 1. Eureka:实现服务注册与发现. 2. ...

  4. .NET Core微服务之路:不断更新中的目录 (v0.42)

    原文:.NET Core微服务之路:不断更新中的目录 (v0.42) 微服务架构,对于从事JAVA架构的童鞋来说,早已不是什么新鲜的事儿,他们有鼎鼎大名的Spring Cloud这样的全家桶框架支撑, ...

  5. NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成...

    原文:NET Core微服务之路:自己动手实现Rpc服务框架,基于DotEasy.Rpc服务框架的介绍和集成 本篇内容属于非实用性(拿来即用)介绍,如对框架设计没兴趣的朋友,请略过. 快一个月没有写博 ...

  6. .NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(一)

    原文:.NET Core微服务之路:基于Consul最少集群实现服务的注册与发现(一) Consul介绍 Consul是HashiCorp公司推出的开源工具[开源地址:https://github.c ...

  7. ASP.NET CORE 微服务(简化版)实战系列-没有比这性价比再高的实战课程了

    ASP.NET CORE 微服务(简化版)实战系列,最后1天298,现在注册购买再减50.作者jesse 腾飞在2.14 早上我买了他的课程后,他才做了下面这个活动: 作者jesse 腾飞花了大量的时 ...

  8. .NET Core微服务之路:基于Ocelot的API网关实现--http/https协议篇

    前言 最近一直在忙公司和私下的兼职,白天十个小时,晚上四个小时,感觉每天都是打了鸡血似的,精神满满的,连自己那已经学打酱油的娃都很少关心,也有很长一段时间没有更新博客了,特别抱歉,小伙伴们都等得想取关 ...

  9. NET Core微服务之路:基于Ocelot的API网关实现--http/https协议篇

    前言 最近一直在忙公司和私下的兼职,白天十个小时,晚上四个小时,感觉每天都是打了鸡血似的,精神满满的,连自己那已经学打酱油的娃都很少关心,也有很长一段时间没有更新博客了,特别抱歉,小伙伴们都等得想取关 ...

最新文章

  1. WOR文件转换成GST文件
  2. img=img%3e128 matlab,Matlab中一般的数值计算和使用
  3. 机器学习笔记(3):线性代数回顾
  4. 中国500多名理工科研究生被美国拒签!美国「制裁清单」影响开始深入校园!...
  5. [转]将微信和支付宝支付的个二维码合二为一
  6. 【Kafka】Kafka如何开启SSL 控制台消费与生产 代码消费与生产
  7. java系列:引用类型和基本类型
  8. 【NIO】dawn在buffer用法
  9. python教程-Python快速教程
  10. PetShop 4.0 官方详解
  11. HDOJ 1007(T_T)
  12. 使用Python实现XML文件转为Excel文件
  13. 打印时电脑蓝屏或重启的解决办法
  14. 短视频去水印威信小程序源码下载,内附去水印解析接口
  15. 【PDF下载】大数据峰会之地产大数据趋势与应用实践
  16. 【数字IC设计/FPGA】推挽输出和开漏输出
  17. 数据库管理及常用语句
  18. Java后端验证苹果登录
  19. ARM-LINUX-GCC交叉编译工具链必知必会
  20. Crypto菜狗的学习日志之古典密码(一)

热门文章

  1. 如何使用智能铃声避免在Android中令人尴尬的大声铃声
  2. Django中session和cookie简单的使用
  3. wepy学习笔记之环境搭建
  4. python之路day10-命名空间和作用域、函数嵌套,作用域链、闭包
  5. 一份详尽的利用 Kubeadm部署 Kubernetes 1.13.1 集群指北
  6. vue实现首屏加载等待动画 避免首次加载白屏尴尬
  7. JAVA生成并导出json文件
  8. #celery#周期性任务
  9. 可能会紧急用到的Linux命令
  10. VMware 虚拟机(linux)增加根目录磁盘空间