Golang логирование ошибок

Package log implements a simple logging package. It defines a type, Logger,
with methods for formatting output. It also has a predefined ‘standard’
Logger accessible through helper functions Print[f|ln], Fatal[f|ln], and
Panic[f|ln], which are easier to use than creating a Logger manually.
That logger writes to standard error and prints the date and time
of each logged message.
Every log message is output on a separate line: if the message being
printed does not end in a newline, the logger will add one.
The Fatal functions call os.Exit(1) after writing the log message.
The Panic functions call panic after writing the log message.

  • Constants
  • func Fatal(v …any)
  • func Fatalf(format string, v …any)
  • func Fatalln(v …any)
  • func Flags() int
  • func Output(calldepth int, s string) error
  • func Panic(v …any)
  • func Panicf(format string, v …any)
  • func Panicln(v …any)
  • func Prefix() string
  • func Print(v …any)
  • func Printf(format string, v …any)
  • func Println(v …any)
  • func SetFlags(flag int)
  • func SetOutput(w io.Writer)
  • func SetPrefix(prefix string)
  • func Writer() io.Writer
  • type Logger
    • func Default() *Logger
    • func New(out io.Writer, prefix string, flag int) *Logger
    • func (l *Logger) Fatal(v …any)
    • func (l *Logger) Fatalf(format string, v …any)
    • func (l *Logger) Fatalln(v …any)
    • func (l *Logger) Flags() int
    • func (l *Logger) Output(calldepth int, s string) error
    • func (l *Logger) Panic(v …any)
    • func (l *Logger) Panicf(format string, v …any)
    • func (l *Logger) Panicln(v …any)
    • func (l *Logger) Prefix() string
    • func (l *Logger) Print(v …any)
    • func (l *Logger) Printf(format string, v …any)
    • func (l *Logger) Println(v …any)
    • func (l *Logger) SetFlags(flag int)
    • func (l *Logger) SetOutput(w io.Writer)
    • func (l *Logger) SetPrefix(prefix string)
    • func (l *Logger) Writer() io.Writer
  • Logger
  • Logger.Output

View Source

const (
	Ldate         = 1 << iota     
	Ltime                         
	Lmicroseconds                 
	Llongfile                     
	Lshortfile                    
	LUTC                          
	Lmsgprefix                    
	LstdFlags     = Ldate | Ltime 
)

These flags define which text to prefix to each log entry generated by the Logger.
Bits are or’ed together to control what’s printed.
With the exception of the Lmsgprefix flag, there is no
control over the order they appear (the order listed here)
or the format they present (as described in the comments).
The prefix is followed by a colon only when Llongfile or Lshortfile
is specified.
For example, flags Ldate | Ltime (or LstdFlags) produce,

2009/01/23 01:23:23 message

while flags Ldate | Ltime | Lmicroseconds | Llongfile produce,

2009/01/23 01:23:23.123123 /a/b/c/d.go:23: message

This section is empty.

Fatal is equivalent to Print() followed by a call to os.Exit(1).

Fatalf is equivalent to Printf() followed by a call to os.Exit(1).

Fatalln is equivalent to Println() followed by a call to os.Exit(1).

Flags returns the output flags for the standard logger.
The flag bits are Ldate, Ltime, and so on.

Output writes the output for a logging event. The string s contains
the text to print after the prefix specified by the flags of the
Logger. A newline is appended if the last character of s is not
already a newline. Calldepth is the count of the number of
frames to skip when computing the file name and line number
if Llongfile or Lshortfile is set; a value of 1 will print the details
for the caller of Output.

Panic is equivalent to Print() followed by a call to panic().

Panicf is equivalent to Printf() followed by a call to panic().

Panicln is equivalent to Println() followed by a call to panic().

Prefix returns the output prefix for the standard logger.

Print calls Output to print to the standard logger.
Arguments are handled in the manner of fmt.Print.

Printf calls Output to print to the standard logger.
Arguments are handled in the manner of fmt.Printf.

Println calls Output to print to the standard logger.
Arguments are handled in the manner of fmt.Println.

SetFlags sets the output flags for the standard logger.
The flag bits are Ldate, Ltime, and so on.

SetOutput sets the output destination for the standard logger.

SetPrefix sets the output prefix for the standard logger.

Writer returns the output destination for the standard logger.

A Logger represents an active logging object that generates lines of
output to an io.Writer. Each logging operation makes a single call to
the Writer’s Write method. A Logger can be used simultaneously from
multiple goroutines; it guarantees to serialize access to the Writer.

package main

import (
	"bytes"
	"fmt"
	"log"
)

func main() {
	var (
		buf    bytes.Buffer
		logger = log.New(&buf, "logger: ", log.Lshortfile)
	)

	logger.Print("Hello, log file!")

	fmt.Print(&buf)
}
Output:

logger: example_test.go:19: Hello, log file!

Default returns the standard logger used by the package-level output functions.

New creates a new Logger. The out variable sets the
destination to which log data will be written.
The prefix appears at the beginning of each generated log line, or
after the log header if the Lmsgprefix flag is provided.
The flag argument defines the logging properties.

func (l *Logger) Fatal(v ...any)

Fatal is equivalent to l.Print() followed by a call to os.Exit(1).

Fatalf is equivalent to l.Printf() followed by a call to os.Exit(1).

func (l *Logger) Fatalln(v ...any)

Fatalln is equivalent to l.Println() followed by a call to os.Exit(1).

func (l *Logger) Flags() int

Flags returns the output flags for the logger.
The flag bits are Ldate, Ltime, and so on.

Output writes the output for a logging event. The string s contains
the text to print after the prefix specified by the flags of the
Logger. A newline is appended if the last character of s is not
already a newline. Calldepth is used to recover the PC and is
provided for generality, although at the moment on all pre-defined
paths it will be 2.

package main

import (
	"bytes"
	"fmt"
	"log"
)

func main() {
	var (
		buf    bytes.Buffer
		logger = log.New(&buf, "INFO: ", log.Lshortfile)

		infof = func(info string) {
			logger.Output(2, info)
		}
	)

	infof("Hello world")

	fmt.Print(&buf)
}
Output:

INFO: example_test.go:36: Hello world
func (l *Logger) Panic(v ...any)

Panic is equivalent to l.Print() followed by a call to panic().

Panicf is equivalent to l.Printf() followed by a call to panic().

func (l *Logger) Panicln(v ...any)

Panicln is equivalent to l.Println() followed by a call to panic().

Prefix returns the output prefix for the logger.

func (l *Logger) Print(v ...any)

Print calls l.Output to print to the logger.
Arguments are handled in the manner of fmt.Print.

Printf calls l.Output to print to the logger.
Arguments are handled in the manner of fmt.Printf.

func (l *Logger) Println(v ...any)

Println calls l.Output to print to the logger.
Arguments are handled in the manner of fmt.Println.

func (l *Logger) SetFlags(flag int)

SetFlags sets the output flags for the logger.
The flag bits are Ldate, Ltime, and so on.

SetOutput sets the output destination for the logger.

func (l *Logger) SetPrefix(prefix string)

SetPrefix sets the output prefix for the logger.

Writer returns the output destination for the logger.

На данный момент в файле main.go мы выводим лог с помощью функций log.Printf() и log.Fatal().

В Go, обе эти функции выводят сообщения через стандартный логгер, который по умолчанию добавляет к сообщениям префиксы с локальной датой и временем и записывает их в стандартный поток ошибок (который должен отображаться в окне терминала). Функция log.Fatal() также вызовет os.Exit(1) после того как выведет в терминал сообщение об ошибке, это приведет к мгновенному завершению работы приложения.

Премиум 👑 канал по Golang

Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎

Подписаться на канал

Уроки, статьи и Видео

Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.

Go в ВК

ЧАТ в Telegram

Содержание статьи

  • Разделение логирования для разных задач
  • Логирование ошибок от HTTP-сервера
  • Дополнительные методы логирования
  • Конкурентное логирование в Golang
  • Логирование сообщений в файл

Логгирование можно поделить на два различных типа, или уровня. К первому типу относятся информационные сообщения (вроде «Запуск сервера на :4000«), а ко второму типу относятся сообщения об ошибках.

log.Printf(«Запуск сервера на %s», *addr) // Информационное сообщение

err := http.ListenAndServe(*addr, mux)

log.Fatal(err) // Сообщение об фатальной ошибке в работе программы

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

  • Информационным сообщениям добавим префикс "INFO". Такое сообщение будет выводиться в стандартный поток вывода (stdout);
  • Сообщениям об ошибках добавим префикс "ERROR". Такие сообщения будут выводиться в стандартный поток ошибок (stderr) вместе с соответствующим названием файла и номером строки, которая вызвала логгер для записи (это поможет в отладке на будущее).

Есть несколько способов использования разных логгеров, но самый простой и понятный подход заключается в использовании функции log.New() для создания двух новых настраиваемых логгеров.

Откройте файл main.go и обновите его код следующим образом:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

package main

import (

«flag»

«log»

«net/http»

«os» // новый импорт

)

func main() {

addr := flag.String(«addr», «:4000», «Сетевой адрес веб-сервера»)

flag.Parse()

// Используйте log.New() для создания логгера для записи информационных сообщений. Для этого нужно

// три параметра: место назначения для записи логов (os.Stdout), строка

// с префиксом сообщения (INFO или ERROR) и флаги, указывающие, какая

// дополнительная информация будет добавлена. Обратите внимание, что флаги

// соединяются с помощью оператора OR |.

infoLog := log.New(os.Stdout, «INFO\t», log.Ldate|log.Ltime)

// Создаем логгер для записи сообщений об ошибках таким же образом, но используем stderr как

// место для записи и используем флаг log.Lshortfile для включения в лог

// названия файла и номера строки где обнаружилась ошибка.

errorLog := log.New(os.Stderr, «ERROR\t», log.Ldate|log.Ltime|log.Lshortfile)

mux := http.NewServeMux()

mux.HandleFunc(«/», home)

mux.HandleFunc(«/snippet», showSnippet)

mux.HandleFunc(«/snippet/create», createSnippet)

fileServer := http.FileServer(http.Dir(«./ui/static/»))

mux.Handle(«/static/», http.StripPrefix(«/static», fileServer))

// Применяем созданные логгеры к нашему приложению.

infoLog.Printf(«Запуск сервера на %s», *addr)

err := http.ListenAndServe(*addr, mux)

errorLog.Fatal(err)

}

Отлично… проверим эти изменения в действии!

Попробуйте запустить приложение, затем откройте другое окно терминала и попробуйте запустить его во второй раз. В результате должна появится сообщение об ошибке, потому что сетевой адрес, который наш сервер хочет прослушать (":4000"), уже используется другим процессом.

Логи во втором терминале должны выглядеть следующим образом:

go run ./cmd/web

INFO 2021/01/23 19:26:13 Запуск сервера на :4000

ERROR 2021/01/23 19:26:13 main.go:37: listen tcp :4000: bind: address already in use

exit status 1

Обратите внимание, что у этих двух сообщений разные префиксы — чтобы их можно было легко различить в терминале — и наше сообщение об ошибке также включает в себя название файла и номер строки (main.go:37), которая вызывает логгер для записи возникнувшей ошибки.

На заметку: Если вы хотите включить весь путь файла в лог  вместо просто названия файла, при создании логгера можно использовать флаг log.Llongfile вместо log.Lshortfile. Вы также можете заставить свой логгер  использовать UTC дату (вместо локальной), добавив флаг log.LUTC.

Разделение логирования для разных задач

Большое преимущество логирования сообщений в стандартных потоках (stdout и stderr), как у нас, заключается в том, что само приложение и логирование не связаны. Само приложение не занимается маршрутизацией или хранением логов, и это может упростить управление логами, которое будет различаться в зависимости от среды.

Стандартные потоки отображаются в терминале, поэтому вывод логов можно легко посмотреть после запуска приложения из терминала.

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

Например, можно перенаправить потоки из stdout и stderr в файлы на диске при запуске приложения из терминала следующим образом:

$ go run ./cmd/web >>/tmp/info.log 2>>/tmp/error.log

Логирование ошибок от HTTP-сервера

Нам нужно внести еще одно изменение в коде нашего веб-приложения. По умолчанию, если HTTP-сервер обнаруживает ошибку, он логирует её с помощью стандартного логгера. Но, лучше использовать наш новый логгер errorLog.

Нам требуется инициализировать новую структуру http.Server, содержащую параметры конфигурации для сервера, вместо использования http.ListenAndServe().

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

package main

import (

«flag»

«log»

«net/http»

«os»

)

func main() {

addr := flag.String(«addr», «:4000», «Сетевой адрес веб-сервера»)

flag.Parse()

infoLog := log.New(os.Stdout, «INFO\t», log.Ldate|log.Ltime)

errorLog := log.New(os.Stderr, «ERROR\t», log.Ldate|log.Ltime|log.Lshortfile)

mux := http.NewServeMux()

mux.HandleFunc(«/», home)

mux.HandleFunc(«/snippet», showSnippet)

mux.HandleFunc(«/snippet/create», createSnippet)

fileServer := http.FileServer(http.Dir(«./ui/static/»))

mux.Handle(«/static/», http.StripPrefix(«/static», fileServer))

// Инициализируем новую структуру http.Server. Мы устанавливаем поля Addr и Handler, так

// что сервер использует тот же сетевой адрес и маршруты, что и раньше, и назначаем

// поле ErrorLog, чтобы сервер использовал наш логгер

// при возникновении проблем.

srv := &http.Server{

Addr:     *addr,

ErrorLog: errorLog,

Handler:  mux,

}

infoLog.Printf(«Запуск сервера на %s», *addr)

// Вызываем метод ListenAndServe() от нашей новой структуры http.Server

err := srv.ListenAndServe()

errorLog.Fatal(err)

}

Дополнительные методы логирования

До сих пор мы использовали методы Println(), Printf() и Fatal() для записи логов. Однако Go предоставляет ряд других методов, с которыми стоит ознакомиться.

Как правило, лучше избегать использования методов Panic() и Fatal() за пределами функции main(). Вместо этого рекомендуется возвращать возникшие ошибки, а паниковать или принудительно завершать приложение непосредственно из самого main().

Конкурентное логирование в Golang

Новые логгеры, созданные с помощью log.New(), конкурентно-безопасны. Вы можете делиться одним логгером и использовать его в нескольких горутинах, не беспокоясь об возможных конфликтах между ними из за записи сообщений в одном и том же логгере.

Если у вас есть несколько логгеров, использующих для записи одно и то же место назначения, вам требуется убедиться, что базовый метод  Write() также безопасен для конкурентного использования.

Логирование сообщений в файл

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

f, err := os.OpenFile(«info.log», os.O_RDWR|os.O_CREATE, 0666)

if err != nil {

    log.Fatal(err)

}

defer f.Close()

infoLog := log.New(f, «INFO\t», log.Ldate|log.Ltime)

Скачать исходный код сайта

В конце каждой статьи, вы можете скачать готовый код нашего веб-приложения. В каждой статье мы обновляем код добавляя что-то новое.

Скачать: snippetbox-11.zip

Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.

E-mail: vasile.buldumac@ati.utm.md

Образование

Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники

  • 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
  • 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»

Давайте поговорим о ведении логов

Время на прочтение
5 мин

Количество просмотров 25K

Этот пост вдохновлен темой в форуме Go Forum, начатой Nate Finch. Этот пост сконцентрирован на языке Go, но если пройти мимо этого, я думаю, идеи представленные тут широко применимы.

Почему нет любви?

Пакет log в Go не имеет уровней для логов, вы можете сами вручную добавить приставки DEBUG, INFO, WARN, и ERROR. Также logger тип в Go не имеет возможности включить или выключить эти уровни отдельно для выбранных пакетов. Для сравнения давайте глянем на несколько его замен от сторонних разработчиков.

image

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.

Logging can be a life-saver when it comes to discovering bugs or faults in your Go (Golang) code. The three most popular ways to log errors in Golang are:

  1. Output the errors to the console
  2. Log the errors to a file
  3. Use a logging framework

This article will walk you through how to log errors using each method, when and why you’d want to use each, along with examples.

Basic Logging in Golang

Golang comes with an in-built standard log package which provides basic error logging features. Though it doesn’t provide any log levels like debug, warning, or error, it still has many features to get started with basic logging.

Let’s look at an example to understand it better.


package main

import "log"

func main() {
    log.Println("We are logging in Golang!")
}

When the above code is executed, the log package prints the output to the standard error (stderr) stream and automatically appends a timestamp to each log message.

2022/09/30 02:44:31 We are logging in Golang!

Logging to a File in Golang

Despite the fact that the log package’s default output is to the stderr stream, it may be adjusted to write to any local file or to any other location that accepts the io.Writer interface. You must either create a new file or open an existing one and set it up as the log’s output path in order to save log messages in a file.

Example


package main

import (
    "log"
    "os"
)

func main() {

    file, err := os.OpenFile("myLOG.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
    if err != nil {
        log.Fatal(err)
    }

    log.SetOutput(file)

    log.Println("We are logging in Golang!")
}

In the above code, we have created the file myLOG.txt or opened it if it is already existing in the append and write-only mode. When the above code is executed, the following output is written to the myLOG.txt file:


2022/09/30 03:02:51 We are logging in Golang!

Writing Custom Logs in Golang

The log.New() method may be used to create custom loggers. Three arguments must be sent to a custom logger when using the log.New() function:

  1. out — It specifies the place where the log data has to be written, for instance, a file path. It could be any interface that implements the io.Writer interface.
  2. prefix — a string or text which has to be appended at the beginning of each log line.
  3. flag — These are sets of constants which allow us to define logging properties.

Example: Writing Custom Logs

package main
import (
    "log"
    "os"
)

var (
    WarningLog *log.Logger
    InfoLog   *log.Logger
    ErrorLog   *log.Logger
)

func init() {
    file, err := os.OpenFile("myLOG.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
    if err != nil {
        log.Fatal(err)
    }

    InfoLog = log.New(file, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
    WarningLog = log.New(file, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
    ErrorLog = log.New(file, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
}

func main() {
    InfoLog.Println("Opening the application...")
    InfoLog.Println("Something has occurred...")
    WarningLog.Println("WARNING!!!..")
    ErrorLog.Println("Some error has occurred...")
}


With the help of the destination file path, prefix string, and flags provided in the code above, three logs have been generated. These loggers use the println() method in the main function to write log entries to the file.

When the above code is executed we get the following output written into myLOG.txt file.


INFO: 2022/09/30 03:20:51 main.go:26: Opening the application...
INFO: 2022/09/30 03:20:51 main.go:27: Something has occurred...
WARNING: 2022/09/30 03:20:51 main.go:28: WARNING!!!..
ERROR: 2022/09/30 03:20:51 main.go:29: Some error has occurred…

Logging Frameworks

When used for local development, the log package is fantastic. However, logging frameworks are preferable when working on a larger scale. Two logging frameworks you should know about are Logrus and Rollbar.

Logrus

Logrus is completely API compatible with the log package, supports color-coded formatting of your logs, and works well for structured JSON logging. It can be installed on your system using the command below.

go get "github.com/Sirupsen/logrus"

Example: Logrus

package main

import (
    log "github.com/sirupsen/logrus"
)

func main() {
    log.SetFormatter(&log.JSONFormatter{})
    log.WithFields(
        log.Fields{
            "field1": "foo",
            "field2": "bar",
        },
    ).Info("Log message here!!!")
}


When the above code is executed we get the following output:


{"field1":"bar","field2":"foo","level":"info","msg":"Log message  here!!!","time":"2022-09-30T15:55:24+01:00"}

Rollbar

Rollbar is a real-time error reporting service for Go and other languages. It makes it easy to identify the root cause of bugs through stack traces, local variables, telemetry, suspect deploys, and other metadata. Errors are sent to Rollbar asynchronously in a background goroutine and in return you get instant and accurate alerts — grouped using machine learning to reduce noise.

Example: Rollbar

package main

import (
  "github.com/rollbar/rollbar-go"
  "time"
)

func main() {
  rollbar.SetToken("MY_TOKEN")
  rollbar.SetEnvironment("production")                 // defaults to "development"
  rollbar.SetCodeVersion("v2")                         // optional Git hash/branch/tag (required for GitHub integration)
  rollbar.SetServerHost("web.1")                       // optional override; defaults to hostname
  rollbar.SetServerRoot("github.com/heroku/myproject") // path of project (required for GitHub integration and non-project stacktrace collapsing)  - where repo is set up for the project, the server.root has to be "/"

  rollbar.Info("Message body goes here")
  rollbar.WrapAndWait(doSomething)
}

func doSomething() {
  var timer *time.Timer = nil
  timer.Reset(10) // this will panic
}

Errors are displayed on a real-time feed.

Discover Go errors in real-time

For each error, you can drill down to get request parameters, local variables, affected users and IP addresses, browsers and OSes, deployed code versions, and more.

Get Error Context Fast

The Rollbar Query Language (RQL) allows you to monitor, perform data analysis and build custom reports on your error data, using a familiar SQL-like language.

Debug faster

Conclusion

Error logging can be very helpful in analyzing the health of your application. The built-in Go logging package should only be used when working with small applications. Logging frameworks like Rollbar should be used for logging in large-scale applications.

Track, Analyze and Manage Errors With Rollbar

Managing errors and exceptions in your code is challenging. It can make deploying production code an unnerving experience. Being able to track, analyze, and manage errors in real-time can help you proceed with more confidence. Rollbar automates error monitoring and triaging, making fixing Go errors easier than ever. Try it today!

Search code, repositories, users, issues, pull requests…

Provide feedback

Saved searches

Use saved searches to filter your results more quickly

Sign up

Понравилась статья? Поделить с друзьями:
  • God of war ошибка version not supported
  • Golang возврат ошибок
  • God of war ошибка not enough available memory
  • Gog ошибка e15
  • God of war не запускается ошибка