Інструментування

Інструментування для OpenTelemetry .NET

Інструментування — це процес додавання коду спостереження до вашого застосунку.

Якщо ви інструментуєте застосунок, вам потрібно використовувати OpenTelemetry SDK для вашої мови програмування. Потім ви будете використовувати SDK для ініціалізації OpenTelemetry та API для інструментування вашого коду. Це буде генерувати телеметрію з вашого застосунку та будь-якої бібліотеки, яку ви встановили, що також містить інструментування.

Якщо ви інструментуєте бібліотеку, встановіть лише пакет OpenTelemetry API для вашої мови програмування. Ваша бібліотека не буде генерувати телеметрію самостійно. Вона буде генерувати телеметрію лише тоді, коли буде частиною застосунку, що використовує OpenTelemetry SDK. Для отримання додаткової інформації про інструментування бібліотек дивіться Бібліотеки.

Для отримання додаткової інформації про OpenTelemetry API та SDK дивіться специфікацію.

Примітка щодо термінології

.NET відрізняється від інших мов/середовищ виконання, які підтримують OpenTelemetry. API трасування реалізовано за допомогою API System.Diagnostics, перепрофілюючи наявні конструкції, такі як ActivitySource та Activity, щоб вони відповідали OpenTelemetry під капотом.

Однак, є частини API та термінології OpenTelemetry, які розробники .NET все ще повинні знати, щоб мати змогу інструментувати свої застосунки, які розглядаються тут, а також API System.Diagnostics.

Якщо ви віддаєте перевагу використанню API OpenTelemetry замість API System.Diagnostics, ви можете звернутися до документації OpenTelemetry API Shim для трасування.

Підготовка демонстраційного застосунку

Ця сторінка використовує модифіковану версію демонстраційного застосунку з розділу Початок роботи, щоб допомогти вам навчитися ручному інструментуванню.

Вам не обовʼязково використовувати демонстраційний застосунок: якщо ви хочете інструментувати свій власний застосунок або бібліотеку, дотримуйтесь інструкцій тут, щоб адаптувати процес до свого коду.

Передумови

Створення та запуск HTTP сервера

Для початку налаштуйте середовище в новій теці з назвою dotnet-otel-example. У цій теці виконайте наступну команду:

dotnet new web

Щоб підкреслити різницю між інструментуванням бібліотеки та самостійного застосунку, винесіть кидання кубиків у файл бібліотеки, який потім буде імпортовано як залежність файлом застосунку.

Створіть файл бібліотеки з назвою Dice.cs та додайте до нього наступний код:

/*Dice.cs*/

public class Dice
{
    private int min;
    private int max;

    public Dice(int min, int max)
    {
        this.min = min;
        this.max = max;
    }

    public List<int> rollTheDice(int rolls)
    {
        List<int> results = new List<int>();

        for (int i = 0; i < rolls; i++)
        {
            results.Add(rollOnce());
        }

        return results;
    }

    private int rollOnce()
    {
        return Random.Shared.Next(min, max + 1);
    }
}

Створіть файл застосунку DiceController.cs та додайте до нього наступний код:

/*DiceController.cs*/

using Microsoft.AspNetCore.Mvc;
using System.Net;

public class DiceController : ControllerBase
{
    private ILogger<DiceController> logger;

    public DiceController(ILogger<DiceController> logger)
    {
        this.logger = logger;
    }

    [HttpGet("/rolldice")]
    public List<int> RollDice(string player, int? rolls)
    {
        if(!rolls.HasValue)
        {
            logger.LogError("Відсутній параметр rolls");
            throw new HttpRequestException("Відсутній параметр rolls", null, HttpStatusCode.BadRequest);
        }

        var result = new Dice(1, 6).rollTheDice(rolls.Value);

        if (string.IsNullOrEmpty(player))
        {
            logger.LogInformation("Анонімний гравець кидає кубики: {result}", result);
        }
        else
        {
            logger.LogInformation("{player} кидає кубики: {result}", player, result);
        }

        return result;
    }
}

Замініть вміст файлу Program.cs наступним кодом:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

У вкладеній теці Properties замініть вміст launchSettings.json наступним:

{
  "$schema": "http://json.schemastore.org/launchsettings.json",
  "profiles": {
    "http": {
      "commandName": "Project",
      "dotnetRunMessages": true,
      "launchBrowser": true,
      "applicationUrl": "http://localhost:8080",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Щоб переконатися, що все працює, запустіть застосунок за допомогою наступної команди та відкрийте http://localhost:8080/rolldice?rolls=12 у вашому вебоглядачі:

dotnet run

Ви повинні побачити список з 12 чисел у вікні оглядача, наприклад:

[5,6,5,3,6,1,2,5,4,4,2,4]

Налаштування ручного інструментування

Залежності

Встановіть наступні пакунки NuGet OpenTelemetry:

OpenTelemetry.Exporter.Console

OpenTelemetry.Extensions.Hosting

dotnet add package OpenTelemetry.Exporter.Console
dotnet add package OpenTelemetry.Extensions.Hosting

Для застосунків на основі ASP.NET Core також встановіть пакунок інструментування AspNetCore

OpenTelemetry.Instrumentation.AspNetCore

dotnet add package OpenTelemetry.Instrumentation.AspNetCore

Ініціалізація SDK

Важливо налаштувати екземпляр OpenTelemetry SDK якомога раніше у вашому застосунку.

Щоб ініціалізувати OpenTelemetry SDK для застосунку ASP.NET Core, як у випадку з демонстраційним застосунком, оновіть вміст файлу Program.cs наступним кодом:

using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

// Ідеально, якщо це імʼя буде взято з конфігураційного файлу, файлу констант тощо.
var serviceName = "dice-server";
var serviceVersion = "1.0.0";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenTelemetry()
    .ConfigureResource(resource => resource.AddService(
        serviceName: serviceName,
        serviceVersion: serviceVersion))
    .WithTracing(tracing => tracing
        .AddSource(serviceName)
        .AddAspNetCoreInstrumentation()
        .AddConsoleExporter())
    .WithMetrics(metrics => metrics
        .AddMeter(serviceName)
        .AddConsoleExporter());

builder.Logging.AddOpenTelemetry(options => options
    .SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(
        serviceName: serviceName,
        serviceVersion: serviceVersion))
    .AddConsoleExporter());

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

Якщо ініціалізувати OpenTelemetry SDK для консольного застосунку, додайте наступний код на початку вашої програми, під час будь-яких важливих операцій запуску.

using OpenTelemetry.Logs;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

//...

var serviceName = "MyServiceName";
var serviceVersion = "1.0.0";

var tracerProvider = Sdk.CreateTracerProviderBuilder()
    .AddSource(serviceName)
    .ConfigureResource(resource =>
        resource.AddService(
          serviceName: serviceName,
          serviceVersion: serviceVersion))
    .AddConsoleExporter()
    .Build();

var meterProvider = Sdk.CreateMeterProviderBuilder()
    .AddMeter(serviceName)
    .AddConsoleExporter()
    .Build();

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder.AddOpenTelemetry(logging =>
    {
        logging.AddConsoleExporter();
    });
});

//...

tracerProvider.Dispose();
meterProvider.Dispose();
loggerFactory.Dispose();

Для цілей налагодження та локальної розробки приклад експортує телеметрію до консолі. Після того, як ви закінчите налаштування ручного інструментування, вам потрібно налаштувати відповідний експортер, щоб експортувати дані телеметрії застосунку до одного або більше бекендів телеметрії.

Приклад також налаштовує обовʼязковий стандартний атрибут SDK service.name, який містить логічне імʼя сервісу, та необовʼязковий, але дуже рекомендований атрибут service.version, який містить версію API або реалізації сервісу. Існують альтернативні методи налаштування атрибутів ресурсу. Для отримання додаткової інформації дивіться Ресурси.

Щоб перевірити ваш код, зберіть та запустіть застосунок:

dotnet build
dotnet run

Трасування

Ініціалізація трасування

Щоб увімкнути трасування у вашому застосунку, вам потрібно мати ініціалізований TracerProvider, який дозволить вам створювати Tracer.

Якщо TracerProvider не створено, API OpenTelemetry для трасування використовуватимуть реалізацію no-op і не генеруватимуть дані.

Якщо ви дотримувалися інструкцій щодо ініціалізації SDK вище, у вас вже налаштовано TracerProvider. Ви можете продовжити з налаштуванням ActivitySource.

Налаштування ActivitySource

У будь-якому місці вашого застосунку, де ви пишете код ручного трасування, слід налаштувати ActivitySource, який буде використовуватися для трасування операцій з елементами Activity.

Зазвичай рекомендується визначати ActivitySource один раз на застосунок/сервіс, який інструментується, але ви можете створити кілька ActivitySource, якщо це підходить для вашого сценарію.

У випадку з демонстраційним застосунком ми створимо новий файл Instrumentation.cs як користувацький тип для зберігання посилань на ActivitySource.

using System.Diagnostics;

/// <summary>
/// Рекомендується використовувати користувацький тип для зберігання посилань на ActivitySource.
/// Це дозволяє уникнути можливих конфліктів типів з іншими компонентами в DI контейнері.
/// </summary>
public class Instrumentation : IDisposable
{
    internal const string ActivitySourceName = "dice-server";
    internal const string ActivitySourceVersion = "1.0.0";

    public Instrumentation()
    {
        this.ActivitySource = new ActivitySource(ActivitySourceName, ActivitySourceVersion);
    }

    public ActivitySource ActivitySource { get; }

    public void Dispose()
    {
        this.ActivitySource.Dispose();
    }
}

Потім ми оновимо Program.cs, щоб додати обʼєкт Instrument як залежність:

//...

// Зареєструйте клас Instrumentation як синглтон у DI контейнері.
builder.Services.AddSingleton<Instrumentation>();

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

У файлі застосунку DiceController.cs ми будемо посилатися на цей екземпляр activitySource, і той самий екземпляр activitySource також буде передано до файлу бібліотеки Dice.cs

/*DiceController.cs*/

using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Net;

public class DiceController : ControllerBase
{
    private ILogger<DiceController> logger;

    private ActivitySource activitySource;

    public DiceController(ILogger<DiceController> logger, Instrumentation instrumentation)
    {
        this.logger = logger;
        this.activitySource = instrumentation.ActivitySource;
    }

    [HttpGet("/rolldice")]
    public List<int> RollDice(string player, int? rolls)
    {
        List<int> result = new List<int>();

        if (!rolls.HasValue)
        {
            logger.LogError("Відсутній параметр rolls");
            throw new HttpRequestException("Відсутній параметр rolls", null, HttpStatusCode.BadRequest);
        }

        result = new Dice(1, 6, activitySource).rollTheDice(rolls.Value);

        if (string.IsNullOrEmpty(player))
        {
            logger.LogInformation("Анонімний гравець кидає кубики: {result}", result);
        }
        else
        {
            logger.LogInformation("{player} кидає кубики: {result}", player, result);
        }

        return result;
    }
}
/*Dice.cs*/

using System.Diagnostics;

public class Dice
{
    public ActivitySource activitySource;
    private int min;
    private int max;

    public Dice(int min, int max, ActivitySource activitySource)
    {
        this.min = min;
        this.max = max;
        this.activitySource = activitySource;
    }

    //...
}

Створення Activities

Тепер, коли ви ініціалізували activitySources, ви можете створювати activities.

Код нижче ілюструє, як створити activity.

public List<int> rollTheDice(int rolls)
{
    List<int> results = new List<int>();

    // Рекомендується створювати activities, лише коли виконуються операції, які варто вимірювати окремо.
    // Занадто багато activities ускладнює візуалізацію в інструментах, таких як Jaeger.
    using (var myActivity = activitySource.StartActivity("rollTheDice"))
    {
        for (int i = 0; i < rolls; i++)
        {
            results.Add(rollOnce());
        }

        return results;
    }
}

Якщо ви дотримувалися інструкцій, використовуючи демонстраційний застосунок до цього моменту, ви можете скопіювати код вище у ваш файл бібліотеки Dice.cs. Тепер ви повинні побачити activities/spans, що генеруються вашим застосунком.

Запустіть свій застосунок наступним чином, а потім надішліть йому запити, відвідавши http://localhost:8080/rolldice?rolls=12 за допомогою вашого оглядача або curl.

dotnet run

Через деякий час ви повинні побачити spans, виведені в консолі експортером ConsoleExporter, щось на кшталт цього:

Activity.TraceId:            841d70616c883db82b4ae4e11c728636
Activity.SpanId:             9edfe4d69b0d6d8b
Activity.TraceFlags:         Recorded
Activity.ParentSpanId:       39fcd105cf958377
Activity.ActivitySourceName: dice-server
Activity.DisplayName:        rollTheDice
Activity.Kind:               Internal
Activity.StartTime:          2024-04-10T15:24:00.3620354Z
Activity.Duration:           00:00:00.0144329
Resource associated with Activity:
    service.name: dice-server
    service.version: 1.0.0
    service.instance.id: 7a7a134f-3178-4ac6-9625-96df77cff8b4
    telemetry.sdk.name: opentelemetry
    telemetry.sdk.language: dotnet
    telemetry.sdk.version: 1.7.0

Створення вкладених Activities

Вкладені відрізки дозволяють відстежувати роботу, яка має вкладену природу. Наприклад, функція rollOnce() нижче представляє вкладену операцію. Наступний приклад створює вкладений відрізок, який відстежує rollOnce():

private int rollOnce()
{
    using (var childActivity = activitySource.StartActivity("rollOnce"))
    {
      int result;

      result = Random.Shared.Next(min, max + 1);

      return result;
    }
}

Коли ви переглядаєте відрізки в інструменті візуалізації трасування, rollOnce childActivity буде відстежуватися як вкладена операція в activity rollTheDice.

Отримання поточного Activity

Іноді корисно зробити щось з поточним/активним Activity/Span у певний момент виконання програми.

var activity = Activity.Current;

Теґи Activity

Теґи (еквівалент Attributes) дозволяють прикріплювати пари ключ/значення до Activity, щоб він містив більше інформації про поточну операцію, яку він відстежує.

private int rollOnce()
{
  using (var childActivity = activitySource.StartActivity("rollOnce"))
    {
      int result;

      result = Random.Shared.Next(min, max + 1);
      childActivity?.SetTag("dicelib.rolled", result);

      return result;
    }
}

Додавання подій до Activities

Відрізки можуть бути анотовані іменованими подіями (називаються Span Events), які можуть містити нуль або більше Span Attributes, кожен з яких сам по собі є парою ключ:значення, автоматично поєднаною з часовою міткою.

myActivity?.AddEvent(new("Init"));
...
myActivity?.AddEvent(new("End"));
var eventTags = new ActivityTagsCollection
{
    { "operation", "calculate-pi" },
    { "result", 3.14159 }
};

activity?.AddEvent(new("End Computation", DateTimeOffset.Now, eventTags));

Відрізок може бути повʼязаний з нулем або більше інших Відрізків, які є повʼязаними через Span Link. Посилання можуть використовуватися для представлення пакетних операцій, де Відрізок був ініційований кількома ініціюючими Відрізками, кожен з яких представляє один вхідний елемент, що обробляється в пакеті.

var links = new List<ActivityLink>
{
    new ActivityLink(activityContext1),
    new ActivityLink(activityContext2),
    new ActivityLink(activityContext3)
};

var activity = MyActivitySource.StartActivity(
    ActivityKind.Internal,
    name: "activity-with-links",
    links: links);

Встановлення статусу Activity

Статус можна встановити на Відрізок, зазвичай використовується для вказівки, що Відрізок не завершився успішно — Error. Стандартно, всі відрізки мають статус Unset, що означає, що відрізок завершився без помилок. Статус Ok зарезервований для випадків, коли потрібно явно позначити відрізок як успішний, а не залишати його зі статусом Unset (тобто “без помилок”).

Статус можна встановити в будь-який час до завершення відрізка.

Статус може бути встановлений у будь-який час до завершення відрізка.

Може бути гарною ідеєю записувати помилки, коли вони трапляються. Рекомендується робити це разом з встановленням статусу відрізка.

private int rollOnce()
{
    using (var childActivity = activitySource.StartActivity("rollOnce"))
    {
        int result;

        try
        {
            result = Random.Shared.Next(min, max + 1);
            childActivity?.SetTag("dicelib.rolled", result);
        }
        catch (Exception ex)
        {
            childActivity?.SetStatus(ActivityStatusCode.Error, "Щось пішло не так!");
            childActivity?.AddException(ex);
            throw;
        }

        return result;
    }
}

Наступні кроки

Після того, як ви налаштували ручне інструментування, ви можете скористатися бібліотеками інструментування. Як випливає з назви, вони будуть інструментувати відповідні бібліотеки, які ви використовуєте, і генерувати відрізки (activities) для таких речей, як вхідні та вихідні HTTP-запити та інше.

Вам також потрібно налаштувати відповідний експортер, щоб експортувати ваші дані телеметрії до одного або більше бекендів телеметрії.

Ви також можете подивитись автоматичне інструментування для .NET, яке наразі перебуває в бета-версії.


Востаннє змінено December 26, 2024: [uk] Ukrainian documentation for OpenTelemetry (2a3c5648)