Первое, что мы обнаружили — хаос в логировании. Три сервиса писали логи в файлы на диск, два использовали plaintext-формат со своими уникальными шаблонами, остальные выводили в stdout, но без структуры. При инциденте инженеру приходилось подключаться к каждому серверу отдельно и парсить логи grep-ом.
Мы установили единый стандарт: все сервисы пишут структурированные JSON-логи в stdout/stderr. Никаких файлов на диске, никакой самодеятельности с ротацией.
Каждая строка лога — валидный JSON со стандартизированным набором полей:
{
"timestamp": "2026-03-15T14:22:03.451Z",
"level": "error",
"service": "billing-service",
"version": "1.4.2",
"trace_id": "abc123def456",
"user_id": "usr_98712",
"message": "Payment processing failed",
"error": "connection refused: payment-provider:443",
"duration_ms": 3021
}
Пример настройки структурированного логирования в Go-сервисе:
package main
import (
"os"
"log/slog"
)
func initLogger() *slog.Logger {
level := slog.LevelInfo
if os.Getenv("LOG_LEVEL") == "debug" {
level = slog.LevelDebug
}
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: level,
})
logger := slog.New(handler)
slog.SetDefault(logger)
return logger
}
// Использование
func processOrder(ctx context.Context, orderID string) error {
slog.InfoContext(ctx, "processing order",
"order_id", orderID,
"trace_id", traceIDFromCtx(ctx),
)
// ...
}
Для Python-сервисов мы использовали python-json-logger:
import logging
from pythonjsonlogger import jsonlogger
def setup_logging():
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
'%(timestamp)s %(level)s %(name)s %(message)s',
rename_fields={'levelname': 'level', 'asctime': 'timestamp'},
datefmt='%Y-%m-%dT%H:%M:%S.%fZ'
)
handler.setFormatter(formatter)
logging.root.addHandler(handler)
logging.root.setLevel(
getattr(logging, os.environ.get('LOG_LEVEL', 'INFO'))
)
Это устранило необходимость писать Grok-паттерны для парсинга, что раньше было источником постоянных проблем при обновлении форматов.
Оставить комментарий