惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Latest news
Latest news
G
GRAHAM CLULEY
P
Privacy International News Feed
Know Your Adversary
Know Your Adversary
N
Netflix TechBlog - Medium
C
CERT Recently Published Vulnerability Notes
O
OpenAI News
T
Tenable Blog
Attack and Defense Labs
Attack and Defense Labs
S
Schneier on Security
The GitHub Blog
The GitHub Blog
M
MIT News - Artificial intelligence
PCI Perspectives
PCI Perspectives
博客园 - 司徒正美
Microsoft Azure Blog
Microsoft Azure Blog
MyScale Blog
MyScale Blog
Martin Fowler
Martin Fowler
S
Secure Thoughts
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
T
The Exploit Database - CXSecurity.com
U
Unit 42
The Register - Security
The Register - Security
Google DeepMind News
Google DeepMind News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
WordPress大学
WordPress大学
B
Blog
Project Zero
Project Zero
NISL@THU
NISL@THU
Cloudbric
Cloudbric
TaoSecurity Blog
TaoSecurity Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Security Archives - TechRepublic
Security Archives - TechRepublic
I
Intezer
L
Lohrmann on Cybersecurity
Webroot Blog
Webroot Blog
P
Proofpoint News Feed
C
Check Point Blog
Schneier on Security
Schneier on Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Hugging Face - Blog
Hugging Face - Blog
I
InfoQ
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News

博客园 - ddrsql

关于git分支堆叠 记录团队使用git合并代码丢失 记一次对老服务改造 国产麒麟(Kylin-Server-10)系统无外网环境安装docker vue3学习之axios、mockjs、nswag vue3学习之tabler组件Layout布局 vue3学习之BootstrapVueNext Framework升级到Core以及Dapper支持达梦数据库 Avalonia集成Prism与Abp Avalonia中使用EF增删改查DM数据库 Linux中使用原生Wpf之Avalonia WPF通过wine适配统信uos系统 单体仓库下通过helm优雅的将微服务部署到k8s DevOps 前端项目(angular、vue、react)打包静态资源生成一份Docker镜像支持部署不同环境 VS使用IIS调试(非附加进程).net core、.net framework程序 k8s集群通过nginx-ingress做tcp、udp 4层网络转发 DevOps .net core Jenkins持续集成Linux、Docker、K8S swaggerui集成oauth implicit redis cluster集群web管理工具 relumin
从“黑盒”到“全景”:在.NET中拥抱OpenTelemetry与SigNoz的可观测工具
ddrsql · 2026-03-03 · via 博客园 - ddrsql

项目简介本仓库是一个实战指南与示例集合,展示了如何在不同版本的 ABP 框架(从传统的 .NET Framework 4.6.2 到现代的 Volo.Abp)中集成 OpenTelemetry (OTel),并将可观测性数据(Tracing, Logs, Metrics)统一输出到开源观测平台 SigNoz

🚀 为什么需要可观测性?

作为开发者,你是否曾为这些问题困扰过?

  • 问题排查像“开盲盒”:用户报告接口慢,你只能埋头翻看分散的日志文件,试图从碎片信息中拼凑出一次请求的完整路径。
  • 监控数据“烟囱林立”:Metrics、Tracing、Logs 分散在不同的系统里,查看一个完整的问题上下文需要在多个标签页间反复横跳。
  • 技术栈绑定之痛:一旦选用了某个APM厂商的SDK,迁移成本高到让人望而却步。

如果你对以上任何一点深感共鸣,那么是时候了解 OpenTelemetry(简称OTel)​ 和 SigNoz​ 这个组合了。它们一个定义了开放标准,一个提供了开箱即用的实现。

OpenTelemetry:可观测性

想象一下,你的应用是一个由微服务、数据库、消息队列组成的复杂城市。过去,每个组件(或APM厂商)都说着自己的“方言”(私有协议),导致沟通成本极高。
OpenTelemetry (OTel) 就是为解决这个问题而生的。​ 它由CNCF孵化,目标是为可观测性数据(追踪、指标、日志)提供一套与供应商无关的、统一的采集和传输标准。你可以把它理解为可观测性领域的“普通话”或“USB-C接口”。
它的核心优势在于:

  • 供应商中立: 用一套SDK和API完成数据采集,通过简单的配置即可将数据发送到任何支持OTLP协议的后端(如kibana、Jaeger、zipkin、Tempo、SigNoz)。
  • 一次集成,多处可用: 避免了对特定APM厂商的锁定,未来切换后端成本极低。

演示架构

SigNoz:开箱即用的全景观测平台

Signoz 是一个基于 OpenTelemetry、ClickHouse、Go、TypeScript/React 等技术栈构建的开源可观测性平台,用于统一采集、分析和可视化日志、指标和链路追踪数据。
技术架构

SigNoz - 日志性能基准测试(与ElasticSearch、Loki比较)
ClickHouse vs. Elasticsearch:十亿行数据的较量

🛠️ 环境搭建:安装signoz

使用 Docker Compose 快速安装:
https://signoz.io/docs/install/docker/

git clone -b main https://github.com/SigNoz/signoz.git && cd signoz/deploy/
cd docker
docker compose up -d --remove-orphans


安装完成后,访问 http://127.0.0.1:8080 即可看到 SigNoz 的仪表盘。

📂 示例项目导航

本仓库包含针对不同 ABP 版本的集成方案,请根据你的项目背景选择:

项目名称 框架版本 说明 快速跳转
AbpFramework .NET Framework 4.6.2 适用于传统的 ABP (Legacy) 项目,集成 log4net 日志导出。 查看详情
AbpCore .NET Core 3.1+ 适用于标准的 ABP 框架(基于 .NET Core 版本)。 查看详情
VoloAbp .NET 6/8+ 适用于最新的 Volo.Abp (vNext) 框架集成。 查看详情

🏁 使用效果

集成通过AOP(面向切面编程)拦截器实现了应用内方法的自动追踪,并在以下监控维度中清晰地展现了效果:

Services(服务监控)


Logs(日志)


Traces(链路追踪)


被追踪的业务方法示例

以下是一个简单的服务类方法,其调用链将被追踪。方法内记录了日志,便于在Traces和Logs中关联观察。

public virtual void Test()
{
   Logger.LogInformation($"{nameof(BookAppService)}.{nameof(Test)}");
   TestPublic();
   TestPublicVirtual();
   TestPrivate();
}
public virtual void TestPublic()
{
   Logger.LogInformation($"{nameof(BookAppService)}.{nameof(TestPublic)}");
}
public virtual void TestPublicVirtual()
{
   Logger.LogInformation($"{nameof(BookAppService)}.{nameof(TestPublicVirtual)}");
}
private void TestPrivate()
{
   Logger.LogInformation($"{nameof(BookAppService)}.{nameof(TestPrivate)}");
}

通过AOP拦截器实现方法级追踪

VoloAbp中通过定义的OTelActivityInterceptor,它继承自Volo.Abp框架的AbpInterceptor。该拦截器负责在目标方法执行前后自动创建和停止OpenTelemetry的Activity,从而实现无侵入式的耗时追踪。AbpFramework OTelActivityInterceptor AbpCore OTelActivityInterceptor基本类似

public class OTelActivityInterceptor : AbpInterceptor, ITransientDependency
{
   private readonly ActivitySource _activitySource;
   //private readonly IJsonSerializer _jsonSerializer;
   private readonly ILogger<OTelActivityInterceptor> _logger;
   private readonly IConfiguration _configuration;
   private readonly OTelOptions _oTelOptions;

   public OTelActivityInterceptor(
      ActivitySource activitySource,
      //IJsonSerializer jsonSerializer, 
      ILogger<OTelActivityInterceptor> logger,
      IConfiguration configuration,
      IOptionsSnapshot<OTelOptions> oTelOptions
      )
   {
      _activitySource = activitySource;
      //_jsonSerializer = jsonSerializer;
      _logger = logger;
      _configuration = configuration;
      _oTelOptions = oTelOptions.Value;
   }

   public override async Task InterceptAsync(IAbpMethodInvocation invocation)
   {
      _logger.LogDebug($"方法调用前:{invocation.Method.DeclaringType?.Name}.{invocation.Method.Name}");
      if (!_oTelOptions.Enabled)
      {
            await invocation.ProceedAsync();
            return;
      }

      if (!OTelActivityHelper.IsOTelActivityMethod(invocation.Method, out var oTelActivityAttribute))
      {
            await invocation.ProceedAsync();
            return;
      }

      // https://opentelemetry.io/docs/languages/dotnet/traces/best-practices/
      Activity activity = null;
      try
      {
            activity = _activitySource.StartActivity(invocation.Method.DeclaringType?.Name + "." + invocation.Method.Name);
            //if (activity != null && activity.IsAllDataRequested == true)
            //{
            //    activity.SetTag("", "");
            //}
      }
      finally
      {
            await invocation.ProceedAsync();
            activity?.Stop();
            activity?.Dispose();
      }

      _logger.LogDebug($"方法调用后:{invocation.Method.DeclaringType?.Name}.{invocation.Method.Name}");
   }
}

📚 参考资料