记录日志有哪些好的技巧?

栏目: Java · 发布时间: 6年前

内容简介:在软件开发中,我们出于各种目的,需要将程序运行中的一些状态记录在日志中。日志记录,并不是越多越好,也不是记录的越频繁越好,而是需要我们精心设计记录日志的时机、内容、格式(以方便后续解析、查询日志)等等。本文简单介绍了记录日志的一些基本原则和注意事项,更具体的记日志的技巧、最佳实践、日志框架等请参考子文档

一、简介

在软件开发中,我们出于各种目的,需要将程序运行中的一些状态记录在日志中。

日志记录,并不是越多越好,也不是记录的越频繁越好,而是需要我们精心设计记录日志的时机、内容、格式(以方便后续解析、查询日志)等等。

本文简单介绍了记录日志的一些基本原则和注意事项,更具体的记日志的技巧、最佳实践、日志框架等请参考子文档 Go如何记录日志

二、记录日志的目的(why)

开发调试

目的是开发期调试程序使用,这种日志量比较大,且没有什么实质性的意义,只应该出现在开发期,而不应该在项目上线之后输出。

记录用户行为

这种类型的日志,记录用户的操作行为,用于大数据分析,比如监控、风控、推荐等等。这种日志,一般是给其他团队分析使用,而且可能是多个团队,因此一般会有一定的格式要求,开发者应该按照这个格式来记录,便于其他团队的使用。当然,要记录哪些行为、操作,一般也是约定好的,因此,开发者主要是执行的角色。

程序运行状况

记录程序的运行状况,特别是非预期的行为、异常情况,这种日志,主要是给开发、维护人员使用。什么时候记录,记录什么内容,完全取决于开发人员,开发者具有高度自主性。本文讨论的主要也是指这种类型的日志,因为作为一个服务端开发、运维人员,程序运行日志往往是解决线上问题的救命稻草。

系统、机器状况

比如网络请求、系统CPU、内存、IO使用情况等等,这种日志主要是给运维人员使用,生成各种更直观的展现形式,在系统出问题的时候报警。

三、日志的要素(what)

每条日志都可以被当作一个事件(event),纪录了该事件发生时各个信息:

时间

日志的时间可以包含多种含义,不同含义的时间传递不同的信息:

  1. 指事件发生的时间,而不是日志被打印的时间。该时间附近范围内,结合该事件及服务器的网络、CPU、IO等状况,可以了解他们之间的相关性,有助于分析事件发生的原因。
  2. 持续时间。例如网络请求的耗时、处理请求的各个阶段的耗时等。
  3. 事件发生的顺序。通过时间戳的顺序,了解到一系列事件的发生顺序。对多进程、多线程、分布式系统有帮助。

位置

事件发生在哪个模块、哪个文件、哪个函数、哪一行代码里。

级别

日志的重要程度。用于:

  1. 不同的环境(测试、生产)下,打印不同级别的日志
  2. 不同级别的日志产生不同级别的监控报警

内容

简明扼要的描述发生了什么样的事情。目的是通过日志本身,而不是重新阅读相关代码来搞清楚发生了什么事情。

例如:logger.warn('user_login failed due to unvalid_username')

唯一标识

不管是面向用户的服务、面向机器集群的服务,都需要一个唯一标识作为日志的主体,以方便查找该事件主体的其他信息。(很多元数据是不会记在日志里的,只会记一个唯一标识)

例如:logger.warn('user_login failed due to password, username %s', username)

事件上下文

除了时间、位置、级别、内容,其他的一些有用的信息。

例如:

  1. logger.warn('user_login failed due to wrong password, username %s', username)
  2. logger.warn('user_login failed due to invalid password, username %s', username)
  3. logger.warn('user_login failed due to empty password, username %s', username)
    为了获取更丰富的上下文,有些数据需要从 Nginx 那里获取并传给本服务,然后打印。参考: 和日志相关的Nginx设置

格式化

将上述的各个信息按照固定的顺序打印出来,不仅方便查找(例如使用grep,sed,awk等),也方便收集日志的程序(ES)解析日志。

其他

根据各自的业务特点,还可以在日志中记录(包括、不限于):

  1. 错误次数
  2. 当前正在处理的请求数
  3. 处理的进度(33%,50%,78%。。。)
  4. IP
  5. 问题出现时的请求链接

四、记录日志的一些原则和技巧

使用框架或模块

Java : Log4jLog4j2Commons LoggingSlf4jLogback

Python 内置的 logging 模块

beego 框架里的 github.com/astaxie/beego/logs 模块

其他的一些 Go 的框架:

  1. Logrus: github.com/Sirupsen/logrus (Docker use this)
  2. github.com/op/go-logging
  3. github.com/golang/glog (from Google, implementation of their C++ glog library in Go)
  4. github.com/cihub/seelog

不能出错

记录日志的目的是为了方便发现问题,解决问题,那么就需要保证记录日志本身不能出错。

尤其是对于 Error、Fatal级别的日志,出现的概率低,所以要做好单元测试,以保证记录日志本身是没问题的,是不会影响正常业务的。

避免敏感信息

  1. 避免记录用户密码。
  2. 避免记录用户个人信息,如身份证号、手机号等。应该只在日志里记录该用户的唯一标识,然后根据该唯一标识去其他的系统(例如数据库)查看详细信息。

记录“不可能发生”的事件

虽然正常逻辑下,某些情况是永远都不可能发生的,但是还是需要给这些不可能发生的情况打印一条日志。

例如 条件语句里的 else,switch 里的 default,都需要进行防御式的编程,同时记录日志。

Lazy logging

日志本身是一个字符串,可以通过多种方式拼接而成。如果根据log level,该条日志不应被打印,那么就应该 避免 拼接这个操作,也应该 避免 字符串拼接里的函数的调用。

例如:

#coding=utf-8
import logging

logger = logging.getLogger('LazyLogging')
logger.setLevel(logging.DEBUG)
hander = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
hander.setFormatter(formatter) 
logger.addHandler(hander)

def getUserCount():
    logger.info('getUserCount is called')
    return 1

logger.debug("There are " + str(getUserCount()) + " users logged in now.")
  1. log level 设置为 DEBUG(第5行),那么会先打印第12行,再打印第15行
  2. log level 设置为 INFO,依然会打印第12行,说明:
    1. 问题1: 即便没有打印第15行,依然生成了一个字符串
    2. 问题2: 而且调用了 getUserCount 这个函数
  3. 解决上述的问题1,可以把第15行修改为:
logger.debug("There are %s users logged in now.", getUserCount())
  1. 为了解决上述的问题2,一个办法是:
class lazyEval:
    def __init__(self, f, *args):
        self.func = f
        self.args = args

    def __str__(self):
        return str(self.func(*self.args))

logger.debug("There are %s users logged in now.", lazyEval(getUserCount))

Java里的方式:

logger.debug(``"There are {} users logged in now."``, () -> getUserCount());

目前Golang里也存在这样的问题,但是还没找到怎么做到 lazyLogging。

但是,可以看出上面的方式也不优雅,所以应当 避免 在打印日志时调用函数。

异步打印日志

互联网应用程序中,高并发下的写日志会带来大量的IO操作,从而影响正常服务的性能。这时候的记日志就需要专门的服务去做,例如把日志打到消息队列里,然后再写到磁盘上。

设置缓存

默认的日志是随时 flush 到console、文件里的,通过设置缓存进行批量操作,可以一定程度上优化服务性能。

对日志归档、分类

打印到一个文件里,会使得该文件越来越大,不方便查找。

按日志属性分类:access log,error log,

按日期归档:lathspell-api.2018-01-17.log,lathspell-api.2018-01-18.log

按大小切分:lathspell-api.2018-01-17.log.1,lathspell-api.2018-01-17.log.2,lathspell-api.2018-01-17.log.3 (避免文件太大)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

.NET设计规范

.NET设计规范

克瓦林纳 / 葛子昴 / 人民邮电出版社 / 2006-7 / 49.00元

本书为框架设计师和广大开发人员设计高质量的软件提供了权威的指南。书中介绍了在设计框架时的最佳实践,提供了自顶向下的规范,其中所描述的规范普遍适用于规模不同、可重用程度不同的框架和软件。这些规范历经.net框架三个版本的长期开发,凝聚了数千名开发人员的经验和智慧。微软的各开发组正在使用这些规范开发下一代影响世界的软件产品。. 本书适用于框架设计师以及相关的专业技术人员,也适用于高等院校相关专业......一起来看看 《.NET设计规范》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换