Что является основной единицей в распределенной трассировке (distributed tracing)?

Ответ

Основной и минимальной единицей в распределенной трассировке является Span (отрезок, промежуток).

Span представляет собой отдельную логическую единицу работы или операцию в системе (например, HTTP-запрос, вызов функции, запрос к базе данных). Каждый span содержит важную информацию:

  • Имя операции.
  • Время начала и окончания.
  • Атрибуты (теги): пары ключ-значение для контекста (например, http.method="GET").
  • События (логи): временные метки с описанием событий, произошедших во время выполнения span.
  • Идентификаторы:
    • Trace ID: уникальный идентификатор всего сквозного запроса.
    • Span ID: уникальный идентификатор данного span.
    • Parent Span ID: идентификатор родительского span (если он есть).

Совокупность всех span с одинаковым Trace ID, выстроенных в иерархию с помощью Parent Span ID, образует Trace (трассу) — полную картину прохождения одного запроса через всю систему.

Пример создания span в Go с использованием OpenTelemetry:

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
)

func processRequest(ctx context.Context) {
    // Получаем глобальный tracer
    tracer := otel.Tracer("my-app-tracer")

    // Начинаем новый span
    ctx, span := tracer.Start(ctx, "processRequest")
    defer span.End() // Важно завершить span

    // Добавляем атрибуты для контекста
    span.SetAttributes(attribute.String("user.id", "12345"))

    // ... здесь выполняется полезная работа ...

    // Добавляем событие, чтобы отметить момент времени
    span.AddEvent("Cache lookup finished")

    // ...
}

Ответ 18+ 🔞

А, слушай, смотри, вот есть такая штука — распределённая трассировка. Так вот, её основная единица, с которой всё начинается, это Span (ну, типа отрезок или промежуток, если по-нашему).

Представь себе, что Span — это одна отдельная операция, кусок работы в системе. Ну, например, один ебучий HTTP-запрос, вызов какой-нибудь функции или запрос к базе данных, чтобы она там что-то нашла. В каждом таком спэне живёт куча полезной инфы:

  • Имя операции — ну, чтобы понятно было, что это.
  • Время старта и время, когда всё закончилось — хронометраж, блядь.
  • Атрибуты (или теги): Это парочки «ключ-значение», которые контекст добавляют. Типа http.method="GET" или user.role="admin". Без них нихуя не разберёшься потом.
  • События (логи): Это такие временные метки с описанием, что интересного случилось прямо в середине выполнения спэна. Как будто закладки в книжке.
  • Идентификаторы, ёпта:
    • Trace ID: Уникальный айдишник на ВЕСЬ сквозной запрос, от начала и до пизды.
    • Span ID: Уникальный айдишник конкретно этого спэна.
    • Parent Span ID: А это, если есть, айдишник спэна-родителя. Благодаря этому вся хуйня выстраивается в дерево, а не в кучу.

И вот когда ты собираешь ВСЕ спэны с одним и тем же Trace ID и склеиваешь их в иерархию через Parent Span ID — получается Trace (трасса). Это уже полная, блядь, картина, как один запрос прошарился по всей твоей системе, от сервиса к сервису. Красота, да?

Вот, смотри, как это выглядит в коде на Go с OpenTelemetry:

import (
    "context"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
)

func processRequest(ctx context.Context) {
    // Берём глобальный трасер
    tracer := otel.Tracer("my-app-tracer")

    // Начинаем новый спэн. Всё по-взрослому.
    ctx, span := tracer.Start(ctx, "processRequest")
    defer span.End() // ЭТО ОЧЕНЬ ВАЖНО, ЧТОБЫ СПЭН ЗАКОНЧИТЬ, А ТО БУДЕТ ВИСЕТЬ!

    // Накидываем атрибутов для ясности
    span.SetAttributes(attribute.String("user.id", "12345"))

    // ... а тут у нас идёт полезная работа, ради которой всё и затевалось ...

    // Добавляем событие — типа, отметили важный момент
    span.AddEvent("Cache lookup finished")

    // ...
}

Вот и вся магия. Главное — не забывать span.End() вызывать, а то спэн так и будет висеть в памяти, как манда с ушами, и трассировку потом читать будет невозможно.