Observability в Go: трейсинг, метрики и логи для профи

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Ваше приложение работает, но вы уверены, что знаете, что внутри?

Представьте: вы запустили микросервис на Go в продакшн. Он не падает, не выдаёт 500-х ошибок, но пользователи жалуются, что страница грузится «как-то медленно». Вы лезете в мониторинг — CPU 30%, память в норме. Что делать? Без observability в Go вы — капитан корабля без карты и компаса.

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

Почему Go — идеальный язык для observability?

Go создавался для бэкенд-систем, где важна производительность и надёжность. Стандартная библиотека (net/http, database/sql) уже умеет отдавать метрики и поддерживать контексты для трейсинга. А экосистема OpenTelemetry Go и Prometheus Go делает внедрение observability простым и элегантным.

Но главное — в Go нет «магии». Вы пишете код, который можно измерить, трассировать и логировать без боли. Давайте посмотрим, как это делается.

Трейсинг в Go: OpenTelemetry в действии

Трейсинг (трассировка) — это история одного запроса от входа в систему до выхода. Он показывает, сколько времени занял каждый шаг: запрос к БД, вызов внешнего API, обработка данных. В Go трейсинг строится на контекстах (context.Context).

Настройка OpenTelemetry SDK

Установим пакеты:

go get go.opentelemetry.io/otel \
  go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \
  go.opentelemetry.io/otel/sdk/trace

Теперь инициализируем трейсер:

package main

import (
    "context"
    "log"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/trace"
)

func initTracer() {
    exporter, err := otlptracehttp.New(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    tp := trace.NewTracerProvider(
        trace.WithBatcher(exporter),
        trace.WithSampler(trace.AlwaysSample()),
    )
    otel.SetTracerProvider(tp)
}

Создаём спан (span) в HTTP-обработчике

Вот как выглядит трейсинг в реальном коде:

func userHandler(w http.ResponseWriter, r *http.Request) {
    tracer := otel.Tracer("user-service")
    ctx, span := tracer.Start(r.Context(), "getUser")
    defer span.End()

    // Имитация запроса к БД
    user, err := getUserFromDB(ctx, r.URL.Query().Get("id"))
    if err != nil {
        span.RecordError(err)
        span.SetStatus(codes.Error, err.Error())
        http.Error(w, err.Error(), 500)
        return
    }

    span.SetAttributes(
        attribute.String("user.id", user.ID),
        attribute.Int64("user.age", int64(user.Age)),
    )

    // Вложенный спан для внешнего вызова
    _, childSpan := tracer.Start(ctx, "callAnalyticsAPI")
    defer childSpan.End()

    callAnalyticsAPI(user)
    
    w.Write([]byte("OK"))
}

Каждый спан содержит имя, время начала и конца, атрибуты и статус. В Jaeger или Grafana Tempo вы увидите полную карту запроса.

Практический совет

Не забывайте передавать контекст через всё приложение. В Go это делается автоматически, если вы используете context.Context в сигнатурах функций. OpenTelemetry Go сам подхватит контекст из HTTP-запроса, если вы используете middleware.

Метрики: Prometheus и экспорт в Go

Метрики — это цифры, которые говорят о состоянии системы: RPS, latency, количество ошибок, использование памяти. Prometheus Go — стандарт де-факто.

Создаём собственные метрики

Установите клиентскую библиотеку:

go get github.com/prometheus/client_golang/prometheus

Пример: считаем количество запросов и их длительность.

var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests",
        },
        []string{"method", "path", "status"},
    )
    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "Duration of HTTP requests in seconds",
            Buckets: prometheus.DefBuckets,
        },
        []string{"method", "path"},
    )
)

func init() {
    prometheus.MustRegister(httpRequestsTotal)
    prometheus.MustRegister(httpRequestDuration)
}

Middleware для автоматического сбора метрик

Сделаем простой middleware, который оборачивает любой HTTP-обработчик:

func metricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        wrapped := &responseWriter{ResponseWriter: w}
        next.ServeHTTP(wrapped, r)

        duration := time.Since(start).Seconds()
        httpRequestsTotal.WithLabelValues(
            r.Method, r.URL.Path, strconv.Itoa(wrapped.status),
        ).Inc()
        httpRequestDuration.WithLabelValues(
            r.Method, r.URL.Path,
        ).Observe(duration)
    })
}

type responseWriter struct {
    http.ResponseWriter
    status int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.status = code
    rw.ResponseWriter.WriteHeader(code)
}

Теперь добавьте эндпоинт /metrics для Prometheus:

http.Handle("/metrics", promhttp.Handler())
http.ListenAndServe(":8080", nil)

Проверьте: curl http://localhost:8080/metrics — вы увидите все метрики.

Что ещё можно измерять?

  • Количество активных горутин
  • Размер очереди сообщений
  • Время выполнения внешних вызовов (например, к Redis или PostgreSQL)
  • Количество ошибок валидации

Логирование: от текста к структуре

Логирование в Go прошло долгий путь от простого log.Println до структурированных логов с уровнями и контекстом. Сегодня стандарт — zerolog (быстрый) или slog (из коробки в Go 1.21+).

Почему slog?

Начиная с Go 1.21, пакет log/slog встроен в стандартную библиотеку. Он поддерживает уровни (DEBUG, INFO, WARN, ERROR), структурированные поля и кастомные обработчики.

package                

Блоги

Рекомендаций по книгам