Skip to content

Основные концепции

Понимание основных концепций DD — это основа для эффективного использования библиотеки. В этой главе описаны архитектура Logger, система полей, конвейер обработки и иерархия интерфейсов.

Архитектура Logger

Логирование в DD строится вокруг трёх основных типов:

text
Logger (логгер)

  ├── Прямое использование → logger.Info("message")

  └── WithFields() → LoggerEntry (Entry с предустановленными полями)

                        └── entry.Info("message")  // Автоматически несёт предустановленные поля

Logger

Logger — основной логгер, создаваемый через dd.New():

go
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(), представляет собой неизменяемый контейнер предустановленных полей:

go
// Создание 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 предоставляет глобальный логгер, подходящий для простых сценариев или быстрого прототипирования:

go
// Прямое использование пакетных функций (через глобальный Logger)
dd.Info("Глобальный лог")

// Эквивалентно
dd.Default().Info("Глобальный лог")

Система полей

Тип Field

Field — базовая единица структурированного лога, состоящая из пары ключ-значение:

go
// Конструкторы полей покрывают все распространённые типы
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 по цепочке:

go
// Первый уровень: поля уровня сервиса
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

Конвейер обработки логов

Каждый лог проходит следующий процесс обработки:

text
Вызов 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 определяет четыре интерфейса, поддерживающих точное внедрение зависимостей:

text
CoreLogger                    ← Базовое логирование: Debug/Info/Warn/Error/Fatal + WithFields

    ├── LevelLogger           ← Управление уровнями: GetLevel/SetLevel/IsLevelEnabled (встраивает CoreLogger)

    └── ConfigurableLogger    ← Управление конфигурацией: Writer/безопасность/контекст/хуки (встраивает CoreLogger)

LogProvider                   ← Полный функционал: независимый плоский интерфейс, содержащий все методы
go
// Нужен только базовый логгер? Внедрите CoreLogger
type Service struct {
    log dd.CoreLogger
}

// Нужно динамически регулировать уровень? Внедрите LevelLogger
type Handler struct {
    log dd.LevelLogger
}

Лучшие практики

В конструкторах принимайте минимально необходимый интерфейс, а не конкретный тип. Это делает код более тестируемым и гибким.

Потокобезопасная модель

Основной принцип проектирования DD: безопасное использование из нескольких goroutine без дополнительной синхронизации.

КомпонентМеханизм безопасности
LoggerВсе методы безопасны для конкурентного вызова
LoggerEntryНеизменяемый, только для чтения после создания
ConfigМетод Clone() для безопасного копирования
WritersАтомарные указатели, чтение без блокировок
SensitiveDataFilterРазделение чтения и записи, отдельная goroutine
HookRegistryRWMutex защищает регистрацию и чтение (Logger удерживает его указатель через atomic.Value)
go
// Безопасно: несколько 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 поддерживает три цели вывода, которые можно свободно комбинировать:

go
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

Следующие шаги