log
стандартной библиотеки Go;zap
— одним из самых быстрых.log
. Этот пакет поставляется вместе с Go, прост в применении и генерирует полезные временные метки без какой-либо настройки.log
на примере:package main
import (
"log"
)
func main() {
log.Print("Logging in Go!")
}
$ go build basicLog.go
$ ./basicLog
2022/03/07 09:01:51 Logging in Go!
log.Print()
и log.Println()
— наиболее часто используемые функции в пакете log
, а log
не поддерживает уровни без дополнительных действий, он предлагает другие функции, которые выводят сообщения об ошибках в сочетании с другим действием.log.Fatal()
, регистрируют сообщение на уровне Fatal
, а затем завершают процесс. log.Panic()
регистрирует сообщение на уровне Panic
, за которым следует вызов panic()
— встроенной функции Go.log
по умолчанию печатает в stderr. Но что, если вы хотите вести журналы в другом месте? Функция log.SetOutput()
позволяет указать место назначения вывода io.Writer
для логера. Пакет log
даёт возможность делать запись в любой объект, который реализует интерфейс io.Writer
.package main
import (
"log"
"os"
)
func main() {
// создаём файл info.log и обрабатываем ошибку, если что-то пошло не так
file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
// откладываем закрытие файла
defer file.Close()
// устанавливаем назначение вывода в файл info.log
log.SetOutput(file)
log.Print("Logging to a file in Go!")
}
info.log
, и сообщения будут записаны в этот файл.log.New()
:func main() {
flog, err := os.OpenFile(`server.log`, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer flog.Close()
mylog := log.New(flog, `serv `, log.LstdFlags|log.Lshortfile)
mylog.Println(`Start server`)
mylog.Println(`Finish server`)
}
server.log
со следующим содержимым:serv 2021/06/24 09:44:42 main.go:15: Start server
serv 2021/06/24 09:44:42 main.go:16: Finish server
log.New()
есть несколько параметров — рассмотрим их подробнее:io.Writer
. В примере это открытый для записи файл flog
, точнее указатель на объект os.File
, который реализует интерфейс io.Writer
.serv
.log
даёт возможность добавлять контекстную информацию, например имя файла, номер строки, дату и время, — это делается с помощью флагов логера. В примере указаны следующие флаги:
log.LstdFlags
— добавляет вывод даты и времени;log.Lshortfile
— добавляет вывод имени файла и строки, в которой произошла запись в лог.log.New()
возвращает указатель на потокобезопасную переменную типа log.Logger
, которую нужно использовать для логирования в указанный вами файл.log.SetFlags()
:func main(){
// создаём файл info.log и обрабатываем ошибку, если что-то пошло не так
file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
// откладываем закрытие файла
defer file.Close()
// устанавливаем назначение вывода в файл info.log
log.SetOutput(file)
// добавляем флаги для вывода даты, времени, имени файла
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
log.Print("Logging to a file in Go!")
}
$ ./logs
$ cat info.log
2022/03/07 15:43:22 logs.go:17: Logging to a file in Go!
log.New()
, где, кстати, и флаги можно по-другому настроить. Тогда ошибки будут записываться в один логер, а информация о запросах — в другой.log
может быть недостаточно. log
не поддерживает работу с уровнями — чтобы их настроить, следует выбрать внешний пакет. Перед тем как рассматривать сторонние библиотеки, расскажем, что такое уровни логирования, где и когда их использовать.Fatal
/Panic
— это ошибки, которые вызывают аварийную ситуацию или приводят к выходу из программы. При таких ошибках дальнейшая работа программы невозможна. На этом уровне фиксируются события только с уровнем Fatal
.Error
— это ошибки, которые не ожидаются. Требуется выяснение причины и устранение этих ошибок. На этом уровне фиксируются события с уровнями Error
и Fatal
.Warning
— это предупреждения. Они не влияют на работу программы, но могут напомнить о причинах ошибок в будущем. На этом уровне фиксируются события с уровнями Warning
, Error
и Fatal
.Info
или ошибкой Error
. Может так случиться, что вы зарегистрируете ошибку на уровне Warning
и будете долго её искать, потому что это вроде как не ошибка, а предупреждение.Info
— это информационные сообщения о возникающих событиях. На этом уровне фиксируются события с уровнями Info
, Warning
, Error
и Fatal
.Info
можно устанавливать везде, где хочется вставить информацию о работе приложения. Этот уровень тоже не рекомендуют из-за его неоднозначности, так как он может означать всё что угодно. Лучше использовать уровень Debug
/Trace
.Debug
— это служебная информация для отладки и настройки программы. На этом уровне фиксируются события с уровнями Debug
, Info
, Warning
, Error
и Fatal
.Debug
/Trace
стоит использовать там, где нужно показать больше информации о том, что происходит в проекте. Например, какие приходят запросы, как они обрабатываются, какие возвращаются ответы, какие значения имеют переменные на определённых уровнях бизнес-логики. Всё это может помочь в случае поломки (или непройденного автотеста) определить, как менялись данные и почему они имеют такие значения. Так как это отладочная информация, в продакшене уровень Debug
лучше отключать.logrus
и zap
.logrus
предназначен для структурированного логирования и хорошо подходит для работы с JSON. В этом формате удобно анализировать журналы событий.logrus
, вы можете определить стандартные поля для добавления в журналы функцией WithFields()
. Пакет поставляется с семью уровнями логирования: Trace
, Debug
, Info
, Warn
, Error
, Fatal
и Panic
. Вы можете совершать вызовы к регистратору с соответствующими уровнями. Уровни не только дают больше контекста регистрируемым сообщениям, распределяя их по группам, но и бывают полезны, когда вам не нужна вся информация о поведении приложения.logrus
на практике. Начнём с загрузки и установки пакета в рабочей среде:go get github.com/sirupsen/logrus
logrus
работает с уровнями:package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func main() {
// создаём файл info.log и обрабатываем ошибку
file, err := os.OpenFile("info.log", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
// откладываем закрытие файла
defer file.Close()
// устанавливаем вывод логов в файл
log.SetOutput(file)
// устанавливаем вывод логов в формате JSON
log.SetFormatter(&log.JSONFormatter{})
// устанавливаем уровень предупреждений
log.SetLevel(log.WarnLevel)
// определяем стандартные поля JSON
log.WithFields(log.Fields{
"genre": "metal",
"name": "Rammstein",
}).Info("Немецкая метал-группа, образованная в январе 1994 года в Берлине.")
log.WithFields(log.Fields{
"omg": true,
"name": "Garbage",
}).Warn("В 2021 году вышел новый альбом No Gods No Masters.")
log.WithFields(log.Fields{
"omg": true,
"name": "Linkin Park",
}).Fatal("Группа Linkin Park взяла паузу после смерти вокалиста Честера Беннингтона 20 июля 2017 года.")
}
info.log
:{"level":"warning","msg":"В 2021 году вышел новый альбом No Gods No Masters.","name":"Garbage","omg":true,"time":"2022-03-10T10:56:04-08:00"}
{"level":"fatal","msg":"Группа Linkin Park взяла паузу после смерти вокалиста Честера Беннингтона 20 июля 2017 года.","name":"Linkin Park","omg":true,"time":"2022-03-10T10:56:04-08:00"}
Warn
: log.SetLevel(log.WarnLevel)
.level
, в лог будут записываться разные данные.package main
import (
"os"
"github.com/sirupsen/logrus"
)
func DifferentLevels(level logrus.Level) {
logrus.SetOutput(os.Stdout)
logrus.SetLevel(level)
logrus.WithFields(logrus.Fields{
"genre": "metal",
"name": "Rammstein",
}).Info("Ich Will")
logrus.WithFields(logrus.Fields{
"genre": "post-grunge",
"name": "Garbage",
}).Warn("I Think I’m Paranoid")
contextLogger := logrus.WithFields(logrus.Fields{
"common": "Any music is awesome",
"other": "I also should be logged always",
})
contextLogger.Warn("I will be logged with common and other fields")
contextLogger.Error("Me too, maybe")
logrus.WithFields(logrus.Fields{
"genre": "rock",
"name": "The Rasmus",
}).Fatal("Livin' in a World Without You")
}
logrus.InfoLevel
;logrus.ErrorLevel
;logrus.WarnLevel
.1. INFO[0000] Ich Will genre=metal name=Rammstein
2. WARN[0000] I Think I’m Paranoid genre=post-grunge name=Garbage
3. WARN[0000] I will be logged with common and other fields common="Any music is awesome" other="I also should be logged always"
4. ERRO[0000] Me too, maybe common="Any music is awesome" other="I also should be logged always"
5. FATA[0000] Livin' in a World Without You genre=rock name="The Rasmus"
zap
предназначен для минимального использования рефлексии и форматирования строк. Без рефлексии нужно было бы явным образом указывать все имена полей и ссылаться на их значения для сериализации. Рефлексия позволяет программе самой определить все имеющиеся поля и получить их текстовые имена.zap
известен своей скоростью и малым выделением памяти, что очень важно для высоконагруженных сервисов. Если судить по бенчмарку, zap
примерно в пять раз быстрее, чем logrus
.SugaredLogger
, который в 4–10 раз быстрее, чем другие структурированные логеры. Этот вариант поддерживает как структурированное логирование, так и обычное, в стиле printf()
. Ещё SugaredLogger
удобен тем, что его API-интерфейсы свободно типизированы и принимают переменное количество пар "ключ", значение
.zap
можно командой go get
:go get -u go.uber.org/zap
zap
предлагает предустановленный логер, если вам нужно использовать его сразу же. Вот пример:package main
import (
"go.uber.org/zap"
)
func main() {
// добавляем предустановленный логер NewDevelopment
logger, err := zap.NewDevelopment()
if err != nil {
// вызываем панику, если ошибка
panic("cannot initialize zap")
}
// это нужно добавить, если логер буферизован
// в данном случае не буферизован, но привычка хорошая
defer logger.Sync()
// для примера берём простой URL
const url = "http://example.com"
// делаем логер SugaredLogger
sugar := logger.Sugar()
// выводим сообщение уровня Info с парой "url": url в виде JSON, это SugaredLogger
sugar.Infow(
"Failed to fetch URL",
"url", url,
)
// выводим сообщение уровня Info, но со строкой URL, это тоже SugaredLogger
sugar.Infof("Failed to fetch URL: %s", url)
// выводим сообщение уровня Error со строкой URL, и это SugaredLogger
sugar.Errorf("Failed to fetch URL: %s", url)
// переводим в обычный Logger
plain := sugar.Desugar()
// выводим сообщение уровня Info обычного регистратора (не SugaredLogger)
plain.Info("Hello, Go!")
// также уровня Warn (не SugaredLogger)
plain.Warn("Simple warning")
// и уровня Error, но добавляем строго типизированное поле "url" (не SugaredLogger)
plain.Error("Failed to fetch URL", zap.String("url", url))
}
zap.NewDevelopment()
возвращает предустановленный логер разработки, который печатает сообщения в удобном для чтения формате. По умолчанию логеры не буферизованы, но, поскольку низкоуровневые API-интерфейсы zap
допускают буферизацию, вызывать Sync
перед завершением процесса — это хорошая практика.SugaredLogger
и выводим три сообщения: два из них — уровня Info
, одно — уровня Error
. После этого используем методы Info()
, Warn()
и Error()
для вывода сообщений обычного логера.go run main.go
2022-11-17T23:17:00.534+0300 INFO zap_logger/main.go:18 Failed to fetch URL {"url": "http://example.com"}
2022-11-17T23:17:00.534+0300 INFO zap_logger/main.go:23 Failed to fetch URL: http://example.com
2022-11-17T23:17:00.534+0300 ERROR zap_logger/main.go:24 Failed to fetch URL: http://example.com
main.main
/Users/lekan/go/src/github.com/lekan-pvp/zap_logger/main.go:24
runtime.main
/usr/local/go/src/runtime/proc.go:250
2022-11-17T23:17:00.534+0300 INFO zap_logger/main.go:26 Hello, Go!
2022-11-17T23:17:00.534+0300 WARN zap_logger/main.go:27 Simple warning
main.main
/Users/lekan/go/src/github.com/lekan-pvp/zap_logger/main.go:27
runtime.main
/usr/local/go/src/runtime/proc.go:250
2022-11-17T23:17:00.534+0300 ERROR zap_logger/main.go:28 Simple error {"url": "http://example.com"}
main.main
/Users/lekan/go/src/github.com/lekan-pvp/zap_logger/main.go:28
runtime.main
/usr/local/go/src/runtime/proc.go:250
Info
выводится обычное сообщение. Оно содержит временную метку, уровень, местоположение кода, откуда был сделан вызов, и текст сообщения.Warn
и Error
выводится ещё и трассировка стека вызовов. Так можно определить место возникновения ошибки или предупреждения.zap
на самом деле много интересных возможностей. Будет время — почитай об этом в документации на сайте Go.log
.zap
.zap
.logrus
.