.NET 中使用 OpenTelemetry Traces 追踪应用程序

来自:博客园
时间:2024-06-05
阅读:

上一次我们讲了 OpenTelemetry Logs。今天继续来说说 OpenTelemetry Traces
在今天的微服务和云原生环境中,理解和监控系统的行为变得越来越重要。在当下我们实现一个功能可能需要调用了 N 个方法,涉及到 N 个服务。方法之间的调用如蜘蛛网一样。分布式追踪这个时候就至关重要。它可以把我们程序的调用链可视化。这对于运维人员监控程序状态,开发人员 trouble shooting 都非常用帮助。

什么是 OpenTelemetry Traces

OpenTelemetry TracesOpenTelemetry 提供的一种遥测数据类型,用于记录和描述在分布式系统中的单个操作或工作单元的生命周期。
OpenTelemetry 中,一个 Trace 可以被视为由一系列相关的事件组成的时间线,这些事件被称为 Spans。每个 Span 可以包含多个属性、注释和事件,用于描述在该 Span 的生命周期中发生的特定操作或事件。
例如,一个 HTTP 请求可以被表示为一个 Span,其中包含了请求的开始时间、结束时间、HTTP 方法、URL、状态码等信息。如果这个请求还调用了其他的服务或数据库,那么这些调用也可以被表示为与原始请求 Span 相关联的子 Span

注意:SpanOpenTelemetry 定义的概念,在 .NET 中使用 Activity 表示一个 Span

以上的话呢比较官方,是我用 ChatGPT 生成的。还是直接用代码来演示一下效果大家好理解。

示例:追踪 Http 与 Database

在日常的开发活动中,httpdatabase 操作基本就是涵盖了 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 界面进行查看。

.NET 中使用 OpenTelemetry Traces 追踪应用程序
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);
            }
        }
    }

ControllerGet 方法可以接受一个 city 的参数,然后调用 GetWeatherInfoFromWebservice 模拟从其他服务获取数据,再调用 FormatWeatherInfo 方法来模拟对获取的天气数据进行格式化。每个方法中都加入了 Task.Delay 来模拟耗时。
首先我们会实例化一个 ActivitySource。然后在每个需要追踪的方法最顶上调用 _source.StartActivity 得到一个 Activity 实例。这时候 Activity 就开始计时了。但是为啥没有 Stop 呢?
显然是 using 帮我们调用了。 以上代码可能对业务代码侵入的比较严重,那么可以使用 AOP 技术进行解耦。这里就不展开了。

在 Seq 中查看自定义的 Trace

运行程序,使用 postman 进行调用。然后打开 Seq 界面查看 Trace
.NET 中使用 OpenTelemetry Traces 追踪应用程序
通过以上图片可以清晰看到:GET WeatherForecast 接口调用了 CallWeatherForecastCallWeatherForecast 又调用了 GetWeatherInfoFromWebserviceFormatWeatherInfo。以及这些方法与整个 http 请求的耗时。可以说是非常非常直观。
.NET 中使用 OpenTelemetry Traces 追踪应用程序
点击 CallWeatherForecast 这一行,还可以看到我们设置的 tag 的内容。

总结

以上我们可以看到如果你想对 http 接口以及 database 操作进行追踪,只需要简单的几行代码就可以完成而且全程无侵入。如果你想对程序进行更细致的追踪还可以使用自定义的 Activity 进行扩展,整个过程也毫无难度。希望这篇内容对想要学习 .NET 程序可观测的同学有所帮助。

返回顶部
顶部