В пакете логирования 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 предоставляет следующие уровни логирования:
Итак, вот два примера, на которые явно повлияли другие библиотеки ведения журналов на других языках. На самом деле их родословная восходит к syslog(3), а может быть, даже раньше. И я думаю, что они ошибаются.
Я хочу занять противоречивую позицию. Я думаю, что все библиотеки логирования плохи, потому что предлагают слишком много функций; сбивающий с толку набор выбора, который ослепляет программиста как раз в тот момент, когда он должен ясно думать о том, как общаться с читателем из будущего; тот, кто будет потреблять их логи.
Я утверждаю, что успешные пакеты ведения логов нуждаются в гораздо меньшем количестве функций и, конечно же, меньшем количестве уровней.
Информирования никто не читает, потому что по определению ничего не пошло не так. Возможно, в будущем что-то пойдет не так, но это похоже на чью-то проблему.
Кроме того, если вы используете какое-то уровневое ведение логов, то зачем вам устанавливать уровень предупреждение? Вы бы установили уровень на информирование или ошибки. Установка уровня предупреждения является признанием того, что вы, вероятно, обрабатываете ошибки на уровне предупреждения.
Уберите уровень предупреждение, это либо информационное сообщение, либо состояние ошибки.
Принято считать, что библиотеки не должны использовать panic, но если вызов log.Fatal имеет тот же эффект, то, безусловно, это тоже должно быть запрещено.
Предположения о том, что эту проблему очистки можно решить путем регистрации обработчиков завершения работы в системе ведения логирования, создают тесную связь между вашей системой ведения логов и каждым местом, где происходят операции очистки; это также нарушает разделение задач.
Не логируйтесь на фатальном уровне, предпочитайте вместо этого возвращать вызывающему коду ошибку. Если ошибка всплывает вплоть до main.main, то это подходящее место для обработки любых действий по очистке перед выходом.
В Go, если вызов функции или метода возвращает значение ошибки, в реальности у вас есть два варианта решения:
Позвольте мне попытаться убедить вас с помощью этого фрагмента кода:
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.Info должен просто записать эту строку в вывод лога. Не должно быть возможности отключить его, поскольку пользователю следует сообщать только то, что полезно для него. Если возникает ошибка, которую невозможно обработать, она должна всплыть в main.main, где программа завершается. Незначительное неудобство, связанное с необходимостью вставлять префикс fatal перед окончательным сообщением логироыания или писать непосредственно в os.Stderr с помощью fmt.Fprintf, не является достаточным оправданием для пакета ведения лога, расширяющего метод log.Fatal.
log.Debug, совсем другое дело. Это должен контролировать разработчик или инженер службы поддержки. Во время разработки отладочные операторы должны быть многочисленными, не прибегая к уровню трассировки или 2 уровня отладки. Пакет логирования должен поддерживать детальное управление для включения или отключения отладки и только отладки операторов в пакете или, возможно, еще более тонкой области.
К примеру:
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 предоставляет следующие уровни логирования:
- Info
- Warn
- Error
- Fatal
- Trace
- Debug
- Info
- Warn
- Error
- Critical
Итак, вот два примера, на которые явно повлияли другие библиотеки ведения журналов на других языках. На самом деле их родословная восходит к syslog(3), а может быть, даже раньше. И я думаю, что они ошибаются.
Я хочу занять противоречивую позицию. Я думаю, что все библиотеки логирования плохи, потому что предлагают слишком много функций; сбивающий с толку набор выбора, который ослепляет программиста как раз в тот момент, когда он должен ясно думать о том, как общаться с читателем из будущего; тот, кто будет потреблять их логи.
Я утверждаю, что успешные пакеты ведения логов нуждаются в гораздо меньшем количестве функций и, конечно же, меньшем количестве уровней.
Итак давайте поговорим про Warnings
Начнем с самого простого. Уровень логирования предупреждение по сути никому не нужен.Информирования никто не читает, потому что по определению ничего не пошло не так. Возможно, в будущем что-то пойдет не так, но это похоже на чью-то проблему.
Кроме того, если вы используете какое-то уровневое ведение логов, то зачем вам устанавливать уровень предупреждение? Вы бы установили уровень на информирование или ошибки. Установка уровня предупреждения является признанием того, что вы, вероятно, обрабатываете ошибки на уровне предупреждения.
Уберите уровень предупреждение, это либо информационное сообщение, либо состояние ошибки.
Дальше у нас уровень Fatal
Fatal уровень фактически обрабатывает сообщение, а затем вызывает os.Exit(1). В принципе это означает:
- Операторы defer в других горутинах не выполняются.
- буферы не сбрасываются.
- временные файлы и каталоги не удаляются.
Принято считать, что библиотеки не должны использовать panic, но если вызов log.Fatal имеет тот же эффект, то, безусловно, это тоже должно быть запрещено.
Предположения о том, что эту проблему очистки можно решить путем регистрации обработчиков завершения работы в системе ведения логирования, создают тесную связь между вашей системой ведения логов и каждым местом, где происходят операции очистки; это также нарушает разделение задач.
Не логируйтесь на фатальном уровне, предпочитайте вместо этого возвращать вызывающему коду ошибку. Если ошибка всплывает вплоть до main.main, то это подходящее место для обработки любых действий по очистке перед выходом.
Так дальше у нас уровень error
Обработка ошибки и ведение логов тесно связаны между собой, поэтому, на первый взгляд, ведение логирования на уровне error должно быть легко оправдано. Я с этим не согласен.В Go, если вызов функции или метода возвращает значение ошибки, в реальности у вас есть два варианта решения:
- обработать ошибку.
- вернуть ошибку вызывающему абоненту. Вы можете упаковать ошибку в подарочную упаковку, но это не важно.
Позвольте мне попытаться убедить вас с помощью этого фрагмента кода:
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. Что осталось?Я считаю, что есть только две вещи, которые вы должны обрабатывать:
- Вещи, которые волнуют разработчиков при разработке или отладке программного обеспечения.
- Вещи, которые волнуют пользователей при использовании вашего программного обеспечения
log.Info должен просто записать эту строку в вывод лога. Не должно быть возможности отключить его, поскольку пользователю следует сообщать только то, что полезно для него. Если возникает ошибка, которую невозможно обработать, она должна всплыть в main.main, где программа завершается. Незначительное неудобство, связанное с необходимостью вставлять префикс fatal перед окончательным сообщением логироыания или писать непосредственно в os.Stderr с помощью fmt.Fprintf, не является достаточным оправданием для пакета ведения лога, расширяющего метод log.Fatal.
log.Debug, совсем другое дело. Это должен контролировать разработчик или инженер службы поддержки. Во время разработки отладочные операторы должны быть многочисленными, не прибегая к уровню трассировки или 2 уровня отладки. Пакет логирования должен поддерживать детальное управление для включения или отключения отладки и только отладки операторов в пакете или, возможно, еще более тонкой области.
В итоге что мы получаем
Если бы это был опрос, я бы попросил вас выбрать между:- ведение логов важно
- ведение логов сложно
Что плохого в пакете логирования на Go?
В пакете логирования Go нет уровней логирования, вам нужно вручную добавлять префиксы, такие как отладка, информация, предупреждение и ошибка, самостоятельно. Кроме того, у типа логгера Go нет...
habr.com