Что такое Observability (наблюдаемость) и какие инструменты вы используете для её обеспечения?

Ответ

Observability (наблюдаемость) — это способность понимать внутреннее состояние системы, анализируя её внешние выходные данные. В отличие от простого мониторинга, который отслеживает заранее известные метрики, наблюдаемость позволяет задавать системе произвольные вопросы для отладки неизвестных проблем.

Она строится на трёх столпах (Three Pillars of Observability):

  1. Логи (Logs): Записи о событиях, произошедших в системе. В Go для структурированного логирования я использую:

    • slog: Стандартная библиотека, добавленная в Go 1.21.
    • Zap или Logrus: Популярные сторонние библиотеки для высокопроизводительного структурированного логирования.
    • Логи обычно отправляются в централизованные системы, такие как Loki или Elasticsearch.
  2. Метрики (Metrics): Агрегированные числовые данные за определённый период времени (например, RPS, задержка, использование CPU).

    • Prometheus: Стандарт де-факто для сбора метрик. Приложения на Go с помощью клиентской библиотеки prometheus/client_golang предоставляют HTTP-эндпоинт /metrics, откуда Prometheus периодически собирает данные.
    • Grafana: Используется для визуализации метрик, собранных Prometheus.
  3. Трассировка (Traces): Отслеживание полного пути одного запроса через все микросервисы, которые он затрагивает. Это критически важно для отладки в распределённых системах.

    • OpenTelemetry (OTel): Текущий индустриальный стандарт для сбора трассировок, метрик и логов. Он предоставляет API и SDK для инструментирования кода.
    • Jaeger или Zipkin: Бэкенды для хранения и визуализации распределённых трасс, собранных с помощью OpenTelemetry.

Пример инструментирования функции с помощью OpenTelemetry:

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

var tracer = otel.Tracer("my-service-tracer")

func GetUserFromDB(ctx context.Context, userID int) (User, error) {
    // Начинаем новый span (участок трассировки)
    ctx, span := tracer.Start(ctx, "GetUserFromDB")
    defer span.End() // Обязательно завершаем span

    // Добавляем атрибуты для детализации
    span.SetAttributes(attribute.Int("db.user.id", userID))

    // ... логика обращения к базе данных ...

    return user, nil
}

Комплексное использование этих трёх компонентов даёт полное представление о поведении и производительности системы.