Почему ваше Go-приложение молчит, и как это исправить?
Вы написали идеальный микросервис на Go. Он проходит все тесты, быстро стартует и потребляет копейки памяти. Вы выкатываете его в прод — и через час пользователи жалуются на тормоза. Вы заходите на сервер, смотрите на пустой терминал и понимаете: приложение молчит. Нет логов, нет метрик, нет понимания, что пошло не так.
Знакомая ситуация? Добро пожаловать в мир observability (наблюдаемости) Go-приложений. Без мониторинга и логирования ваш код — это чёрный ящик. Сегодня мы разберём, как превратить его в прозрачную систему, где каждая задержка и каждая ошибка видны как на ладони.
Я покажу три рабочих инструмента, реальные примеры кода на Go и дам чек-лист, который спасёт ваш продакшн. Поехали!
1. Структурированное логирование: как перестать писать в консоль и начать жить
Наивное fmt.Println("Ошибка:", err) — это прошлый век. В современном логировании Go нужен порядок: JSON-формат, уровни (debug, info, warn, error), поля для поиска. Встречайте короля — библиотеку zap от Uber.
Почему именно zap?
- Скорость: он быстрее стандартного
logв 10-100 раз на высоких нагрузках. - Структура: вы добавляете ключи и значения, а не лепите строки через плюс.
- Гибкость: можно писать в файл, в stdout, в Elasticsearch — куда угодно.
Установка и базовый пример
go get -u go.uber.org/zap
Код простого логгера:
package main
import (
"go.uber.org/zap"
)
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync() // сбрасываем буфер перед выходом
logger.Info("Сервис запущен",
zap.String("env", "production"),
zap.Int("port", 8080),
)
// Пример с ошибкой
err := someFunction()
if err != nil {
logger.Error("Не удалось выполнить операцию",
zap.Error(err),
zap.String("module", "database"),
)
}
}
На выходе вы получаете чистый JSON:
{"level":"info","ts":1700000000.123,"msg":"Сервис запущен","env":"production","port":8080}
Теперь любой инструмент вроде Kibana или Grafana Loki сможет разобрать эти логи по полям. Хотите найти все ошибки в модуле "database" за последний час? Один клик.
Совет: не логируйте секреты и PII
Добавьте проверку: если поле содержит пароль или email — маскируйте его. Используйте zap.String("user", "***") для чувствительных данных.
2. Метрики и мониторинг: превращаем код в дашборд
Логи — это хорошо, но они говорят о прошлом. Метрики — это пульс вашего приложения в реальном времени. Для мониторинга Go приложений стандарт де-факто — Prometheus + его клиентская библиотека.
Что измерять?
- RPS (запросов в секунду) — нагрузка на сервис.
- Latency (p50, p95, p99) — время ответа.
- Error rate — процент ошибок.
- Goroutines — утечки горутин.
- Memory / CPU — ресурсы.
Пример: добавляем метрики HTTP-сервера
package main
import (
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
requestCount = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "endpoint", "status"},
)
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests in seconds",
Buckets: prometheus.DefBuckets,
},
[]string{"method", "endpoint"},
)
)
func init() {
prometheus.MustRegister(requestCount)
prometheus.MustRegister(requestDuration)
}
func main() {
// Middleware для подсчёта
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
timer := prometheus.NewTimer(requestDuration.WithLabelValues(r.Method, r.URL.Path))
defer timer.ObserveDuration()
// ваша логика
w.WriteHeader(http.StatusOK)
w.Write([]byte("Hello, observability!"))
requestCount.WithLabelValues(r.Method, r.URL.Path, "200").Inc()
})
http.Handle("/metrics", promhttp.Handler())
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}
Теперь по адресу /metrics Prometheus будет собирать данные. Добавьте Grafana — и вы увидите живые графики. А alerts в Prometheus (например, если error rate > 5%) отправят уведомление в Telegram.
3. Трейсинг: находим иголку в стоге микросервисов
Когда у вас 10 Go-сервисов, логов и метрик уже мало. Запрос проходит через API Gateway → Auth → Core → DB. Где тормоз? Без трейсинга вы будете гадать. Трейсинг Go (распределённая трассировка) рисует карту всего пути запроса.
Инструменты: OpenTelemetry + Jaeger
OpenTelemetry — это стандарт. Он собирает spans (единицы работы) и отправляет их в Jaeger или Zipkin.
Пример: трейсинг HTTP-запроса
package main
import (
"context"
"log"
"net/http"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.opentelemetry.io/otel/trace"
)
func initTracer() (*sdktrace.TracerProvider, error) {
exp, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exp),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-go-service"),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}
func main() {
tp, err := initTracer()
if err != nil {
log.Fatal(err)