Идентификатор потока журнала Go в Gorilla Handler

Как получить thread id или любой другой уникальный идентификатор http-запроса, обрабатываемого обработчиком при регистрации внутри Gorilla Handlers?
В Java, когда Tomcat или другой контейнер обрабатывает несколько HTTP-запросов, идентификатор потока помогает отслеживать все сообщения журнала для соответствующей обработки http-запросов.
Чему соответствует Go? Учитывая Rest API, разработанный с использованием библиотеки Gorilla, как я могу отслеживать все операторы журнала конкретного http-запроса внутри обработки обработчика?


person suman j    schedule 26.11.2015    source источник


Ответы (2)


Библиотека gorilla/handlers не предоставляет возможности сделать это по умолчанию: функции ведения журнала входят в систему. Форматы Apache, которые этого не предусматривают.

Также имейте в виду, что «идентификатор потока» здесь не имеет смысла — вам нужен идентификатор запроса, связанный с *http.Request.

Вы можете написать собственное промежуточное ПО RequestID, которое создает идентификатор и сохраняет его в контексте запроса, чтобы другие промежуточное ПО/обработчики могли извлекать его по мере необходимости:

package main

import (
    "crypto/rand"
    "encoding/base64"
    "net/http"

    "github.com/gorilla/context"
)

const ReqID string = "gorilla.RequestID"

// RequestID wraps handlers and makes a unique (32-byte) request ID available in
// the request context.
// Example:
//      http.Handle("/", RequestID(LoggingHandler(YourHandler)))
//
//      func LoggingHandler(h http.Handler) http.Handler {
//          fn := func(w http.ResponseWriter, r *http.Request) {
//              h.ServeHTTP(w, r)
//
//              id := GetRequestID(r)
//              log.Printf("%s | %s", id, r.RemoteAddr)
//          }
//
//          return http.HandlerFunc(fn)
//      }
func RequestID(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        b := make([]byte, 8)
        _, err = rand.Read(&b)
        if err != nil {
            http.Error(w, http.StatusText(500), 500)
            return
        }

        base64ID := base64.URLEncoding.EncodeToString(b)

        context.Set(r, ReqID, base64ID)

        h.ServeHTTP(w, r)
        // Clear the context at the end of the request lifetime
        context.Clear(r)
    }

    return http.HandlerFunc(fn)
}

func GetRequestID(r *http.Request) string {
    if v, ok := context.GetOK(r, ReqID); ok {
        if id, ok := v.(string); ok {
            return id
        }
    }

    return ""
}

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

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

  • Добавьте к идентификатору префикс имени хоста — полезно, если вы собираете журналы из нескольких процессов/компьютеров)
  • Укажите отметку времени или увеличивающееся целое число как часть окончательного идентификатора, чтобы помочь отслеживать запросы с течением времени.
  • Сравните это.

Обратите внимание, что при чрезвычайно высоких нагрузках (например, десятки тысяч запросов в секунду — десятки миллионов обращений в день) это может быть неэффективно, но вряд ли станет узким местом для > 99 % пользователей.

PS: я могу посмотреть на реализацию handlers.RequestID в библиотеке gorilla/handlers в какой-то момент - если вы хотите ее увидеть, поднимите вопрос в репозитории, и я посмотрю, смогу ли я найти время для реализации более полной взяться за вышеперечисленное.

person elithrar    schedule 26.11.2015
comment
При этом везде, где нам нужно ведение журнала, нам нужно передать экземпляр запроса для получения идентификатора. Это болезненно. Из обработчиков мы можем вызывать множество функций, которые могут быть служебными функциями/методами базы данных и т. д. Все эти методы можно изменить, чтобы они принимали экземпляр запроса. - person suman j; 10.12.2015
comment
Больше не используйте gorilla/context, поскольку он родился задолго до того, как появился context.Context плохо сочетается с поверхностным копированием запроса, который выполняет http.Request.WithContext (добавленный в net/http Go 1.7 и выше). - person Feiyu Zhou; 12.11.2020

На основе https://groups.google.com/forum/#!searchin/golang-nuts/Logging$20http$20thread/golang-nuts/vDNEH3_vMXQ/uyqGEwdchzgJ, ThreadLocal концепция невозможна с Go.

Везде, где вам нужно ведение журнала, требуется передать экземпляр http Request, чтобы можно было получить контекст, связанный с запросом, и можно было получить уникальный идентификатор из этого контекста для запроса. Но нецелесообразно передавать экземпляр запроса всем слоям/методам.

person suman j    schedule 10.12.2015