-
-
Save alsritter/33524c0f41777c327efd97d79c8f0692 to your computer and use it in GitHub Desktop.
Go 模拟 try-catch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// error/custom_error.go | |
package my_error | |
import ( | |
"encoding/json" | |
"fmt" | |
"runtime" | |
"strings" | |
) | |
// StError 定义一个标准的错误接口 | |
type StError interface { | |
Error() string | |
GetMsg() string | |
GetLevel() int | |
} | |
const ( | |
LevelFatal = 1 | |
LevelWarn = 2 | |
skipLogCaller = 2 | |
skipNewTradeErrCaller = 3 | |
) | |
// levelMap 错误等级日志字符串map | |
//"level": levelMap[e.level] | |
var levelMap = map[int]string{ | |
LevelFatal: "Fatal", | |
LevelWarn: "Warn", | |
} | |
func NewFatalErr(msg string, originErrList ...error) StError { | |
err := newStError(LevelFatal, msg, originErrList...) | |
err.genCallerInfo(skipNewTradeErrCaller) | |
return err | |
} | |
func NewWarnErr(msg string, originErrList ...error) StError { | |
err := newStError(LevelWarn, msg, originErrList...) | |
err.genCallerInfo(skipNewTradeErrCaller) | |
return err | |
} | |
func newStError(level int, msg string, originErrList ...error) *customError { | |
tradeErr := &customError{ | |
level: level, | |
msg: msg, | |
} | |
tradeErr.initLogMsgAndOriginErr(originErrList...) | |
return tradeErr | |
} | |
// initLogMsgAndOriginErr 初始化 msg 和 OriginErr | |
func (e *customError) initLogMsgAndOriginErr(originErrList ...error) { | |
// 把传入的异常添加进 OriginErr 异常链 | |
for _, originErr := range originErrList { | |
err, ok := originErr.(*customError) | |
if ok { | |
e.originErrS = append(e.originErrS, err) | |
} else { | |
e.msg += originErr.Error() | |
} | |
} | |
} | |
type customError struct { | |
level int | |
msg string | |
callerLogMsgS []string //[]callerLogMsg | |
callerStackS []string //[]funcName | |
callerStackMap map[string]string //map[funcName]logMsg | |
originErrS []*customError | |
} | |
func (e *customError) GetMsg() string { | |
return e.msg | |
} | |
func (e *customError) GetLevel() int { | |
return e.level | |
} | |
func (e *customError) Error() string { | |
// 取得当前方法都上一层方法名以及该方法的位置(log) | |
funName, logStr := getCallerFuncNameAndLogStr(skipLogCaller) | |
// 根据这个方法名生成调用链日志 | |
e.genCallerLogStr(funName, logStr) | |
list := e.getLogStrMapList() | |
bytes, _ := json.Marshal(list) | |
return string(bytes) | |
} | |
// genCallerInfo 生成调用栈的相关信息属性填充 callerStackMap 和 callerStackS | |
func (e *customError) genCallerInfo(skip int) { | |
if len(e.originErrS) == 0 { | |
e.callerStackMap = make(map[string]string) | |
// 取出调用栈50个数据 | |
pcSlice := make([]uintptr, 50) | |
count := runtime.Callers(skip, pcSlice) | |
pcSlice = pcSlice[:count] | |
frames := runtime.CallersFrames(pcSlice) | |
var frame runtime.Frame | |
more := count > 0 | |
for more { | |
frame, more = frames.Next() | |
if frame.Function == "RecoverToError" || frame.Function == "PanicError" { // 跳过 | |
continue | |
} | |
e.callerStackS = append(e.callerStackS, frame.Function) | |
e.callerStackMap[frame.Function] = formatCallerLogStr(frame.Function, frame.Line) | |
} | |
} | |
} | |
// getLogStrMapList 递归获取 logStrMap | |
func (e *customError) getLogStrMapList() []map[string]interface{} { | |
var logMapList []map[string]interface{} | |
logMapList = append(logMapList, e.getLogStrMap()) | |
for _, originErr := range e.originErrS { | |
list := originErr.getLogStrMapList() | |
logMapList = append(logMapList, list...) | |
} | |
return logMapList | |
} | |
// getLogStrMap 取得当前层的调用信息 | |
func (e *customError) getLogStrMap() map[string]interface{} { | |
caller := "" | |
for _, name := range e.callerLogMsgS { | |
if strings.Contains(name, "RecoverToError") { | |
continue | |
} | |
caller += name + " | " | |
} | |
return map[string]interface{}{ | |
"level": levelMap[e.level], | |
"caller": caller, | |
"msg": e.msg, | |
} | |
} | |
// genCallerLogStr 递归把 callerStackS 里面的调用链添加到 callerLogMsgS 里面(因为 originErrS 是一个链) | |
func (e *customError) genCallerLogStr(endFuncName, endLogStr string) { | |
if endFuncName == "" || endLogStr == "" { | |
return | |
} | |
// 检查调用栈是否还存在信息 | |
if len(e.callerStackS) != 0 && len(e.callerStackMap) != 0 { | |
var callerLogJoinSlice, endCallerFuncSlice []string | |
// 找到产生异常的那个方法的下标 | |
for index, funcName := range e.callerStackS { | |
if funcName == endFuncName { | |
// 取得异常方法后面的调用方法 | |
endCallerFuncSlice = e.callerStackS[:index] | |
break | |
} | |
} | |
// 把异常方法对应的日志添加进来 | |
if len(endCallerFuncSlice) >= 0 { | |
callerLogJoinSlice = append(callerLogJoinSlice, endLogStr) | |
for i := len(endCallerFuncSlice) - 1; i >= 0; i-- { | |
callerLogJoinSlice = append(callerLogJoinSlice, e.callerStackMap[endCallerFuncSlice[i]]) | |
} | |
// 设置 callerLogMsgS 为新的调用链 | |
e.callerLogMsgS = callerLogJoinSlice | |
} | |
} | |
for _, err := range e.originErrS { | |
err.genCallerLogStr(endFuncName, endLogStr) | |
} | |
} | |
// getCallerFuncNameAndLogStr 获取调用者的方法名和格式化的日志字符串 | |
func getCallerFuncNameAndLogStr(skip int) (funcName, logStr string) { | |
pc, _, line, ok := runtime.Caller(skip) // skip 是打印上一层调用的函数 | |
if !ok { | |
return "", "" | |
} | |
funcName = runtime.FuncForPC(pc).Name() | |
return funcName, formatCallerLogStr(funcName, line) | |
} | |
// formatCallerLogStr 格式化调用链日志字符串 | |
func formatCallerLogStr(funcName string, line int) string { | |
return fmt.Sprintf("%s: %d ", funcName, line) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// error/handler_error.go | |
package my_error | |
import "fmt" | |
// RecoverToError 捕获执行 f 方法的异常并转成 StError(类似 Java 中的 catch 的过程) | |
func RecoverToError(f func()) (retErr StError) { // 这里通过具名返回值的方式传递这个 retErr | |
defer func() { | |
panicErr := recover() | |
if panicErr != nil { | |
if err, ok := panicErr.(StError); ok { | |
retErr = err | |
} else { // 非 StError 类型的错误 | |
retErr = NewFatalErr(fmt.Sprint(panicErr)) | |
} | |
} | |
}() | |
f() | |
return nil | |
} | |
// PanicError 向上抛异常(类似 Java 中的 throw 的过程) | |
func PanicError(err StError) { | |
if err != nil { | |
panic(err) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
my_error "sterror/error" | |
) | |
func Pass() { | |
if true { // 模拟因为什么原因要抛出异常 | |
my_error.PanicError(my_error.NewFatalErr("系统异常")) // 向上抛异常 | |
} | |
} | |
func Pass02() { | |
if err := my_error.RecoverToError(Pass); err != nil { | |
my_error.PanicError(my_error.NewFatalErr("包装第一层", err)) // 继续向上抛异常 | |
} | |
} | |
func Pass03() { | |
if err := my_error.RecoverToError(Pass02); err != nil { | |
my_error.PanicError(my_error.NewFatalErr("包装第二层", err)) | |
} | |
} | |
func Pass04() { | |
if err := my_error.RecoverToError(Pass03); err != nil { | |
my_error.PanicError(my_error.NewFatalErr("包装第三层", err)) | |
} | |
} | |
func Pass05() { | |
if err := my_error.RecoverToError(Pass04); err != nil { | |
my_error.PanicError(err) | |
} | |
} | |
func Pass06() { | |
if err := my_error.RecoverToError(Pass05); err != nil { | |
my_error.PanicError(err) | |
} | |
} | |
func Pass07() { | |
if err := my_error.RecoverToError(Pass06); err != nil { | |
fmt.Println(err.Error()) // 最终打印错误(职责链的最顶层) | |
} | |
} | |
func main() { | |
Pass07() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
看起来不错,是不是改成一个项目,更容易让更多的人用?