Go 錯誤堆疊資訊之 CockroachDB errors 庫

邱浩騫發表於2020-12-31


1. Go 標準庫 errors 太簡單

由 fmt.Errorf() 構造的 Go 中最常見的“簡單”錯誤物件類似於帶有錯誤介面的包含在結構中的字串:其 Error() 方法返回構造錯誤時設定的字串。

// Go 標準庫 errors
// 使用 fmt.Errorf() 構造 Go 中最常見的“簡單”錯誤物件類似於帶有錯誤介面的包含在結構中的字串:列印錯誤物件只會顯示該字串。
// 使用 Go 的錯誤包 errors 的建構函式構建錯誤 errors.New() 結果一樣。

func testSimpleCustomError() {
	err := fmt.Errorf("fmt.Errorf")
	fmt.Println(err) // "hello"

	err = errors.New("errors.New")
	fmt.Println(err) // "errors.New"
}

什麼都沒有,僅此而已。列印錯誤物件也會顯示該字串。順便說一句,使用 Go 的錯誤包 errors 的建構函式構建錯誤 errors.New() 結果一樣。



2. CockroachDB錯誤處理庫

使用 Dave Cheney 的錯誤庫[1],或者甚至更好的 CockroachDB 錯誤庫[2](通過導github.com/cockroachdb/errors),則簡單錯誤也會在構造錯誤時自動捕獲堆疊跟蹤。僅當詳細列印錯誤時才顯示堆疊跟蹤。這樣可以更輕鬆地排除錯誤的來源:


2.1 CockroachDB 新建堆疊錯誤

使用 Dave Cheney 的錯誤庫[1],或者甚至更好的 CockroachDB 錯誤庫[2](通過匯入 ithub.com/cockroachdb/errors)則簡單錯誤也會在構造錯誤時自動捕獲堆疊跟蹤。

建議使用CockroachDB庫的以下方法代替GO的標準錯誤處理庫:

errors.New():直接替換 Go 標準庫的 errors.New(),但它會帶有堆疊跟蹤;
errors.Errorf() 或 errors.Newf():用堆疊跟蹤的方式替換 Go 標準庫的 fmt.Errorf();

func testCockroachdbError() {

	err := cockroachdb_errors.New("cockroachdb_errors.New")
	fmt.Println("只會顯示錯誤字串:", err)

	fmt.Println("顯示錯誤堆疊資訊:", err)
	fmt.Printf("%+v \n", err) // 推薦
}

在這裡插入圖片描述

2.2 CockroachDB 新增錯誤上下文字首

當從多個位置呼叫相同的邏輯,並且可能因錯誤而失敗時,則希望將訊息字首新增到任何返回的錯誤物件。

這有助於提供有關“錯誤發生的位置”的更多上下文,以便在執行時出現錯誤時(何時出現錯誤),可以清楚地瞭解哪個程式碼路徑產生了錯誤。

在 channel 的場景出現錯誤是特別有用。

func foo() error { return errors.New("boo") }

func bar() error {
	if err := foo(); err != nil {
		return cockroachdb_errors.Wrap(err, "bar")
	}
	return nil
}

func baz() error {
	// 當提供 nil 錯誤作為輸入時,errors.Wrap() 返回nil。這使我們可以消除 if err != nil 條件。
	return cockroachdb_errors.Wrap(foo(), "baz")
}

func testCockroachdbErrorWrap() {
	err1 := bar()
	fmt.Printf("%+v \n", err1)
	err2 := baz()
	fmt.Printf("%+v \n", err2)
}

在這裡插入圖片描述

1.3. CockroachDB 次要錯誤

如果在處理錯誤時遇到錯誤,該怎麼辦?

我們希望以某種方式返回有關這兩個錯誤的詳細資訊,以幫助進行故障排除。
同時,出於原因分析的目的,我們要謹慎地將遇到的第一個錯誤保留為 “主要” 錯誤。

次要錯誤註解不會影響主要錯誤上返回的文字,程式碼的行為就像僅發生了主要錯誤一樣。但是,在詳細列印過程中會顯示第二個錯誤的資訊;

func testCockroachdbSecondaryError() {
	err := errors.New("主要錯誤")
	err = cockroachdb_errors.Wrap(err, "主要資訊字首")
	err = cockroachdb_errors.WithSecondaryError(err, cockroachdb_errors.New("次要錯誤"))

	fmt.Println(err) // 只列印 "主要錯誤"

	fmt.Printf("%+v \n", err) // 列印只要錯誤和次要錯誤的堆疊資訊
}

在這裡插入圖片描述



總結

Go 庫通過 Go 自己的錯誤包中的 fmt.Errorf() 和 errors.New() 提供了錯誤介面的簡化實現。

改用 CockroachDB 錯誤庫,代替 Go 的錯誤包和 Dave Cheney的 pkg/errors,可以獲得更好的體驗。

它的錯誤建構函式 errors.New()/errors.Newf()(別名為 errors.Errorf())自動在錯誤物件中包含堆疊跟蹤,可以使用 fmt.Printf("%+v" ,err) 列印堆疊追蹤。

它還提供了錯誤包裝器的詞彙表。最常見的是帶有 errors.Wrap()/errors.Wrapf() 的訊息字首註釋,用於註釋從多個位置呼叫的函式的呼叫路徑。這還包括幕後的堆疊跟蹤。

另一個常見的包裝器解決了在處理另一個錯誤時遇到錯誤時如何在 Go 中執行的令人困惑的問題:使用輔助原因註解,並使用 errors.WithSecondaryCause() 或 errors.CombineErrors() 附加,Go 程式碼可以保留兩個錯誤,因此程式設計師在故障排除期間可以同時看到兩者。

CockroachDB 錯誤庫中的錯誤還提供了一致的行為,並且在詳細格式化錯誤時提供了有用的顯示結構,從而避免了巨大的 Go 錯誤列印災難。

筆記原始碼: https://github.com/qiuyunzhao/go_basis/blob/master/12_%E9%94%99%E8%AF%AF%E5%A4%84%E7%90%86/02_%E8%87%AA%E5%AE%9A%E4%B9%89%E9%94%99%E8%AF%AF%26%E9%94%99%E8%AF%AF%E5%A0%86%E6%A0%88/main.go

相關文章