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

前言

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

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

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

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

640?wx_fmt=png

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

一:准备

    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

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

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

640?wx_fmt=png


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
640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/316770.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HDU5519:Kykneion asma(容斥)

计数的核心是双射。 解析 这也叫数位dp呗… 首先&#xff0c;这个题目的形式很容易令人想到硬币购物&#xff0c;考虑用容斥解决&#xff0c;暴力枚举强制超额的数的集合然后分别计算贡献。 然而&#xff0c;暴力dp计算贡献的复杂度是 O(n2)O(n^2)O(n2) 的。 考虑换一种统计…

Dancing Stars on Me HDU - 5533

Dancing Stars on Me HDU - 5533 题意&#xff1a; LYD又来了&#xff0c;这次他碰到了一个小问题&#xff0c;给定n个点&#xff0c;点的坐标为均为整数&#xff0c;问这些点能否组成正多边形&#xff1f; 题解&#xff1a; 如果是正多边形&#xff0c;那中心的坐标就是所…

[HDU 3625] Examining the Rooms(第一类斯特林数)

Examining the Roomsproblemsolutioncodeproblem hdu 3625 solution 之前考试有一道题&#xff1a;最多砸开 kkk 扇门&#xff0c;采取最有操作&#xff0c;求把 nnn 个门都打开的方案数。 本题稍稍多了一个不能砸开第一扇门的限制&#xff0c;以及求的是概率。 概率好说&…

P5801 [SEERC2019]Game on a Tree(博弈论)

解析 一直在路径可逆上做功夫&#xff0c;跑偏了。 题目可以转化为一个模型&#xff1a;给出一张无向图&#xff0c;每次可以移动到未移动过的点上&#xff0c;不能移动者判负。 这个的做法就是判断是否完美匹配&#xff0c;完美匹配则后手必胜&#xff0c;否则先手必胜。 …

House Building HDU - 5538

House Building HDU - 5538 题意&#xff1a; 有n * m的地方&#xff0c;每个地方都有不同高度的1 * 1的正方形块&#xff0c;相邻块紧密排列&#xff0c;问这些块的表面积是多少&#xff1f; 题解&#xff1a; 对于位置为(i,j)的一个高度为x的块&#xff0c;他的测表面积为…

.NET 中创建支持集合初始化器的类型

对象初始化器和集合初始化器只是语法糖&#xff0c;但是能让你的代码看起来更加清晰。至少能让对象初始化的代码和其他业务执行的代码分开&#xff0c;可读性会好一些。本文将编写一个类型&#xff0c;可以使用集合初始化器构造这个类型。不只是添加元素的集合初始化器&#xf…

【无码专区8】三角形二维数点——计数有多少个给定点落在三角形区域内

因为只有std&#xff0c;没有自我实现&#xff0c;所以是无码专区 主要是为了训练思维能力 solution才是dls正解&#xff0c;但是因为只有潦草几句&#xff0c;所以大部分会有我自己基于正解上面的算法实现过程&#xff0c;可能选择的算法跟std中dls的实现不太一样。 std可能…

P3426 [POI2005]SZA-Template(kmp、dp)

解析 做出来了就是胜利&#xff01; 个人感觉虽然我这个nxt树的码量会大一点&#xff0c;但是思路其实比较自然。&#xff08;看题解区也有&#xff09; 也是一个相当可取的做法。 现在来讲讲巧妙的dp做法。 考虑直接嗯设&#xff1a;fif_ifi​ 表示覆盖 [1,i][1,i ][1,i] 的…

为什么我的会话状态在ASP.NET Core中不工作了?

原文&#xff1a;Why isnt my session state working in ASP.NET Core? Session state, GDPR, and non-essential cookies作者&#xff1a;Andrew Lock译文&#xff1a;https://www.cnblogs.com/lwqlun/p/10526380.html译者&#xff1a;Lamond Lu在本篇博客中&#xff0c;我将…

Jury Compromise POJ - 1015

Jury Compromise POJ - 1015 题意&#xff1a; 在遥远的国家佛罗布尼亚&#xff0c;嫌犯是否有罪&#xff0c;须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n个人作为陪审团的候选人&#xff0c;然后再从这n个人中选m人组成陪审团。选m人的办法是&#xff1a; 控…

叁仟柒佰万(mex+线段树+dp+前缀和优化+双指针+桶)

叁仟柒佰万problemsolutioncode(50’)code(90’)code(100’)problem 多组数据。 给定一个序列 aaa&#xff0c;你可以将它划分成任意多段&#xff0c;满足每一个段的 mex 相同。 求方案数&#xff0c;对 109710^971097 取模。 T≤10,n≤3e5T\le 10,n\le 3e5T≤10,n≤3e5。 …

模板:吉司机线段树

所谓吉司机线段树&#xff0c;就是吉司机种的线段树 &#xff08;逃&#xff09; 解析 之前也看过一些相关内容的博客&#xff0c;但是因标记乱七八糟&#xff0c;感觉过于高深而放弃了… 直到看到洛谷这篇博客&#xff0c;完全颠覆了我的认知。&#xff08;怎么感觉语气像营…

现身说法:实际业务出发分析百亿数据量下的多表查询优化

今天给大家带来的讨论主题是通过实战经验来对百亿数据量下的多表数据查询进行优化&#xff0c;俗话说的好&#xff0c;一切脱离业务的架构都是耍流氓&#xff0c;接下来我就整理一下今天早上微信群里石头哥给大家分享的百亿数据量多表查询架构以及优化思路。由于本文内容整理自…

Help Jimmy POJ - 1661

Help Jimmy POJ - 1661 题意&#xff1a; 场景中包括多个长度和高度各不相同的平台。地面是最低的平台&#xff0c;高度为零&#xff0c;长度无限。 Jimmy老鼠在时刻0从高于所有平台的某处开始下落&#xff0c;它的下落速度始终为1米/秒。当Jimmy落到某个平台上时&#xff0c…

[HDU 6643] Ridiculous Netizens(点分治+根号分治+dp)

HDU 6643 Ridiculous Netizens problem hdu6643 题目大意&#xff1a;给定一棵无根树&#xff0c;以及每个点的点权 wiw_iwi​。 定义一个连通块的价值为连通块内点的点权之积。 求有多少个连通块价值 ≤m\le m≤m。 n≤2e3,m≤1e6n\le 2e3,m\le 1e6n≤2e3,m≤1e6。 solu…

SP422 TRANSP2 - Transposing is Even More Fun(Burnside引理,莫比乌斯反演)

解析 很巧妙的题。 关键是要利用好边长为2的整数次幂的性质。 对下标从1开始党极不友好。 首先显然答案就是 2ab2^{ab}2ab -环。 让下标均从0开始。 对于一个点 (i,j)(i,j)(i,j)&#xff0c;它原来的内存地址为 i∗2aji*2^aji∗2aj&#xff0c;转置后的地址为 j∗2bij*2^bij∗…

Fireworks(2020 ICPC南京)

Fireworks 题意&#xff1a; 你每做一个烟花要n分钟&#xff0c;释放已做好的所有烟花需要m分钟&#xff0c;每只烟花成功释放的概率为p。问你在采取最优策略的前提下&#xff0c;直到成功释放第一个烟花时最小的期望时间花费。 题解&#xff1a; 最佳策略是&#xff1a;每…

ASP.NET Core 沉思录 - ServiceProvider 的二度出生

ASP.NET Core 终于将几乎所有的对象创建工作都和依赖注入框架集成了起来。并对大部分的日常工作进行了抽象。使得整个框架扩展更加方便。各个部分的集成也更加容易。今天我们要思考的部分仍然是从一段每一个工程中都大同小异的代码开始的。IWebHostBuilder CreateWebHostBuilde…

[SPOJ - FTOUR2] Free tour II(点分治 + 背包dp + 启发式合并)

SPOJ - FTOUR2 Free tour II problem 给定一棵树&#xff0c;以及 mmm 个拥挤城市编号&#xff0c;选择一条最多包含 kkk 个拥挤城市的简单路径。 每条边有一个有趣度 www&#xff0c;可正可负。简单路径的价值定义为包含边的有趣度之和。 求最大价值。n≤2e5,∣w∣≤1e4n\…

PKUSC2022 游记

前言 1001822698014095235。 题的难度感觉比去年难不少&#xff0c;主要的体现在于两天的T1都没有之前那么可做了&#xff0c;在信息差的影响下几乎成了两场崩盘场。 由于比赛时间变短的原因&#xff0c;模拟反而比历年简单不少。 遗憾&#xff1a; d1t3的网络流莫名其妙无法…