Основные концепции
Понимание основных концепций DD — это основа для эффективного использования библиотеки. В этой главе описаны архитектура Logger, система полей, конвейер обработки и иерархия интерфейсов.
Архитектура Logger
Логирование в DD строится вокруг трёх основных типов:
Logger (логгер)
│
├── Прямое использование → logger.Info("message")
│
└── WithFields() → LoggerEntry (Entry с предустановленными полями)
│
└── entry.Info("message") // Автоматически несёт предустановленные поляLogger
Logger — основной логгер, создаваемый через dd.New():
logger, err := dd.New(dd.DefaultConfig())
if err != nil {
log.Fatal(err)
}
defer logger.Close()
logger.Info("Сервис запущен")
logger.InfoWith("Обработка запроса",
dd.String("method", "GET"),
dd.Int("status", 200),
)Каждый Logger имеет независимую конфигурацию, цели вывода, фильтр безопасности и жизненный цикл, может безопасно разделяться между модулями.
LoggerEntry
LoggerEntry создаётся через WithFields(), представляет собой неизменяемый контейнер предустановленных полей:
// Создание Entry с предустановленными полями
requestLog := logger.WithFields(
dd.String("service", "user-api"),
dd.String("version", "2.1.0"),
)
// Каждый вызов автоматически несёт предустановленные поля
requestLog.Info("Сервис запущен")
// Вывод: ... Сервис запущен service=user-api version=2.1.0
requestLog.InfoWith("Пользователь вошёл",
dd.String("user", "alice"),
)
// Вывод: ... Пользователь вошёл service=user-api version=2.1.0 user=aliceНеизменяемая конструкция
Каждый вызов WithFields() создаёт новый LoggerEntry, оригинальный Entry не затрагивается. Это означает, что вы можете безопасно повторно использовать один и тот же Entry в разных goroutine.
Глобальный логгер
DD предоставляет глобальный логгер, подходящий для простых сценариев или быстрого прототипирования:
// Прямое использование пакетных функций (через глобальный Logger)
dd.Info("Глобальный лог")
// Эквивалентно
dd.Default().Info("Глобальный лог")Система полей
Тип Field
Field — базовая единица структурированного лога, состоящая из пары ключ-значение:
// Конструкторы полей покрывают все распространённые типы
dd.String("method", "GET") // Строка
dd.Int("status", 200) // Целое число
dd.Float64("latency", 0.123) // Число с плавающей точкой
dd.Bool("success", true) // Логическое значение
dd.Duration("elapsed", 150*time.Millisecond) // Временной интервал
dd.Time("timestamp", time.Now()) // Временная метка
dd.Err(err) // Ошибка (ключ фиксирован как "error")
dd.ErrWithKey("db_error", err) // Ошибка (пользовательский ключ)
dd.Any("data", payload) // Любой типЦепочная передача полей
Поля могут передаваться между Logger и Entry по цепочке:
// Первый уровень: поля уровня сервиса
serviceLog := logger.WithFields(
dd.String("service", "api-gateway"),
)
// Второй уровень: поля уровня запроса (добавляются к сервисным)
requestLog := serviceLog.WithFields(
dd.String("request_id", "req-001"),
dd.String("path", "/api/users"),
)
// Третий уровень: фактический лог (добавляются ещё поля)
requestLog.InfoWith("Обработка завершена",
dd.Int("status", 200),
dd.Duration("elapsed", 50*time.Millisecond),
)
// Вывод содержит: service=api-gateway request_id=req-001 path=/api/users status=200 elapsed=50msКонвейер обработки логов
Каждый лог проходит следующий процесс обработки:
Вызов logger.InfoWith("msg", fields...)
│
▼
① Проверка уровня ─── Уровень не включён → немедленный возврат (нулевые накладные расходы)
│
▼
② Фильтрация безопасности ─── Конфиденциальные данные в сообщении и полях → [REDACTED]
│
▼
③ Извлечение контекста ── Вызов зарегистрированных экстракторов для добавления статических/глобальных полей (вызывается с context.Background(), не может прочитать request-scoped TraceID)
│
▼
④ Хук BeforeLog
│
▼
⑤ Форматирование ──── Текстовый формат или формат JSON
│
▼
⑥ Ограничение размера безопасности ─── Превышение Security.MaxMessageSize → усечение (0 = без ограничения)
│
▼
⑦ Запись ────── Вывод в один или несколько Writer
│
▼
⑧ Хук AfterLog
│
▼
⑨ Обработка Fatal ── Только LevelFatal: сначала асинхронно закрывает Logger (макс. ожидание 5с, вызывает хук OnClose и сбрасывает writer), затем вызывает os.Exit(1) или пользовательский FatalHandlerПроектирование производительности
Проверка уровня (шаг ①) использует атомарные операции, без блокировок, практически нулевые накладные расходы. Фильтрация безопасности (шаг ②) имеет защиту по таймауту, предотвращающую длительную блокировку основного потока (большой ввод через goroutine + таймаут обеспечивает возврат в худшем случае примерно за 50мс). Обработка Fatal (шаг ⑨) асинхронно запускает Close логгера (со сбросом и хуком OnClose), ожидая максимум 5с; defer в пользовательском main при этом не выполняется, но собственный Close логгера будет вызван.
Иерархия интерфейсов
DD определяет четыре интерфейса, поддерживающих точное внедрение зависимостей:
CoreLogger ← Базовое логирование: Debug/Info/Warn/Error/Fatal + WithFields
│
├── LevelLogger ← Управление уровнями: GetLevel/SetLevel/IsLevelEnabled (встраивает CoreLogger)
│
└── ConfigurableLogger ← Управление конфигурацией: Writer/безопасность/контекст/хуки (встраивает CoreLogger)
LogProvider ← Полный функционал: независимый плоский интерфейс, содержащий все методы// Нужен только базовый логгер? Внедрите CoreLogger
type Service struct {
log dd.CoreLogger
}
// Нужно динамически регулировать уровень? Внедрите LevelLogger
type Handler struct {
log dd.LevelLogger
}Лучшие практики
В конструкторах принимайте минимально необходимый интерфейс, а не конкретный тип. Это делает код более тестируемым и гибким.
Потокобезопасная модель
Основной принцип проектирования DD: безопасное использование из нескольких goroutine без дополнительной синхронизации.
| Компонент | Механизм безопасности |
|---|---|
| Logger | Все методы безопасны для конкурентного вызова |
| LoggerEntry | Неизменяемый, только для чтения после создания |
| Config | Метод Clone() для безопасного копирования |
| Writers | Атомарные указатели, чтение без блокировок |
| SensitiveDataFilter | Разделение чтения и записи, отдельная goroutine |
| HookRegistry | RWMutex защищает регистрацию и чтение (Logger удерживает его указатель через atomic.Value) |
// Безопасно: несколько goroutine разделяют один Logger
var logger *dd.Logger // Инициализируется один раз
func handleRequest(w http.ResponseWriter, r *http.Request) {
// Безопасно: конкурентный вызов
logger.InfoWith("Запрос получен",
dd.String("path", r.URL.Path),
dd.String("method", r.Method),
)
}Система целей вывода
DD поддерживает три цели вывода, которые можно свободно комбинировать:
logger, err := dd.New(dd.Config{
Targets: []dd.OutputTarget{
dd.ConsoleOutput(), // Консоль
dd.FileOutput("logs/app.log"), // Файл (автоматическая ротация)
dd.CustomOutput(customWriter), // Пользовательский io.Writer
},
})
if err != nil {
log.Fatal(err)
}
defer logger.Close()Встроенные компоненты Writer:
| Компонент | Назначение |
|---|---|
FileWriter | Запись в файл + ротация по размеру/времени + сжатие |
BufferedWriter | Буферизованная запись, уменьшение количества I/O |
MultiWriter | Многоцелевая доставка, запись в несколько Writer |
Следующие шаги
- Структурированное логирование -- подробное описание использования полей
- Вывод в файл и ротация -- конфигурация файловых логов
- Фильтрация конфиденциальных данных -- практика фильтрации безопасности
- Справочник API -- полная документация API