← Назад к вопросам
Как внедрить логгер в приложение?
2.0 Middle🔥 202 комментариев
#Observability#Микросервисы и архитектура
Комментарии (2)
🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Внедрение логгера в Go-приложение
Внедрение логгера в Go-приложение — это критически важная задача для обеспечения наблюдаемости, отладки и мониторинга. Вот профессиональный подход, который я использую в продакшен-приложениях.
Выбор архитектурного подхода
Существует три основных подхода к внедрению логгера:
- Глобальный логгер — простой, но проблематичный для тестирования
- Внедрение через параметры функций — чисто, но создаёт загромождение
- Внедрение через структуры (Dependency Injection) — наиболее рекомендуемый
Структура с внедрённым логгером
package main
import (
"context"
"github.com/sirupsen/logrus"
"go.uber.org/zap"
)
// Определяем интерфейс логгера
type Logger interface {
Info(ctx context.Context, msg string, fields ...interface{})
Error(ctx context.Context, msg string, fields ...interface{})
Debug(ctx context.Context, msg string, fields ...interface{})
WithFields(fields map[string]interface{}) Logger
}
// Реализация сервиса с внедрённым логгером
type UserService struct {
logger Logger
repo UserRepository
}
func NewUserService(logger Logger, repo UserRepository) *UserService {
return &UserService{
logger: logger,
repo: repo,
}
}
func (s *UserService) CreateUser(ctx context.Context, user *User) error {
// Логируем начало операции с контекстными полями
s.logger.Info(ctx, "Creating user",
"user_id", user.ID,
"email", user.Email,
)
// Бизнес-логика
err := s.repo.Save(ctx, user)
if err != nil {
// Логируем ошибку с дополнительной информацией
s.logger.Error(ctx, "Failed to create user",
"error", err,
"user_id", user.ID,
)
return err
}
s.logger.Info(ctx, "User created successfully")
return nil
}
Фабрика и конфигурация логгера
package logger
import (
"os"
"github.com/sirupsen/logrus"
)
// Конфигурация логгера
type Config struct {
Level string `json:"level"` // debug, info, warn, error
Format string `json:"format"` // json, text
Output string `json:"output"` // stdout, file path
WithCaller bool `json:"with_caller"` // включать информацию о вызове
}
// Реализация логгера на основе logrus
type LogrusLogger struct {
logger *logrus.Entry
}
func NewLogger(cfg Config) (Logger, error) {
log := logrus.New()
// Устанавливаем уровень логирования
level, err := logrus.ParseLevel(cfg.Level)
if err != nil {
level = logrus.InfoLevel
}
log.SetLevel(level)
// Настраиваем формат
if cfg.Format == "json" {
log.SetFormatter(&logrus.JSONFormatter{
DisableTimestamp: false,
CallerPrettyfier: func(f *runtime.Frame) (string, string) {
return "", fmt.Sprintf("%s:%d", f.File, f.Line)
},
})
}
// Настраиваем вывод
if cfg.Output != "stdout" {
file, err := os.OpenFile(cfg.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.SetOutput(file)
}
}
// Добавляем caller если нужно
if cfg.WithCaller {
log.SetReportCaller(true)
}
return &LogrusLogger{
logger: logrus.NewEntry(log),
}, nil
}
func (l *LogrusLogger) Info(ctx context.Context, msg string, fields ...interface{}) {
entry := l.withContext(ctx).WithFields(l.fieldsToMap(fields))
entry.Info(msg)
}
Интеграция с контекстом
// Ключи для контекста
type contextKey string
const (
loggerKey contextKey = "logger"
requestIDKey contextKey = "request_id"
)
// Middleware для HTTP-обработчиков
func LoggingMiddleware(logger Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Создаём request ID
requestID := uuid.New().String()
// Создаём логгер с request ID
requestLogger := logger.WithFields(map[string]interface{}{
"request_id": requestID,
"method": r.Method,
"path": r.URL.Path,
"ip": r.RemoteAddr,
})
// Добавляем в контекст
ctx := context.WithValue(r.Context(), loggerKey, requestLogger)
ctx = context.WithValue(ctx, requestIDKey, requestID)
// Продолжаем цепочку
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
// Получение логгера из контекста
func FromContext(ctx context.Context) Logger {
if logger, ok := ctx.Value(loggerKey).(Logger); ok {
return logger
}
return defaultLogger // возвращаем fallback логгер
}
Практические рекомендации
Структурированное логирование
- Всегда используйте структурированное логирование (JSON или key-value пары)
- Добавляйте контекстные поля:
request_id,user_id,correlation_id - Избегайте чувствительных данных: никогда не логируйте пароли, токены, персональные данные
Уровни логирования
// Правильное использование уровней
logger.Debug(ctx, "Detailed internal state", "state", state) // Для разработки
logger.Info(ctx, "Business operation", "user_id", userID) // Бизнес-события
logger.Warn(ctx, "Unexpected situation", "attempt", attempt) // Нестандартные ситуации
logger.Error(ctx, "Operation failed", "error", err) // Ошибки, требующие внимания
Тестирование
// Mock логгер для тестов
type MockLogger struct {
Messages []string
}
func (m *MockLogger) Info(ctx context.Context, msg string, fields ...interface{}) {
m.Messages = append(m.Messages, msg)
}
func TestUserService(t *testing.T) {
mockLogger := &MockLogger{}
service := NewUserService(mockLogger, mockRepo)
// Тестируем...
assert.Contains(t, mockLogger.Messages, "Creating user")
}
Продвинутые техники
- Сэмплинг для высоконагруженных эндпоинтов
- Асинхронное логирование через буферизованный канал
- Интеграция с трейсингом (OpenTelemetry, Jaeger)
- Динамическое изменение уровня логирования без перезапуска
- Логирование в метрики для алертинга
Распространённые ошибки
- Игнорирование контекста — теряется связь между логами
- Слишком много/мало информации — находите баланс
- Блокирующие операции в основном потоке выполнения
- Отсутствие ротации логов — риск заполнения диска
Заключение
Правильное внедрение логгера — это не просто техническая задача, а важная часть архитектуры приложения. Используйте интерфейсы, внедрение зависимостей и структурированное логирование. Интегрируйте логгер с контекстом и трейсингом для полной наблюдаемости. Выбирайте проверенные решения как zap для производительности или logrus для удобства, но всегда абстрагируйтесь через собственный интерфейс для гибкости и тестируемости.