Зарегистрируйтесь, чтобы продолжить обучение

Логирование в веб-приложении Веб-разработка на Go

Во всех программах со временем появляются ошибки. Чтобы понимать, где они возникают в коде, используются методы наблюдения.

В этом уроке рассмотрим самый распространенный из методов — логирование. Мы разберем, как он используется в Go-приложениях, что можно сделать с помощью стандартного пакета log и почему ему предпочитают сторонний пакет logrus.

Что такое логирование и как им пользоваться

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

Рассмотрим, как используется логирование в Go-приложениях на примере двух пакетов:

  • Стандартный пакет log
  • Сторонний пакет logrus

Разберем их подробнее.

Логируем со стандартным пакетом log

В Go есть стандартный пакет log для логирования. С его помощью можно:

  • Установить место записи логов
  • Писать логи построчно
  • Заканчивать выполнение всей программы в случае фатальных ошибок

Так это выглядит в коде:

// Возвращаем объект глобального логгера, который
// по умолчанию пишет логи в стандартный вывод
// операционной системы (os.Stdout)
l := log.Default()
// Меняем вывод логов в стандартный вывод ошибок
// операционной системы
l.SetOutput(os.Stderr)
// Логируем строку "something went wrong" в stderr
l.Println("something went wrong")
// Логируем строку "something went wrong" в stderr
// и паникуем с тем же сообщением
l.Panicln("something went wrong with panic")
// Логируем строку "something went wrong" в stderr
// и завершаем выполнение программы с тем же сообщением
l.Fatalln("something went wrong with fatal error")

Рассмотрим, как используется пакет log на конкретном примере. Допустим, у нас есть следующее веб-приложение:

package main

import (
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Welcome to Hexlet"))
    })

    http.ListenAndServe(":80", nil)
}

Здесь есть два места, где функции возвращают ошибки, но не логируются:

  • Функция w.Write() — возвращает ошибку, если не удалось отправить ответ клиенту
  • Функция http.ListenAndServe() — возвращает ошибку, если не удалось запустить веб-приложение на заданном порту

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

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

Используем пакет log для этого веб-приложения, чтобы видеть ошибки:

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, err := w.Write([]byte("Welcome to Hexlet"))
        if err != nil {
            log.Printf("welcome to hexlet error: %s\n", err.Error())
        }
    })

    port := "80"
    log.Println("Starting a web-server on port " + port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Теперь при первом запуске программы в консоли отобразится вывод:

$ go run main.go
2022/04/13 11:02:59 Starting a web-server on port 80

А если попытаться поднять второй сервер, то отобразится ошибка:

$ go run main.go
2022/04/13 11:03:51 Starting a web-server on port 80
2022/04/13 11:03:51 listen tcp :80: bind: address already in use
exit status 1

Стандартный пакет log подходит для случаев, как наш пример. При этом в реальных приложениях он не используется из-за следующих недостатков:

  • Нет четко разделенных уровней логирования: DEBUG, INFO, ERROR
  • Пакет работает только со строками. Если нужно логировать другой тип данных, сначала придется вручную конвертировать его в строку

Из-за этих недостатков появилось много сторонних пакетов для логирования. Один из самых популярных — logrus. Рассмотрим, как его использовать в Go-приложениях.

Логируем со сторонним пакетом logrus

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

Рассмотрим основные возможности пакета logrus:

// Меняем вывод логов в стандартный вывод операционной системы
// Здесь можно установить любой вывод, реализующий
// интерфейс io.Writer
logrus.SetOutput(os.Stdout)
// Логируем строку "info message" на уровне INFO
logrus.Info("info message")
// Логируем строку "something went wrong" на уровне ERROR
logrus.Error("something went wrong")
// Логируем строку "something went wrong" на уровне ERROR
// вместе с ошибкой с текстом "invalid"
logrus.WithError(errors.New("invalid")).Error("something went wrong")
// Логируем строку "debug info" на уровне DEBUG
// с дополнительной информацией
logrus.WithFields(logrus.Fields{
    "count":   22,
    "time":    time.Now(),
    "message": "hello",
}).Debug("debug info")

Как видно из кода, у logrus есть преимущества перед log. Например, с его помощью можно логировать на определенных уровнях. Поэтому стандартный пакет можно смело заменять сторонним:

package main

import (
    "github.com/sirupsen/logrus"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _, err := w.Write([]byte("Welcome to Hexlet"))
        if err != nil {
            // Ошибка логируется функцией WithError
            logrus.WithError(err).Error("write hello hexlet")
        }
    })

    port := "80"
    // Дополнительная информация передается функцией WithFields
    logrus.WithFields(logrus.Fields{
        "port": port,
    }).Info("Starting a web-server on port")
    logrus.Fatal(http.ListenAndServe(":"+port, nil))
}

Снова пытаемся запустить веб-приложение в двух процессах и при запуске второго получаем ошибку:

INFO[0000] Starting a web-server on port                 port=80
FATA[0000] listen tcp :80: bind: address already in use
exit status 1

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

Выводы

  • С помощью логирования можно найти места с ошибками в работающей программе
  • У стандартной библиотеки логирования log в Go есть недостатки: нет четко разделенных уровней логирования и он работает только со строками. Из-за этих недостатков в проектах используют сторонние пакеты
  • Со сторонним пакетом logrus можно писать логи на разных уровнях, а еще добавлять в сообщения контекст в виде любого типа данных

Дополнительные материалы

  1. Стандартный пакет log
  2. Библиотека для логирования logrus

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»