skyapm-dotnet 源码执行
监听System.Data.SqlClient 为例
通过观察者模式和 DiagnosticListener获取监听数据,
在开始InstrumentationHostedService实现IHostedService启动
然后通过 DiagnosticListener.AllListeners.Subscribe();监听
1 2 3 4 5 6 7 8 9 10 11 12 13 | 然后 TracingDiagnosticProcessorObserver : IObserver<DiagnosticListener>{ public void OnNext(DiagnosticListener listener) { foreach ( var diagnosticProcessor in _tracingDiagnosticProcessors.Distinct(x => x.ListenerName)) { if (listener.Name == diagnosticProcessor.ListenerName) { Subscribe(listener, diagnosticProcessor); _logger.Information( $ "Loaded diagnostic listener [{diagnosticProcessor.ListenerName}]." ); } } } }<br><br> |
private readonly IEnumerable<ITracingDiagnosticProcessor> _tracingDiagnosticProcessors;
1 2 3 4 5 6 7 8 9 10 11 12 | 通过next()获取所有监听,然后注入 ITracingDiagnosticProcessor 下的自定义诊断对象,获取诊断名称(ITracingDiagnosticProcessor 为所有诊断的对象的父接口 ) 通过遍历对比,然后订阅,订阅前会先将诊断对象获取它的 方法,以及方法里的参数,和参数对应的特性类型(ObjectAttribute,PropertyAttribute ,这两个特性类型会存入对应特性属性值中后续在获取加监听对象的匿名参数时会获取到),并将获取到的方法存入集合,还会将对象集合转为字典,转为字典作用就是在注册时 检查是否重复注册( listener.Subscribe(diagnosticProcessor, diagnosticProcessor.IsEnabled);第二个参数执行委托返回 bool ,判断啊诊断名称是否存在) //订阅代码 protected virtual void Subscribe(DiagnosticListener listener, ITracingDiagnosticProcessor tracingDiagnosticProcessor) { var diagnosticProcessor = new TracingDiagnosticObserver(tracingDiagnosticProcessor, _loggerFactory); listener.Subscribe(diagnosticProcessor, diagnosticProcessor.IsEnabled); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | TracingDiagnosticObserver : IObserver<KeyValuePair< string , object >> 订阅明细 _methodCollection = new TracingDiagnosticMethodCollection(tracingDiagnosticProcessor) . ToDictionary(method => method.DiagnosticName); 这里 通过 字典里的名称执行 public void OnNext(KeyValuePair< string , object > value) { if (!_methodCollection.TryGetValue(value.Key, out var method)) return ; try { method.Invoke(value.Key, value.Value); } catch (Exception exception) { _logger.Error( "Invoke diagnostic method exception." , exception); } } TracingDiagnosticMethod.cs private readonly IParameterResolver[] _parameterResolvers; 这里通过前面注册时会有一个 参数数组,和传入的参数 通过Resolve 里(这里ObjectAttribute和 PropertyAttribute)抽象类ParameterBinderAttribute 为了前面注册时获取两个特性值 ObjectAttribute 时直接返回传入对象,(_parameterResolvers[i] 里的参数) ParameterBinderAttribute 是 { var property = value.GetType().GetProperty(Name); return property?.GetReflector()?.GetValue(value); } //执行方法和参数 public void Invoke( string diagnosticName, object value) { if (_diagnosticName != diagnosticName) { r eturn; } var args = new object [_parameterResolvers.Length]; or ( var i = 0; i < _parameterResolvers.Length; i++) { args[i] = _parameterResolvers[i].Resolve(value); } _reflector.Invoke(_tracingDiagnosticProcessor, args); } |
1 | <br> |
执行到 这诊断对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 先创建上下文,添加前面传入的参数值到上下文 [DiagnosticName(SqlClientDiagnosticStrings.SqlBeforeExecuteCommand)] public void BeforeExecuteCommand([Property(Name = "Command" )] DbCommand sqlCommand) { var context = _tracingContext.CreateExitSegmentContext(ResolveOperationName(sqlCommand), _peerFormatter.GetDbPeer(sqlCommand.Connection)); context.Span.SpanLayer = Tracing.Segments.SpanLayer.DB; context.Span.Component = Common.Components.SQLCLIENT; context.Span.AddTag(Common.Tags.DB_TYPE, "sql" ); context.Span.AddTag(Common.Tags.DB_INSTANCE, sqlCommand.Connection.Database); context.Span.AddTag(Common.Tags.DB_STATEMENT, sqlCommand.CommandText); } //执行完成后发布,通过注入ITracingContext 上下文 [DiagnosticName(SqlClientDiagnosticStrings.SqlAfterExecuteCommand)] public void AfterExecuteCommand() { var context = _contextAccessor.Context; if (context != null ) { _tracingContext.Release(context); } } TracingContext.cs 在Release 方法 通过ISegmentDispatcher _segmentDispatcher ConcurrentQueue<SegmentRequest> _segmentQueue; _segmentDispatcher.Dispatch(segmentContext);将行下文 写入 队列中 InstrumentStartup.cs 最后通过GRPC发送 在监听的上一行有先买你的 foreach DiagnosticListener.AllListeners.Subscribe(_observer); 注入了 IEnumerable<IExecutionService> _services;这个接口获取了 所有grpc 推送,差不多有些话要做一些数据准八日,如cpu 等 foreach ( var service in _services) await service.StartAsync(cancellationToken); ExecutionService.cs 通过Time 定时 执行一次远程推送 public Task StartAsync(CancellationToken cancellationToken = default (CancellationToken)) { _cancellationTokenSource = new CancellationTokenSource(); var source = CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token, cancellationToken); _timer = new Timer(Callback, source, DueTime, Period); // Logger.Information($ "Loaded instrument service [{GetType().FullName}]." ); return Task.CompletedTask; } private async void Callback( object state) { if (!(state is CancellationTokenSource token) || token.IsCancellationRequested || !CanExecute()) return ; try { await ExecuteAsync(token.Token); } catch (Exception ex) { Logger.Error(GetType().FullName + ".ExecuteAsync(token.Token) fail" , ex); } } |
1 | <br><br><br><br><br><br><br><br><br><br> |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)