Golang Panic을 Rocover 할 때 Error Stack Trace 함께 출력하기

Share
Golang Panic을 Rocover 할 때  Error Stack Trace 함께 출력하기

나는 Go에서 Error가 발생할 때 Stack Trace를 함께 출력하는 ‘Golang Error Stack Trace와 로깅’ 라는 글을 쓴 적이 있다. 하지만 시스템을 운영하면서 Panic을 Recover한 경우 Error Stack Trace가 출력되지 않는다는 것을 발견했다.

이 글은 ‘Golang Error Stack Trace와 로깅’ 에 이어지는 글로 Panic을 Recover한 경우에도 Stack Trace를 출력하는 방법을 소개한다.

Panic

우선 Go에서는 Java와 같은 Exception이 없다. 명시적으로 Error를 전달하여 처리하며 관용적으로 마지막 반환 값을 사용한다. 아래 코드를 보면 f1 함수를 호출할 때 반환 값의 error가 nil 이 아니면 error가 발생한 것이다.

func main() {
	result, err := f1(1)
	if err != nil {
		fmt.Println("error", err)
	} else {
		fmt.Println("result", result)
	}
}

func f1(arg int) (int, error) {
	if arg == 42 {
		return -1, errors.New("can't work with 42")
	}

	return arg + 3, nil
}

예상되는 Error는 위와 같이 처리하지만 런타임 상에서 예상치 못하는 에러가 발생하는 경우 어떻게 될까? Panic이 발생한다.

panic은 일반적으로 무언가가 예상치 못하게 잘못되었음을 의미합니다. 패닉의 일반적인 사용은 어떤 함수가 어떻게 처리할지 모르는 (또는 하고 싶지 않은) 에러값을 반환했을때 중단을 하기 위함입니다. https://mingrammer.com/gobyexample/panic/

Panic이 발생하면 프로그램이 즉시 종료된다. 아래 코드를 실행해 보자.

func main() {
	result, err := f1(42)
	if err != nil {
		fmt.Println("error", err)
	} else {
		fmt.Println("result", result)
	}
}

func f1(arg int) (int, error) {
	if arg == 42 {
		var str *string
		if *str == "test" {
			// ...
		}

		return arg + 4, nil
	}

	return arg + 3, nil
}

실행 결과는 아래와 같다.

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x0 pc=0x102756b60]

goroutine 1 [running]:
main.f1(...)
        /Users/yooyoungmo/go/src/article-panic-stack-trace/main.go:19
main.main()
        /Users/yooyoungmo/go/src/article-panic-stack-trace/main.go:8 +0x20

Process finished with the exit code 2

str 포인터 변수에 값을 할당하지 않고 값에 접근했기 때문에 invalid memory address or nil pointer dereference 에러 메시지와 함께 Panic이 발생하여 프로그램이 종료되었다.

Recover

앞서 Panic이 발생하면 프로그램이 즉시 종료된다고 언급했다. 하지만 Panic 이 발생하여도 종료되지 않아야 하는 경우가 있는데 대표적으로 웹 서버 애플리케이션이 있다. 웹 애플리케이션의 특징 상 여러 사용자가 동시에 사용하는데 Panic이 발생했다고 서버가 종료되어서는 안되기 때문이다.

Go에서는 Panic이 발생할 때 Recover라는 복구 방법을 지원한다. Panic이 발생하면 즉시 종료하지 않고 recover 를 통해 Panic을 복구하는 것이다.

func main() {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("Recovered. Error:\n", err)
			// panic 복구
			// ...
		}
	}()
	
	result, err := f1(42)
	if err != nil {
		fmt.Println("error", err)
	} else {
		fmt.Println("result", result)
	}
}

func f1(arg int) (int, error) {
	if arg == 42 {
		var str *string
		if *str == "test" {
			// ...
		}

		return arg + 4, nil
	}

	return arg + 3, nil
}

위의 코드를 실행한 결과는 아래와 같다.

Recovered. Error:
 runtime error: invalid memory address or nil pointer dereference

Process finished with the exit code 0

Rocover 시에 Error Stack Trace 출력하기

recover를 통해 반환받은 Error를 출력해 보면 에러 메시지만 있다. 하지만 이 정보만으로는 어느 코드에서 Panic이 발생한 것인지 알 수 없다.

아래와 같이 debug.Stack()을 추가하면 Error Stack Trace를 확인할 수 있다.

defer func() {
	if err := recover(); err != nil {
		fmt.Println(fmt.Sprintf("Recovered. Error: %v \n %v", err, string(debug.Stack())))
	}
}()

 

Recovered. Error: runtime error: invalid memory address or nil pointer dereference 
 goroutine 1 [running]:
runtime/debug.Stack()
        /usr/local/go/src/runtime/debug/stack.go:24 +0x68
main.main.func1()
        /Users/yooyoungmo/go/src/article-panic-stack-trace/main.go:11 +0x38
panic({0x1023c6220, 0x10243d1b0})
        /usr/local/go/src/runtime/panic.go:838 +0x204
main.f1(...)
        /Users/yooyoungmo/go/src/article-panic-stack-trace/main.go:28
main.main()
        /Users/yooyoungmo/go/src/article-panic-stack-trace/main.go:17 +0x3c

Echo 웹 프레임워크로 확장하기

‘Golang Error Stack Trace와 로깅’ 글에서 Echo 웹 프레임워크에서 커스텀 에러 핸들러를 만들어 Stack Trace를 로깅하는 방법을 소개했다.

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

Panic을 Recover 한 경우 Stack Trace가 출력되지 않기 때문에 아래와 같이 변경하면 Stack Trace를 출력할 수 있다.

func customHTTPErrorHandler(err error, c echo.Context) {
	// ...
	if code == http.StatusInternalServerError {
		log.Errorf("%+v \ndebug stack %v", err, string(debug.Stack()))
		c.String(code, "Internal Server Error")
	} else {
		c.JSON(code, msg)
	}
}

 

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 김형준