Логирование
July 3, 2023

10 факторов, которые следует учитывать при выборе фреймворка для ведения лога 

Фреймворк журналирования (логирования) — это инструмент, который помогает стандартизировать процесс ведения журнала в вашем приложении. Хотя некоторые языки программирования предлагают встроенные модули ведения журналов как часть своих стандартных библиотек, большинство фреймворков являются сторонними библиотеками, такими как Log4j (Java), Zerolog (Go) или Winston (Node.js). Иногда организации предпочитают разрабатывать собственные решения для ведения журналов, но это обычно ограничивается более крупными компаниями с узкоспециализированными потребностями.

Чтобы считаться фреймворком, библиотека должна удовлетворительно решать общие проблемы ведения журналов. Это включает в себя сбор всей соответствующей информации о записанном событии, такой как временные метки и уровни журнала. В идеале журналы должны создаваться в структурированном формате, таком как JSON, чтобы их можно было легко анализировать. Их также следует транспортировать в различные места назначения, такие как консоль, файлы или службы управления журналами. Кроме того, фреймворк должен быть достаточно гибким, чтобы адаптироваться к различным средам и сценариям развертывания, обеспечивая при этом оптимальную производительность даже при работе с большим объемом журналов.

Из-за этих соображений такие пакеты, как модуль ведения журнала в Ruby или модуль консоли в Node.js, не считаются надежными решениями для ведения журнала на производственной зоне. Хотя они могут соответствовать определенным критериям ведения журнала, им не хватает многих важных функций, необходимых для ведения журнала. Поэтому в большинстве сценариев мы рекомендуем вам отказаться от встроенного решения для ведения журнала, предоставляемого с языком, в пользу сторонней библиотеки.

В этой статье будут рассмотрены важные факторы, которые следует учитывать при выборе фреймворка. Он также предоставит примеры конкретных фреймворков, которые соответствуют критериям на некоторых из самых популярных языков.

1. Поддержка уровней журнала

Любая достойный фремворк предоставит уровни журнала, чтобы различать записанные события в зависимости от их серьезности. Тем не менее, конкретные предоставляемые уровни будут отличаться от библиотеки к библиотеке, поэтому вам может потребоваться заранее решить, какие уровни необходимы. В идеале инфраструктура должна позволять настраивать уровни ведения журналов в соответствии с конкретными потребностями, даже если параметры по умолчанию неудовлетворительны. Например, в случае с Node.js популярная библиотека журналирования Winston по умолчанию использует следующие уровни:

{
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  verbose: 4,
  debug: 5,
  silly: 6
}

Но вы можете легко изменить его на что-то более типичное, например:

const logLevels = {
  fatal: 0,
  error: 1,
  warn: 2,
  info: 3,
  debug: 4,
  trace: 5,
};

const logger = winston.createLogger({
  levels: logLevels,
});

2. Влияние на поведение приложения и тестирование

Операторы журнала должны соответствовать основному требованию не влиять на поведение программы. По сути, они не должны влиять на ход выполнения и корректность приложения. Кроме того, должна быть возможность просто отключить ведение журнала во время тестирования, чтобы предотвратить влияние на результаты тестирования. Это помогает избежать ненадежных тестов, вызванных различиями в выводе журнала, и гарантирует, что тесты сосредоточены исключительно на предполагаемой функциональности, не затрагивая операторы журналирования.

3. Поддержка структурированных форматов

В настоящее время большинство систем ведения журналов по умолчанию генерируют неструктурированные или частично структурированные данные журнала, предназначенные в первую очередь для потребления человеком. Эти журналы состоят из строк, которые часто содержат встроенные переменные, требующие последующего анализа и извлечения. Основным недостатком неструктурированных журнальных записей является сложность автоматизации их обработки из-за отсутствия стандартизированного формата. Поиск определенных событий в неструктурированных журналах обычно требует создания настраиваемых регулярных выражений, которые могут потребовать модификации при изменении данных журнала.

DEBUG [2023-06-18 10:23:45] [app.js:123] - User authentication successful for user 'john.doe'.
INFO [2023-06-18 11:45:32] [api.js:456] - Request received from IP '192.168.0.1' to access resource '/api/data'.
ERROR [2023-06-18 13:57:21] [db.js:789] - Database connection failed. Error: Timeout reached while connecting to the database.

Напротив, структурированные журналы состоят из объектов, а не строк. Каждое свойство внутри объекта может быть автоматически извлечено и обработано инструментами обработки журналов, что позволяет выполнять поиск, оповещения и генерировать форматы вывода, понятные людям. Хорошая новость заключается в том, что многие фреймворки ведения журналов поддерживают уже структурированное ведение журналов. Некоторые фреймворки по умолчанию используют структурированное ведение журналов , но вы должны активировать его в других . Большинство фреймворков используют JSON для служб управления из-за его повсеместной поддержки среди различных инструментов и служб ведения журналов.

{"timestamp": "2023-06-18 10:23:45", "file": "app.js", "line": 123, "level": "DEBUG", "message": "User authentication successful for user 'john.doe'"}
{"timestamp": "2023-06-18 11:45:32", "file": "api.js", "line": 456, "level": "INFO", "message": "Request received from IP '192.168.0.1' to access resource '/api/data'"}
{"timestamp": "2023-06-18 13:57:21", "file": "db.js", "line": 789, "level": "ERROR", "message": "Database connection failed. Error: Timeout reached while connecting to the database"}

Основным недостатком структурированного ведения журналов является то, что им обычно не хватает естественного языка и удобочитаемости, которые обеспечивают неструктурированные журналы, что затрудняет быстрое понимание информации, содержащейся в журнале, без дополнительных инструментов.

Поэтому, возможно, стоит рассмотреть структуру, которая упрощает предварительную обработку вывода журнала во время разработки. Это обеспечивает более удобное для человека отображение, возможно, с цветным выводом консоли, в то время как производственные системы используют более эффективный структурированный формат в сочетании со службами управления журналами.

4. Поддержка регистрации контекстных данных

Хорошая структура ведения журнала должна обеспечивать адекватную поддержку добавления контекстных данных в записи журнала. Такие данные могут включать в себя номер файла и строки события, идентификаторы запроса или сеанса, идентификаторы пользователей, идентификаторы процессов или любые другие соответствующие данные, которые обеспечивают понимание конкретного контекста события журнала. В идеале платформа должна обеспечивать гибкость добавления контекстных данных в определенные точки журнала, в определенных областях или даже во всех журналах приложений.

Например, с Pino вы можете добавить контекстные данные в точку журнала следующим образом:

logger.error(
  { transaction_id: '12343_ff', user_id: 'johndoe' },
  'Transaction failed'
);

Вы также можете добавить контекст к группе журналов, используя дочерние регистраторы:

function getEntity(entityID) {
  const childLogger = logger.child({ entity_id: entityID });
  childLogger.trace('getEntity invoked');
  childLogger.trace('getEntity completed');
}

getEntity('entity_id');

Наконец, вы можете добавить универсальные данные во все журналы при создании логгера:

const pino = require('pino');

const logger = pino({
  formatters: {
    bindings: (bindings) => {
      return { pid: bindings.pid, host: bindings.hostname, node_version: process.version };
    },
  },
});

5. Поведение при обработке ошибок

При рассмотрении структуры ведения журналов вы должны проверить, как она обрабатывает ошибки, поскольку они часто являются основным объектом производственного ведения журналов. Хорошая структура должна собирать всю необходимую информацию о возникшей ошибке, включая полную трассировку стека. Удивительно, но некоторые популярные фреймворки ошибаются , поэтому очень важно тщательно протестировать и внимательно изучить документацию, чтобы проверить возможности обработки ошибок выбранного вами решения для ведения журналов.

Некоторые фреймворки выводят всю трассировку стека в виде строки в свойстве объекта, например:

{"level":"error","time":1643706943924,"pid":13185,"hostname":"Kreig","err":{"type":"Error","message":"ValidationError: email address in invalid","stack":"Error: ValidationError: email address in invalid\n    at Object.<anonymous> (/home/ayo/dev/betterstack/demo/snippets/main.js:3:14)\n    at Module._compile (node:internal/modules/cjs/loader:1097:14)\n    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1149:10)\n    at Module.load (node:internal/modules/cjs/loader:975:32)\n    at Function.Module._load (node:internal/modules/cjs/loader:822:12)\n    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)\n    at node:internal/main/run_main_module:17:47"},"msg":"ValidationError: email address in invalid"}

При работе с языком, который использует исключения, вы также должны выяснить, может ли платформа перехватывать и регистрировать неперехваченные исключения до выхода из программы, чтобы вы могли получить представление о причине и контексте сбоя, что позволяет более эффективно решать основные проблемы.

const winston = require('winston');
const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.json(),
  transports: [new winston.transports.Console()],
  // Winston can automatically catch and log uncaught exceptions and promise
  // rejections before the program exits
  exceptionHandlers: [
    new winston.transports.File({ filename: 'exception.log' }),
  ],
  rejectionHandlers: [
    new winston.transports.File({ filename: 'rejections.log' }),
  ],
});

6. Влияние на производительность приложений

Ведение журналов не должно значительно снижать производительность вашего приложения или потреблять чрезмерные ресурсы даже при создании большого объема журналов. Поэтому также важно выбрать платформу ведения журнала с хорошими характеристиками производительности. Например, начинать новый проект с Logrus для структурированного ведения журналов в Go — плохое решение, даже несмотря на то, что он обладает многими характеристиками хорошего фреймворка для ведения журналов. Он на порядок медленнее, чем более новые варианты, такие как Zap , Slog и Zerolog , и выделяет намного больше памяти.

Выбрав структуру, которая сводит к минимуму выделение объектов (objects allocated), вы гарантируете, что ваши операторы ведения журнала добавляют минимум накладных расходов во время выполнения вашего приложения.

7. Адекватные опции переноса журнала

Перенос записей журнала в одно или несколько мест назначения является еще одним важным фактором при выборе среды ведения журнала. Как минимум, фреймворк должен поддерживать запись журналов в стандартный вывод/стандартный вывод ошибок и в файл. В идеале, однако, он должен обеспечивать гибкость в настройке нужных вам мест для вывода журнала, поскольку современные приложения, как правило, используют веб-агрегаторы журналов в дополнение к локальному механизму.

import winston from 'winston';
import { Logtail } from '@logtail/node';
import { LogtailTransport } from '@logtail/winston';

const logtail = new Logtail('<your_source_token>');

const logger = winston.createLogger({
  // Winston can automatically send logs to multiple destinations such as the
  // console and a web-based log management solution. You can even use different
  // formats for each destination based on how the log data is used
  transports: [
    new winston.transports.Console(),
    new LogtailTransport(logtail),
  ],
});

export default logger;

Если у вас есть полный контроль над средой вашего приложения, вы можете выводить журналы на консоль или в файл и использовать специальный поставщик журналов, такой как Fluentd, Logstash или Vector, для сбора, преобразования и пересылки записей в другие места назначения по мере необходимости. Такой подход обеспечивает централизованное управление журналами и гибкость при маршрутизации журналов в разные системы.

Однако в ситуациях, когда платформа развертывания ограничивает доступ к среде приложения, возможность платформы ведения журналов напрямую отправлять журналы в конечные пункты назначения становится решающей. Поддержка ротации файлов журналов также является полезной функцией, но мы считаем, что такие задачи обычно лучше всего выполнять с помощью стандартных инструментов Linux, таких как Logrotate.

8. Поддержка выборки журнала

В современной среде, где часто создается большое количество журналов, управление всеми записями и их хранение могут стать довольно дорогостоящими. Выборка журнала (log sampling) — это метод, используемый для смягчения этой проблемы путем выборочного захвата и сохранения подмножества событий журнала вместо перекодирования каждой отдельной записи.

Концепция выборки журналов заключается в сохранении репрезентативной выборки журналов при отбрасывании остальных, что снижает требования к хранилищу и связанные с этим расходы. Он включает в себя случайный или систематический выбор части событий журнала для хранения на основе заранее определенных критериев.

Частота выборки определяет долю сохраняемых журналов по сравнению с общим объемом сгенерированных журналов. Например, частота выборки 10 % означает, что только 10 % событий журнала будут сохранены и проанализированы, а остальные 90 % будут отброшены.

Конечно, вы должны тщательно продумать частоту и критерии выборки, чтобы обеспечить репрезентативность захваченных журналов и отсутствие потери критических событий из-за выборки. Вот пример, который настраивает регистратор Zap в Go для выборки записей журнала с тем же уровнем и сообщением:

func createLogger() *zap.Logger {
    stdout := zapcore.AddSync(os.Stdout)

    level := zap.NewAtomicLevelAt(zap.InfoLevel)
    productionCfg := zap.NewProductionEncoderConfig()
    jsonEncoder := zapcore.NewJSONEncoder(productionCfg)
    jsonOutCore := zapcore.NewCore(jsonEncoder, stdout, level)

    samplingCore := zapcore.NewSamplerWithOptions(
        jsonOutCore,
        time.Second, // interval
        5, // log first five entries
        0, // thereafter log zero entires within the interval
    )

    return zap.New(samplingCore)
}

Здесь только первые пять записей с одинаковым уровнем и сообщением записываются с интервалом в одну секунду. Любая другая запись в этом интервале будет автоматически отброшена, поскольку 0здесь указано. Вы можете увидеть это в действии, войдя в forцикл:

func main() {
    logger := createLogger()

    defer logger.Sync()

    for i := 1; i <= 100; i++ {
        logger.Info("an info message")
    }
}

Без выборки журнала при выполнении цикла будет создано 100 идентичных журналов. Однако при выборке журнала регистрируются только пять записей, а остальные отбрасываются. Таким образом, вы можете значительно снизить стоимость хранения журналов, не ставя под угрозу вашу способность собирать соответствующую информацию о вашей программе.

9. Настройка и расширяемость

Помимо того, что в настоящее время предлагает инфраструктура, вам также необходимо оценить ее гибкость, чтобы адаптироваться к изменяющимся требованиям и интеграции с более широкой экосистемой наблюдаемости. Например, должны быть API-интерфейсы, позволяющие настраивать формат журнала, добавлять настраиваемые поля, выполнять пользовательскую логику до или после обработки событий журнала, интегрироваться со сторонними системами и т. д.!

// Implementing Zerolog's Hook interface allows you to execute some code
// each time a log event is captured
type Hook interface {
    Run(e *zerolog.Event, level zerolog.Level, message string)
}

10. Репутация и поддержка сообщества

Еще одним важным соображением при выборе среды ведения журналов является ее репутация в сообществе разработчиков. Устоявшиеся фреймворки для ведения журналов, которые использовались в течение нескольких лет, часто имеют множество доступных пользовательских интерфейсов и отчетов. Этот опыт может послужить ценной информацией и помочь вам в процессе принятия решений при выборе между несколькими хорошими вариантами.

Вы также должны учитывать размер и уровень активности сообщества фреймворка. Активное сообщество указывает на текущую разработку, исправление ошибок и поддержку со стороны других разработчиков. Проверьте качество документации проекта и наличие руководств и форумов сообщества или каналов чата для помощи в решении проблем.

Некоторые рекомендации по фреймворку для ведения журналов

Чтобы упростить процесс выбора фреймворка для ведения журнала, мы составили список рекомендуемых фреймворков для нескольких популярных языков программирования. Эти фреймворки были выбраны на основе их возможностей, поддержки сообществом и признания. Чтобы углубиться в эти библиотеки, вы можете изучить приведенные ниже руководства для получения более подробной информации.

1. Pino (Node.js)

Pino — это высокопроизводительная структура структурированного ведения журналов для приложений Node.js, которая предлагает множество полезных функций. Хотя он по умолчанию ведет журнал в формате JSON, он предоставляет модуль pino-pretty для повышения удобочитаемости вывода журнала в процессе разработки. Кроме того, он предлагает гибкость для создания журналов в других форматах с использованием транспортных модулей.

Он также встроен в веб-фреймворк Fastify и легко интегрируется с другими популярными веб-фреймворками Node.js, такими как Express. Он выделяется своей уникальной функцией редактирования журналов, которая помогает обеспечить безопасность конфиденциальных данных, предотвращая их включение в журналы. По сравнению с Winston , еще одним популярным вариантом ведения журнала, Pino легче, проще в использовании и поставляется с более разумными настройками по умолчанию и более высокой производительностью при загрузке. В целом, мы рекомендуем Pino для структурированного ведения журналов в приложениях Node.js.

2. Zerolog, Zap или Slog (Go)

Zerolog выделяется как самая быстрая среда ведения журнала в экосистеме Go с минимальными выделениями. Он ориентирован исключительно на создание структурированного вывода, но также предоставляет удобный для человека цветной консольный вывод для сред разработки. Он предлагает такие функции, как выборка журнала, ловушки и отформатированные трассировки стека для регистрации ошибок.

Если вы ищете более настраиваемую структуру, стоит рассмотреть пакет Zap от Uber . Он стал пионером в подходе к ведению журналов с нулевым выделением памяти, используемом Zerolog, и предлагает настраиваемый API, который должен соответствовать большинству требований. Еще один предстоящий вариант — Slog , API структурированного ведения журналов, интегрированный в стандартную библиотеку Go, который, как ожидается, будет доступен начиная с версии Go 1.21. Slog можно использовать как автономное средство ведения журнала или как внешний интерфейс ведения журнала, обеспечивая гибкость и отделяя ведение журнала приложения от какой-либо конкретной платформы.

3. Monolog (PHP)

Monolog — самая популярная среда ведения журналов PHP. Он получил значительную поддержку и широкое распространение, будучи интегрированным в основные платформы приложений PHP, такие как Laravel и Symfony, чтобы обеспечить надежное решение для ведения журналов. Он поддерживает различные форматы журналов, включая JSON, и несколько обработчиков для переноса ваших журналов в различные места назначения. Он также реализует интерфейс регистратора PSR-3 (общий интерфейс PHP для объектов регистратора), что способствует взаимодействию и упрощает будущие переходы на альтернативные совместимые библиотеки, если это необходимо.

4. SLF4J с Log4J2 или Logback (Java)

Простой фасад ведения журналов для Java (SLF4J) служит фасадом ведения журналов, который абстрагирует различные среды ведения журналов Java . Внедряя общий уровень API, SLF4J обеспечивает плавную миграцию между различными платформами ведения журналов с минимальными нарушениями. Если вы обнаружите, что один фреймворк не соответствует вашим потребностям или требованиям, вы можете легко переключиться на другой без существенных изменений кода. При рассмотрении реализаций SLF4J высоко ценятся как Log4j2 , так и Logback . Тем не менее, Log4j2 выделяется как более активно поддерживаемая структура и лучшая производительность из двух.

5. Loguru (Python)

Хотя встроенный модуль ведения журнала в стандартной библиотеке Python достаточно надежен и широко используется в сообществе, его установка и настройка иногда могут быть сложными даже для простых задач. Если вы находите родной loggingAPI громоздким, отличной альтернативой для изучения является Loguru . Это наиболее известный сторонний фреймворк для ведения журналов для Python, который позиционирует себя как более простая альтернатива, предварительно настроенная (но настраиваемая) со многими функциями, обсуждаемыми в этой статье. Примечательно, что он сохраняет совместимость со стандартным модулем ведения журнала, обеспечивая плавный переход, если вы решите перейти со встроенного модуля.

6. Семантический логгер (Ruby)

Стандартный модуль ведения журналов Ruby не соответствует нашим требованиям к хорошей среде ведения журналов из-за отсутствия в нем контекстного ведения журналов и других дополнительных функций. Поэтому мы рекомендуем использовать стороннюю платформу, такую ​​как Semantic Logger , которая поддерживает структурированное и контекстно-зависимое ведение журнала в формате JSON, несколько мест назначения вывода и многое другое. Он также соответствует стандартному интерфейсу ведения журналов Ruby, поэтому вам нужно только изменить способ создания регистратора для миграции.

Заключительные мысли и следующие шаги

Выбор фреймворка является важным решением для вашего приложения, поскольку он будет играть важную роль в регистрации событий, которые помогут вам эффективно управлять приложением и устранять неполадки. Мы надеемся, что рекомендации, представленные выше, помогут вам выбрать фреймворк, который наилучшим образом соответствует вашим требованиям.

После того как вы интегрировали инфраструктуру ведения журналов в свой проект, следующим шагом будет обеспечение отправки записанных данных в службу управления журналами, чтобы их можно было эффективно агрегировать, хранить и анализировать. Используя Better Stack , вы можете централизовать данные журналов, упростить анализ журналов и включить мониторинг в реальном времени, чтобы важные события быстро отслеживались и устранялись.

Спасибо за прочтение и удачной регистрации!

Источник: https://betterstack.com/community/guides/logging/logging-framework/