




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
代123456789package代123456789package("fmtfuncmain()iferr:=funcA();err!={log.Fatalf("callfuncgotfailed:%v",err)return}log.Println("callfunc}funcfuncA()erroriferr:=funcB();err!={return}returnfmt.Errorf("func}funcfuncB()errorreturnfmt.Errorf("func}执行上面的代码$gorun2021/07/0208:06:55callfuncgotfailed:funccalledexitstatus这时我们想定位问题,但不知体是哪行代码报的错误,只能靠猜,还不一定能猜到。为了解决这个问题,我们可以加一些Debug信息,来协助我们定位问题。这样做在测试环境是没问题的,但是上环境,一方面修改、发布都比较麻烦,另一方面问题可能比较难重现。这时候我们会想,要是能打印错误的堆栈就好了。例如:1234567892021/07/0214:17:03callfuncgotfailed:funccalledexitstatus通过上面的错误输出,我们可以很容易地知道是哪行代码报的错,从而极大提高问题定位的效率,降低定位的难度。所以,在我看来,一个优秀的errors包,首先需要支持错误堆栈。其次,能够支持不同的打印格式。例如%+v、%v、s等格式,可以根据需要打印不同丰富度的错误信息。再次,能支持Wrap/Unwrap功能,也就是在已有的错误上,追加一些新的信息。例如errors.Wrap(err"openfilefailed"。Wrap常用在调用函数中,调用函数可以基于被调函数报错时的错误Wrap一些自己的信息,丰富报错信息,方便后期的错误代代12345678funcfuncA()erroriferr:=funcB();err!=nilreturnerrors.Wrap(err,"callfuncB}returnerrors.New("funccalled}funcfuncfuncB()errorreturnerrors.New("funccalled11这里要注意,不同的错误类型,Wrap函数的逻辑也可以不同。另外,在调用Wrap时,也会生成一个错误堆栈节点。然能够嵌套error,那有时候还可能需要获取被嵌套的还有,错误包应该有Is方法。在实际开发中,我们经常需要判断某个error是否是指定的error。在Go1.13之前,也就是没有wraperror的时候,我们要判断error是不iferr==os.ErrNotExist//normal3但是现在,因为有了wraperror,这样判断就会有问题。因为你根本不知道返回的err是不是一个嵌套的error,嵌套了几层。这种情况下,我们的错误包就需要提供Is函1funcIs(err,targeterror)当err和target是同一个,或者err是一个wraperror的时候,如果target也包在这个嵌套error链中,返回true,否则返回fasle。另外,错误包应该支持As函数在Go1.13之前,没有wraperror的时候,我们要把error转为另外一个error,般都是使用typeassertion或者typeswitch,也就是类型断言。例如:ifperr,ok:=err.(*os.PathError);ok33但是现在,返回的err可能是嵌套的error,甚至好几层嵌套,这种方式就不能用了。所以,我们可以通过实现As函数来完成这种功能。现在我们把上面的例子,用As函数实现varperriferrors.As(err,&perr)4这样就可以完全实现类型断言的功能,而且还更强大,因为它可以处理wraperror最后,能够支持两种错误创建方式:非格式化创建和格式化创建。例如errors.New("filenoterrors.Errorf("file%snotfound","iam- /pkg/errors包进行了二次封装,用来支持上一讲所介绍的错误明确优秀的错误包应该具备的功能后,我们来看下错误包的实现。实现的源码存/marmotedu/errors /pkg/errors/errors.go中增加新的withCode结构体,来引 和具体的错误信息。typewithCodestruct error//errorcodeint//456causeerror//cause*stack//错误堆}456causeerror//cause*stack//错误堆}1package2import5 code 89funcmain()iferr:=bindUser();err!=nil//%s:Returnstheuser-safeerrorstringmappedtotheerrorcodeorfmt.Println("====================>%sfmt.Printf("%s\n\n",//%v:Aliasforfmt.Println("====================>%vfmt.Printf("%v\n\n",//%-v:Outputcallerdetails,usefulforfmt.Println("====================>%-vfmt.Printf("%-v\n\n",//%+v:Outputfullerrorstackdetails,usefulforfmt.Println("====================>%+vfmt.Printf("%+v\n\n",//%#-v:Outputcallerdetails,usefulfortroubleshootingwithJSONfmt.Println("====================>%#-vfmt.Printf("%#-v\n\n",//%#+v:Outputfullerrorstackdetails,usefulfordebuggingwithJSONfmt.Println("====================>%#+vfmt.Printf("%#+v\n\n",//dosomebusinessprocessbasedontheerroriferrors.IsCode(err,code.ErrEncodingFailed)fmt.Println("thisisaErrEncodingFailed {fmt.Println("thisisa}//wecanalsofindthecause}48funcbindUser()erroriferr:=getUser();err!=nil//Step3:Wraptheerrorwithanewerrormessageandanewerrorcodereturnerrors.WrapC(err,code.ErrEncodingFailed,"encodinguser'Lingfei}return}funcgetUser()erroriferr:=queryDatabase();err!=nil//Step2:Wraptheerrorwithanewerrorreturnerrors.Wrap(err,"getuser}return}funcqueryDatabase()error//Step1.Createerrorwithspecifiederrorreturnerrors.WithCode(code.ErrDatabase,"user'LingfeiKong'not}iferrors.IsCode(err,上述代码中,通过WithCode函数来创建新的withCode类型的错误;通过来将一个error封装成一个withCode类型的错误;通过 IsCode来判断一个erroriferrors.IsCode(err,withCode误实现了一个funcw*withCodeFormat(statefmt.State,verbrune)方法,该方法用来打印不同格式的错误信息,见下表:例如,%+v会打印以下错误信11getuserfailed.-#1那么你可能会问,这些错误信息中的100101错误码,还有Databaseerror这种对外展首先withCode中包含了int类型的错误码,例如100101 MustRegister,将一个Coder到 1varcodes=代typeCoderinterface//HTTPstatusthatshould代typeCoderinterface//HTTPstatusthatshouldbeusedfortheassociatederrorHTTPStatus()45//External(user)facing67String()8//Referencereturnsthesfor9Reference()//CodereturnsthecodeofCode()13这样withCode的Format方法,就能够通过withCode中的code字段获取到对应的Coder,并通过Coder提供的HTTPStatus、String、Reference、Code函数,来获取withCode中code的详细信息,最后格式化打印。这里要注意,我们实现了两个函数:Register和MustRegister,二者唯一区是:当重复定义同一个错误Code,MustRegisterpanic,这样可以防止后面XXX()和MustXXX()的函数命名方式,是一种Go代码设计技巧,在Go代码中经常使用,例如Go标准库中 MustXXX会在某种情况不满足时panic。因此使用MustXXX的开发者看到函数名就会有一个心理预期:使用不当,会造成程序panic。最后,我还有一个建议:在实际的生产环境中,我们可以使用JSON格式打印日志,JSON格式的日志可以非常方便的供日志系统解析。我们可以根据需要,选择%#-v或%#+v两种格 在这里,我们把这个错误包跟go标准库的errors包,以/pkg/errors包进行对比,来看看它们的性 1547 734 2881 3116 7159 10646 11896 持平。在对比性能时,重点关注ns/op,也即每次error操作耗费的纳秒数。另外,我们还需要测试不同error嵌套深度下的error操作性能,嵌套越深,性能越差。例如:在嵌套深度为10的时候, /pkg/errors包ns/op值为1547,/marmotedu/errors包ns/op值为1772。可以看到,二者性能基本保一致具体性能数据对比见下表我们是通过Ben arkErrors测试函数来测试error包性能的,你感可以打开链根据我的开发经验,我推荐两种记录错误的方式,可以帮你快速定位问 /marmotedu/errors包提供的错误堆栈能力,来错误。具体你下面的代码示例。以下代码保存在 代代123456package("7789code)funcmain()iferr:=getUser();err!={fmt.Printf("%+v\n",}}funcgetUser()erroriferr:=queryDatabase();err!={returnerrors.Wrap(err,"getuserreturn}funcqueryDatabase()errorreturn}"user'LingfeiKong'not执行上述的代码$gorungetuserfailed.-#1你使用这种方法时,我推荐的用法是,在错误最开始处使用errors.WithCode(创建一个withCode类型的错误。上层在处理底层返回的错误时,可以根据需要,使用Wrap函数基于该错误封装新的错误信息。如果要包装的error不是用/marmotedu/errors包创建的,建议用errors.WithCode()新建error1package23import456"7"89code )funcmain()iferr:=getUser();err!=nilfmt.Printf("%v\n",}}funcgetUser()erroriferr:=queryDatabase();err!=nilreturn}return}funcqueryDatabase()erroropts:= []string{"test.log",ErrorOutputPaths:}defererr:=errors.WithCode(code.ErrDatabase,"user'LingfeiKong'notiferr!=nillog.Errorf("%v",}return}执行以上代码1123$gorun代2021-07-03DatabaseDatabase当错误发生时,调用log打印错误。通过log的caller能,可以定位到log句的当代码调用第包的函数时,第包函数出错时打印错误信息。比如iferr:=os.Chdir("/root");err!=nillog.Errorf("changedirfailed:%v",3接下来,我们看一个依据上一讲介绍的错误码规范的具体错误码/marmotedu/sample-codesample-code实现了两类错误码,分别是通用错误码(sample-code/base.go)和业务模块相关的错误码(sample-code/apiserver.go)。首先,我们来看通用错误码的定代代123456789//通用:基本错//Codemuststartwith1xxxxxconst(//ErrSuccess-200:ErrSuccessint=iota+//ErrUnknown-500:Internalservererror.//ErrBind-400:Erroroccurredwhilebindingtherequestbodytothestr//ErrValidation-400:Validationfailed.//ErrTokenInvalid-401:Tokeninvalid.)在代码中,我们通常使用整型常量(ErrSucess)来代替整型错误码(100001),因为使用ErrScess时,一看就知道它代表的错误类型,可以方便开发者使用。错误码用来指代一个错误类型,该错误类型需要包含一些有用的信息,例如对应的HTTPStatusCode、对外展示的Message,以及跟该错误匹配的帮助文档。所以,我们还需要实现一个Coder来承载这些信息。这里,我们定义了一个实现了/marmotedu/errors.Coder接口的ErrCode结构代代123456789//ErrCodeimplementstypeErrCodestruct/marmotedu/errors`.Coder//CreferstothecodeoftheC//HTTPstatusthatshouldbeusedfortheassociatederrorHTTP//External(user)facingerrortext.Extstring//RefspecifythereferenceRefstring.}可以看到ErrCode结构体包含了以下信int类型的业务码对应的HTTPStatusCode。错误的参考文档下面是一个具体的Coder示例23456"1coder:= MustRegister函数,将Coder到 1coder:=一个项目有很多个错误码,如果每个错误码都手动调用Mustegister函数会很麻烦,这里我们通过代码自动生成的方法,来生成reister函数调用://go:generatecodegen-//go:generatecodegen-type=int-doc-output//go:generatecodegen-type=int会调用codegen工具,生成funcinit()register(ErrSuccess,200,register(ErrUnknown,500,"Internalserverregister(ErrBind,400,"Erroroccurredwhilebindingtherequestbodytoregister(ErrValidation,400,"Validation//otherregisterfunction7这些register调用放 函数中,在加载程序的时候被初始化这里要注意,在的时候,我们会检查HTTPStatusCode,只允许定义200、400、401、403、404、500这6个HTTP错误码。这里通过程序保证了错误码是符合HTTPStatusCode使用要求的。//go:generatecodegen-type=int-doc- 我们提供API文档时,也需要记着提供一份错误码描述文档,这样客户端才可以根据错误//ErrSuccess-200:ErrSuccessint=iota+codegen工具之所以能够生成sample_code_generated.go和error_code_generated.md,是因为我们的错误码注释是有规定格式的:错误码整型常量>-<对应的HTTPStatusCode>:<ExternalMessage>.。codegen工具可以在IAM项目 下,执行以下命令来安装1$make安装完codegen工具后,可以在 /marmotedu/sample-code包根 执行gogenerate命令,来生成sample_code_generated.go和xxxx_generated.xx来命名,这样通过generated,我们就知道这个文件是代码自动在实际的开发中,我们可以将错误码独立成一个包,放在下,这样可以方便整个应用调用。例如IAM的错误码就放在IAM项目 下 我们的错误码是分服务和模块的,所以这里建议你把相同的服务放在同一个Go文件中,例如IAM的错误码存放文件:12$lsbase.goapiserver.go一个应用中会有多个服务,例如IAM应用中,就包含了iam-apiserver、iam-authz-server、iam-pump三个服务。这些服务有一些通用的错误码,为了便于,可以将这些通用的错误码统一放在base.go源码文件中。其他的错误码,我们可以按服务分别放在不同的文件中:iam-apiserver服务的错误码统一放在apiserver.go文件中;iam-authz-server的错误码统一存放在authzserver.go文件中。其他服务以此类推。另外,同一个服务中不同模块的错误码,可以按以下格式来组织:相同模块的错误码放在同一个cost代码块中,不同模块的错误码放在不同的onst代码块中。每个onst块的开头注释就是该模块的错误码定义。例如:代代123456789//iam-apiserver:usererrors.const(//ErrUserNotFound-404:UsernotErrUserNotFoundint=iota+//ErrUserAlreadyExist-400:Useralready)//iam-apiserver:secreterrors.const(//ErrEncrypt-400:SecretreachthemaxaxCountint=iota+ErrSecretNotFound-404:Secretnot)IAM项目的错误码定义记录文档为code_specification.md。这个文档中记录了错误码用的。这里,我就举一个在ginweb框架中使用该错误码的例子://ResponsedefinesprojectresponseformatwhichinmarmotedutypeResponsestruct errors.Code Reference interface{}78//WriteResponseusedtowriteanerrorandJSONdataintofuncWriteResponse(c*gin.Context,errerror,datainterface{})err!=nilcoder:=c.JSON(coder.HTTPStatus(), Reference: }23
c.JSON(http.StatusOK,Response{Da
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 天津市红桥区复兴中学2025年初三4月中考复习质量监测卷(七)物理试题含解析
- 江西服装学院《大学英语非艺术类》2023-2024学年第一学期期末试卷
- 西藏林芝二中2024-2025学年高三第三次模拟考试试题英语试题含解析
- 浙江树人学院《建筑结构设计A》2023-2024学年第二学期期末试卷
- 郑州旅游职业学院《计算机视觉基础及应用》2023-2024学年第二学期期末试卷
- 中南林业科技大学《中外剧作家及作品研究》2023-2024学年第二学期期末试卷
- 临沂科技职业学院《建筑工程制图与识图》2023-2024学年第二学期期末试卷
- 山东省滨州市三校联考2024-2025学年高三下学期动态性教学质量检测试题考前适应卷物理试题含解析
- 上饶幼儿师范高等专科学校《物流专业英语》2023-2024学年第二学期期末试卷
- 山东司法警官职业学院《建筑设备基础》2023-2024学年第一学期期末试卷
- 2024年地理中考模拟考试地理(江苏泰州卷)(A4考试版)
- 乳腺癌诊治指南与规范(2025年版)解读
- 2024年上海嘉定区区属国有企业招聘真题
- 2025河北建投水务招聘29人易考易错模拟试题(共500题)试卷后附参考答案
- 常德辅警考试题库
- 基于核心素养的初中历史跨学科教学策略研究
- 有理数的加法说课课件2024-2025学年人教版数学七年级上册
- GB/T 18655-2025车辆、船和内燃机无线电骚扰特性用于保护车载接收机的限值和测量方法
- 2025年江苏南通苏北七市高三二模语文作文分析
- 2025年上半年宣城市广德县人民法院招考书记员兼任法警易考易错模拟试题(共500题)试卷后附参考答案
- 2025届云南省曲靖一中高三下学期第三次四校联考历史试题试卷含解析
评论
0/150
提交评论