Есть еще одна проблема логирования, с которой нам нужно разобраться. При просмотре файла handlers.go
вы заметите, что функция обработчик home()
по-прежнему записывает сообщения об ошибках с использованием стандартного логгера от Go, а не созданный нами логгер errorLog
, которого нам нужно использовать.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
func home(w http.ResponseWriter, r *http.Request) { ... ts, err := template.ParseFiles(files...) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) return } err = ts.Execute(w, nil) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) } } |
Возникает вопрос: как сделать наш новый логгер errorLog
доступным для функции home
из main()
?
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
В текущем виде кода, логгеры infoLog и errorLog не видны из за области видимости функции main()
.
У большинства веб-приложений будет несколько зависимостей, к которым их обработчики должны обращаться. Например пул соединений с базой данных, централизованные обработчики ошибок и кэши шаблонов. Однако, ответ на вопрос который нам действительно нужен, это: как сделать любую зависимость доступной нашим обработчикам?
Для этого есть несколько способов. Самый простой — просто поместить зависимости в глобальные переменные которых видно везде. В целом рекомендуется внедрять зависимости в обработчики. Это делает код более явным, менее подверженным ошибкам и более простым для модульного тестирования, чем в случае использования глобальных переменных.
Для приложений, в которых все обработчики находятся в одном пакете (как наше приложение), отличным способом внедрения зависимостей будет их размещение в структуру application
.
Ниже мы продемонстрируем всё на примере.
Откройте файл main.go
и создайте новую структуру application
следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package main import ( "flag" "log" "net/http" "os" ) // Создаем структуру `application` для хранения зависимостей всего веб-приложения. // Пока, что мы добавим поля только для двух логгеров, но // мы будем расширять данную структуру по мере усложнения приложения. type application struct { errorLog *log.Logger infoLog *log.Logger } func main() { ... } |
Затем в файле handlers.go
обновим обработчик home()
так, чтобы ему стали доступны поля из структуры application
, для этого он должен стать частью этой структуры.
Если данная тема кажется запутанной, то советую ознакомиться методами в golang из нашего курса по изучению 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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
package main import ( "fmt" "html/template" "net/http" "strconv" ) // Меняем сигнатуры обработчика home, чтобы он определялся как метод // структуры *application. func (app *application) home(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } files := []string{ "./ui/html/home.page.tmpl", "./ui/html/base.layout.tmpl", "./ui/html/footer.partial.tmpl", } ts, err := template.ParseFiles(files...) if err != nil { // Поскольку обработчик home теперь является методом структуры application // он может получить доступ к логгерам из структуры. // Используем их вместо стандартного логгера от Go. app.errorLog.Println(err.Error()) http.Error(w, "Внутренняя ошибка сервера", 500) return } err = ts.Execute(w, nil) if err != nil { // Обновляем код для использования логгера-ошибок // из структуры application. app.errorLog.Println(err.Error()) http.Error(w, "Внутренняя ошибка сервера", 500) } } // Меняем сигнатуру обработчика showSnippet, чтобы он был определен как метод // структуры *application func (app *application) showSnippet(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.URL.Query().Get("id")) if err != nil || id < 1 { http.NotFound(w, r) return } fmt.Fprintf(w, "Отображение выбранной заметки с ID %d...", id) } // Меняем сигнатуру обработчика createSnippet, чтобы он определялся как метод // структуры *application. func (app *application) createSnippet(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.Header().Set("Allow", http.MethodPost) http.Error(w, "Метод запрещен!", 405) return } w.Write([]byte("Создание новой заметки...")) } |
Обновляем файл main.go
в котором мы определяем структуру application:
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 40 41 42 43 44 45 46 |
package main import ( "flag" "log" "net/http" "os" ) type application struct { errorLog *log.Logger infoLog *log.Logger } 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) // Инициализируем новую структуру с зависимостями приложения. app := &application{ errorLog: errorLog, infoLog: infoLog, } // Используем методы из структуры в качестве обработчиков маршрутов. mux := http.NewServeMux() mux.HandleFunc("/", app.home) mux.HandleFunc("/snippet", app.showSnippet) mux.HandleFunc("/snippet/create", app.createSnippet) fileServer := http.FileServer(http.Dir("./ui/static/")) mux.Handle("/static/", http.StripPrefix("/static", fileServer)) srv := &http.Server{ Addr: *addr, ErrorLog: errorLog, Handler: mux, } infoLog.Printf("Запуск сервера на %s", *addr) err := srv.ListenAndServe() errorLog.Fatal(err) } |
Такой подход может показаться немного сложным и запутанным, особенно когда альтернативой является простое создание глобальных переменных логгеров infoLog
и errorLog
. По мере того, как приложение будет расти, наши обработчики будут требовать больше зависимостей. Тогда мы и увидим всю прелесть паттерна Dependency Injection.
Тестирование логгеров в Golang
Давайте попробуем специально создать ошибку в наше веб-приложение.
Откройте терминал и переименуйте ui/html/home.page.tmpl
на ui/html/home.page.bak
.Теперь при запуске веб-приложения и создании HTTP-запроса к домашней страницы, результатом будет ошибка, потому что файл ui/html/home.page.tmpl
больше не существует.
Попробуйте сделать изменение:
1 2 |
$ cd $HOME/code/snippetbox $ mv ui/html/home.page.tmpl ui/html/home.page.bak |
Затем, запустите приложение и выполните запрос на http://127.0.0.1:4000. В браузере вы должны получить HTTP ответ Внутренняя ошибка сервера
, а также соответствующее сообщение об ошибке в терминале, наподобие следующего:
Обратите внимание, что у сообщения об ошибке теперь есть префикс ERROR
она была сгенерирована на 29-й строки файла handlers.go
. Это наглядно демонстрирует, что созданный нами логгер errorLog
передается обработчику home()
в качестве зависимости и работает как и ожидалось.
Оставьте пока преднамеренную ошибку с несуществующим файлом шаблона
home.page.tmpl
— она нам понадобится в следующем уроке.
Замыкания для внедрения зависимостей
Паттерн, который мы используем для внедрения зависимостей, не будет работать, если обработчики распределены по нескольким пакетам. В этом случае, альтернативным подходом будет создание пакета config
, который экспортирует структуру Application
. К примеру:
1 2 3 4 5 6 7 |
func main() { app := &config.Application{ ErrorLog: log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) } mux.Handle("/", handlers.Home(app)) } |
1 2 3 4 5 6 7 8 9 10 11 12 |
func Home(app *config.Application) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { ... ts, err := template.ParseFiles(files...) if err != nil { app.ErrorLog.Println(err.Error()) http.Error(w, "Внутренняя ошибка сервера", 500) return } ... } } |
Вы можете найти более подробный пример использования паттерна замыкания ознакомившись со статьей: Замыкания и анонимные функции
Скачать исходный код
На данный момент, если вы запустите код веб-приложения, то оно будет работать, но возникнет ошибка при заходе на главную страницу. Если вы прошли урок из этой статьи, то вы должны понимать, что мы специально её оставили для будущего урока.
Скачать: snippetbox-12.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, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»