Использовать идентификатор запроса во всех регистраторах

#go

# #Вперед

Вопрос:

У меня есть сервер веб-приложений, использующий go http, и я хочу, чтобы каждый запрос имел контекст с uuid, для этого я могу использовать контекст http-запроса https://golang.org/pkg/net/http/#Request .Контекст

мы используем logrus и запускаем его в одном файле и используем экземпляр регистратора в других файлах.

что мне нужно, так это напечатать идентификатор запроса во всех журналах, но не добавлять новые параметры к каждой печати журнала, я хочу сделать это один раз в каждом http-запросе (передать req-id), и все распечатанные журналы будут иметь его, ничего с ним не делая

например, если id=123 so log.info("foo") будет печатать // id=123 foo

Я пробовал со следующим, но не уверен, что это правильный путь, пожалуйста, посоветуйте.

 package main

import (
    "context"
    "errors"

    log "github.com/sirupsen/logrus"
)

type someContextKey string

var (
    keyA = someContextKey("a")
    keyB = someContextKey("b")
)

func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, keyA, "foo")
    ctx = context.WithValue(ctx, keyB, "bar")

    logger(ctx, nil).Info("did nothing")
    err := errors.New("an error")
    logger(ctx, err).Fatal("unrecoverable error")
}

func logger(ctx context.Context, err error) *log.Entry {
    entry := log.WithField("component", "main")

    entry = entry.WithField("ReqID", "myRequestID")

    return entry
}
 

https://play.golang.org/p/oCW09UhTjZ5

Ответ №1:

Каждый раз, когда вы вызываете logger функцию, вы создаете новый *log .Ввод и повторная запись идентификатора запроса к нему. Из вашего вопроса следовало, что вы этого не хотите.

 func main() {
    ctx := context.Background()
    ctx = context.WithValue(ctx, keyA, "foo")
    ctx = context.WithValue(ctx, keyB, "bar")

    lg := logger(ctx)
    lg.Info("did nothing")
    err := errors.New("an error")
    lg.WithError(err).Fatal("unrecoverable error")
}

func logger(ctx context.Context) *log.Entry {
    entry := log.WithField("component", "main")
    entry = entry.WithField("ReqID", "myRequestID")

    return entry
}
 

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

Что мы сделали в нашей компании, так это создали тонкий слой вокруг logrus , в котором есть дополнительный метод WithRequestCtx , чтобы мы могли передавать контекст запроса, и он извлекал сам идентификатор запроса (который мы записали в контекст в промежуточном программном обеспечении). Если идентификатор запроса не присутствовал, в запись журнала ничего не добавлялось. Это, однако, снова добавило идентификатор запроса к каждой записи журнала, как и в вашем примере кода.

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

Примечание 2: тем временем мы находимся в процессе замены logrus на zerolog, чтобы сделать его более легким.

Комментарии:

1. у вас есть пример кода для этого случая в zerolog? У меня такая же проблема, и в итоге я передаю каждый http-запрос всем функциям, использующим log

2. С zerolog у вас есть , например log.Info().String("requestID", requestID) . Я использую оболочку вокруг zerolog, которая имеет дополнительные методы, такие как WithReqContext . Этот метод извлекает, например, идентификатор транзакции (который был помещен в контекст промежуточным программным обеспечением) из контекста и добавляет его в запись журнала.