Что такое OpenTelemetry и для чего он используется в бэкенде?

Ответ

OpenTelemetry (OTel) — это открытый стандарт и набор инструментов для сбора, обработки и экспорта телеметрических данных (трассировок, метрик и логов) из ваших приложений. Его главная цель — унифицировать процесс инструментирования кода для наблюдаемости (observability).

Ключевые концепции:

  • API: Набор интерфейсов, которые вы используете в коде своего приложения для генерации телеметрии (например, tracer.Start(ctx, "my-operation")). Код приложения зависит только от API, а не от конкретной реализации.
  • SDK: Реализация API от OpenTelemetry. SDK отвечает за обработку, сэмплирование и экспорт данных.
  • Exporter: Компонент, который отправляет обработанные данные в выбранную вами систему мониторинга (бэкенд), например, в Jaeger, Prometheus, Zipkin или Datadog.

Зачем это нужно?

  1. Стандартизация: Вам не нужно изучать проприетарные агенты и библиотеки для каждого бэкенда мониторинга.
  2. Отсутствие привязки к вендору (Vendor Lock-in): Вы можете легко переключиться с Jaeger на Datadog, просто поменяв экспортер в конфигурации, не изменяя код приложения.
  3. Корреляция данных: OTel позволяет связывать трейсы, метрики и логи между собой, давая полную картину происходящего в системе.

Пример на Go (настройка экспорта трейсов в Jaeger):

package main

import (
    "context"
    "log"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/sdk/resource"
    tracesdk "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

// newTracerProvider создает и настраивает провайдер трассировки.
func newTracerProvider(url string) (*tracesdk.TracerProvider, error) {
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
    if err != nil {
        return nil, err
    }

    tp := tracesdk.NewTracerProvider(
        tracesdk.WithBatcher(exporter),
        tracesdk.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("my-app"), // Имя вашего сервиса
        )),
    )
    return tp, nil
}

func main() {
    tp, err := newTracerProvider("http://localhost:14268/api/traces")
    if err != nil {
        log.Fatal(err)
    }

    // Регистрируем наш провайдер как глобальный.
    otel.SetTracerProvider(tp)

    // Важно не забыть корректно завершить работу провайдера.
    defer func() {
        if err := tp.Shutdown(context.Background()); err != nil {
            log.Printf("Error shutting down tracer provider: %v", err)
        }
    }()

    ctx, span := otel.Tracer("main").Start(context.Background(), "my-main-operation")
    defer span.End()

    // ... ваша бизнес-логика здесь ...
    log.Println("Operation completed.")
}

Ответ 18+ 🔞

А, ну вот, смотри, опять про эту вашу наблюдаемость, блядь! OpenTelemetry, сука! Это ж как в том анекдоте: "все хотят, чтобы было, но никто не хочет этим заниматься". Так вот, это как раз тот самый инструмент, который делает так, чтобы было, но при этом не привязывает тебя к одному вендору намертво, как сука к будке.

Представь, ты пишешь код, и тебе надо понять, где он тормозит, блядь. Раньше ты ставил агента от Jaeger, потом от Datadog, потом ещё от кого-то — и в итоге у тебя в зависимостях был пиздец какой-то зоопарк, а переехать никуда не мог, потому что код завязан на их библиотеки. Это как жениться на первой встречной — потом развод, алименты, нервотрёпка, в рот меня чих-пых!

А OTel — это как брачный контракт, ёпта. Ты пишешь код по одному стандартному API, а под капотом можешь ставить любую реализацию (это SDK) и отправлять данные куда угодно через экспортер. Хочешь — в Jaeger, хочешь — в Prometheus, а завтра передумал — хуй с горы, переключил конфиг, и всё летит в другую систему. Код приложения вообще не трогаешь! Гениально же, блядь!

Что там у них под капотом, ёперный театр:

  • API — это твои ручки, которыми ты в коде говоришь: "эй, я тут операцию начал" или "вот эту метрику запиши". Чистые интерфейсы, нихуя лишнего.
  • SDK — это уже рабочий мужик, который берёт твои команды, обрабатывает их, решает, что сэмплировать, а что нет, и готовит к отправке. Без него API — просто пустая болтовня.
  • Exporter — это курьер, который берёт готовую пачку данных и несёт её в конкретную контору (Jaeger, Zipkin, Datadog). Меняешь курьера — меняешь адресата. Всё просто, как три копейки.

И главная фишка — корреляция. Раньше трейсы жили отдельно, логи отдельно, метрики отдельно. А теперь, блядь, можно по ID трейса найти все логи и метрики, которые к нему относятся. Это как найти все носки от одного комплекта после стирки — чувство полного, блядь, удовлетворения!

Ну и примерчик на Go, чтобы не быть голословным, сука. Вот как настроить, чтобы трейсы улетали в Jaeger:

package main

import (
    "context"
    "log"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/sdk/resource"
    tracesdk "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
)

// newTracerProvider создает и настраивает провайдер трассировки.
func newTracerProvider(url string) (*tracesdk.TracerProvider, error) {
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint(url)))
    if err != nil {
        return nil, err
    }

    tp := tracesdk.NewTracerProvider(
        tracesdk.WithBatcher(exporter),
        tracesdk.WithResource(resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("my-app"), // Имя вашего сервиса
        )),
    )
    return tp, nil
}

func main() {
    tp, err := newTracerProvider("http://localhost:14268/api/traces")
    if err != nil {
        log.Fatal(err)
    }

    // Регистрируем наш провайдер как глобальный.
    otel.SetTracerProvider(tp)

    // Важно не забыть корректно завершить работу провайдера.
    defer func() {
        if err := tp.Shutdown(context.Background()); err != nil {
            log.Printf("Error shutting down tracer provider: %v", err)
        }
    }()

    ctx, span := otel.Tracer("main").Start(context.Background(), "my-main-operation")
    defer span.End()

    // ... ваша бизнес-логика здесь ...
    log.Println("Operation completed.")
}

Видишь? Настроили провайдер, зарегистрировали его глобально, создали спан (это кусок трейса) — и всё, пиздец, ты в игре. А завтра, если начальство скажет "переезжаем с Jaeger на что-то другое", ты просто в этом коде поменяешь экспортер на другой, и даже, блядь, моргнуть не успеешь. Красота же, ёбушки-воробушки!