[Перевод] Что плохого в пакете логирования на Go?

7a53e8c0ee1c344926d98111d3d2dd9b

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

К примеру:

package log
import (
    "io/ioutil"
    "log"
    "os"
)

var (
    Trace = log.New(ioutil.Discard, "TRACE ", log.LstdFlags)
    Debug = log.New(os.Stdout, "DEBUG ", log.LstdFlags)
    // etc
)

В то время как пакет glog от Google предоставляет следующие уровни логирования:

  1. Info

  2. Warn

  3. Error

  4. Fatal

Посмотрим теперь на другую библиотеку, loggo, которую разработали Juju, предоставляет следующие уровни:

  1. Trace

  2. Debug

  3. Info

  4. Warn

  5. Error

  6. Critical

Loggo также предоставляет возможность настроить детализацию ведения журнала для каждого пакета.

Итак, вот два примера, на которые явно повлияли другие библиотеки ведения журналов на других языках. На самом деле их родословная восходит к syslog (3), а может быть, даже раньше. И я думаю, что они ошибаются.

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

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

Итак давайте поговорим про Warnings

Начнем с самого простого. Уровень логирования предупреждение по сути никому не нужен.

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

Кроме того, если вы используете какое-то уровневое ведение логов, то зачем вам устанавливать уровень предупреждение? Вы бы установили уровень на информировпния или ошибки. Установка уровня предупреждения является признанием того, что вы, вероятно, обрабатываете ошибки на уровне предупреждения.

Уберите уровень предупреждение, это либо информационное сообщение, либо состояние ошибки.

Дальше у нас уровень Fatal

Fatal уровень фактически обрабатывает сообщение, а затем вызывает os.Exit (1). В принципе это означает:

  1. Операторы defer в других горутинах не выполняются.

  2. буферы не сбрасываются.

  3. временные файлы и каталоги не удаляются.

По сути, log.Fatal является менее подробным, чем panic, но семантически эквивалентен ему.

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

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

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

Так дальше у нас уровень error

Обработка ошибки и ведение логов тесно связаны между собой, поэтому, на первый взгляд, ведение логирования на уровне error должно быть легко оправдано. Я с этим не согласен.

В Go, если вызов функции или метода возвращает значение ошибки, в реальности у вас есть два варианта решения:

  1. обработать ошибку.

  2. вернуть ошибку вызывающему абоненту. Вы можете упаковать ошибку в подарочную упаковку, но это не важно.

Если вы решите обработать ошибку, записав ее в лог, по определению это больше не ошибка — вы ее обработали. Акт обработки ошибки обрабатывает ошибку, поэтому больше не следует обрабатывать ее как ошибку.

Позвольте мне попытаться убедить вас с помощью этого фрагмента кода:

err := someFunc()
if err != nil {
  log.Error("Error",err)
  return err
}

Вы никогда не должны обрабатывать что-либо на уровне ошибки, потому что вы должны либо обработать ошибку, либо передать ее обратно вызывающему.

Чтобы было ясно, я не говорю, что вы не должны обрабатывать возникновение условия.

if err := someFunc(); err != nil {
        log.Infof("Info: %v", err)
        someOperation()
}

но на самом деле log.Info и log.Error имеют одну и ту же цель.

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

Что дальше?

Мы исключили предупреждения, заявили, что ничего не должно обрабатыватся на уровне ошибок, и показали, что только верхний уровень приложения должен вести себя как log.Fatal. Что осталось?

Я считаю, что есть только две вещи, которые вы должны обрабатывать:

  1. Вещи, которые волнуют разработчиков при разработке или отладке программного обеспечения.

  2. Вещи, которые волнуют пользователей при использовании вашего программного обеспечения

Очевидно, что это отладочный и информационный уровни соответственно.

log.Info должен просто записать эту строку в вывод лога. Не должно быть возможности отключить его, поскольку пользователю следует сообщать только то, что полезно для него. Если возникает ошибка, которую невозможно обработать, она должна всплыть в main.main, где программа завершается. Незначительное неудобство, связанное с необходимостью вставлять префикс fatal перед окончательным сообщением логироыания или писать непосредственно в os.Stderr с помощью fmt.Fprintf, не является достаточным оправданием для пакета ведения лога, расширяющего метод log.Fatal.

log.Debug, совсем другое дело. Это должен контролировать разработчик или инженер службы поддержки. Во время разработки отладочные операторы должны быть многочисленными, не прибегая к уровню трассировки или 2 уровня отладки. Пакет логирования должен поддерживать детальное управление для включения или отключения отладки и только отладки операторов в пакете или, возможно, еще более тонкой области.

В итоге что мы получаем

Если бы это был опрос, я бы попросил вас выбрать между:

  1. ведение логов важно

  2. ведение логов сложно

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

Что вы думаете?

© Habrahabr.ru