Этот пост вдохновлен темой в форуме Go Forum, начатой Nate Finch. Этот пост сконцентрирован на языке Go, но если пройти мимо этого, я думаю, идеи представленные тут широко применимы.
Пакет log в Go не имеет уровней для логов, вы можете сами вручную добавить приставки DEBUG, INFO, WARN, и ERROR. Также logger тип в Go не имеет возможности включить или выключить эти уровни отдельно для выбранных пакетов. Для сравнения давайте глянем на несколько его замен от сторонних разработчиков.
![image](https://habrastorage.org/r/w1560/getpro/habr/post_images/df2/fc3/3ed/df2fc33ed7ebb1457c25060bbb1f98a7.png)
glog от Google имеет уровни:
Посмотрим на другую библиотеку, loggo, разработанную для Juju, в ней доступны уровни:
Loggo также имеет возможность задать уровень детализации лога для нужных пакетов по отдельности.
Перед вами два примера, явно созданных под влиянием других библиотек для логирования на других языках.
Фактически их происхождение можно проследить до syslog(3), возможно, даже раньше. И я думаю, что они не правы.
Я хочу занять противоречивую позицию. Я думаю, что все библиотеки журналов плохи, потому что предлагают слишком много функций; ошеломляющий набор возможностей, который ошеломляет программиста прямо в тот момент, когда он должен ясно думать о том, как общаться с читателем из будущего, с тем, кто будет просматривать эти журналы.
Я утверждаю, что для успешных пакетов логов требуется гораздо меньше возможностей и, конечно, меньше уровней.
Давайте начнем с самого простого. Никому не нужен уровень журнала WARNING (предупреждение).
Никто не читает предупреждения, потому что по определению ничего плохого не произошло. Возможно, что-то может пойти не так в будущем, но это звучит как чья-то, a не моя проблема.
Кроме того, если вы используете какое-то многоуровневое логирование, зачем вам устанавливать уровень WARNING? Вы установили бы уровень INFO или ERROR. Установка уровня WARNING означает, что вы, вероятно, регистрируете ошибки на уровне WARNING.
Исключите уровень warning — это или информационное сообщение, или ошибка.
Уровень FATAL фактически заносит сообщение в лог, а затем вызывает os.Exit(1). В принципе это означает:
По сути, log.Fatal менее многословен, но семантически эквивалентен панике.
Общепринято, что библиотеки не должны использовать panic1, но если вызов log.Fatal2 имеет тот же эффект, он также должен быть запрещен.
Предположения, что эта проблема очистки может быть решена путем регистрации обработчиков завершения работы в логгере, обеспечивает тесную связь между используемым логгером и каждым местом, где происходят операции очистки. Это также нарушает разделение интересов.
Не записывайте сообщения с уровнем FATAL, предпочтите вместо этого вернуть ошибку вызывающей стороне. Если ошибка доходит до main.main, то это правильное место для выполнения любых действий по очистке перед завершением программы.
Обработка ошибок и ведение журнала (лога) тесно связаны, поэтому, на первый взгляд, регистрация на уровне ошибок (ERROR) должна быть легко оправданной. Я не согласен.
В Go, если вызов функции или метода возвращает значение ошибки, то реально у вас есть два варианта:
Если вы решите обработать ошибку, записав ее в лог, то по определению это больше уже не ошибка — вы ее обработали. Сам акт регистрации ошибки и есть обработка ошибки, следовательно, больше не целесообразно ее записывать в лог как ошибку.
Позвольте мне убедить вас с помощью этого фрагмента кода:
Вы никогда не должны регистрировать что-либо на уровне ошибки, потому что вы должны либо обработать ошибку, либо передать ее вызывающей стороне.
Нужно четко понимать, я не говорю, что вы не должны записывать в лог, что произошла смена состояния:
Но в действительности log.Info и log.Error имеют одну и ту же цель.
Я не говорю «не регистрируйте ошибки»! Вместо этого я ставлю вопрос, что является наименьшим возможным API для ведения журнала (логирования)? И когда дело доходит до ошибок, я считаю, что подавляющая часть вещей, записанных на уровне ERROR, просто делается так, потому что они связаны с ошибкой. На самом деле они просто информационные, поэтому мы можем удалить логирование на уровне ошибок (ERROR) из нашего API.
Мы исключили предупреждения (WARNING), аргументировали, что ничего не должно регистрироваться на уровне ошибок (ERROR), и показали, что только верхний уровень приложения должен иметь своего рода log.Fatal поведение. Что осталось?
Я считаю, что есть только две вещи, которые вы должны заносить в лог:
Очевидно, это уровни отладка (DEBUG) и информационный (INFO) соответственно.
log.Info должен просто записать эту строку в вывод журнала. Не должно быть возможности отключить его, так как пользователю следует рассказывать только то, что ему полезно. Если возникает ошибка, которая не может быть обработана, она должна появиться в main.main там, где программа завершается. Незначительные неудобства, связанные с необходимостью вставки префикса FATAL перед окончательным сообщением журнала или записи непосредственно в os.Stderr с помощью fmt.Fprintf, не является достаточным основанием для расширения пакета матодом log.Fatal.
log.Debug, это совсем другое дело. Он нужен разработчику или инженера поддержки для контроля работы программы. Во время разработки выражения отладки (debug) должны быть многочисленными, не прибегая к уровню трассировки (trace) или debug2 (ты знаешь кто ты). Пакет ведения логов должен поддерживать детализированное управление для включения или отключения выражений отладки, для нужных пакетов пакете или, возможно, даже в более узкой области видимости.
Если бы это был опрос в Твиттере, я бы попросил вас выбрать между
Но дело в том, что ведение лога является и тем и другим. Решение этой проблемы ДОЛЖНО состоять в том, чтобы разрушать и беспощадно сокращать ненужные отвлечения.
Как вы думаете? Это достаточно сумасбродно, чтобы работать, или просто сумасбродно?
Некоторые библиотеки могут использовать panic/recover в качестве механизма внутреннего потока управления, но основная мантра заключается в том, что они не должны допускать утечки этих операций потока управления за границу пакета.
По иронии судьбы, хотя в нем отсутствует уровень вывода DEBUG, стандартный пакет логирования Go имеет функции Fatal и Panic. В этом пакете количество функций, которые приводят к внезапному завершению работы программы, превышает число тех, которые этого не делают.
Автор данной статьи, Дейв Чини, является автором многих популярных пакетов для Go, например github.com/pkg/errors и github.com/davecheney/httpstat. Авторитет и опыт автора вы можете оценить самостоятельно.
Насчет ведения логов обеспокоено достаточно много разработчиков, некоторые обсуждали внесение Logger интерфейса в стандартную библиотеку Go, чтобы упорядочить ведение логов в библиотеках и так стимулировать их разработчиков. Ребята даже оформили свое предложение и выставили документ на обсуждение.
Плюс презентация размышление Нужен ли нам новый логер и каким он должен быть? от Chris Hines.
Есть несколько реализаций идей Дейва go-log и немного отходящий в вопросе уровня ERROR и более тщательно продуманный пакет logr.
Почему нет любви?
Пакет log в Go не имеет уровней для логов, вы можете сами вручную добавить приставки DEBUG, INFO, WARN, и ERROR. Также logger тип в Go не имеет возможности включить или выключить эти уровни отдельно для выбранных пакетов. Для сравнения давайте глянем на несколько его замен от сторонних разработчиков.
![image](https://habrastorage.org/getpro/habr/post_images/df2/fc3/3ed/df2fc33ed7ebb1457c25060bbb1f98a7.png)
glog от Google имеет уровни:
- Info
- Warning
- Error
- Fatal (завершает программу)
Посмотрим на другую библиотеку, loggo, разработанную для Juju, в ней доступны уровни:
- Trace
- Debug
- Info
- Warning
- Error
- Critical
Loggo также имеет возможность задать уровень детализации лога для нужных пакетов по отдельности.
Перед вами два примера, явно созданных под влиянием других библиотек для логирования на других языках.
Фактически их происхождение можно проследить до syslog(3), возможно, даже раньше. И я думаю, что они не правы.
Я хочу занять противоречивую позицию. Я думаю, что все библиотеки журналов плохи, потому что предлагают слишком много функций; ошеломляющий набор возможностей, который ошеломляет программиста прямо в тот момент, когда он должен ясно думать о том, как общаться с читателем из будущего, с тем, кто будет просматривать эти журналы.
Я утверждаю, что для успешных пакетов логов требуется гораздо меньше возможностей и, конечно, меньше уровней.
Давайте поговорим о предупреждениях (WARNING)
Давайте начнем с самого простого. Никому не нужен уровень журнала WARNING (предупреждение).
Никто не читает предупреждения, потому что по определению ничего плохого не произошло. Возможно, что-то может пойти не так в будущем, но это звучит как чья-то, a не моя проблема.
Кроме того, если вы используете какое-то многоуровневое логирование, зачем вам устанавливать уровень WARNING? Вы установили бы уровень INFO или ERROR. Установка уровня WARNING означает, что вы, вероятно, регистрируете ошибки на уровне WARNING.
Исключите уровень warning — это или информационное сообщение, или ошибка.
Давайте поговорим об уровне невосстановимой ошибки (fatal)
Уровень FATAL фактически заносит сообщение в лог, а затем вызывает os.Exit(1). В принципе это означает:
- отложенные выражения в других подпрограммах(горутинах) не выполняются;
- буферы не очищаются;
- временные файлы и каталоги не удаляются.
По сути, log.Fatal менее многословен, но семантически эквивалентен панике.
Общепринято, что библиотеки не должны использовать panic1, но если вызов log.Fatal2 имеет тот же эффект, он также должен быть запрещен.
Предположения, что эта проблема очистки может быть решена путем регистрации обработчиков завершения работы в логгере, обеспечивает тесную связь между используемым логгером и каждым местом, где происходят операции очистки. Это также нарушает разделение интересов.
Не записывайте сообщения с уровнем FATAL, предпочтите вместо этого вернуть ошибку вызывающей стороне. Если ошибка доходит до main.main, то это правильное место для выполнения любых действий по очистке перед завершением программы.
Давайте поговорим об ошибке (уровень ERROR)
Обработка ошибок и ведение журнала (лога) тесно связаны, поэтому, на первый взгляд, регистрация на уровне ошибок (ERROR) должна быть легко оправданной. Я не согласен.
В Go, если вызов функции или метода возвращает значение ошибки, то реально у вас есть два варианта:
- обработать ошибку.
- вернуть ошибку своему вызвавшей стороне. Вы можете красиво завернуть ошибку в подарочную упаковку (wrap), но это не важно для этого обсуждения.
Если вы решите обработать ошибку, записав ее в лог, то по определению это больше уже не ошибка — вы ее обработали. Сам акт регистрации ошибки и есть обработка ошибки, следовательно, больше не целесообразно ее записывать в лог как ошибку.
Позвольте мне убедить вас с помощью этого фрагмента кода:
err := somethingHard()
if err != nil {
log.Error("oops, something was too hard", err)
return err // what is this, Java ?
}
Вы никогда не должны регистрировать что-либо на уровне ошибки, потому что вы должны либо обработать ошибку, либо передать ее вызывающей стороне.
Нужно четко понимать, я не говорю, что вы не должны записывать в лог, что произошла смена состояния:
if err := planA(); err != nil {
log.Infof("could't open the foo file, continuing with plan b: %v", err)
planB()
}
Но в действительности log.Info и log.Error имеют одну и ту же цель.
Я не говорю «не регистрируйте ошибки»! Вместо этого я ставлю вопрос, что является наименьшим возможным API для ведения журнала (логирования)? И когда дело доходит до ошибок, я считаю, что подавляющая часть вещей, записанных на уровне ERROR, просто делается так, потому что они связаны с ошибкой. На самом деле они просто информационные, поэтому мы можем удалить логирование на уровне ошибок (ERROR) из нашего API.
Что осталось?
Мы исключили предупреждения (WARNING), аргументировали, что ничего не должно регистрироваться на уровне ошибок (ERROR), и показали, что только верхний уровень приложения должен иметь своего рода log.Fatal поведение. Что осталось?
Я считаю, что есть только две вещи, которые вы должны заносить в лог:
- вещи, о которых заботятся разработчики, когда они разрабатывают или отлаживают программу;
- вещи, которые волнуют пользователей при использовании вашей программы.
Очевидно, это уровни отладка (DEBUG) и информационный (INFO) соответственно.
log.Info должен просто записать эту строку в вывод журнала. Не должно быть возможности отключить его, так как пользователю следует рассказывать только то, что ему полезно. Если возникает ошибка, которая не может быть обработана, она должна появиться в main.main там, где программа завершается. Незначительные неудобства, связанные с необходимостью вставки префикса FATAL перед окончательным сообщением журнала или записи непосредственно в os.Stderr с помощью fmt.Fprintf, не является достаточным основанием для расширения пакета матодом log.Fatal.
log.Debug, это совсем другое дело. Он нужен разработчику или инженера поддержки для контроля работы программы. Во время разработки выражения отладки (debug) должны быть многочисленными, не прибегая к уровню трассировки (trace) или debug2 (ты знаешь кто ты). Пакет ведения логов должен поддерживать детализированное управление для включения или отключения выражений отладки, для нужных пакетов пакете или, возможно, даже в более узкой области видимости.
Заключение
Если бы это был опрос в Твиттере, я бы попросил вас выбрать между
- ведение логов — это важно
- ведение логов — это трудно
Но дело в том, что ведение лога является и тем и другим. Решение этой проблемы ДОЛЖНО состоять в том, чтобы разрушать и беспощадно сокращать ненужные отвлечения.
Как вы думаете? Это достаточно сумасбродно, чтобы работать, или просто сумасбродно?
Примечания
Некоторые библиотеки могут использовать panic/recover в качестве механизма внутреннего потока управления, но основная мантра заключается в том, что они не должны допускать утечки этих операций потока управления за границу пакета.
По иронии судьбы, хотя в нем отсутствует уровень вывода DEBUG, стандартный пакет логирования Go имеет функции Fatal и Panic. В этом пакете количество функций, которые приводят к внезапному завершению работы программы, превышает число тех, которые этого не делают.
Об авторе
Автор данной статьи, Дейв Чини, является автором многих популярных пакетов для Go, например github.com/pkg/errors и github.com/davecheney/httpstat. Авторитет и опыт автора вы можете оценить самостоятельно.
От переводчика
Насчет ведения логов обеспокоено достаточно много разработчиков, некоторые обсуждали внесение Logger интерфейса в стандартную библиотеку Go, чтобы упорядочить ведение логов в библиотеках и так стимулировать их разработчиков. Ребята даже оформили свое предложение и выставили документ на обсуждение.
Плюс презентация размышление Нужен ли нам новый логер и каким он должен быть? от Chris Hines.
Есть несколько реализаций идей Дейва go-log и немного отходящий в вопросе уровня ERROR и более тщательно продуманный пакет logr.