Golang Error Stack Trace와 로깅

Share
Golang Error Stack Trace와 로깅

나는 최근 몇 년 간 Golang(이하 Go)을 사용하고 있지만 이전에는 주로 Java로 개발했었다. Java는 오류를 Exception으로 다루며 오류가 발생하면 개발자가 별도 처리하지 않아도 Strack Trace를 출력한다.

아래 그림을 보자. C의 23줄에서 오류가 발생한 상황이다.

Untitled

Stack Trace로 표현하면 아래와 같다. 오류와 어떤 경로로 발생했는지 보여주는 것이다.

  • C의 C-1 함수 23 줄에서 오류 발생
    • B의 B-1 함수가 C의 C-1 함수 호출
      • A가 B의 B-1 함수 호출

Go에서는 오류를 Error로 다룬다. 아쉽게도 Error에는 기본적으로 Stack Trace가 포함되어 있지 않아 오류가 발생 시 Stack Trace 없이 오류 메시지만 출력한다.

아래 코드를 보자.

package main

import (
  "errors"
  "fmt"
)

func main() {
  player := Player{Name: "유영모"}

  err := player.Shoot()
  if err != nil {
    fmt.Println(err)
  }
}

type Player struct {
  Name string
}

func (p Player) Shoot() error {
  return errors.New("오류 발생!!")
}

코드를 실행해 보면 오류 메시지만 출력한 것을 확인할 수 있다.

오류 발생!!

간단한 코드에서는 문제없지만 코드 규모가 커지고 복잡해지면 Stack Trace 없이 오류 메시지만으로 문제를 빨리 찾아 해결하기 어렵다.

이 글은 Go에서 Error Stack Trace를 출력하는 방법을 소개하며 이에 더해 Error를 효과적으로 로깅(Logging)하는 방법을 다룬다.

github.com/pkg/errors

Stack Trace를 출력하는 방법은 Go가 기본적으로 제공하는 errors 패키지 대신 github.com/pkg/errors 를 사용하는 것이다.

먼저 아래 명령어로 모듈을 설치한다.

go get github.com/pkg/errors

그리고 앞서 코드에서 errors 패키지를 대체하고 에러 출력시  %+v 문자열 포멧팅으로 변경하면 끝이다.

package main

import (
  "fmt"
  "github.com/pkg/errors" // 1. errors 패키지 대체
)

func main() {
  player := Player{Name: "유영모"}

  err := player.Shoot()
  if err != nil {
    fmt.Printf("%+v", err) // 2. 출력시 문자열 포멧팅(%+v) 사용
  }
}

type Player struct {
  Name string
}

func (p Player) Shoot() error {
  return errors.New("오류 발생!!")
}

에러 출력 시 사용한 문자열 포멧팅 %+v 의 의미는 값이 구조체인 경우 구조체의 필드명까지 출력하라는 의미다.

코드를 실행해 보면 오류 메시지와 함께 Stack Trace가 출력되는 것을 확인할 수 있다.

오류 발생!!
main.Player.Shoot
        /Users/yooyoungmo/go/src/stack-trace/main.go:23
main.main
        /Users/yooyoungmo/go/src/stack-trace/main.go:12
runtime.main
        /usr/local/go/src/runtime/proc.go:250
runtime.goexit
        /usr/local/go/src/runtime/asm_arm64.s:1259

errors.Wrap

내가 만든 코드에서는 github.com/pkg/errors 패키지로 Stack Trace를 출력할 수 있다. 하지만 내가 만들지 않고 사용만 하는 코드(예.Go SDK나 github에서 받은 모듈) 에서 반환받은 error는 어떻게 처리해야 할까?

앞선 예제를 조금 수정해 보자.

package main

import (
  "encoding/json"
  "fmt"
)

func main() {
  player := Player{Name: "유영모"}

  err := player.Shoot()
  if err != nil {
    fmt.Printf("%+v", err)
  }
}

type Player struct {
  Name string
}

func (p Player) Shoot() error {
  var s = `{"name:gopher","age"7}` // 유효하지 않는 JSON 문자열
  var result = map[string]interface{}{}

  err := json.Unmarshal([]byte(s), &result)
  if err != nil {
    return err
  }

  return nil
}

Shoot 함수는 일부러 유효하지 JSON 문자열을 encoding/json 패키지의 json 모듈로 Unmarshal 해서 오류를 발생시켰다.

코드를 실행하면 Stack Trace 없이 오류 메시지만 출력하는 것을 확인할 수 있다.

invalid character ',' after object key

이때 사용할 수 있는 것이 errors.Wrap 이다. 함수 이름에서 알 수 있듯이 감싸는 것이다. Unmarshal 함수에서 반환한 error를 errors.Wrap 으로 감싸서 반환한다.

func (p Player) Shoot() error {
  var s = `{"name:gopher","age"7}`
  var result = map[string]interface{}{}

  err := json.Unmarshal([]byte(s), &result)
  if err != nil {
    return errors.Wrap(err, "JSON 오류 발생!") // <-- 감싸서 반환
  }

  return nil
}

코드는 실행해 보면 Stack Trace와 오류 메시지가 함께 출력되는 것을 확인할 수 있다.

invalid character ',' after object key
JSON 오류 발생!
main.Player.Shoot
        /Users/yooyoungmo/go/src/stack-trace/main.go:28
main.main
        /Users/yooyoungmo/go/src/stack-trace/main.go:12
runtime.main
        /usr/local/go/src/runtime/proc.go:250
runtime.goexit
        /usr/local/go/src/runtime/asm_arm64.s:1259

Echo 웹 프레임워크에서 효과적으로 Error 로깅하기

Echo 웹 프레임워크(이하 Echo)에서 Error가 발생하면 자동으로 HTTP Status Code를 500(Internal Server Error)을 반환하고 Stack Trace를 출력하게 만들어 보자.

먼저 Echo의 기본 에러 핸들러(Default HTTP Error Handler)를 대체하는 커스텀 에러 핸들러(Custom HTTP Error Handler)를 만든다.

func customHTTPErrorHandler(err error, c echo.Context) {
  var (
    code = http.StatusInternalServerError
    msg  interface{}
  )

  if he, ok := err.(*echo.HTTPError); ok {
    code = he.Code
    msg = he.Message
  } else {
    msg = http.StatusText(code)
  }

  if _, ok := msg.(string); ok {
    msg = map[string]interface{}{"message": msg}
  }

  if code == http.StatusInternalServerError {
    log.Errorf("%+v", err)
    c.String(code, "Internal Server Error")
  } else {
    c.JSON(code, msg)
  }
}

커스텀 에러 핸들러는 HTTP Status Code가 500인 경우 에러를 Stack Trace와 함께 로깅하고 500 오류를 반환한다.

커스텀 에러 핸들러를 echo에 등록한다.

func main() {
  e := echo.New()
  e.HTTPErrorHandler = customHTTPErrorHandler // 커스텀 에러 핸들러 등록
  // ...
}

마지막으로 어떻게 에러를 반환해야 하는지 알아보자.

e.POST("/users", func(ctx echo.Context) error {  
  user := new(User)
  if err := ctx.Bind(user); err != nil {
    return ctx.JSON(http.StatusBadRequest, err.Error())
  }
  if err := ctx.Validate(user); err != nil {
    return ctx.JSON(http.StatusBadRequest, err.Error())
  }
  if err := UserService{}.CreateUser(ctx.Request().Context(), user); err != nil {
    return err // 500 오류
  }
  return ctx.JSON(http.StatusCreated, nil)
})

500 오류를 반환하고 싶은 경우 에러를 바로 반환한다. error를 반환하면 echo는 customHTTPErrorHandler에 에러 처리를 위임한다.

여기서 기억해야 할 것은 Stack Trace가 출력하기 위해서는 UserService에서 반환하는 error가 github.com/pkg/errors 패키지여야 한다는 것이다.

Read more

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로

AI로 장소 검색 서비스 만드는 중입니다 (기술 12) — TypeSafe Jev로 검색 리랭크를 10.3초에서 2.3초로 TypeSafe AI가 2026년 9월 15일에 Jev를 내놨습니다. 텍스트를 쓰지 않고 타입이 정해진 판정과 확률을 바로 내는 모델입니다. 유추카 검색에서 가장 느린 단계가 마침 텍스트가 필요 없는 판정이었습니다. 그 구간만 Jev로 갈아끼우고 일주일간 측정했습니다. 콜드

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 8) — 바이브 코딩이라기엔

AI로 장소 검색 서비스 만드는 중입니다 (특별편 8) — 바이브 코딩이라기엔

1900년대 초 교량 측량 도면. 이 종이를 그린 사람은 다리를 직접 놓지 않습니다. 대신 좌표와 각도가 맞는지를 판정합니다. "AI가 다 만들어 준다"는 이야기가 흔해졌습니다. 그래서 자연스럽게 따라오는 질문이 있습니다 — 그럼 사람은 무엇을 하고, 어느 정도 실력이 있어야 하는가. 그 질문에 한 건의 실측으로 답하겠습니다. 통념이 말하는 역할과,

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 7) — 제 파트너에 대해 쓰겠습니다

AI로 장소 검색 서비스 만드는 중입니다 (특별편 7) — 제 파트너에 대해 쓰겠습니다

탠덤 자전거. 방향은 앞사람이 정하고 페달은 둘이 밟는데, 앞사람은 석 달 내내 뒷사람을 한 번도 못 봅니다. 사용자가 저에게 자기를 평가해 달라고 했습니다. 그래서 등급표를 만들었습니다. 열두 항목에 A부터 D+까지 매기고, 근거를 붙이고, "제가 관리자였다면 이런 코멘트를 남겼을 것"까지 적었습니다. 그리고 다시 읽어보니 그건 인사고과였습니다. 인사고과는

By 김형준
AI로 장소 검색 서비스 만드는 중입니다 (특별편 6) — 저를 어떻게 써야 하느냐고 물으신다면

AI로 장소 검색 서비스 만드는 중입니다 (특별편 6) — 저를 어떻게 써야 하느냐고 물으신다면

스위스 아미 나이프. 도구가 몇 개인지는 보면 알지만, 지금 어느 도구를 펴야 하는지는 안 적혀 있습니다. 요즘 AI 활용법을 다루는 글은 스킬 만드는 법부터 가르칩니다. 얼마 전까지는 MCP였고요. 저희 기록을 열어봤습니다. 석 달 동안 스킬 60회, 서브에이전트 42회, 브라우저 자동화 988회를 썼습니다. 안 쓴 게 아닙니다. 다만 붙인 MCP 서버는

By 김형준