日志打印对研发来说,是很有必要的, 如何打印好日志,让日志能反映出处理流程,让日志能反映出问题所在,这个很重要,不好的日志,会加大研发排查问题的难度,过多的日志也会对研发造成干扰,如何打印日志,成了研发必须要掌握的技能。
级别 | 使用场景 | 是否需要报警 | 是否要立即处理 |
---|---|---|---|
DEBUG | 研发调试时使用的日志,生产环境不记录DEBUG日志,生产环境不应打印DEBUG日志 | 不用 | 不用 |
INFO | 业务流程的关键信息,可用于线上的case排查。数量不宜过多,单条日志长度不宜过大 | 不用 | 不用 |
Warn | 警告信息,指的业务发生了不符合流程的事件,但一般不影响业务的正常执行。常见场景:用户输入参数错误;rpc访问失败但重试成功;rpc访问失败但有容灾; | 不用 | 有空了看看 |
ERROR | 服务出现了异常,例如MySQL/Redis错误、下游调用失败、内存不足。每一条Error日志都需要研发同学关注 | 要 | 及时查看 |
Fatal | 导致系统崩溃的错误信息(使用量较少) | 要 | 立即处理 |
记录完整
logger.Errorf(ctx, "[rpc][people_corehr_starter] code not 0 : %v", resp)
logger.Infof("[AccessLog][func: %v][req: %+v]", methodName, request)
问题:直接打印使用了%v
打印了整个结构体。
结构体往往包含许多字段,我们在打印日志时往往只关注其中一小部分字段的值,但很多同学为了方便会直接打印整个结构体;这种方式不仅仅会造成资源的浪费还有可能会导致安全的问题。
for _,v:= range value1{
...
for _,v:= range value2{
...
logger.Infof(ctx, "[RowDetailPrepareData.BuildRowDimensionInfo][%s:%s] show_row_ActiveWorkforce %s %s : %d",
wpProgrammeID, prepareData.DraftID, wpRow.ID, domain.GetRowDimensionUniqKey(ctx, wpRow), wpRow.ActiveWorkforce)
}
}
剖析:在双重的for循环中打印了info日志,造成正常请求产生大量的info日志影响系统性能。
if err != nil {
return result, errors.New("transfer interface to map[string]interface failed")
}
未在错误发生地打印错误信息,导致原始的错误信息丢失
logger.Warnf(ctx, "[ReleasePreTransfer] removePreTransferInfo fail. err=%s. uniqueIDList=%s",
removePreTransferErr.Error(), releasePreTransferReq.UniqueID)
问题:使用Base64对敏感的用户信息加密后打印在日志中。
通过Base64对敏感信息进行转换后,仅仅只能做到表面的脱敏,本质上Base64加密是开发人员规避安全检测的手段,虽然进行Base64转换后可以避免安全日志扫描的检测,Base64、Unicode 等有明确的特征(比如 Base64 只包含 64 个可打印字符),很容易被识别并被反解,从而导致信息泄露。
日志打印应杜绝安全问题;禁止通过Base64,Unicode等编码方式对敏感数据进行日志打印。
if err != nil || resp == nil {
logger.Warnf(ctx, "[AggregateTimeline] err=%s", err.Error())
return nil, err
}
问题:日志级别不合理,接口访问失败且无重试逻辑,业务接口已无法正常工作,应打印Error日志。
当前服务中warn日志泛滥的情况较为普遍,且很少有人关注warn日志,在大家的日常日志打印中应更加谨慎的使用warn日志。
func checkBaseResp(ctx context.Context, base *base.BaseResp) error {
if base == nil {
logger.Errorf(ctx, "[wukong][checkBaseResp] base response illegal")
return errors.New("base response illegal")
}
if constant.WuKongRespSpecialCode.Contains(int64(base.StatusCode)) {
logger.Infof(ctx, "[Wukong][checkBaseResp] success. code: %d, message: %s",
base.StatusCode, base.StatusMessage)
return nil
}
if base.GetStatusCode() != constant.ErrCodeSuccess {
logger.Errorf(ctx, "[Wukong][checkBaseResp] failed. code: %d, message: %s",
base.StatusCode, base.StatusMessage)
return errors.New("wukong checkBaseResp failed")
}
return nil
}
func (mr *MasterDataRPC) AggregateTimeline(ctx context.Context, request *readmodel.AggregateTimelineRequest) (*readmodel.AggregateTimelineResponse, error) {
ctx = BuildOpenAPIMGGrayContext(ctx, constant.OpenAPIMethodAggregateTimeline)
resp, err := client.AggregateTimeline(ctx, request)
if err != nil || resp == nil {
logger.Errorf(ctx, "[AggregateTimeline] err=%s", err.Error())
return nil, err
}
err = checkBaseResp(ctx, resp.BaseResp)
if err != nil {
logger.Errorf(ctx, "[AggregateTimeline] checkBaseResp failed")
return nil, err
}
return resp, nil
}
代码:
logger.Errorf(ctx, "[Wukong][checkBaseResp] failed. code: %d, message: %s",
base.StatusCode, base.StatusMessage)
logger.Errorf(ctx, "[wukong][checkBaseResp] base response illegal")
logger.Errorf(ctx, "[AggregateTimeline] checkBaseResp failed")
问题:同一错误在不同日志中重复打印
【建议】同一错误在调用链中仅打印一次错误日日志
问题代码:
logger.Infof(ctx, "[NotifyEventHandler][HandleMessage] Start to HandleMessage Before unmarshal")
logrus.Warnf("[GetPkgIDByDptID] can't find pkgID,dptID:%s", DptID)
问题:
案例一:处理MQ消息的入口日志却未打印MQ消息的唯一标记MsgID
案例二:日志打印时未传入ctx导致日志缺乏logID,无法通过logID检索到该日志 案例三:在程序的关键分岔点未打印日志
案例四:在特殊的条件分岔未打印日志 【强制】关键日志必须打印路径,打印日志必须带上关键信息 【强制】日志打印时必须携带logID