上一次我们讲了 opentelemetry logs。今天继续来说说 opentelemetry traces。
在今天的微服务和云原生环境中,理解和监控系统的行为变得越来越重要。在当下我们实现一个功能可能需要调用了 n 个方法,涉及到 n 个服务。方法之间的调用如蜘蛛网一样。分布式追踪这个时候就至关重要。它可以把我们程序的调用链可视化。这对于运维人员监控程序状态,开发人员 trouble shooting 都非常用帮助。
什么是 opentelemetry traces
opentelemetry traces 是 opentelemetry 提供的一种遥测数据类型,用于记录和描述在分布式系统中的单个操作或工作单元的生命周期。
在 opentelemetry 中,一个 trace 可以被视为由一系列相关的事件组成的时间线,这些事件被称为 spans。每个 span 可以包含多个属性、注释和事件,用于描述在该 span 的生命周期中发生的特定操作或事件。
例如,一个 http 请求可以被表示为一个 span,其中包含了请求的开始时间、结束时间、http 方法、url、状态码等信息。如果这个请求还调用了其他的服务或数据库,那么这些调用也可以被表示为与原始请求 span 相关联的子 span。
注意:span 是 opentelemetry 定义的概念,在 .net 中使用 activity 表示一个 span。
以上的话呢比较官方,是我用 chatgpt 生成的。还是直接用代码来演示一下效果大家好理解。
示例:追踪 http 与 database
在日常的开发活动中,http 与 database 操作基本就是涵盖了 99% 的场景。很多时候我们希望监控应用程序对每个请求的响应速度,以及其中数据库操作的耗时。这是一个非常非常常见的需求。以下使用一个用户登录接口来演示。
安装依赖
<packagereference include="npgsql" version="8.0.3" /> <packagereference include="npgsql.opentelemetry" version="8.0.3" /> <packagereference include="opentelemetry.exporter.opentelemetryprotocol" version="1.8.1" /> <packagereference include="opentelemetry.extensions.hosting" version="1.8.1" /> <packagereference include="opentelemetry.instrumentation.aspnetcore" version="1.8.1" />
使用 nuget 安装以上包。
注入服务
var otel = builder.services.addopentelemetry();
// configure opentelemetry resources with the application name
otel.configureresource(resource => resource
.addservice(builder.environment.applicationname));
otel.withtracing(tracing =>
{
tracing
.addaspnetcoreinstrumentation()
.addnpgsql()
.addotlpexporter(otlpoptions =>
{
otlpoptions.protocol = otlpexportprotocol.httpprotobuf;
otlpoptions.endpoint = new uri("http://192.168.0.1:5341/ingest/otlp/v1/traces");
});
});跟 logs 类似,我们使用 withtracing 扩展方法来对 traces 进行配置。
- 调用
addaspnetcoreinstrumentation方法来添加对aspnetcore框架的跟踪支持。这将自动跟踪应用程序中的http请求和响应,并生成相应的跟踪数据。 - 调用
addnpgsql方法来添加对npgsql库的跟踪支持。这将自动跟踪应用程序中使用npgsql库进行的数据库操作,并生成相应的跟踪数据。 - 我们调用
addotlpexporter方法来添加一个otlp(opentelemetry protocol)导出器。这个导出器将把跟踪数据发送到指定的otlp接收端。在这里,我们将跟踪数据发送到"http://192.168.0.201:5341/ingest/otlp/v1/traces"这个地址。
登录代码
public class userrepository
{
private readonly string _connectionstring = "host=127.0.0.1;username=postgres;password=123456";
public async task<user> getuserasync(string username, string password)
{
using var conn = new npgsqlconnection(_connectionstring);
conn.open();
using var cmd = new npgsqlcommand("select * from t_users where username = @username and password = @password", conn);
cmd.parameters.addwithvalue("username", username);
cmd.parameters.addwithvalue("password", password);
using var reader = await cmd.executereaderasync();
if (reader.read())
{
return new user
{
id = reader.getstring(0),
username = reader.getstring(1),
password = reader.getstring(2),
// 其他字段...
};
}
return null;
}
}
public class user
{
public string id { get; set; }
public string username { get; set; }
public string password { get; set; }
// 其他字段...
} [httppost]
public async task<string> login([frombody] loginmodel model)
{
var user = await new userrepository().getuserasync(model.username, model.password);
if (user != null)
{
return "ok";
}
return "error";
}平平无奇的代码,简单演示一下用用户名密码进行登录。在这里我想指出的一个点是:
其中并没有任何
trace的代码会侵入到我们的业务中。
在 seq 中查看 trace
以上就是所有的关键代码。让我们运行程序使用 postman 调用登录接口。打开 seq 界面进行查看。

trace 的信息已经到了 seq 里。可以看到整个 post account 接口耗时 326ms,其中 postgres 耗时 42 ms。点击每一行都有更详细的属性。比如 postgres 里包含了 connection string,sql statement 等非常有用的信息。
示例:自定义 trace 内容
以上示例能是使用现成的库进行 trace。虽然绝大多数情况下已经够用了。但是有的时候我们想更加详细的对我们的程序进行追踪,那么就需要自己来定义 span(activity)来实现了。以下就让我们通过一个获取天气的接口来演示如何自定义 activity。
添加 trace 的 source
otel.withtracing(tracing =>
{
tracing
.addsource("mytracesample")
....
});编写接口
[apicontroller]
[route("[controller]")]
public class weatherforecastcontroller : controllerbase
{
private readonly ilogger<weatherforecastcontroller> _logger;
private readonly activitysource _source;
public weatherforecastcontroller(ilogger<weatherforecastcontroller> logger)
{
_logger = logger;
_source = new activitysource("mytracesample", "1.0.0");
}
[httpget]
public async task<string> get([fromquery]string city)
{
_logger.loginformation("hello weatherforecast");
using (var activity = _source.startactivity("callweatherforecast")) {
activity?.addtag("city", city);
await task.delay(100);
await getweatherinfofromwebservice();
await formatweatherinfo();
}
return "24°c";
}
async task getweatherinfofromwebservice()
{
using (var activity = _source.startactivity("getweatherinfofromwebservice"))
{
await task.delay(200);
}
}
async task formatweatherinfo()
{
using (var activity = _source.startactivity("formatweatherinfo"))
{
await task.delay(300);
}
}
}在 controller 的 get 方法可以接受一个 city 的参数,然后调用 getweatherinfofromwebservice 模拟从其他服务获取数据,再调用 formatweatherinfo 方法来模拟对获取的天气数据进行格式化。每个方法中都加入了 task.delay 来模拟耗时。
首先我们会实例化一个 activitysource。然后在每个需要追踪的方法最顶上调用 _source.startactivity 得到一个 activity 实例。这时候 activity 就开始计时了。但是为啥没有 stop 呢?
显然是 using 帮我们调用了。 以上代码可能对业务代码侵入的比较严重,那么可以使用 aop 技术进行解耦。这里就不展开了。
在 seq 中查看自定义的 trace
运行程序,使用 postman 进行调用。然后打开 seq 界面查看 trace。

通过以上图片可以清晰看到:get weatherforecast 接口调用了 callweatherforecast , callweatherforecast 又调用了 getweatherinfofromwebservice 与 formatweatherinfo。以及这些方法与整个 http 请求的耗时。可以说是非常非常直观。

点击 callweatherforecast 这一行,还可以看到我们设置的 tag 的内容。
总结
以上我们可以看到如果你想对 http 接口以及 database 操作进行追踪,只需要简单的几行代码就可以完成而且全程无侵入。如果你想对程序进行更细致的追踪还可以使用自定义的 activity 进行扩展,整个过程也毫无难度。希望这篇内容对想要学习 .net 程序可观测的同学有所帮助。
到此这篇关于.net 中使用 opentelemetry traces 追踪应用程序的文章就介绍到这了,更多相关.net opentelemetry traces 追踪应用程序内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论