幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

Golang中使用注释注解打印日志

  背景
  前一段时间线上出现了一个问题:在压测后偶尔会出现一台机器查询数据无结果但是没有返回err的情况,导致后续处理都出错。由于当时我们仅在最外层打印了err,没有打印入参和出参,所以导致很难排查问题到底出现在哪一环节。
  经过艰难地排查出问题后,感觉需要在代码里添加打印关键函数的入参和出参数,但这个逻辑都是重复的,也不想将这一逻辑侵入开发流程,所以就想到了代码生成方式的注释注解。即我们提前定义好一个注释注解(例如:Log()),并且在Docker中编译前运行代码生成的逻辑,将所有拥有该注释注解的函数进行修改,在函数体前面添加打印入参和出参的逻辑。这样就不需要让日志打印侵入到业务代码中,并且后续可以很方便替换成其他的打印逻辑(例如根据Log内的参数或者返回值等自定义日志级别)。编写代码
  我们可以使用AST的方式去解析、识别并修改代码,goast已经提供了相应的功能,我们查看我们关心的节点部分及其相关的信息结构,可以使用goastviewer直接查看AST,当然也可以本地进行调试。遍历。go文件
  首先需要使用filepath。Walk函数遍历指定文件夹下的所有文件,对每一个文件都会执行传入的walkFn函数。walkFn函数会将。go文件解析成AST,将其交由注释注解的处理器处理,然后根据是否修改了AST决定是否生成新的代码。walkFn函数会对每个。go文件处理,并调用注解处理器funcwalkFn(pathstring,infoos。FileInfo,errerror)error{如果是文件夹,或者不是。go文件,则直接返回不处理ifinfo。IsDir()!strings。HasSuffix(info。Name(),。go){returnnil}将。go文件解析成ASTfileSet,file,err:parseFile(path)如果注解修改了内容,则需要生成新的代码iflogannotation。Overwrite(path,fileSet,file){buf:bytes。Buffer{}iferr:format。Node(buf,fileSet,file);err!nil{panic(err)}如果不需要替换,则生成到另一个文件if!replace{lastSlashIndex:strings。LastIndex(path,)genDirPath:path〔:lastSlashIndex〕geniferr:os。Mkdir(genDirPath,0755);err!nilos。IsNotExist(err){panic(err)}pathgenDirPathpath〔lastSlashIndex1:〕}iferr:ioutil。WriteFile(path,buf。Bytes(),info。Mode());err!nil{panic(err)}}returnnil}遍历AST
  当注释注解处理器拿到AST后,就需要使用astutil。Apply函数遍历整颗AST,并对每个节点进行处理,同时为了方便修改时添加import,我们包一层函数供内部调用,并把一些关键信息打包在一起。Overwrite会对每个file处理,运行注册的注解handler,并返回其是否被修改funcOverwrite(filepathstring,fileSettoken。FileSet,fileast。File)(modifiedbool){初始化处理本次文件所需的信息对象info:Info{Filepath:filepath,NamedImportAdder:func(namestring,pathstring)bool{returnastutil。AddNamedImport(fileSet,file,name,path)},}遍历当前文件ast上的所有节点astutil。Apply(file,nil,func(cursorastutil。Cursor)bool{处理log注解info。Nodecursor。Node()nodeModified,err:Handler。Handle(info)iferr!nil{panic(err)}ifnodeModified{modifiednodeModified}returntrue})return}识别注释注解
  接下来我们就需要识别注释注解,跳过不相关的节点,示例中不做额外处理,仅当注释为Log()才认为需要处理,可以根据需要添加相应的逻辑。func(hhandler)Handle(infoInfo)(modifiedbool,errerror){log注解只用于函数funcDecl,ok:info。Node。(ast。FuncDecl)if!ok{return}如果没有注释,则直接处理下一个iffuncDecl。Docnil{return}如果不是可以处理的注解,则直接返回doc:strings。Trim(funcDecl。Doc。Text(),)ifdoc!Log(){return}。。。}获取函数入参和出参
  首先我们需要获取函数的入参和出参,这里我们以出参举例。出参定义在funcDecl。Type。Results,并且可能没有指定名称,所以需要先为以0,1,。。。这样的形式为没有名称的变量设置默认名称,然后按照顺序获取所有变量的名称列表。SetDefaultNames给没有名字的Field设置默认的名称默认名称格式:0,1,。。。true:表示至少设置了一个名称false:表示未设置过名称funcSetDefaultNames(fields。。。ast。Field)bool{index:0for,field:rangefields{iffield。Namesnil{field。NamesNewIdents(fmt。Sprintf(v,index))index}}returnindex0}获取打印语句
  假设我们所需的打印语句为:log。Logger。WithContext(ctx)。WithField(filepath,filepath)。Infof(format,arg0,arg1),那么函数选择器的表达式可以直接使用parser。ParseExpr函数生成,其中的参数(format,arg0,arg1)手动拼接即可。NewCallExpr产生一个调用表达式待产生表达式:log。Logger。WithContext(ctx)。Infof(arg0,arg1)其中:funcSelectorlog。Logger。WithContext(ctx)。Infofargs(arg0,arg1)调用语句:NewCallExpr(log。Logger。WithContext(ctx)。Infof,arg0,arg1)funcNewCallExpr(funcSelectorstring,args。。。string)(ast。CallExpr,error){获取函数对应的表达式funcExpr,err:parser。ParseExpr(funcSelector)iferr!nil{returnnil,err}组装参数列表argsExpr:make(〔〕ast。Expr,len(args))fori,arg:rangeargs{argsExpr〔i〕ast。NewIdent(arg)}returnast。CallExpr{Fun:funcExpr,Args:argsExpr,},nil}
  由于出参需要等函数执行完毕后执行,所以打印出参的语句还需要放在defer函数内执行。NewFuncLitDefer产生一个defer语句,运行一个匿名函数,函数体是入参语句列表funcNewFuncLitDefer(funcStmts。。。ast。Stmt)ast。DeferStmt{returnast。DeferStmt{Call:ast。CallExpr{Fun:NewFuncLit(ast。FuncType{},funcStmts。。。),},}}修改函数体
  至此我们已经获得了打印入参和出参的语句,接下来就是把他们放在原本函数体的最前面,保证开始和结束时执行。toBeAddedStmts:〔〕ast。Stmt{ast。ExprStmt{X:beforeExpr},离开函数时的语句使用defer调用NewFuncLitDefer(ast。ExprStmt{X:afterExpr}),}我们将添加的语句放在函数体最前面funcDecl。Body。Listappend(toBeAddedStmts,funcDecl。Body。List。。。)运行
  为了测试我们的注释注解是否工作正确,我们使用如下代码进行测试:packagemainimport(contextlogannotationtestdatalog)funcmain(){fn(context。Background(),1,2,3,true)}Log()funcfn(ctxcontext。Context,aint,b,cstring,dbool)(int,string,string){log。Logger。WithContext(ctx)。Infof(fnexecuting。。。)returna,b,c}
  运行gorunlogannotationcmdgeneratorUsersidealismWorkspacesGogolanglogannotationtestdata执行代码生成,在UsersidealismWorkspacesGogolanglogannotationtestdatagen下可找到生成的代码:packagemainimport(contextlogannotationtestdatalog)funcmain(){fn(context。Background(),1,2,3,true)}funcfn(ctxcontext。Context,aint,b,cstring,dbool)(0int,1string,2string){log。Logger。WithContext(ctx)。WithField(filepath,UsersidealismWorkspacesGogolanglogannotationtestdatamain。go)。Infof(fnstart,params:v,v,v,v,a,b,c,d)deferfunc(){log。Logger。WithContext(ctx)。WithField(filepath,UsersidealismWorkspacesGogolanglogannotationtestdatamain。go)。Infof(fnend,results:v,v,v,0,1,2)}()log。Logger。WithContext(ctx)。Infof(fnexecuting。。。)returna,b,c}
  可以看到已经按照我们的想法正确生成了代码,并且运行后能按照正确的顺序打印正确的入参和出参。实际使用时会在ctx中加入apm的traceId,并且在logrus的Hooks中将其在打印前放入到Fields中,这样搜索的时候可以将同一请求的所有日志聚合在一起。INFO〔0000〕fnstart,params:1,2,3,truefilepathUsersidealismWorkspacesGogolanglogannotationtestdatamain。goINFO〔0000〕fnexecuting。。。INFO〔0000〕fnend,results:1,2,3filepathUsersidealismWorkspacesGogolanglogannotationtestdatamain。go扩展
  以上代码是一种简单方式地定制化处理注解,仅处理了打印日志这一逻辑,当然还存在更多扩展的可能性和优化。注册自定义注解(这样可以把更多重复逻辑抽出来,例如:参数校验、缓存等逻辑)同时使用多个注解注解解析成语法树,支持注解参数生成的代码仅在需要时换行
  相关Demo可以在golanglogannotation找到。

09年季后赛,霍华德淘汰詹姆斯,为何却会输给科比?科比以及詹姆斯这两人在我国有着非常多的粉丝群体,很多人认为科比是80以及90后的青春,而詹姆斯是95。00后的青春。而让很多球迷们感到遗憾的是,这两人没有在总决赛中碰面。其中在……人民日报每日摘抄(204)1。功崇惟志,业广惟勤。2。业绩是干出来的,奇迹是干出来的。3。若问何花开不败,英雄创业越千秋。4。岁月为证,山河为凭。5。树高千尺,其根必深;江河万里……用艾灸治疗慢性胃炎有奇效?中医学会艾灸你的胃不痛了艾灸的文化传承在我国已有上千年的历史了,是中医治疑症中的比较常用的一种疗法,在中医占有举足轻重的地位。不及如此,艾灸疗法已在世界各地普及,是世界医学的组成部分之一。艾灸的……双语阅读移动中的阿拉斯加冰川MalaspinaontheMo19862000尽管南极洲和格陵兰岛的冰流失量更大,而且看起来更加剧烈,但北极岛屿和中纬度山脉的冰川流失却相当显着。NASA领导的一个研究小组最近开发了一种工具,可以帮助……从北京出发短途自驾游短途自驾游路线推荐今天小编推荐的这些地方,既没有大量游人的喧嚣,又有不可多得的美景。下面跟随小编一起去看看吧!春风十里,不如景区有你,赶紧行动吧!1。黄峪口风景区黄峪口风景区位于密云……30!米兰好猛,连续3次掀翻意甲劲旅,皮奥利目标6分登顶8月22日凌晨,意甲第二轮有好戏上演,AC米兰客场挑战亚特兰大。此前,首轮两队均取得开门红,都展现出不错的竞技状态,红黑军团虽然防守端状态略显起伏,但球队的进攻端相当强势;而蓝……伤感难过的短句深情催泪看了想哭的伤感短句1、许多人在分手时,总痛苦万分,有抑郁有抓狂,有卑躬屈膝。但许多年后,你再回头来看,却会发现那时的自己有多可笑。一世的缘分,机会只有一次。你离开不靠谱的人,机会却留给值得爱的人……一个敢学一个敢教!鱼腩球队来华,向足协学习如何进世界杯2亿中国球迷,只有少数人找到了这里,点击关注加入我们有了您的关注,小金定当加倍努力,回报球迷读者国足距离2022卡塔尔世界杯可以说是渐行渐远,6轮过后5个积分排名小……vivoS12pro手机价格大跳水?8GB256GB1亿像素导语:今年手机市场竞争激烈是不争的事实,为了市场的占有率,各大品牌都在争先恐后地发布新机,而新机的发布,旧机型必然要大幅降价销售,对于消费者来说很多机型又是入手好时机。国……宝宝不爱吃米饭?这么做更受欢迎,无油烟,非油炸常有妈妈说家里的宝宝挑食期每天都要追着喂饭,经常要绞尽脑汁给他换花样,很多东西一看头就扭开了,但不管怎么说,米饭还是要吃的,米饭能做的美食花样多且口感棒,说它能72变真的……王心凌转发只有一个中国被台媒暗讽舔很快王心凌再度在湖南卫视浪姐舞台翻红,成团出道!【作为湖南人,很骄傲哇〔呲牙〕】而她却撞在枪口上了,83继欧阳娜娜,刘畊宏后被台媒喷的很厉害,如果没有出这趟事,我想,一切都美……含寄生虫比较多的3种鱼,重金属超标,鱼贩子宁愿丢掉都不吃如果你也喜欢美食,点击关注,每天不断更新精彩内容!导语:含寄生虫比较多的3种鱼,重金属超标,鱼贩子宁愿丢掉都不吃!相信大家也知道,每年到了当下这个季节的时候,天气越……
终于知道为什么长不高了,怎么样才能长高儿童长高一般没有太多的征兆。儿童长高的征兆一般很少能觉察到,长高对于个人来说,几乎是没有感觉的。因为它主要是成骨细胞在不断地分泌骨基质,这样骨头就会不断地变长,但这只是很轻微的……夏季赛WE遭弹幕搞事尬吹!腿哥坐镇二路,当众吐槽这也能黑?近日2022年《英雄联盟》LPL夏季赛前期赛程逐渐进入白热化,在此期间各大参赛队伍也纷纷为了晋级机会进行着最后努力。熟悉本届夏季赛的网友应该都知道,作为目前圈内公认含金量最高的……手机为什么会越用越卡?原因主要来自这三点,看懂不用年年换新机其实绝大多数朋友更换手机的一个重要原因就是手机卡顿,手机卡顿也是一部手机没办法改变的命运,再出色的手机也终究会有被时代淘汰的一天。手机越用越卡也不是毫无根据的,一般来自这三点原……总是胃酸烧心怎么办?总是胃酸烧心怎么办?胃酸烧心,在西医来说,就是慢性卡他性胃炎,或者糜烂性胃炎,也叫胃溃疡。西医治疗胃酸烧心,无非就是XX拉唑类,或者XX替丁类,再就是抑制胃酸的药和中和胃……让助理跪地换鞋手接漱口水,犯一次错扣1000,明星哪来的优越对于很多追星族来说,明星助理绝对是理想职业,毕竟能与自己的偶像更进一步,说不定还能来一段霸道总裁爱上我的偶像剧情节。可事实上明星助理可没有那么好当,他们负责的工作多且杂,……施罗德命中7记三分爆砍38分回到国家队为什么都那么厉害?世预赛德国对阵波兰的比赛,德国凭借施罗德的出色发挥德国9383波兰,施罗德全场投篮25投10中,三分18中7,罚球12中11爆砍38分7篮板5助攻1抢断1盖帽填满数据栏!有些球……入选全明星次数最多的十位球员!科比位列第三!詹姆斯仅输一人美媒列出入选全明星次数最多的十位球员10。杰里韦斯特14次入选全明星全明星选择:(19611974)全明星赛MVP:1972年错过全明星赛:1969、1……立秋将至,入秋第一良果要这样吃有约君说立秋将至,天气将开始遂渐干燥起来,最好多吃一些润燥的食物,梨当仁不让成为入秋第一良果!梨味甘微酸、性凉,入肺、胃经,具有生津、润燥、清热、……做羊肉时,加上这4种食材配料,味鲜肉嫩,全家都爱吃羊肉讲究鲜和嫩。做羊肉时,加上这几种食材配料,味鲜肉嫩,全家都爱吃!1、萝卜和根茎类食材俗话说冬吃萝卜夏吃姜,不劳医生开药方。萝卜跟羊肉可以说是绝配,在炖煮的过程中……韩国颜VS中国颜,鹿晗孟美岐变化不大,而王一博稍微有些潦草大家都知道,每个人的样子都是不断的在变化的,而且不同时期在不同地方时候的造型也是不同的,相信大家都知道,有很多的明星都是在韩国出道的,之后才回国,所以今天就来看看他们的韩国颜和……徕芬科技入库广东省2022年科技型中小企业名单日前,根据《科技型中小企业评价办法》(国科发政〔2017〕115号)和《科技型中小企业评价服务工作指引》(国科火字〔2022〕67号)的有关要求,经公示通过,东莞市徕芬电子科技……张若昀娱乐圈最专情的人,最大的收获是娶到了樱桃女孩2017年,娱乐圈正在被各种分手、离婚、出轨的新闻洗礼。吃瓜群众们见状纷纷大呼:以后再也不相信爱情了。就在这时张若昀正式公开了与相恋7年的唐艺昕的恋爱关系。两……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网