EchoMiddleTechnical
Как прокидывать request id/correlation id через middleware?
Request ID генерируется middleware (встроенным или своим), кладётся в echo.Context через c.Set и в заголовок ответа X-Request-ID, затем прокидывается в context.Context для использования в сервисах и логере.
Request ID / Correlation ID в Echo
Request ID нужен для сквозной трассировки запроса через все слои: middleware, хендлер, сервис, репозиторий, внешние вызовы. Стандартная схема: middleware генерирует (или берёт из заголовка) ID, кладёт его в echo.Context и в context.Context, а логер достаёт его из контекста при каждой записи.
1. Встроенный middleware RequestID
package main
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
e.Use(middleware.RequestIDWithConfig(middleware.RequestIDConfig{
Generator: func() string {
return "req-" + echo.MustGetRequestID()
},
RequestIDHandler: func(c echo.Context, id string) {
// Дополнительная обработка после генерации
c.Set("requestID", id)
},
TargetHeader: echo.HeaderXRequestID, // "X-Request-ID"
}))
e.Logger.Fatal(e.Start(":8080"))
}
2. Кастомный middleware с прокидыванием в context.Context
package middleware
import (
"context"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
)
type contextKey string
const RequestIDKey contextKey = "requestID"
func RequestID() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Берём из входящего заголовка или генерируем новый
id := c.Request().Header.Get(echo.HeaderXRequestID)
if id == "" {
id = uuid.New().String()
}
// Кладём в echo.Context для хендлеров
c.Set(string(RequestIDKey), id)
// Кладём в context.Context для сервисов
req := c.Request().WithContext(
context.WithValue(c.Request().Context(), RequestIDKey, id),
)
c.SetRequest(req)
// Прокидываем в ответ
c.Response().Header().Set(echo.HeaderXRequestID, id)
return next(c)
}
}
}
// Хелпер для извлечения из context.Context
func GetRequestID(ctx context.Context) string {
if id, ok := ctx.Value(RequestIDKey).(string); ok {
return id
}
return ""
}
3. Интеграция со структурированным логером (zerolog)
package main
import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"myapp/middleware"
)
func someService(ctx context.Context) {
requestID := middleware.GetRequestID(ctx)
log.Ctx(ctx).Info().
Str("request_id", requestID).
Msg("processing request")
}
// Или настроить zerolog так, чтобы request_id автоматически
// добавлялся из контекста через zerolog.Ctx(ctx)
func setupLogger(ctx context.Context, requestID string) context.Context {
logger := zerolog.Ctx(ctx).With().
Str("request_id", requestID).
Logger()
return logger.WithContext(ctx)
}
4. Прокидывание в исходящие HTTP-запросы
func callExternalAPI(ctx context.Context, url string) (*http.Response, error) {
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
req.Header.Set("X-Request-ID", middleware.GetRequestID(ctx))
req.Header.Set("X-Correlation-ID", middleware.GetRequestID(ctx))
return http.DefaultClient.Do(req)
}
Подводные камни
- Брать ID только из входящего заголовка без генерации запасного — при его отсутствии строка окажется пустой во всех логах.
- Доверять значению
X-Request-IDот клиента без валидации — возможно внедрение произвольных строк в логи (log injection). - Класть ID только в
echo.Context, но не вcontext.Context— сервисный слой, не знающий об Echo, не сможет его получить. - Не добавлять ID в заголовок ответа — клиент не сможет сопоставить ошибку с конкретным запросом в логах.
- Использовать
math/randвместоcrypto/randили UUID — коллизии при высокой нагрузке. - Не прокидывать ID во внешние HTTP-вызовы — трассировка обрывается на границе сервиса.
- Регистрировать middleware RequestID после Logger — логер запишет запрос без ID, если стоит раньше в цепочке.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.