Ответ
Основной и минимальной единицей в распределенной трассировке является 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() вызывать, а то спэн так и будет висеть в памяти, как манда с ушами, и трассировку потом читать будет невозможно.