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

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threatpost
P
Privacy International News Feed
T
Tenable Blog
Know Your Adversary
Know Your Adversary
C
Cisco Blogs
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
G
GRAHAM CLULEY
爱范儿
爱范儿
U
Unit 42
K
Kaspersky official blog
C
Cybersecurity and Infrastructure Security Agency CISA
Jina AI
Jina AI
O
OpenAI News
L
LangChain Blog
PCI Perspectives
PCI Perspectives
P
Privacy & Cybersecurity Law Blog
Latest news
Latest news
Cisco Talos Blog
Cisco Talos Blog
F
Full Disclosure
L
Lohrmann on Cybersecurity
V
V2EX
L
LINUX DO - 热门话题
S
Security Affairs
量子位
Martin Fowler
Martin Fowler
云风的 BLOG
云风的 BLOG
Schneier on Security
Schneier on Security
月光博客
月光博客
MyScale Blog
MyScale Blog
C
CERT Recently Published Vulnerability Notes
AWS News Blog
AWS News Blog
博客园 - 叶小钗
Forbes - Security
Forbes - Security
W
WeLiveSecurity
T
Troy Hunt's Blog
J
Java Code Geeks
Hacker News - Newest:
Hacker News - Newest: "LLM"
Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
Apple Machine Learning Research
Apple Machine Learning Research
雷峰网
雷峰网
Google Online Security Blog
Google Online Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
TaoSecurity Blog
TaoSecurity Blog
H
Help Net Security
The Cloudflare Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

博客园 - Hey,Coder!

jaeger界面显示异常 c# opentelemetry 自定义export c# opentelemetry 自定义export c# 生成对象的初始化代码 Elasticsearch ILM 与 Data Stream 概念及实例说明文档 portainer httphelper封装 Ocelot + Consul + SignalR 粘性会话 正交软件架构 docker postgresql17 主从复制 rabbitmq总结与c#示例 docker rabbitmq quartz dashboard docker consul registrator 服务自动注册consul 微服务动态扩容 ubuntu24.04 安装docker RAG 检索增强生成 软考 - 架构设计师 知识点总结 c# MailKit3.4.3 发送邮件、附件 nginx 反向代理postgresql c# 信号量 elsa 3.5 中间件记录最后执行的节点数据 xp密钥 c# System.Text.Json 反序列化Dictionary<string,object>时未转换基础类型的处理方法 docker配置代理 openclaw 接入 LMStudio的模型服务 wpf canvas 移动 缩放 windows平台openclaw搭建 wpf scrollerview触摸滚动 c# 动态切换sqlsugar连接字符串 c# Elastic.Clients.Elasticsearch 动态查询 c# es 封装Elastic.Clients.Elasticsearch c# scrollerview滚动到指定元素位置 WPF 重写Expander 基于elsa工作流封装一套变量、组件的体系 常用活动重写 DynamicExpresso 轻量级动态表达式库 c# elsa 3.5.2 程序化工作流常用功能及自定义中间件 leaflet docker 连接老版本的sqlserver ssl错误 c# quartz 动态创建任务 控制任务运行和停止 依赖注入 cefsharp 模拟点击 批量重置数据库的数据 docker 复制远程镜像本地并创建容器 c# newtonsoft dynamic
.net opentelemetry collector jaeger
Hey,Coder! · 2026-07-23 · via 博客园 - Hey,Coder!

说明

可以使用jaeger的all in one直接采集数据而不经过collector。
但是为了整体架构的可扩展性、可修改性,可以通过collector这个组件灵活扩展,后续只需要增加
新的export就可以发送到其他服务,如Prometheus等。
如果考虑到整体的部署成本和可维护性,根据团队人员配置情况可以考虑直接采集到jaeger而不经过collector。
直接用all in one时就不用修改端口,直接部署即可。(jaeger all in on默认的4317/4318端口和collector端口重复)

部署 Jaeger(all-in-one)

内存存储,测试可以临时使用,后参考下文的持久化存储

docker run -d --name jaeger \
  --restart=always \
  -p 16686:16686 \
  -p 14317:4317 \
  -p 14318:4318 \
  -e COLLECTOR_OTLP_ENABLED=true \
  -e COLLECTOR_OTLP_GRPC_HOST_PORT=0.0.0.0:4317 \
  jaegertracing/all-in-one:latest

端口说明

  • 16686:Jaeger UI(浏览器访问)
  • 14317:映射到容器内 4317(OTLP gRPC)
  • 14318:映射到容器内 4318(OTLP HTTP)

验证 Jaeger 启动成功:

docker logs jaeger --tail 10

应该看到 OTLP gRPC listening on 0.0.0.0:4317 等信息。


持久化存储

持久化存储到内置的badger存储中,后续可以考虑改成存储到es或OpenSearch中。
需要注意data路径的权限问题。

sudo docker run -d --name jaeger \
  --restart=always \
  -p 16686:16686 \
  -p 14317:4317 \
  -p 14318:4318 \
  -e SPAN_STORAGE_TYPE=badger \
  -e BADGER_EPHEMERAL=false \
  -e BADGER_DIRECTORY_VALUE=/badger/data \
  -e BADGER_DIRECTORY_KEY=/badger/key \
  -e COLLECTOR_OTLP_ENABLED=true \
  -e COLLECTOR_OTLP_GRPC_HOST_PORT=0.0.0.0:4317 \
  -e COLLECTOR_OTLP_HTTP_HOST_PORT=0.0.0.0:4318 \
  -e BADGER_SPAN_STORE_TTL=336h \
  -v /server/dev/jaeger/data:/badger \
  jaegertracing/all-in-one:latest

部署 OpenTelemetry Collector

创建 Collector 配置文件

~/otel-demo/otel-collector-config.yaml

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    timeout: 5s
    send_batch_size: 1024

exporters:
  debug:
    verbosity: detailed

  otlp/jaeger:
    endpoint: "<宿主机IP>:14317"   # 替换为服务器实际 IP
    tls:
      insecure: true

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [debug, otlp/jaeger]

注意endpoint 必须填写宿主机 IP(例如 192.168.1.100),不能写 localhost127.0.0.1,因为 Collector 容器内的 localhost 指向自身,不是宿主机。端口使用映射后的 14317

启动 Collector

docker run -d --name otel-collector \
  --restart=always \
  -p 4317:4317 -p 4318:4318 \
  -v $(pwd)/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \
  otel/opentelemetry-collector-contrib:latest

验证 Collector 启动:

docker logs otel-collector --tail 10

.net端配置

NuGet 包

dotnet add package OpenTelemetry.Extensions.Hosting
dotnet add package OpenTelemetry.Instrumentation.AspNetCore
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol

注入

builder.AddOT(builder.Configuration);

对应内容如下

public static void AddOT(this WebApplicationBuilder builder, IConfiguration configuration)
{
    builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource
        .AddService(
            serviceName: "MyApi",
            serviceVersion: "1.0.0",
            serviceInstanceId: Environment.MachineName
        )
        .AddAttributes(new Dictionary<string, object>
        {
            ["deployment.environment"] = "development",
            ["host.name"] = Environment.MachineName
        })
    )
    .WithTracing(tracing => tracing
        // 全量采样,方便调试(生产环境务必改回默认或按比率采样)
        .SetSampler(new AlwaysOnSampler())
        // 自动采集 ASP.NET Core 请求
        .AddAspNetCoreInstrumentation(options =>
        {
            options.RecordException = true;  // 记录异常信息
            options.EnrichWithHttpRequest = (activity, request) =>
            {
                activity.SetTag("http.client_ip", request.HttpContext.Connection.RemoteIpAddress?.ToString());
            };
        })
        // 自动采集 HttpClient 调用(如果应用有对外请求)
        .AddHttpClientInstrumentation()
        // 发送到 Collector
        .AddOtlpExporter(options =>
        {
            //改成对应的ip
            //options.Endpoint = new Uri("http://localhost:4317");
            options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
        })
    );
}

验证完整链路

检查 Collector 日志

触发请求后查看

docker logs otel-collector --tail 30

应该看到 Span #0 等输出,且没有 failed to exportno children to pick from 错误。

在 Jaeger UI 查看

浏览器打开 http://<服务器IP>:16686(例如 http://192.168.1.100:16686)。

  • Service 下拉框手动输入 MyApi
  • 点击 Find Traces
  • 应该看到多条 trace,点击可查看瀑布图

常见问题与排查

问题 原因 解决
Collector 日志报 no children to pick from Collector 无法连接到 Jaeger 的 OTLP 端口 检查 endpoint 是否为宿主机 IP + 14317,并确认 Jaeger 已启动且监听 0.0.0.0:4317
Jaeger UI 看不到服务 数据未到达 Jaeger 检查 Collector 日志有无错误,确认 .NET 应用已触发请求
.NET 应用启动报 gRPC 连接失败 Collector 未启动或端口被占用 确认 Collector 容器运行中,且 -p 4317:4317 映射正确
Jaeger 重启后 trace 消失 all-in-one 使用内存存储 开发环境正常,生产环境需配置持久化后端

整体架构图

.NET 8 App (localhost:4317) ──OTLP/gRPC──→ Collector (宿主机:4317)
                                              │
                                              ├─ debug 日志输出
                                              └─ OTLP/gRPC (宿主机IP:14317) ──→ Jaeger (宿主机:14317→容器4317)
                                                                                    │
                                                                                    └─ UI :16686

所有容器均不使用 Docker 网络,通过宿主机 IP + 映射端口通信。

扩展

如果需要记录接口的入参和出参,可以通过自定义Tag字段来记录,通过EnrichWithHttpRequest、EnrichWithHttpResponse回调来记录接口的入参出参。
需要注意,.net下读取body需要设置enablebuffer,可以参考(https://www.cnblogs.com/ives/p/21796524/otlp_export)。

//在var app = builder.Build();后加上
app.Use((context, next) =>
{
    context.Request.EnableBuffering(); // 允许请求体被多次读取
    return next();
});


//在app.UseRouting();后加上
app.UseMiddleware<ResponseCaptureMiddleware>();  // 放在路由之后、端点之前

注入的代码示例如下

 public static void AddOT(this WebApplicationBuilder builder, IConfiguration configuration)
 {
     builder.Services.AddOpenTelemetry()
     .ConfigureResource(resource => resource
         .AddService(
             serviceName: "MyApi",
             serviceVersion: "1.0.0",
             serviceInstanceId: Environment.MachineName
         )
         .AddAttributes(new Dictionary<string, object>
         {
             ["deployment.environment"] = "development",
             ["host.name"] = Environment.MachineName
         })
     )
     .WithTracing(tracing => tracing
         // 全量采样,方便调试(生产环境务必改回默认或按比率采样)
         .SetSampler(new AlwaysOnSampler())
         // 自动采集 ASP.NET Core 请求
         .AddAspNetCoreInstrumentation(options =>
         {
             options.RecordException = true;  // 记录异常信息

             // 1) 过滤:排除健康检查
             options.Filter = ctx =>
                 !ctx.Request.Path.StartsWithSegments("/health");

             // 2) 记录异常详情
             options.RecordException = true;

             // 3) 入参:EnrichWithHttpRequest
             options.EnrichWithHttpRequest = async (activity, request) =>
             {
                 var ctx = request.HttpContext;

                 // —— 基础信息 ——
                 activity.SetTag("request.protocol", request.Protocol);
                 activity.SetTag("request.scheme", request.Scheme);
                 activity.SetTag("request.host", request.Host.ToString());
                 activity.SetTag("request.path", request.Path.ToString());
                 activity.SetTag("request.query_string", request.QueryString.ToString());

                 // —— 客户端 IP ——
                 activity.SetTag("request.client_ip",
                     ctx.Connection.RemoteIpAddress?.ToString());

                 // —— Query 参数(扁平化)——
                 foreach (var q in request.Query)
                 {
                     activity.SetTag($"request.query.{q.Key}", q.Value.ToString());
                 }

                 // —— 指定 Header(注意脱敏)——
                 if (request.Headers.TryGetValue("X-Correlation-Id", out var corr))
                     activity.SetTag("request.correlation_id", corr.ToString());
                 if (request.Headers.TryGetValue("User-Agent", out var ua))
                     activity.SetTag("request.user_agent", ua.ToString());

                 // —— 请求体(仅当存在且体积可控)——
                 if (request.ContentLength > 0 && request.ContentLength <= 1024 * 1024)
                 {
                     try
                     {
                         // 确保已启用缓冲(以防外部未设置)
                         request.EnableBuffering();

                         // 1. 将 Body 内容复制到 MemoryStream
                         var memoryStream = new MemoryStream();
                         await request.Body.CopyToAsync(memoryStream);
                         memoryStream.Position = 0;

                         // 2. 读取 Body 内容
                         var reader = new StreamReader(memoryStream, Encoding.UTF8, leaveOpen: true);
                         var body = reader.ReadToEnd();
                         activity.SetTag("request.body", body);

                         // 3. 将 request.Body 替换为 MemoryStream(可 Seek)
                         memoryStream.Position = 0;
                         request.Body = memoryStream; // 替换后,MVC 模型绑定就能正常读取了
                     }
                     catch (Exception ex)
                     {
                         // 兜底,防止读取 Body 导致主程序崩溃
                         activity.SetTag("request.body.error", ex.Message);
                     }
                 }
             };

             // 4) 出参:EnrichWithHttpResponse
             options.EnrichWithHttpResponse = async (activity, response) =>
             {
                 activity.SetTag("response.status_code", response.StatusCode);
                 activity.SetTag("response.content_type", response.ContentType);

                 if (response.HttpContext.Items.TryGetValue("response_body", out var bodyObj))
                 {
                     var body = bodyObj as string;
                     if (!string.IsNullOrEmpty(body))
                     {
                         // 脱敏
                         //body = MaskSensitiveFields(body);
                         activity.SetTag("response.body", body);
                     }
                 }
             };

             // 5) 异常:EnrichWithException
             options.EnrichWithException = (activity, exception) =>
             {
                 activity.SetTag("exception.type", exception.GetType().FullName);
                 activity.SetTag("exception.message", exception.Message);
                 // 堆栈通过 RecordException=true 自动作为 ActivityEvent 附加
             };
         })
         // 自动采集 HttpClient 调用(如果应用有对外请求)
         .AddHttpClientInstrumentation()
         // 发送到 Collector
         .AddOtlpExporter(options =>
         {
             //options.Endpoint = new Uri("http://localhost:4317");
             options.Endpoint = new Uri("http://127.0.0.1:14317");
             options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
         })
     );
 }

至此,完整的 .NET 8 → OpenTelemetry Collector → Jaeger 链路搭建完成。后续如需增加 Prometheus 或日志收集,只需修改 Collector 配置,应用代码无需改动。