Logrus — это структурированный логгер для Go (golang), полностью API-совместимый с логгером стандартной библиотеки.
Логрус находится в режиме обслуживания. Мы не будем вводить новые функции. Это просто слишком сложно сделать так, чтобы не сломать проекты многих людей, а это последнее, чего вы хотите от своей библиотеки журналирования (опять же...).
Это не значит, что Логрус мертв. Logrus будет продолжать поддерживаться для обеспечения безопасности, исправления ошибок (обратной совместимости) и производительности (где мы ограничены интерфейсом).
Я считаю, что самый большой вклад Логруса — это то, что он сыграл свою роль в сегодняшнем широком использовании структурированного журналирования в Golang. Кажется, нет причин делать серьезную, ломающую версию Logrus V2, поскольку фантастическое сообщество Go создало их независимо. Появилось множество фантастических альтернатив. Логрус выглядел бы так же, если бы он был переработан с учетом того, что мы знаем о структурированном журналировании в Go сегодня. Посмотрите, например, Zerolog, Zap и Apex.
Видите странные проблемы с учетом регистра? Раньше было возможно импортировать Logrus как в верхнем, так и в нижнем регистре. Из-за среды пакетов Go это вызвало проблемы в сообществе, и нам нужен был стандарт. В некоторых средах возникли проблемы с вариантом верхнего регистра, поэтому было решено использовать нижний регистр. Все, что использует logrus
, должно будет использовать строчные буквы: github.com/sirupsen/logrus
. Любой пакет, который не является таковым, следует изменить.
Чтобы исправить Glide, прочтите эти комментарии. Подробное объяснение проблемы с регистром см. в этом комментарии.
Хорошая цветовая маркировка в разработке (когда подключен телетайп, в противном случае просто текст):
С помощью log.SetFormatter(&log.JSONFormatter{})
для удобного анализа с помощью logstash или Splunk:
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
{"level":"warning","msg":"The group's number increased tremendously!",
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
При использовании log.SetFormatter(&log.TextFormatter{})
по умолчанию, когда TTY не подключен, вывод совместим с форматом logfmt:
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
Чтобы обеспечить такое поведение, даже если подключен телетайп, настройте форматтер следующим образом:
log . SetFormatter ( & log. TextFormatter {
DisableColors : true ,
FullTimestamp : true ,
})
Если вы хотите добавить вызывающий метод в качестве поля, проинструктируйте регистратор через:
log . SetReportCaller ( true )
Это добавляет вызывающего абонента как «метод» следующим образом:
{ "animal" : " penguin " , "level" : " fatal " , "method" : " github.com/sirupsen/arcticcreatures.migrate " , "msg" : " a penguin swims by " ,
"time" : " 2014-03-10 19:57:38.562543129 -0400 EDT " }
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
Обратите внимание, что это добавляет измеримые накладные расходы — стоимость будет зависеть от версии Go, но в недавних тестах с 1.6 и 1.7 она составляла от 20 до 40 %. Вы можете проверить это в своей среде с помощью тестов:
go test -bench=.*CallerTracing
Название организации было изменено на строчные буквы, и оно не будет изменено обратно. Если вы получаете конфликты импорта из-за чувствительности к регистру, используйте импорт в нижнем регистре: github.com/sirupsen/logrus
.
Самый простой способ использования Logrus — это экспортированный журнал регистрации на уровне пакета:
package main
import (
log "github.com/sirupsen/logrus"
)
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
}). Info ( "A walrus appears" )
}
Обратите внимание, что он полностью совместим с API-интерфейсом с регистратором stdlib, поэтому вы можете везде заменить импорт log
на log "github.com/sirupsen/logrus"
, и теперь у вас будет гибкость Logrus. Вы можете настроить его по своему усмотрению:
package main
import (
"os"
log "github.com/sirupsen/logrus"
)
func init () {
// Log as JSON instead of the default ASCII formatter.
log . SetFormatter ( & log. JSONFormatter {})
// Output to stdout instead of the default stderr
// Can be any io.Writer, see below for File example
log . SetOutput ( os . Stdout )
// Only log the warning severity or above.
log . SetLevel ( log . WarnLevel )
}
func main () {
log . WithFields (log. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 122 ,
}). Warn ( "The group's number increased tremendously!" )
log . WithFields (log. Fields {
"omg" : true ,
"number" : 100 ,
}). Fatal ( "The ice breaks!" )
// A common pattern is to re-use fields between logging statements by re-using
// the logrus.Entry returned from WithFields()
contextLogger := log . WithFields (log. Fields {
"common" : "this is a common field" ,
"other" : "I also should be logged always" ,
})
contextLogger . Info ( "I'll be logged with common and other field" )
contextLogger . Info ( "Me too" )
}
Для более продвинутого использования, например для входа в несколько мест из одного приложения, вы также можете создать экземпляр logrus
Logger:
package main
import (
"os"
"github.com/sirupsen/logrus"
)
// Create a new instance of the logger. You can have any number of instances.
var log = logrus . New ()
func main () {
// The API for setting attributes is a little different than the package level
// exported logger. See Godoc.
log . Out = os . Stdout
// You could set this to any `io.Writer` such as a file
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
// log.Info("Failed to log to file, using default stderr")
// }
log . WithFields (logrus. Fields {
"animal" : "walrus" ,
"size" : 10 ,
}). Info ( "A group of walrus emerges from the ocean" )
}
Logrus поощряет тщательное, структурированное ведение журнала с помощью полей журнала вместо длинных, неразборчивых сообщений об ошибках. Например, вместо: log.Fatalf("Failed to send event %s to topic %s with key %d")
, вам следует регистрировать гораздо более доступное для обнаружения:
log . WithFields (log. Fields {
"event" : event ,
"topic" : topic ,
"key" : key ,
}). Fatal ( "Failed to send event" )
Мы обнаружили, что этот API заставляет вас думать о журналировании таким образом, чтобы получать гораздо более полезные сообщения журнала. Мы сталкивались с бесчисленными ситуациями, когда всего лишь одно добавленное поле в уже существующий оператор журнала могло бы сэкономить нам часы. Вызов WithFields
не является обязательным.
В общем, использование Logrus любой из функций семейства printf
следует рассматривать как подсказку о необходимости добавления поля, однако вы все равно можете использовать функции семейства printf
с Logrus.
Часто полезно иметь поля , всегда прикрепленные к операторам журнала в приложении или его частях. Например, вы можете захотеть всегда регистрировать request_id
и user_ip
в контексте запроса. Вместо того, чтобы писать log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
в каждой строке, вы можете создать logrus.Entry
для передачи:
requestLogger := log . WithFields (log. Fields { "request_id" : request_id , "user_ip" : user_ip })
requestLogger. Info ( "something happened on that request" ) # will log request_id and user_ip
requestLogger . Warn ( "something not great happened" )
Вы можете добавить хуки для уровней логирования. Например, чтобы отправить ошибки в службу отслеживания исключений Error
, Fatal
и Panic
, информацию в StatsD или войти в несколько мест одновременно, например, в системный журнал.
Логрус поставляется со встроенными крючками. Добавьте их или свой собственный хук в init
:
import (
log "github.com/sirupsen/logrus"
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
"log/syslog"
)
func init () {
// Use the Airbrake hook to report errors that have Error severity or above to
// an exception tracker. You can create custom hooks, see the Hooks section.
log . AddHook ( airbrake . NewHook ( 123 , "xyz" , "production" ))
hook , err := logrus_syslog . NewSyslogHook ( "udp" , "localhost:514" , syslog . LOG_INFO , "" )
if err != nil {
log . Error ( "Unable to connect to local syslog daemon" )
} else {
log . AddHook ( hook )
}
}
Примечание. Перехватчик системного журнала также поддерживает подключение к локальному системному журналу (например, «/dev/log» или «/var/run/syslog» или «/var/run/log»). Для получения подробной информации, пожалуйста, проверьте README перехватчика системного журнала.
Список известных на данный момент сервисных перехватчиков можно найти на этой вики-странице.
Logrus имеет семь уровней ведения журнала: трассировка, отладка, информация, предупреждение, ошибка, фатальный и паника.
log . Trace ( "Something very low level." )
log . Debug ( "Useful debugging information." )
log . Info ( "Something noteworthy happened!" )
log . Warn ( "You should probably take a look at this." )
log . Error ( "Something failed but I'm not quitting." )
// Calls os.Exit(1) after logging
log . Fatal ( "Bye." )
// Calls panic() after logging
log . Panic ( "I'm bailing." )
Вы можете установить уровень ведения журнала в Logger
, тогда он будет регистрировать только записи с этой серьезностью или чем-то выше:
// Will log anything that is info or above (warn, error, fatal, panic). Default.
log . SetLevel ( log . InfoLevel )
Может быть полезно установить log.Level = logrus.DebugLevel
в среде отладки или подробной информации, если ваше приложение имеет такую возможность.
Примечание. Если вам нужны разные уровни журнала для глобального ( log.SetLevel(...)
) и системного журнала, проверьте README перехватчика системного журнала.
Помимо полей, добавленных с помощью WithField
или WithFields
некоторые поля автоматически добавляются во все события регистрации:
time
. Временная метка создания записи.msg
. Сообщение журнала передается в {Info,Warn,Error,Fatal,Panic}
после вызова AddFields
. Например, Failed to send event.
level
. Уровень журналирования. Например, info
. Логрус не имеет понятия об окружающей среде.
Если вы хотите, чтобы перехватчики и форматтеры использовались только в определенных средах, вам следует сделать это самостоятельно. Например, если в вашем приложении есть глобальная переменная Environment
, которая представляет собой строковое представление среды, вы можете сделать:
import (
log "github.com/sirupsen/logrus"
)
func init () {
// do something here to set environment depending on an environment variable
// or command-line flag
if Environment == "production" {
log . SetFormatter ( & log. JSONFormatter {})
} else {
// The TextFormatter is default, you don't actually have to do this.
log . SetFormatter ( & log. TextFormatter {})
}
}
Эта конфигурация предназначена для использования logrus
, но JSON в рабочей среде в основном полезен только в том случае, если вы выполняете агрегацию журналов с помощью таких инструментов, как Splunk или Logstash.
Встроенные средства форматирования журналов:
logrus.TextFormatter
. Регистрирует событие в цветах, если стандартный вывод является tty, в противном случае — без цветов.ForceColors
значение true
. Чтобы принудительно не отображать цветной вывод даже при наличии телетайпа, установите для поля DisableColors
значение true
. Для Windows см. github.com/mattn/go-colorable.DisableLevelTruncation
значение true
.PadLevelText
значения true
включает такое поведение путем добавления заполнения к тексту уровня.logrus.JSONFormatter
. Записывает поля в формате JSON.Сторонние форматировщики журналов:
FluentdFormatter
. Форматирует записи, которые могут анализироваться Kubernetes и Google Container Engine.GELF
. Форматирует записи так, чтобы они соответствовали спецификации Graylog GELF 1.1.logstash
. Поля журналов как события Logstash.prefixed
. Отображает источник записей журнала вместе с альтернативным макетом.zalgo
. Призыв Силы Залго.nested-logrus-formatter
. Преобразует поля logrus во вложенную структуру.powerful-logrus-formatter
. получить имя файла, номер строки журнала и имя последней функции при печати журнала; Сохранение журнала в файлы.caption-json-formatter
. Добавлен форматировщик сообщений logrus в формате json с удобочитаемой подписью. Вы можете определить свой форматтер, реализовав интерфейс Formatter
, требующий метода Format
. Format
принимает *Entry
. entry.Data
— это тип Fields
( map[string]interface{}
) со всеми вашими полями, а также полями по умолчанию (см. раздел «Записи» выше):
type MyJSONFormatter struct {
}
log . SetFormatter ( new ( MyJSONFormatter ))
func ( f * MyJSONFormatter ) Format ( entry * Entry ) ([] byte , error ) {
// Note this doesn't include Time, Level and Message which are available on
// the Entry. Consult `godoc` on information about those fields or read the
// source of the official loggers.
serialized , err := json . Marshal ( entry . Data )
if err != nil {
return nil , fmt . Errorf ( "Failed to marshal fields to JSON, %w" , err )
}
return append ( serialized , 'n' ), nil
}
io.Writer
Логрус можно трансформировать в io.Writer
. Этот писатель является концом io.Pipe
, и вы обязаны закрыть его.
w := logger . Writer ()
defer w . Close ()
srv := http. Server {
// create a stdlib log.Logger that writes to
// logrus.Logger.
ErrorLog : log . New ( w , "" , 0 ),
}
Каждая строка, записанная этому писателю, будет напечатана обычным способом с использованием средств форматирования и перехватчиков. Уровень этих записей — info
.
Это означает, что мы можем легко переопределить стандартный библиотечный регистратор:
logger := logrus . New ()
logger . Formatter = & logrus. JSONFormatter {}
// Use logrus for standard log output
// Note that `log` here references stdlib's log
// Not logrus imported under the name `log`.
log . SetOutput ( logger . Writer ())
Ротация журналов не предусмотрена в Логрусе. Ротация журналов должна выполняться внешней программой (например, logrotate(8)
), которая может сжимать и удалять старые записи журнала. Это не должно быть функцией регистратора уровня приложения.
Инструмент | Описание |
---|---|
Логрус Мате | Logrus mate — это инструмент для управления регистраторами Logrus. Вы можете инициализировать уровень регистратора, перехватывать и форматировать его с помощью файла конфигурации, регистратор будет генерироваться с разными конфигурациями в разных средах. |
Помощник Логруса Змеи | Помощник вокруг Logrus для переноса с помощью spf13/Viper для загрузки конфигурации с помощью клыков! А для упрощения настройки Logrus используйте поведение Logrus Mate. образец |
Logrus имеет встроенную возможность подтверждения наличия сообщений журнала. Это реализуется через test
хук и обеспечивает:
test.NewLocal
и test.NewGlobal
), которые по сути просто добавляют test
хукtest.NewNullLogger
), который просто записывает сообщения журнала (и не выводит их): import (
"github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/stretchr/testify/assert"
"testing"
)
func TestSomething ( t * testing. T ){
logger , hook := test . NewNullLogger ()
logger . Error ( "Helloerror" )
assert . Equal ( t , 1 , len ( hook . Entries ))
assert . Equal ( t , logrus . ErrorLevel , hook . LastEntry (). Level )
assert . Equal ( t , "Helloerror" , hook . LastEntry (). Message )
hook . Reset ()
assert . Nil ( t , hook . LastEntry ())
}
Logrus может зарегистрировать одну или несколько функций, которые будут вызываться при регистрации любого сообщения fatal
уровня. Зарегистрированные обработчики будут выполнены до того, как logrus выполнит os.Exit(1)
. Такое поведение может оказаться полезным, если вызывающим абонентам необходимо корректно завершить работу. В отличие от panic("Something went wrong...")
который можно перехватить с помощью отложенного recover
вызов os.Exit(1)
перехватить невозможно.
...
handler := func() {
// gracefully shutdown something...
}
logrus.RegisterExitHandler(handler)
...
По умолчанию Logger защищен мьютексом для одновременной записи. Мьютекс удерживается при вызове перехватчиков и записи журналов. Если вы уверены, что такая блокировка не нужна, вы можете вызвать logger.SetNoLock(), чтобы отключить блокировку.
К ситуациям, когда блокировка не требуется, относятся:
У вас нет зарегистрированных перехватчиков, или вызов перехватчиков уже потокобезопасен.
Запись в logger.Out уже потокобезопасна, например:
logger.Out защищен замками.
logger.Out — это обработчик os.File, открытый с флагом O_APPEND
, и размер каждой записи меньше 4 КБ. (Это позволяет писать в многопоточном/многопроцессном режиме)
(См. http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)