十行代码,让日志存储成本降低80%( 二 )


来看一个例子,我不给任何提示,你能在大脑里很快分析出这个日志的结构吗?
requestParam$&trace@2150435916867358634668899ebccf&scene@test&logTime@2023-06-14 17:44:23&+skuPromiseInfo$&itemId@1234567:1&skuId@8888:1&buyerId@777:1&itemTags@,123:1,2049:1,249:1,&sellerId@6294:1&toCode@371621:1&toTownCode@371621003:1&skuBizCode@TMALL_TAOBAO:1&skuSubBizCode@TMALL_DEFAULT:1&fromCode@DZ_001:1+orderCommonInfo$&orderId@4a04c79734652f6bd7a8876379399777&orderBizCode@TMALL_TAOBAO&orderSubBizCode@TMALL_DEFAULT&toCode@371621&toTownCode@371621003&+
3.工具之乱
有时候甚至会出现,同一个类,同一个方法中,两行不同的日志埋点,打出来的日志格式不一样,落的日志文件也不一样 。为什么会出现这种情况?就是因为用了不同的日志工具 。要究其根源,我们需要分析一下不同的工具究竟是在做什么 。
可以发现,很多工具之间的差别就是支持的参数类型不一样,有些是打印订单对象的,有些是打印消息的,有些是打印调度日志的 。还有一些差别是面向不同业务场景的,比如一盘货专用工具,负卖专用工具 。
还有一些差异是面向不同的异常封装的,有些是打印ExceptionA,有些是打印ExceptionB的 。人间离奇事,莫过于此,或许只能用存在即合理去解释了 。
二、日志分层
我一直信奉极简的设计原则,简单意味着牢不可破 。上面提到,一套日志系统最终的结局一定是走向混乱,既然这种趋势无法避免,那么我们在最初设计的时候就只能确保一件事,保证原始的分类尽量简单,且不重叠 。
其实通用的分类方式无非就两种,一种按职能水平拆分,一种按业务垂直拆分 。一般来说,一级分类,应该采用水平拆分 。因为业务的边界一般是很难划清的,边界相对模糊,职能的边界就相对清晰稳定很多,职能其实反映的是工作流,工作流一经形成,基本不会产生太大的结构性变化 。基于这种思路,我设计了如下的日志分层 。

十行代码,让日志存储成本降低80%

文章插图
从层次上来看,其实只有三层 , 入口 , 内核,出口 。入口日志只负责打印流量入口的出入参,比如HSF,controller 。出口日志负责打印所有第三方服务调用的出入参 。内核日志,负责打印所有中间执行过程中的业务日志 。就三层足矣,足够简单,不重不漏 。
另外把堆栈日志单独拎出来,堆栈相比业务日志有很大的特殊性,本文标题所指出的日志存储降低优化,也只是针对堆栈日志做的优化,这个后面再讲 。
三、格式设计
日志的格式设计也有一些讲究 。首先日志的设计是面向人可读的,这个无需多言 。另外也非常重要的一个点,要面向可监控的设计,这是容易被很多人忽视的一个点 。基于这两个原则,说一下我在格式设计上的一些思路 。
首先要做维度抽象 。既然是面向监控,监控一般需要支持多个维护,比如行业维度,服务维度,商家维度等等 , 那么我们就需要把所有的维度因子抽出来 。那么这些维度实际打印的时候怎么传给logger呢?建议是把他们存到ThreadLocal中,打的时候从上下文中取 。这样做还有一个好处是,日志打印工具设计的时候就会很优雅,只需要传很少的参数 。
格式尽量简单,采用约定大于配置的原则,每一个维度占据一个固定的位置,用逗号分割 。切忌设计一个大而全的模型,然后直接整个的序列化为一个JSON字符串 。
也不要被所谓的扩展性给诱惑,给使用方轻易开出一个能够自定义格式的口子,即便你能轻而易举的提供这种能力 。根据我的经验,这种扩展性一定会被滥用,到最后连设计者也不知道实际的格式究竟是怎样的 。当然这个需要设计者有较高的视野和远见,不过这不是难点,难的还是克制自己炫技的欲望 。
在内容上,尽量打印可以自解释的文本,做到见名知义 。举个例子,我们要打印退款标,退款标原本是用1, 2, 4, 8这种二进制位存储的,打印的时候不要直接打印存储值,翻译成一个能描述它含义的英文code 。
格式示例:
timeStamp|threadName logLevel loggerName|sourceAppName,flowId,traceId,sceneCode,identityCode,loginUserId,scpCode,rpcId,isYace,ip||businessCode,isSuccess||parameters||returnResult||
内容示例:
2023-08-14 14:37:12.919|http-nio-7001-exec-10 INFO c.a.u.m.s.a.LogAspect|default,c04e4b7ccc2a421995308b3b33503dda,0bb6d59616183822328322237e84cc,queryOrderStatus,XIAODIAN,5000000000014,123456,0.1.1.8,null,255.255.255.255||queryOrderStatus,success||{"@type":"com.alibaba.common.model.queryorder.req.QueryOrderListReq","currentUserDTO":{"bizGroup":888,"shopIdList":[123456],"supplierIdList":[1234,100000000001,100000000002,100000000004]},"extendFields":{"@type":"JAVA.util.HashMap"},"invokeInfoDTO":{"appName":"uop-portal","operatorId":"1110","operatorName":"account_ANXRKY8NfqFjXvQ"},"orderQueryDTO":{"extendFields":{"@type":"java.util.HashMap"},"logisTypeList":[0,1],"pageSize":20,"pageStart":1},"routeRuleParam":{"@type":"java.util.HashMap","bizGroup":199000},"rule":{"$ref":"$.routeRuleParam"}}||{"@type":"com.alibaba.common.model.ResultDTO","idempotent":false,"needRetry":false,"result":{"@type":"com.alibaba.common.model.queryorderstatus.QueryOrderStatusResp","extendFields":{"@type":"java.util.HashMap"}},"success":true}||


推荐阅读