iT邦幫忙

2021 iThome 鐵人賽

DAY 10
1
Modern Web

Let's Go! 解剖Go server開發到部署的過程系列 第 10

day 10 - 千萬不要放過error

在Go的世界裡面, 如果error沒接好, 服務就會直接panic了。panic發生在k8s環境中, k8s會幫忙重啟, 但如果是在機房的環境底下, 又沒有重啟的機制..., 你準備好接電話了嗎?/images/emoticon/emoticon37.gif

每個服務都要處理error, 包含套件會發生的Scylla error, Redis error, 程式內的資料與邏輯驗證error, 以及其他統稱為Server error 的錯誤....等, 這些都需要被收集起來回覆給Client端或是留下紀錄debug。

如果把error的處理各自散落在程式碼各處一樣會有維護性差的狀況, 所以可以把Error收在一起, 既能統一輸出格式也方便api 接收端排除問題和處理顯示格式, 對照錯誤查找也快。

這邊粗略寫一下我們平常使用的error內容, 結構裡的CodeMsg用來快速判斷錯誤屬於哪一類, ExtraInfo則是用來說明錯誤發生當下使用的相關資訊, Time負責記錄當下時間, Service用來區分是哪一個服務送出來的錯誤, OriginError是紀錄原始的錯誤訊息。

lib/error
├── error.go
└── error_test.go
package coconutError

import (
	"fmt"
	"time"

	coconutLog "github.com/evelynocean/coconut/lib/log"
)

type Error struct {
	Msg         string                 `json:"msg"`
	Code        int                    `json:"code"`
	ExtraInfo   map[string]interface{} `json:"extrainfo"`
	Time        int64                  `json:"time"`
	Service     string                 `json:"service"`
	OriginError string                 `json:"origin_error"`
}

var (
	Logger *coconutLog.Logger
	// ErrServer 系統錯誤
	ErrServer = &Error{Code: 9999, Msg: "ERROR_SERVER", ExtraInfo: make(map[string]interface{})}
	// ErrRedis redis 相關
	ErrRedis = &Error{Code: 9998, Msg: "ERROR_REDIS", ExtraInfo: make(map[string]interface{})}
)

func init() {
	Logger = coconutLog.New()
}

// ParseError error 輸出前加工
func ParseError(e *Error, err error) *Error {
	e.Service = "Coconut"
	e.Time = time.Now().Unix()
	e.OriginError = err.Error()

	logMsg := map[string]interface{}{
		"service":      "Coconut",
		"time":         time.Now().Unix(),
		"origin_error": err.Error(),
		"code":         e.Code,
		"msg":          e.Msg,
		"extra_info":   e.ExtraInfo,
	}
	Logger.WithFields(logMsg).Errorf(e.Msg)

	return e
}

func (t Error) Error() string {
	return fmt.Sprintf("[Error %d] %s", t.Code, t.Msg)
}

針對error 的部分寫 go test驗證是否符合預期

package coconutError

import (
	"encoding/json"
	"errors"
	"fmt"
	"testing"
)

func TestError(t *testing.T) {
	e := ErrServer
	e.ExtraInfo = map[string]interface{}{
		"aa": 1,
	}

	testError := ParseError(e, errors.New("測試錯誤格式"))
	str, err := json.Marshal(testError)
	if err != nil {
		t.Errorf("json.Marshal error: %s", err)
	}
	fmt.Println(string(str))
}

go test 執行狀況, error log輸出時有轉成JSON的格式, 方便之後的Log資料搜集

-> % go test
{"code":9999,"extra_info":{"aa":1},"fields.msg":"ERROR_SERVER","fields.time":1631846776,"level":"error","msg":"ERROR_SERVER","origin_error":"測試錯誤格式","service":"Coconut","time":"2021-09-17T10:46:16+08:00"}
{"msg":"ERROR_SERVER","code":9999,"extrainfo":{"aa":1},"time":1631846776,"service":"Coconut","origin_error":"測試錯誤格式"}
PASS
ok      github.com/evelynocean/coconut/lib/error        0.576s

上一篇
day 9 - 小範圍開發 & go test
下一篇
day 11 - log服務想說的話
系列文
Let's Go! 解剖Go server開發到部署的過程30

尚未有邦友留言

立即登入留言