Golang, ๊ทธ๋๋ค์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ - 3. error ๋ํ
Golang, ๊ทธ๋๋ค์ ์ด๋ป๊ฒ ํ ๊ฒ์ธ๊ฐ - 3. error ๋ํ ๊ด๋ จ
์ ๊ธ์์๋ ๊ฐ ์๋ฒ์ ์ฐ์ฌํด ์๋ MongoDB ๊ด๋ จ ์ฝ๋๋ฅผ ๊ณตํตํ ๋ฐ ์ถ์ํํ๊ณ , DB error ์ ๋ณด๋ฅผ ๋ด์ ์ ์๋ ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์์ต๋๋ค. ์ด๋ฒ์๋ ์ด๋ฌํ error๋ฅผ ์ด๋ป๊ฒ ์์ ํจ์์ ๋ฐํํ๋์ง์ ๋ํด ์ด์ผ๊ธฐํด๋ณด๋ ค ํฉ๋๋ค.
๊ธฐ์กด ๋ฌธ์ ์
Golang์์ error๋ caller๊ฐ ์ฒ๋ฆฌํ๋ ๊ฒ์ ๊ถ์ฅํ๊ณ ์์ต๋๋ค.
Avoid duplication. If you return an error, it's usually better not to log it yourself but rather let the caller handle it. The caller can choose to log the error, or perhaps rate-limit logging using rate.
์ถ์ฒ: Logging errors
error๋ฅผ ์์ ํจ์๋ก ์ฌ๋ฆฌ๋ฉด, ์์ ํจ์์์ ํด๋น error์ ๋ํด ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ฑฐ๋ ๋ค๋ฅธ ์ฒ๋ฆฌ๋ฅผ ํ๋๋ก ํ๋ ๊ฒ์ ๋๋ค.
ํ์ง๋ง ๋ค์๊ณผ ๊ฐ์ด error๋ฅผ ๋จ์ํ ์ฌ๋ฆฌ๊ธฐ๋ง ํ๋ค๋ฉด ๋๋ฒ๊น ํ ๋ ์ ๋ณด๊ฐ ๋ง์ด ๋ถ์กฑํฉ๋๋ค.
func checkName(name string) error {
newError := errors.New("Invalid Name")
if name != "valid name" {
log.Error(err)
return newError
}
return nil
}
func main() {
name := "Hello"
err := checkName(name)
if err != nil {
log.Error(err)
}
}
Invalid Name
error message๋ง ๋ณด์ผ ๋ฟ, ์ด๋ ํจ์์์ ๋ฐ์ํ๋์ง ์๊ธฐ ์ด๋ ต์ต๋๋ค. ํนํ, ์ฌ์ฌ์ฉ์ด ๋ง์ ํจ์์์ error๊ฐ ๋ฐ์ํ๋ฉด ํด๋น error๊ฐ ์ด๋ค ํธ์ถ ์คํ์ผ๋ก ๋ถ๋ ค ๋ฐ์ํ๋์ง ์ฐพ๊ธฐ ํ๋ค ๊ฒ์ ๋๋ค.
๊ทธ๋์ ๊ธฐ์กด V1 ์ฝ๋์์๋ error๊ฐ ๋ฐ์ํ ์ง์ ์ ํฌํจํ์ฌ, ์์ ํจ์๋ก ์ฌ๋ฆฌ๋ฉด์ ๋ชจ๋ ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ ํธ์ถ ์คํ์ ์ฐพ์ ์ ์๊ฒ ํ์์ต๋๋ค. ํ์ง๋ง ์ด๋ฌํ ๋ฐฉ์์ ํจ์ ์ด๋ฆ
์ ๋๋ง์ด ์ถ๊ฐ์ ์ธ ์ ๋ณด์ผ ๋ฟ, ๋๋จธ์ง๋ ๋ชจ๋ ์ค๋ณต์ด์์ต๋๋ค. ๋ํ, ๋งค๋ฒ ๋ก๊ทธ๋ฅผ ๋จ๊ฒจ์ผํ๋ ๋ถํธํจ๋ ์ปธ์ต๋๋ค.
์ฌ์ ์กฐ์ฌ
์์ ๊ฐ์ ๋นํจ์จ์ ์์ ๋ฉด์ ํธํ๊ฒ ํธ์ถ ์คํ์ ๋จ๊ธฐ๋ ๋ฐฉ๋ฒ์ ๊ณ ๋ฏผํ๊ณ , ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฐพ์์ต๋๋ค.
1. pkg/errors
Wrap()
Go 1.13 ๋ฏธ๋ง ๋ฒ์ ์์๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ error ๋ํ ๋ฐ ์คํ ๊ธฐ๋ฅ์ ์ ๊ณตํ์ง ์์, ์ด๋ฅผ ์ ๊ณตํ๋ pkg/errors
๋ผ๋ ์๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํ์ต๋๋ค. ํ์ฌ๋ ์ถ๊ฐ ๊ฐ๋ฐ ์์ด ์ ์งํ๋ ์ค์
๋๋ค(์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด Go 2์ ํฌํจ๋ ์์ ).
func foo() error {
return errors.Wrap(sql.ErrNoRows, "foo failed") // attach the call stack
// return errors.WithStack(sql.ErrNoRows) // attach the call stack without message
}
func bar() error {
return errors.WithMessage(foo(), "bar failed") // without attaching call stack
}
func main() {
err := bar()
if errors.Cause(err) == sql.ErrNoRows {
fmt.Printf("data not found, %v\n", err)
fmt.Printf("%+v\n", err)
return
}
}
/*
Output:
data not found, bar failed: foo failed: sql: no rows in result set
sql: no rows in result set
foo failed
main.foo
/usr/three/main.go:11
main.bar
/usr/three/main.go:15
main.main
/usr/three/main.go:19
runtime.main
...
*/
๊ฐ๋จํ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
errors.Wrap(err, message)
: ํธ์ถ ์คํ ์ถ๊ฐerrors.WithMessage(err, message)
: ํธ์ถ ์คํ์ ์ถ๊ฐํ์ง ์๊ณ message๋ง ์ถ๊ฐ
์ด๋ ๊ฒ ๋ํ๋ err๋ ์์ ํจ์์์ ๋ค์๊ณผ ๊ฐ์ ํ์์ผ๋ก ๋ก๊ทธ๋ฅผ ์ถ๋ ฅํ ์ ์์ต๋๋ค.
%v
: ํธ์ถ ์คํ์ ์์๋๋ก ๋ชจ๋ context ํ ์คํธ๋ฅผ ํฌํจํ๋ ํ ์ค ๋ฌธ์์ด%+v
: ์ ์ฒด ํธ์ถ ์คํ
2. fmt.Errorf(โ%wโ, err)
Go 1.13 ์ด์ ๋ฒ์ ์์๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ error ๋ํ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์ด, ํธ์ถ ์คํ์ด ํ์ ์๋ ๊ฒฝ์ฐ์๋ ๋ค์๊ณผ ๊ฐ์ด ๊ฐํธํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
func foo() error {
return errors.New("foo error!!")
}
func bar() error {
return fmt.Errorf("%s, %w", "bar", foo())
}
func main() {
err := fmt.Errorf("%s, %w", "main!!", bar())
fmt.Printf("%+v", err)
}
string formatting ๋ฐฉ์๊ณผ ๋น์ทํ๋ฉฐ, %w
๋ฅผ ์ฌ์ฉํ์ฌ message๊ฐ ์๋๋ผ error๋ฅผ ์ง์ ๋ํํ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ %+v
๋ก err๋ฅผ ์ถ๋ ฅํ๋ฉด, ๋ํ๋ error๋ค์ด ์ถ๋ ฅ๋ฉ๋๋ค.
main!!, bar, foo error!!
๊ณ ๋ฏผ
Golang์ Java๋ Python์ฒ๋ผ ๋ง์ ์ฌ๋๋ค์ด ์ฌ์ฉํ๋ ์ธ์ด๊ฐ ์๋์ด์, ์ ๋ฅผ ๋น๋กฏํ์ฌ ์ ํฌ ํ์ ํฉ๋ฅํ์ ๋ถ๋ค ์ค์์๋ Golang์ ์ฒ์ ์ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์์ต๋๋ค. ์ฒ์ ์ ํ๊ณ ๊ฐ๋ฐํ๋ค ๋ณด๋ฉด error ์ฒ๋ฆฌ์์ ์ด๋ ค์์ ๋ง์ด ๊ฒช์ต๋๋ค. ๊ทธ๋์ ์ ๋ error๋ฅผ ์ง๊ด์ ์ด๊ณ ํธํ๊ฒ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ง๋ค๊ณ ์ถ์์ต๋๋ค.
1๋ฒ ๋ฐฉ์์ errors.Wrap()
์ ๋ถ๋ช
ํ ํธํ์ต๋๋ค. ๊ทธ๋ฌ๋ ๋ฌด์์ Wrap()
์ ์คํํ๋ค ๋ณด๋ฉด ํธ์ถ ์คํ์ด ์ฌ๊ท์ ์ผ๋ก ์์ด๋ ๋ฌธ์ ๊ฐ ์์์ต๋๋ค.
๋ฌธ์ ์ฝ๋ ์
func foo() error {
return errors.New("foo Error!")
}
func bar() error {
return errors.Wrap(foo(), "bar message")
}
func main() {
err := errors.Wrap(bar(), "")
fmt.Printf("%+v\n", err)
}
/*
foo Error!
main.foo
main.go:15
main.bar
main.go:18
main.main
main.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1594
bar message
main.bar
main.go:18
main.main
main.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1594
main.main
main.go:22
runtime.main
/usr/local/go-faketime/src/runtime/proc.go:250
runtime.goexit
/usr/local/go-faketime/src/runtime/asm_amd64.s:1594
*/
์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Wrap()
์ ์ตํ์ ํจ์์์ 1๋ฒ๋ง ์ฌ์ฉํ๊ณ ๋๋จธ์ง์์๋ WithMessage()
๋ฅผ ์ฌ์ฉํ๋๋ก ๊ท์น์ ์ ํ๋ฉด ์ด๋จ์ง ์๊ฐํด๋ณด์์ต๋๋ค.
์ฌ๋ฌ ๋ช
์ด ๊ฐ๋ฐํ๊ณ ์๋ก์ด ์ฌ๋์ด ๋ณด๋๋ผ๋ ์ง๊ด์ ์ผ๋ก ์ดํดํ ์ ์์์ง ๊ณ ๋ฏผํด๋ณด์์ ๋, ๊ทธ์ ๋ํ ๋๋ต์ '์๋ ๊ฒ ๊ฐ๋ค'์์ต๋๋ค. ๋งค๋ฒ Wrap์ด ๋์๋์ง ํ์ ํจ์๋ก ๋ด๋ ค๊ฐ ํ์ธํ๋ ๋ฐ ์๊ฐ์ด ์๋ชจ๋๊ณ , ์ถ๊ฐ message๋ฅผ ์ ๊ณ ์ถ์ง ์๋๋ผ๋ ์ค๋ณต ์คํ์ ์์ง ์๊ธฐ ์ํด WithMessage()
์ ๋น ๋ฌธ์์ด(""
)์ด๋ผ๋ ๋ฃ์ด์ผ ํ๋ ๋ถํธํจ์ด ์์์ต๋๋ค. ๊ฒฐ๊ตญ ์ฌ์ฉํ๋ ์ฌ๋์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ์ดํด๊ฐ ์ ํ๋์ด์ผ ํ๊ณ , ์ด๋ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ ๋ฐฉํฅ์ด ์๋๋ผ๊ณ ์๊ฐํ์ต๋๋ค.
๋ฐ๋ฉด 2๋ฒ์ ๋ฐฉ์์ ํธ์ถ ์คํ์ด ์๋ค๋ ๋จ์ ์ด ์์์ต๋๋ค. ๊ทธ๋ฌ๋ formatting ๋ฐฉ์์ผ๋ก error๊ฐ ๋ํ๋๊ณ , message๋ ์ฝ๊ฒ ์ถ๊ฐํ ์ ์์๊ณ , ํธ์ถ ์คํ์ ์ง์ ๋ฃ์ด๋ณผ ์๋ ์๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค. ์ด๋ ๊ฒ ๊ฐ๋ฐ๋ง ๋๋ค๋ฉด 1๋ฒ๊ณผ ๊ฐ์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ํ ์ดํด๊ฐ ์๋๋ผ๋ ์ฝ๊ฒ ์ดํดํ ์ ์๋๋ก ๋ง๋ค ์ ์์ ๊ฒ ๊ฐ์์ต๋๋ค.
๊ทธ๋์ ์ ๋ 2๋ฒ์ ๋ฐฉ์์ ํํ๊ณ , ์ง์ ์คํ ํธ๋ ์ด์ค๋ฅผ ๊ฐ๋ฐํด๋ณด๊ธฐ๋ก ํ์ต๋๋ค.(์ง์ ๋ง๋ค์ด ์ฐ๋ ๊ฒ Golang์ด์ฃ !)
๊ฐ๋ฐ ๊ณผ์
์ฐ์ ์คํ ํธ๋ ์ด์ค์ ์ด๋ค ๋ด์ฉ์ด ํ์ํ ์ง ์๊ฐํด๋ณด์์ต๋๋ค.
- ํจ์์ ์์น ๋ฐ ์ด๋ฆ
- ์ถ๊ฐ message ๋ฐ ์ด์ error ๋ด์ฉ
์ 2๊ฐ์ง๊ฐ ํ์ํ๋ค๊ณ ํ๋จํ๊ณ ์ด๋ฅผ ์ง์ ํ๋์ฉ ๊ตฌํํด ๋๊ฐ์ต๋๋ค.
์์น ๋ฐ ์ด๋ฆ
// {filename:line} [func name]
var funcInfoFormat = "{%s:%d} [%s]"
func getFuncInfo() string {
pc, file, line, _ := runtime.Caller(1)
f := runtime.FuncForPC(pc)
if f == nil {
return fmt.Sprintf(funcInfoFormat, file, line, "unknwon")
}
return fmt.Sprintf(funcInfoFormat, file, line, f.Name())
}
runtime.Caller(skip int)
ํจ์๋ฅผ ํตํ์ฌ, ํ์ฌ ํธ์ถ๋๋ ๊ณณ์์ skip
๋งํผ ์์ ํธ์ถ ์คํ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค. ์ ๊ณตํ๋ ๊ฐ์ pc
(ํ๋ก๊ทธ๋จ ์นด์ดํฐ), file
(ํจ์๊ฐ ์์นํ ํ์ผ ์ด๋ฆ), line
(ํ์ผ ์์ ํจ์ ์์น)์
๋๋ค.
runtime.FuncForPC(pc int)
ํจ์๋ก, ์์์ ์ป์ ์์ ํจ์์ pc
์ ๋ณด๋ฅผ ๋๊ฒจ, ํด๋น ํจ์์ ์ ๋ณด๋ฅผ ํ๋ํ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ง์ง๋ง์ผ๋ก f.Name()
๋ฉ์๋๋ก ํจํค์ง์ด๋ฆ.ํจ์์ด๋ฆ
ํ์์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
์ฌ์ฉ ์
package main
func main() {
hi()
}
func hi() {
fmt.Println(getFuncInfo())
}
/*
{main.go:28} [main.hi]
*/
์ถ๊ฐ message ๋ฐ ์ด์ error ๋ด์ฉ
error๊ฐ ๋ฐ์ํ๋ฉด ์ด๋ค ์ฒ๋ฆฌ๊ฐ ํ์ํ ์ง ์๊ฐํด๋ณด์์ต๋๋ค.
- ๋จ์ํ error๋ฅผ ์์๋ก ์ฌ๋ฆฐ๋ค.
- ๊ธฐ์กด error์ ์ถ๊ฐ message๋ฅผ ๋ด์์ ์์๋ก ์ฌ๋ฆฐ๋ค.
์ 2๊ฐ์ง ๊ฒฝ์ฐ๊ฐ ์์๊ณ , ์ด์ ๋ฐ๋ผ ๋จ์ํ ์ด์ error๋ฅผ ๋ํํ๋ ํจ์์ ์ถ๊ฐ message๋ฅผ ํจ๊ป ๋ํํ๋ ํจ์, ๋ ๊ฐ์ ํจ์๋ฅผ ๋ง๋ค์์ต๋๋ค.
var wrapFormat = "%s\n%w" // "{file:line} [func name] msg \n error"
func Wrap(err error) error {
pc, file, line, ok := runtime.Caller(1)
if !ok {
return fmt.Errorf(wrapFormat, "", err)
}
// {file:line} [funcName] msg
stack := fmt.Sprintf("%s %s", getFuncInfo(pc, file, line), "")
return fmt.Errorf(wrapFormat, stack, err)
}
func WrapWithMessage(err error, msg string) error {
pc, file, line, ok := runtime.Caller(1)
if !ok {
return fmt.Errorf(wrapFormat, msg, err)
}
stack := fmt.Sprintf("%s %s", getFuncInfo(pc, file, line), msg)
return fmt.Errorf(wrapFormat, stack, err)
}
runtime.Caller
์ skip ์กฐ์ ์ด ์ค์ํ๊ธฐ์ getFuncInfo()
๋ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ๋๋ก ์กฐ์ ํ๊ณ , message \n error
์ ํ์์ผ๋ก ๋ํํ์ฌ ๋ก๊น
์ ์คํ์ ์๋จ์์๋ถํฐ error๋ฅผ ์ถ๋ ฅํ๋๋ก ํ์ต๋๋ค.
์์
์ ๋ง์น ํ ์ฝ๋๋ฅผ ๋ค์ ํ์ธํด๋ณด๋ ์ค๋ณต๋๋ ๋ถ๋ถ์ด ์๋นํ ๋ง์์ต๋๋ค. ์ค๋ณต ์ฝ๋๋ฅผ ํจ์๋ก ์ถ์ถํ๊ณ skip
์ ํ ๋ฒ ๋ ์กฐ์ ํ์ฌ ๊ณตํตํํ์ต๋๋ค.
func wrap(err error, msg string) error {
pc, file, line, ok := runtime.Caller(2)
if !ok {
return fmt.Errorf(wrapFormat, msg, err)
}
// {file:line} [funcName] msg
stack := fmt.Sprintf("%s %s", getFuncInfo(pc, file, line), msg)
return fmt.Errorf(wrapFormat, stack, err)
}
์์ ๊ฐ์ด skip์ ์กฐ์ ํ๊ณ ๊ณตํตํํ ์ ์๋ ์์ญ์ ์ถ์ถํ๊ณ , ์ถ๊ฐ message๊ฐ ์๋ ํจ์์ ์๋ ํจ์๋ฅผ ๋ง๋ค์์ต๋๋ค.
func WrapWithMessage(err error, msg string) error {
return wrap(err, msg)
}
func Wrap(err error) error {
return wrap(err, "")
}
์ด๋ ๊ฒ ๋ง๋ ํจ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์์ต๋๋ค.
func main() {
if err := foo(); err != nil {
err = Wrap(err) // ์ถ๊ฐ message๊ฐ ํ์ ์์ ๋
log.Errorf("%+v\n", err)
}
}
func foo() error {
if err := bar(); err != nil { // ํ์ ํจ์์์ error ๋ฐ์
return WrapWithMessage(err, "foo message") // ์ถ๊ฐ message๊ฐ ํ์ํ ๋
}
return nil
}
func bar() error {
return errors.New("bar Error!")
}
{main.go:24} [main.main]
{main.go:17} [main.foo] foo message
bar Error!
๋ง์น๋ฉฐ
์ฌ๊ธฐ๊น์ง DB ๋ ์ด์ด ๊ณตํตํ ๋ฐ error ๋ถ๋ฅ, error ๋ํ๊น์ง ์๋ฃํ์ต๋๋ค.
๊ธฐ์กด V1 ์ฝ๋์์ error๋ฅผ ์์๋ก ๋ณด๋ผ ๋๋ง๋ค ๋งค๋ฒ ๋ก๊น
์ ํ๋ ๋ถํธํจ์ ์์ ๊ณ , ๋จ์ํ Wrap(err)
๋ง์ผ๋ก ํธ์ถ ์คํ์ ์ถ๊ฐํ์ฌ ๋ณด๋ผ ์ ์๊ฒ ๋์์ต๋๋ค.
๋ค์์ผ๋ก๋ ์ด๋ ๊ฒ ์์์ฌ๋ฆฐ error๋ฅผ ์์์์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง์ ๋ํด ์ด์ผ๊ธฐํด๋ณด๋ ค ํฉ๋๋ค.