起因
我司目前使用清真的ApplicationInsights(以下简称Ai)来做程序级监控。(Ai相关文档: https://azure.microsoft.com/zh-cn/services/application-insights/ )
其实一切都蛮好的,但是我们基于Hangfire的Job系统却无法被Ai所监控到,因为Ai它监控的原理是基于HttpModule对请求进行监控,而Hangfire则是通过轮询Storage(如Sql或者队列)来实现对Job的处理。
也就是说Hangfire理论上是没有任何对Hangfire站点本身的请求,它类似于自己一个while(true)死循环不断地轮询Storage拿到Job任务就执行。
关于Hangfire的系列可以参考官方的说明 http://docs.hangfire.io/en/latest/
虽说Hangfire自己有个Dashboard可以对Hangfire执行的任务进行监控,如下
但是一众的其他站点我全部在Azure的Portal里一目了然,就你一个Hangfire的要我跑到你自己家的Dashboard来看终归不爽,而且也容易忘,导致Hangfire站点常年处于被遗忘的角落。
最近发生了一个Hangfire站点因为某些外部原因没起来(宕掉)的事件之后更加速了要将Hangfire的监控统一到Ai里。
搜索
首先去github上找下有没相关解决方案,老实说觉得基于ai的第三方扩展还是蛮多的,在github搜ai相关的还是能搜索出好几页(https://github.com/topics/application-insights),但并没有找到我需要的。
然后google一下找到有人在hangfire论坛里问跟我类似的问题,然而也没有解决方案(https://discuss.hangfire.io/t/integrating-application-insights-appinsights-into-hangfire/3009)。
既然找不到,那干脆自己撸起袖子干。
开干
首先我们回顾下Ai默认自己监控的原理:
前文说了它有个HttpModule会在请求进来的创建一个RequestTelemetry,
并且会在线程上下文内创建个OperationId,然后在该Request的作用于内所有其他数据(如异常/Http请求/Sql请求)都会跟这个Id关联,
这个Id甚至会在你发送Http请求的时候附加在你的Http Header里,然后接收到该Http请求的站点假如也用了Ai的话会根据这个Header里的Id再进行二次关联(调用链路关联)。
先解决AI需要的相关基础知识
我们要自己在Hangfire里弄AI监控的话,其实重点就是怎么能让Request里的Id能传递到Hangfire里,然后在Hangfire一个操作的作用域里保持该Id一致(串联所有操作)。
那问题重点就清楚了,就是如何解决这个Id的问题。
那Ai自己是如何产生或者获取这个Id的呢,在Ai的2.4版本后引入了对System.Diagnostics.DiagnosticSource这个包的依赖。
所有的Id关联它都基于由此包提供的Activity这个类来事现,详情可以参考 http://apmtips.com/blog/2018/01/23/diagnosticssource-design-principles/
如果需要在自己系统里设置一个Id关联的系统的话也强烈推荐使用Activity这个类来进行处理。
别人实现的比自己弄的科学多了,之前我也自己用AsyncLocal来做过,但是后续也都替换成了Activity,
通过Activity.Current可以获取到当前的Activity实例,通过new Activity然后调用其Start方法也能快速启动一个Activity,
到此Ai相关所需要的知识就都准备妥当了。
然后解决Hangfire需要的相关基础知识
从Hangfire的角度来说,它只要提供2个功能支持就可以了。
在任务入队的时候,在数据里面要塞入在当前Request操作的Id,因为我要将Hangfire的操作能够跟Request里相关联起来。
在任务执行的时候,拿到任务数据的时候,要能取出这个Id,然后将这个Id通过Activity进行Start,然后再任务执行完之后要Stop掉这个Activity并释放掉。
(画的图”有点”丑,但大概就这个意思)
为了解决这个问题查找了下Hangfire全局过滤器相关的资料找到它有IServerFilter和IClientFilter这2个东西:
IServerFilter:服务端处理的过滤器,就是Hangfire Server在执行一个Job的时候要进行处理的过滤器,可在此位置给Hangfire的Job设置Ai的Id到上下文。
IClientFilter:客户端处理的过滤器,就是Hangfire Client在一个Job入队的时候要进行处理的过滤器,可在此位置将Request里的Id赋值给Job Data。
那在Client的时候如何解决每次都能自动塞个Id进去呢?
我的解决思路是定义个JobDtoBase,所有Hangfire任务的数据都要继承自这个类,里面就一个Id,然后通过IClientFilter在每次入队的时候都将当前Activity.Current.Id扔进去
(需要考虑Acitivity.Current为null的情况)
然后如何在Server端自动将这个Id来启动一个Activity并完成监控呢?
我是想着通过IServerFilter里通过PerformingContext里获取Job的参数然后用is来判断如果是JobDtoBase就取它的Id出来然后启动Activity并创建RequestTelemetry。
重点是在Server这边执行结束之后需要将RequestTelemetry和Activity释放掉,所以通过ThreadStatic的静态变量来对其保持引用。
效果
效果如何这个问题暂时我只能呵呵哒,因为也是刚折腾出来还没投入线上运行。
不过从测试环境来看,确实能抓到我hangfire的请求并在Ai里作为Request进行展示:
而且还能抓到依赖项跟”Request”的关联:
所以至少测试的情况来说应该是达到目的了,等之后投入线上后在看下最终具体效果。