Golang利用Recover進行錯誤處理
Golang 中的 recover
是一個鮮為人知但非常有趣和強大的功能。讓我們看看它是如何工作的,以及在 Outreach.io 中如何利用它來處理 Kubernetes 中的錯誤。
Panic/Defer/Recover 基本上是 Golang 中對于其他編程語言中 throw/finally/catch 概念的替代品。它們有一些共同之處,但在一些重要細(xì)節(jié)上有所不同。
Defer
要充分理解 recover
,我們首先需要談?wù)?nbsp;defer
語句。defer
關(guān)鍵字前置于函數(shù)調(diào)用之前,使得該調(diào)用在當(dāng)前函數(shù)返回之前執(zhí)行。當(dāng)我們在一個函數(shù)中使用多個 defer
語句時,它們按照后進先出的順序執(zhí)行,這使得創(chuàng)建清理邏輯變得非常容易,如下例所示:
package main import ( "context" "database/sql" "fmt" ) func readRecords(ctx context.Context) error { db, err := sql.Open("sqlite3", "file:test.db?cache=shared&mode=memory") if err != nil { return err } defer db.Close() // 這個函數(shù)調(diào)用將在 readRecords 函數(shù)返回時第三個執(zhí)行 conn, err := db.Conn(ctx) if err != nil { return err } defer conn.Close() // 這個函數(shù)調(diào)用將在第二個執(zhí)行 rows, err := conn.QueryContext(ctx, "SELECT id FROM users") if err != nil { return err } defer rows.Close() // 這個函數(shù)調(diào)用將在第一個執(zhí)行 for rows.Next() { var id int64 if err := rows.Scan(&id); err != nil { return err } fmt.Println("ID:", id) } return nil } func main() { readRecords(context.Background()) }
Panic
我們需要談?wù)摰牡诙€主題是 panic
,它是一個導(dǎo)致當(dāng)前 goroutine 進入 panic 模式的函數(shù)。當(dāng)前函數(shù)中的正常執(zhí)行流程被停止,僅執(zhí)行 defer
語句,然后對調(diào)用者函數(shù)執(zhí)行相同的操作,因此一直冒泡到堆棧的頂部(main 函數(shù)),然后使程序崩潰。panic
可以直接調(diào)用(傳遞一個值作為參數(shù)),也可以由運行時錯誤引起。例如,由于空指針解引用:
package main import "fmt" func main() { var x *string fmt.Println(*x) } // panic: runtime error: invalid memory address or nil pointer dereference
Recover
recover
是一個內(nèi)建函數(shù),它使我們有可能在發(fā)生 panic 時重新獲得控制。它僅在被調(diào)用的延遲函數(shù)中產(chǎn)生效果。在延遲函數(shù)之外調(diào)用時,它總是返回 nil
。如果我們處于 panic 模式,調(diào)用 recover
會返回傳遞給 panic
函數(shù)的值?;臼纠?/p>
package main import "fmt" func main() { defer func() { if r := recover(); r != nil { fmt.Printf("Recovered: %v\\n", r) } }() panic("spam, egg, sausage, and spam") } // Recovered: spam, egg, sausage, and spam
我們可以以同樣的方式從運行時錯誤中恢復(fù):
package main import "fmt" func main() { defer func() { if r := recover(); r != nil { fmt.Printf("Recovered: %v\\n", r) } }() var x *string fmt.Println(*x) } // Recovered: runtime error: invalid memory address or nil pointer dereference
在這種情況下,recover
返回的值的類型是錯誤(更準(zhǔn)確地說是 runtime.errorString
)。
有一個限制:我們不能直接從 recover
塊中返回值,因為在 recover
塊中的 return
語句僅從延遲函數(shù)中返回,而不是從周圍的函數(shù)中返回:
package main import "fmt" func foo() int { defer func() { if r := recover(); r != nil { fmt.Printf("Recovered: %v\\n", r) return 1 // "too many return values" 因為我們僅從匿名函數(shù)返回 } }() panic("spam, egg, sausage, and spam") } func main() { x := foo() fmt.Println(x) }
如果我們想要更改函數(shù)返回的值,我們需要使用命名返回值:
package main import "fmt" func foo() (ret int) { defer func() { if r := recover(); r != nil { fmt.Printf("Recovered: %v\\n", r) ret = 1 } }() panic("spam, egg, sausage, and spam") } func main() { x := foo() fmt.Println("value:", x) } // Recovered: spam, egg, sausage, and spam // value: 1
一個更實際的例子,將 panic 轉(zhuǎn)換為普通錯誤的轉(zhuǎn)換可能如下所示:
package main import ( "fmt" "github.com/google/uuid" ) // processInput 嘗試將輸入字符串轉(zhuǎn)換為 uuid.UUID // 它將 panic 轉(zhuǎn)換為錯誤 func processInput(input string) (u uuid.UUID, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("panic: %v", r) } }() // 一些可能引發(fā) panic 的邏輯(也可以是第三方邏輯),例如: u = uuid.MustParse(input) return u, nil } func main() { u, err := processInput("xxx") if err != nil { fmt.Println(err) } fmt.Println(u) } // panic: uuid: Parse(xxx): invalid UUID length: 3 // 00000000-0000-0000-0000-000000000000
現(xiàn)在讓我們嘗試一些稍微
復(fù)雜的東西。假設(shè)我們在 Kubernetes 中運行,并且我們想要編寫一個通用的 recover
函數(shù),處理所有未捕獲的 panic 和運行時錯誤,并收集它們的堆棧跟蹤,以便我們可以以結(jié)構(gòu)化的方式記錄它們(例如,以 JSON 格式)。
package main import ( "fmt" "log" "os" "github.com/pkg/errors" ) func foo() string { var s *string return *s } func handlePanic(r interface{}) error { var errWithStack error if err, ok := r.(error); ok { errWithStack = errors.WithStack(err) } else { errWithStack = errors.Errorf("%+v", r) } return errWithStack } func main() { logger := log.New(os.Stdout, "", 0) defer func() { if r := recover(); r != nil { err := handlePanic(r) logger.Println( "panic occurred", "msg", err.Error(), "stack", fmt.Sprintf("%+v", err), ) } }() fmt.Println(foo()) } // 輸出: // panic occurred msg: runtime error: invalid memory address or nil pointer dereference // stack: runtime error: invalid memory address or nil pointer dereference // main.handlePanic // /tmp/sandbox239055659/prog.go:19 // main.main.func1...
recover
函數(shù)并不是 Golang 開發(fā)者的日常必備工具,但正如你所看到的,它在某些情況下非常有用。
到此這篇關(guān)于Golang利用Recover進行錯誤處理的文章就介紹到這了,更多相關(guān)go Recover錯誤處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在golang xorm中使用postgresql的json,array類型的操作
這篇文章主要介紹了在golang xorm中使用postgresql的json,array類型的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04Go結(jié)構(gòu)體從基礎(chǔ)到應(yīng)用深度探索
本文深入探討了結(jié)構(gòu)體的定義、類型、字面量表示和使用方法,旨在為讀者呈現(xiàn)Go結(jié)構(gòu)體的全面視角,通過結(jié)構(gòu)體,開發(fā)者可以實現(xiàn)更加模塊化、高效的代碼設(shè)計,這篇文章旨在為您提供關(guān)于結(jié)構(gòu)體的深入理解,助您更好地利用Go語言的強大功能2023-10-10