На данный момент в файле main.go
мы выводим лог с помощью функций log.Printf()
и log.Fatal()
.
В Go, обе эти функции выводят сообщения через стандартный логгер, который по умолчанию добавляет к сообщениям префиксы с локальной датой и временем и записывает их в стандартный поток ошибок (который должен отображаться в окне терминала). Функция log.Fatal()
также вызовет os.Exit(1)
после того как выведет в терминал сообщение об ошибке, это приведет к мгновенному завершению работы приложения.
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Содержание статьи
- Разделение логирования для разных задач
- Логирование ошибок от HTTP-сервера
- Дополнительные методы логирования
- Конкурентное логирование в Golang
- Логирование сообщений в файл
Логгирование можно поделить на два различных типа, или уровня. К первому типу относятся информационные сообщения (вроде «Запуск сервера на :4000«), а ко второму типу относятся сообщения об ошибках.
1 2 3 |
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"
), уже используется другим процессом.
Логи во втором терминале должны выглядеть следующим образом:
1 2 3 4 |
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 в файлы на диске при запуске приложения из терминала следующим образом:
1 |
$ 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 и использовать его в качестве места назначения лога. Например:
1 2 3 4 5 6 7 |
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, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»