事件记录网站源码分享?事件记录器

各位老铁们,大家好,今天由我来为大家分享事件记录网站源码分享,以及事件记录器的相关问题知识,希望对大家有所帮助。如果可以帮助到大家,还望关注收藏下本站,您的支持是我们最大的动力,谢谢大家了哈,下面我们开始吧!

这篇文章是关于“CockroachDBerrors库”[1]的系列文章的第3篇,“CockroachDBerrors库”实际上是Go的标准errors包的通用、开放源码的替代品。

Go1.13的标准库采用了DaveCheney自2015年以来对错误处理的主要贡献:将Go错误对象构造为链表的想法。唉,这种方式给Go开发人员造成了巨大的障碍:使打印错误对象变得困难、几乎不可能。

这就是我所说的“Goerror打印灾难&39;sshortmessagestring.\n//Thisisusede.g.whenformattinganerrorwith%s/%v.\nError()string\n}\n\n//wrappercanbeimplementedbyadditionalerror\n//“layers”,todecorateanerror.Thisinterface\n//isnotpre-definedinthelanguagebutshouldbe\n//implementedbyAPI-conformanterrordecorators.\n//\n//Thisistheinterfacethatpowerstheerroridentification\n//facilitieserrors.Is()anderrors.As().\ntypewrapperinterface{\n//Unwrapaccessesthenextlayerintheerrorobject.\n//Thisusedtobecalled“Cause”inDaveCheney&34;叶”错误;使用修饰层&34;错误,例如使用errors.Wrap()为错误增加前缀信息,errors.WithStack()附加堆栈跟踪或使用%w动词的fmt.Errorf(),这是1.13新增的:err=fmt.Errorf(&34;,err)包装类型通过实现Unwrap()方法来声明其&34;的能力。这是由Go的标准库检查和使用的,特别是errors.Is(),它可以通过查看所有中间层来识别错误是否为特定类型的错误。

抽象的链表使得使用来自不相关的Go包的修饰类型将修饰附加到任何错误对象成为可能。通过将层之间的关系定义为”只是error&34;world&34;world&34;hello&34;hello%w&34;world&34;更长&34;更完整”的Error()结果。

一切似乎都很好,而且自从Gov1.0以来一直很好,但是详细的打印又如何呢?

提醒:详细的打印模式

当使用%+v格式参数时,fmt内部逻辑将采用&34;模式,以显示参数列表中的相应值。默认情况下,详细模式会触发例如在结构类型中显示字段名称。

例如:

s:=struct{aint}{123}\nfmt.Printf(&34;,s)//prints{123}\nfmt.Printf(&34;,s)//verbosemode:prints{a:123}\n

&34;详细模式”的一部分,似乎这是一种自然的选择。

不幸的是,实现这个目标是相当困难的。

基本缺陷1:在包装器中无法自定义

我们试验和设计自己的错误类型,其中一些隐藏的信息只在详细模式下显示。我们可以这样做,如下所示:

typemyErrorstruct{\nmsgstring//publicmessage\ncodeint//hiddencode\n}\n\n//Errorimplementstheerrorinterface.\nfunc(e*myError)Error()string{returne.msg}\n\n//Formatimplementsthefmt.Formatterinterface.\nfunc(e*myError)Format(sfmt.State,verbrune){\nifverb==&39;&&s.Flag(&39;){\n//Verbosemode.\nfmt.Fprintf(s,&34;,e.code,e.msg)\n}else{\nfmt.Fprint(s,e.msg)\n}\n}\n

说明:当我们用%v打印*myError的实例时,我们得到msg的值;使用%+v时,我们得到相同的内容,但有前缀(code:NNN)和字段code的值。

精明的读者可能会注意到此代码看起来不完整,因为它不处理%q等格式动词。这在本节中不直接相关,因此我们暂时忽略它。

除了最后一点,代码似乎工作正常?

唉!

尝试以下代码:

err:=&myError{&34;,123}\nerr=fmt.Errorf(&34;,err)\nfmt.Println(err)//simplemode:printsjust&34;\nfmt.Printf(&34;,err)//verbose:prints…what?\n

我们希望本示例中的代码打印zawaa:(code:213)hello。不幸的是,它不是:由fmt.Errorf返回的错误类型,fmt.Formatter接口不起作用。因此,使用fmt.Errorf时,myError中的自定义信息丢失了!

换句话说,在“标准Go”中,通过Unwrap()方法创建良好的包装错误类型还不够;因此,在&34;中创建成形良好的错误包装类型是不够的。还必须实现适当的Format()方法,在包装错误中,通过fmt.Formatter处理任何可能的自定义格式。

这样有两个主要问题:

有一点很明确:必须实现Format()方法,即使自定义包装不需要自定义格式,以免fmt.Formatter接口对于所有参与者都毫无用处。Go库中没有文档说明此问题。所以大家根本不了解也不知道。实际上,粗略的检查显示,Go生态系统中的许多自定义错误包装器类型均未实现Format(),因此会在其“尾巴”中破坏格式自定义。

基本缺陷2:转发(forwarding)fmt.Formatter的困难

如果我们愿意支付抽象税,并同意所有包装错误类型也将实现fmt.Formatter,那又会这样?怎么会这样呢?

作为支持示例,让我们尝试一个非常简单的包装,它没有任何特殊功能:

typemyWrapperstruct{\ncauseerror//tailoflinkedlist\n}\n\n//Errorimplementstheerrorinterface.\nfunc(e*myWrapper)Error()string{returne.cause.Error()}\n\n//Unwrapimplementstheunwrapinterface.\nfunc(e*myWrapper)Unwrap()error{returne.cause}\n

然后,我们可以开始实现fmt.Formatter。至少,它应该区分冗长和非冗长模式。

但是,如果我们不确定错误原因(errorcause)是否实际实现fmt.Formatter?也许没有。因此,为了减少的惊讶,我们需要做&34;。实现此目的的最佳方法是调用fmt本身逻辑:

//Formatimplementsthefmt.Formatterinterface.\nfunc(e*myWrapper)Format(sfmt.State,verbrune){\nifverb==&39;&&s.Flag(&39;){\n//Verbosemode.Makefmtaskthecause\n//toprintitselfverbosely.\nfmt.Fprintf(s,&34;,e.cause)\n}else{\n//Simplemode.Makefmtaskthecause\n//toprintitselfsimply.\nfmt.Fprint(s,e.cause)\n}\n}\n

这是一个繁琐的模式,只是为了确保e.cause得到打印。

此外,如果e.cause想要了解有关原始格式的信息,那该内容会如何呢?如果与%v?还是%39;%&39;+&39;+&39;-&39;-&39;39;){\nf.WriteByte(&&39;&39;&39;0&39;0&39;.&39;v&39;+&34;%+v\\n&39;s&39;q&v等),并且无法识别除v、s或q以外的任何谓词。

在Go生态系统中探索发现,很少有自定义错误包装类型实现Format()。

实现适当的自定义Format(),以及没有预定义(也不建议)机制在fmt中转发Format()调用这一事实是如此困难,这是Go标准库的基本限制。

(安利:上面的正确代码的副本可作为可重用的fmtfwd.MakeFormat()函数,在go-mtfwd[6]包中。然而,这不是万能药。)

基本缺陷3:不更改API无法修复的问题

Go的团队称自己构建的语言可以最大限度地保持向后兼容性。标准库的添加是通过引入或替换功能,但不会影响现有代码的语义。

在这种情况下,Go开发人员可以做什么来&34;上面确定的问题,而不破坏现有的error代码,也不需要现有包添加&34;的粘附代码,如缺少的Format()转发器?

事实证明,在fmt包中可以直接做的工作不多。

在高级别上,不可能的任务是确保错误链中的所有细节以详细模式打印,同时考虑Format()方法中的自定义行为。

由于不是链中的每一个错误都提供Format()方法,因此fmt代码需要使用Unwrap()方法迭代自身。然后在每个层上都需要打印…东西。但究竟是什么?

它无法调用Error(),因为包装器上的Error()本身将递归,并获取链中其他层的字符串片段;它无法调用Format(),因为包装器上的Format()已经(根据当前生态系统)对错误原因递归递处理。

因为fmt.Formatter接口Format()方法的第一个参数fmt.State,是一个接口类型,因此实际在fmt中会是一个特定的State实例,可以&34;当前错误层内的直接打印,从进一步递归执行打印。

例如,如下Format()实现:

//Formatimplementsthefmt.Formatterinterface.\nfunc(e*myWrapper)Format(sfmt.State,verbrune){\nifverb==&39;&&s.Flag(&39;){\n//Verbosemode.Makefmtaskthecause\n//toprintitselfverbosely.\nfmt.Fprintf(s,&34;,e.code,e.cause)\n}else{\n//Simplemode.Makefmtaskthecause\n//toprintitselfsimply.\nfmt.Fprint(s,e.cause)\n}\n}\n

通过此代码可见,在Format内部调用的fmt.Fprintf或fmt.Fprint的第一参数是fmt.State的实例,这是fmt包负责注入的。简单字符串和非错误值可以传递,每次看到错误值时,它都会被”忽略”,以便外部fmt循环可以转到下一层,而不会重复输出。

这个想法的问题,要知晓Format()方法中是怎么使用fmt.State的。它不适用于实现以下函数的软件包:

func(w*withMessage)Format(sfmt.State,verbrune){\nswitchverb{\n//…\ncase&39;,&39;:\nio.WriteString(s,w.Error())\n}\n}\n

(这个例子来自pkg/errors)。

请注意,与Go生态系统中的许多其他实现一样,此实现也挫败了我们的想法:某些打印使用fmt.State的io.Writer子接口并将.Error()字符串直接传递给它。当包装器的Format()正在打印下一层错误时,无法可靠地从fmt.State中进行检测,从而捕获该错误以执行其他操作。

因此,Go生态系统中“将错误作为链接列表”的集成与fmt.Formatter抽象发生冲突,并创建了一个坑,社区中的每个人都陷入困境,而Go标准库无法帮助任何人在fmt中使用魔术。

也许是救星:pre-1.13xerrors

在进行Go1.13的工作中,2017年成立了一个工作组,研究采用“错误作为链接列表”的方法,并基本上接管了DaveCheney在pkg/errors中的工作。

这就是由JonathanAmsterdam,RussCox,MarcelvanLohuizen和DamienNeil组成的小组开始开发xerrors[7]包,以作为新抽象的原型和研究依据。

这项工作指导作者提出了一些建议:

MarcelvanLohuizen:ErrorPrinting—DraftDesign[8](August2018)JonathanAmsterdam,RussCox,MarcelvanLohuizen,DamienNeil:Proposal:Go2ErrorInspection[9](January2019)

他们的工作主要集中在Unwrap()的语义以及新APIerror.Is()和errors.As()的创建上,以可靠地从错误对象中识别和提取信息。

MarcelvanLohuizen更加关注错误处理的打印方面,并设计了以下提案:

除了fmt.Formatter,error,fmt.Stringer和fmt.GoStringer外,fmt包支持一个新接口:errors.Formatter。新接口将通过错误包装和叶类型实现。提议的接口如下:packageerrorstypeFormatter{error//FormatErrorcanbeimplementedtocustomizetheformatting//oferrors,insteadoffmt.Formatter&39;sFormatError//cantakeoverformattingofbothitself*andall//subsequentlayers*byproducingitscustom//representationforallandthenreturning`nil`,//eventhoughitsUnwrap()methodisstillused//byerrors.Is()toiteratethroughthetail.FormatError(pPrinter)(nexterror)}typePrinterinterface{Print(…)//canbeusedtooutputstuffPrintf(…)//canbeusedtooutputstuff//Detailisa“magic”predicatewhichbothindicateswhether//verbosemodeisrequestedvia%+v,andalsostartsindenting//theoutputperformedbysubsequentPrint()/Printf()callsin//theinterface,sothatthedetailsarevisually“pushedto//theright”.Detail()bool}

一个示例用法如下所示:

//FormatErrorimplementstheerrors.Formatterinterface.\nfunc(e*myWrapper)FormatError(perrors.Printer){\np.Print(&34;)\nifp.Detail(){\np.Printf(&34;,e.code)\n}\nreturne.cause\n}\n

使用此代码,我们将得到以下行为:

err:=errors.New(&34;)\nerr=&myWrapper{cause:err,code:123}\nerr=&myWrapper{cause:err,code:456}\n\nfmt.Println(err)//simplemode:prints&34;\n\nfmt.Printf(&34;,err)\n//prints:\n//\n//always:\n//hidden:456\n//always:\n//hidden:123\n//hello\n

(请注意一些特性:错误是从最外层/头部到最内层/尾部打印的,并且在每个前缀之后,细节之前插入了冒号)。

因此,将fmt代码修改为使用新接口的方式是:

检测Format()方法是否可用。如果是这样,它被调用,结束。否则,如果要打印的对象是错误,它将对其进行迭代:调用FormatError()(如果存在)并使用其返回值作为下一次迭代的输入进行迭代。当错误对象上不存在FormatError()或返回nil时,迭代将停止。如果在迭代结束时仍有Format()或Error()方法可供调用,则将调用该方法以“完成”格式化。

xerror原型能够集中精力仅格式化一层包装器,而又不知道如何正确地将Format()调用转发给其他层。

因此,这是解决上述第二个基本限制的尝试。

哎,它根本无法解决第一个基本限制:如果包装层未实现FormatError(),则fmt代码将仅停止在该级别尝试,并且在错误中进一步进行任何FormatError()或Format()定制链会被丢弃。

此外,许多人不喜欢“从前到后”打印错误的方式:在对错误详细信息进行故障排除时,开发人员发现重要是首先显示链接列表的“最内层”(尾部),然后才是“最外层”(头)。xerrors实现不允许这样做。

最后,无论如何,所有讨论都是没有争议的:没有选择将xerror打印抽象(包括errors.Formatter,errors.Printer和相应的fmt更改)包含在Go1.13中。从Go1.16开始,朝着这个方向进行的任何进一步工作都被推迟[10],具体另行通知。

战略失误:打破与pkg/errors的兼容性

依赖于DaveCheney的pkg/errors的社区项目超过50,000个,该软件包已成为事实上的扩展,能够提供错误包装程序的基本库,并作为错误打印自定义示例,尽管不完善。

甚至有一个扩展的生态系统,它依靠基本的链表抽象,使用一种名为Cause()的方法来接受链中的下一个层次。

Go团队可能已经接受了这种方法,并且可以在“所有错误包装程序都必须以类似于pkg/errors的方式实现Format”的方式进行区分。然后,errors.Is()/errors.As()可能选择了pkg/errors的Cause()抽象。

遗憾的是,Go团队选择了不同的方法名称:Unwrap()。因此,Go1.13发布后开发的新一代错误包已无法重用pkg/errors。

因此,1.13不仅引入了基本限制;这也阻止了Go社区继续可靠地使用pkg/errors。

总结:Goerror打印灾难

在2019年,Go1.13采纳了DaveCheney的2015年建议,将错误对象视为链表。因此,对Unwrap()方法进行了标准化,并使用Is()和As()函数增强了错误包,这些函数可以从以这种方式构造的错误中可靠地提取信息。

不幸的是,fmt软件包没有学习如何打印这种错误的新形状,并且可靠地自定义错误对象的显示已变得不可能。

这是因为与以前的版本一样,fmt仅了解Format(),Error()和String(),并且仅在错误链的顶端或“头”考虑这些方法。

如果一个包定义了自定义包装错误类型,但忘记定义了自定义Format()方法,则fmt将忽略链接列表“尾部”中的任何其他Format()方法,并且自定义项将丢失。

此外,只有Format()方法可以为“详细”和“简单”格式(%v/%+v)提供不同的实现。在实践中,以递归方式在错误链尾部调用进一步的自定义方式,几乎不可能实现包装错误的Format()。

简而言之,错误打印的自定义变得容易出错,并且在Go1.13中基本上不可靠。Go1.13中放弃了的关键Cause()接口,导致与另一个具有某些人们可以达成共识的逻辑的程序包DaveCheney的pkg/errors不兼容。Go团队通过xerrors包来尝试修复Go标准库中的这种情况,实际上并没有成功解决这些问题,存在重大的新缺陷,最终不令人满意。

这就是我们程序员无所适从的方式。

这是Goerror打印灾难,它的悬念留在Go1.16中。

接下来

CockroachDBerror库在错误打印方面花费了大量精力。尽管它不能填补所有空白,但确实可以减轻很多的痛苦。

本系列的下一篇文章进一步说明。

参考文献

DaveCheney:Don’tjustcheckerrors,handlethemgracefully[11].TheGoBlog:WorkingwitherrorsinGo1.13[12].JonathanAmsterdam,etal:Go2errorvalues[13].MarcelvanLohuizen:ErrorPrinting—DraftDesign[14].JonathanAmsterdam,RussCox,MarcelvanLohuizen,DamienNeil:Proposal:Go2ErrorInspection[15].

原文链接:https://dr-knz.net/go-error-printing-catastrophe.html

本文作者:Raphael‘kena’Poss

译者:polarisxu

关于事件记录网站源码分享,事件记录器的介绍到此结束,希望对大家有所帮助。

Published by

风君子

独自遨游何稽首 揭天掀地慰生平