상황별 로그인 이동



방금 일어났는데 오류 로그를 추적하기 위해 taboo을 생성했던 때를 어떻게든 기억합니다. 그런 다음 오류 로그 자체 대신 로거를 전달하면 더 좋을까요?

컨텍스트가 전달되면 내부에 x-request-id가 있는 하위 로거가 포함됩니다. HTTP 처리기에서 요청된 모든 이벤트를 함수의 가장 구석까지 추적합니다.

일어나자마자 바로 쓴거라 잘 될지 모르겠네요. 이는 최적화되지 않았거나 안티패턴일 수 있습니다. 그러나 적어도 이것은 상황에 맞는 로거를 만드는 데 참조가 될 수 있습니다.

나무꾼



먼저 로거가 필요합니다. 무엇이든 될 수 있지만 이 게시물에서는 zerolog을 사용하겠습니다. Zerolog에 대해서는 아무거나 사용할 수 있으므로 설명하지 않겠습니다.

로깅 스타일과 일치하도록 기본 로거를 포맷합니다.

func configureBaseLogger() {
  output := zerolog.ConsoleWriter{
    Out: os.Stdout,
    FormatTimestamp: func(i interface{}) string {
      parse, _ := time.Parse(time.RFC3339, i.(string))
      return parse.Format("2006-01-02 15:04:05")
    },
    FormatLevel: func(i interface{}) string {
      return strings.ToUpper(fmt.Sprintf(" %-6s ", i))
    },
  }

  log.Logger = zerolog.New(output).With().
    Timestamp().CallerWithSkipFrameCount(2).Logger()
}


기본 로거를 테스트해 봅시다

func main() {
  configureBaseLogger()
  log.Info().Msg("testing")
}


산출:

2020-12-19 01:01:37 INFO logger/main.go:57 > testing


LGTM! 핸들러를 계속 만들어 봅시다!

미들웨어



여기에서 미들웨어를 사용하여 요청을 기록하겠습니다. 그래서 내 미들웨어와 일치하는 새로운 유형의 핸들러를 만들 것입니다.

type HandlerFuncWithStatus func(writer http.ResponseWriter, request *http.Request) int

const (
  LoggerCtxKey = iota
)

func middleware(next HandlerFuncWithStatus) http.HandlerFunc {
  return func(writer http.ResponseWriter, request *http.Request) {
    requestId := request.Header.Get("x-request-id")

    if requestId == "" {
      requestId = uuid.New().String()
    }

    subLogger := log.With().Str("requestId", requestId).Logger()

    subLogger.Info().
      Str("path", request.URL.Path).
      Str("method", request.Method).Msg("in")

    ctx := context.WithValue(context.Background(), LoggerCtxKey, subLogger)
    statusCode := next(writer, request.WithContext(ctx))
    writer.WriteHeader(statusCode)

    subLogger.Info().Int("status", statusCode).Msg("out")
  }
}


주요 아이디어는 다음과 같습니다.
  • 존재하는 경우 x-request-id 가져오기, 그렇지 않으면 UUIDv4 형식으로 생성
  • 기본 로거에서 하위 로거를 만든 다음 request id로 시작합니다.
  • 방법과 경로를 기록합니다. 이 단계에서도 무엇이든 할 수 있습니다
  • .
  • 로거를 컨텍스트에 넣고 http 처리기로 전달합니다
  • .
  • 마지막으로 http 상태 코드를 설정하고 기록합니다
  • .

    HTTP 핸들러



    이제 미들웨어를 사용할 http 핸들러를 만들겠습니다. 화려할 필요는 없습니다.

    func anotherFunc(ctx context.Context) {
      logger := ctx.Value(LoggerCtxKey).(zerolog.Logger)
      logger.Info().Msg("inside anotherFunc")
    }
    
    func handler (writer http.ResponseWriter, request *http.Request) int {
      logger := request.Context().Value(LoggerCtxKey).(zerolog.Logger)
      logger.Info().Msg("inside handler")
    
      anotherFunc(request.Context())
    
      return 204
    }
    


    따라서 이제 미들웨어에서 전달된 컨텍스트가 있는 모든 함수는 컨텍스트 키로 로거에 액세스할 수 있습니다.

    모든 코드를 조합해 봅시다.

    결정적인



    이것이 우리 코드의 최종 모습이 될 것입니다.

    package main
    
    import (
      "context"
      "fmt"
      "github.com/google/uuid"
      "github.com/rs/zerolog"
      "github.com/rs/zerolog/log"
      "net/http"
      "os"
      "strings"
      "time"
    )
    
    type HandlerFuncWithStatus func(writer http.ResponseWriter, request *http.Request) int
    
    const (
      LoggerCtxKey = iota
    )
    
    func middleware(next HandlerFuncWithStatus) http.HandlerFunc {
      return func(writer http.ResponseWriter, request *http.Request) {
        requestId := request.Header.Get("x-request-id")
    
        if requestId == "" {
          requestId = uuid.New().String()
        }
    
        subLogger := log.With().Str("requestId", requestId).Logger()
    
        subLogger.Info().
          Str("path", request.URL.Path).
          Str("method", request.Method).Msg("in")
    
        ctx := context.WithValue(context.Background(), LoggerCtxKey, subLogger)
        statusCode := next(writer, request.WithContext(ctx))
        writer.WriteHeader(statusCode)
    
        subLogger.Info().Int("status", statusCode).Msg("out")
      }
    }
    
    func anotherFunc(ctx context.Context) {
      logger := ctx.Value(LoggerCtxKey).(zerolog.Logger)
      logger.Info().Msg("inside anotherFunc")
    }
    
    func handler (writer http.ResponseWriter, request *http.Request) int {
      logger := request.Context().Value(LoggerCtxKey).(zerolog.Logger)
      logger.Info().Msg("inside handler")
    
      anotherFunc(request.Context())
    
      return 204
    }
    
    func configureBaseLogger() {
      output := zerolog.ConsoleWriter{
        Out: os.Stdout,
        FormatTimestamp: func(i interface{}) string {
          parse, _ := time.Parse(time.RFC3339, i.(string))
          return parse.Format("2006-01-02 15:04:05")
        },
        FormatLevel: func(i interface{}) string {
          return strings.ToUpper(fmt.Sprintf(" %-6s ", i))
        },
      }
    
      log.Logger = zerolog.New(output).With().
        Timestamp().CallerWithSkipFrameCount(2).Logger()
    }
    
    func main() {
      configureBaseLogger()
      log.Info().Msg("testing")
    
      http.Handle("/", middleware(handler))
    
      if err := http.ListenAndServe(":8000", nil) ; err != nil {
        log.Error().Msg(err.Error())
      }
    }
    
    


    http://localhost:8000에 액세스하여 사용해 보십시오. 두 번 액세스하면 출력은 다음과 같습니다.

    2020-12-19 01:25:16 INFO logger/main.go:75 > testing
    2020-12-19 01:25:20 INFO logger/main.go:33 > in method=GET path=/ requestId=6eb1f209-dac9-42b2-8ba4-883efffcbd9e
    2020-12-19 01:25:20 INFO logger/main.go:50 > inside handler requestId=6eb1f209-dac9-42b2-8ba4-883efffcbd9e
    2020-12-19 01:25:20 INFO logger/main.go:45 > inside anotherFunc requestId=6eb1f209-dac9-42b2-8ba4-883efffcbd9e
    2020-12-19 01:25:20 INFO logger/main.go:39 > out requestId=6eb1f209-dac9-42b2-8ba4-883efffcbd9e status=204
    2020-12-19 01:25:31 INFO logger/main.go:33 > in method=GET path=/ requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0
    2020-12-19 01:25:31 INFO logger/main.go:50 > inside handler requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0
    2020-12-19 01:25:31 INFO logger/main.go:45 > inside anotherFunc requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0
    2020-12-19 01:25:31 INFO logger/main.go:39 > out requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0 status=204
    


    이제 모든 이벤트를 추적하려면 파일에 기록하고 요청 ID를 grep할 수 있습니다.

    $ grep 88a140b8-2aed-4d82-bf56-0ab9a1c7cce0 /tmp/log
    2020-12-19 01:25:31 INFO logger/main.go:33 > in method=GET path=/ requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0
    2020-12-19 01:25:31 INFO logger/main.go:50 > inside handler requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0
    2020-12-19 01:25:31 INFO logger/main.go:45 > inside anotherFunc requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0
    2020-12-19 01:25:31 INFO logger/main.go:39 > out requestId=88a140b8-2aed-4d82-bf56-0ab9a1c7cce0 status=204
    


    참조용으로 충분할 것입니다. 용도에 따라 코드를 수정하십시오. 나는 성능을 테스트하거나 벤치마킹하지 않았으므로 소금 한 알과 함께 가져 가십시오.

    좋은 웹페이지 즐겨찾기