
























可以使用jaeger的all in one直接采集数据而不经过collector。
但是为了整体架构的可扩展性、可修改性,可以通过collector这个组件灵活扩展,后续只需要增加
新的export就可以发送到其他服务,如Prometheus等。
如果考虑到整体的部署成本和可维护性,根据团队人员配置情况可以考虑直接采集到jaeger而不经过collector。
直接用all in one时就不用修改端口,直接部署即可。(jaeger all in on默认的4317/4318端口和collector端口重复)
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
~/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),不能写localhost或127.0.0.1,因为 Collector 容器内的 localhost 指向自身,不是宿主机。端口使用映射后的14317。
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
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;
})
);
}
触发请求后查看
docker logs otel-collector --tail 30
应该看到 Span #0 等输出,且没有 failed to export 或 no children to pick from 错误。
浏览器打开 http://<服务器IP>:16686(例如 http://192.168.1.100:16686)。
MyApi| 问题 | 原因 | 解决 |
|---|---|---|
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 配置,应用代码无需改动。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。