log 就像车辆保险, 没人愿意为保险付钱, 但是一旦出了问题谁都又想有保险可用
几乎学习每一门语言, 都是从 "hello world" 开始的, 日志是新手程序员学习和调试的一大利器. 当项目上线之后, 也会有各种各样的日志. 可以是记录用户的行为, 服务器的状态, 服务器的异常等.
log
当某天你看到自己的控制台上打印出如下日志:
Error Happended
你觉的你会打日志吗?
当从 log 中看到这条日志的时候, 我们是知道程序出错了, 但是除了知道错了, 我们什么都不知道. 我自己也这么打过, 老板一问, 这个是出的什么问题? 能立刻解决吗? 顿时就傻了. 在我看来, 日志打印是一门艺术, 而且长期被特别是前端程序员忽视
如何打印有效日志, 有以下几个方面:
why to log
when to log
what to log
log and tips
# why to log
为什么要打印日志, 那就要从我们的目的出发, 我们是要检查问题, 还是要记录代码执行顺序, 还是要记录事件发生时间节点, 还是要提供报警机制信息等.
对于调试阶段, 大都是为了处理逻辑时序和问题发生节点; 对于线上项目, 为了准确定位并解决处理问题; 不存在没 bug 的程序, 高级程序员相比与普通程序员, 更多的优势在于他能写出易于维护的代码, 在合适的地方, 打印关键日志内容, 在不需要浏览程序代码的情况下, 便能准确定位出问题.
# when to log
(1) 调试开发日志
目的是开发期调试程序使用, 这种日志量比较大, 且没有什么实质性的意义, 只应该出现在开发期, 而不应该在项目上线之后输出.
(2) 用户行为日志
这种类型的日志, 记录用户的操作行为, 用于大数据分析, 比如监控, 风控, 推荐等等. 这种日志, 一般是给其他团队分析使用, 而且可能是多个团队, 因此一般会有一定的格式要求, 开发者应该按照这个格式来记录, 便于其他团队的使用. 当然, 要记录哪些行为, 操作, 一般也是约定好的, 因此, 开发者主要是执行的角色. 这种日志, 又叫事件上报, 或埋点.
(3)程序运行日志
记录程序的运行状况, 特别是非预期的行为, 异常情况, 这种日志, 主要是给开发, 维护人员使用. 什么时候记录, 记录什么内容, 完全取决于开发人员, 开发者具有高度自主性. 本文讨论的主要也是指这种类型的日志, 因为作为一个服务端开发, 运维人员, 程序运行日志往往是解决线上问题的救命稻草.
(4)记录系统或者机器的状态
比如网络请求, 系统 CPU, 内存, IO 使用情况等等, 这种日志主要是给运维人员使用, 生成各种更直观的展现形式, 在系统出问题的时候报警.
# what to log
一条信息完整的日志, 应该包含
when,where,level,what,who,context
;
我们要记录日志, 总是要在某个时机, 比如用户的某个请求, 某个网络调用, 或者内部状态发生了改变. 在后文中, 统称为事件(event), 即日志内容是对某个事件的描述.
when: the time event happens
when, 就是我们打印日志的时间(时间戳), 注意这里的时间指的是日志记录的事件的发生时间, 而不是日志被最终输出的时间.
where: where the event happens
where, 就是指日志是在哪里的被记录的, 本质上来说, 是事件的产生地点. 根据情况, 可以具体到是哪个模块, 哪个文件, 甚至是哪一个函数, 哪一行代码; 目的是为了能一眼就看出日志在哪里产生的, 而不去 grep 代码.
level:how importance of the event
每一条日志都应该有 log level,log level 代表了日志的重要性, 紧急程度. 例如:
debug,info,warn,error,fatal(critical)
; 调试日志是最不重要的, 是不应该出现在线上项目的, 但是程序运行报错日志却需要认真对待, 因为代表程序已经出现了异常; 如果是 fatal 日志, 即使是在大半夜, 也得立刻起来分析, 处理.
what:what is the log message
what 是日志的主体内容, 应该简明扼要的描述发生的什么事情. 要求可以通过日志本身, 而不是重新阅读产生日志的代码, 来大致搞清楚发生了什么事情. 下面这个日志是不合格的:
def user_login(username, password):
if not valid_username(username):
logger.warn('Error Happened')
return
# some others code
是的, 我知道, 出了问题了, 但是日志应该告诉我出了什么问题, 所以日志至少应该是这样的:
def user_login(username, password):
if not valid_username(username):
logger.warn('user_login failed due to unvalid_username')
return
# some others code
who:the uniq identify
who 代表了事件产生者的唯一标识(identity), 用于区分同样的事件. 特别是在服务器端, 都是大量用户, 请求的并发, 如果日志内容不包含唯一标识信息, 那么这条日志就会淹没在茫茫大海中, 比如下面这条日志:
def user_login(username, password):
# some code has check the username
if not valid_password(password) or not check_password(username, password):
logger.warn('user_login failed due to password')
return
# some others code
有效的日志应该是包含用户信息的:
def user_login(username, password):
# some code has check the username
if not valid_password(password) or not check_password(username, password):
logger.warn('user_login failed due to password, username %s', username)
return
当然, 这个唯一标识是很广泛的, 需要根据具体情况决定, 如果网络请求, 可能更好的是
requestid,sessionid
; 如果是系统日志, 那么可能是进程, 线程 ID; 如果是分布式集群, 那么可能是副本的唯一 id.
context: environment when event happens
日志记录的事件发生的上下文环境直观重要, 能告知事件是在什么样的情况发生的. 当然, 上面提到的 when,where,who 都属于上下文, 这些都是固定的, 通用的. context 专指高度依赖于具体的日志内容的信息, 这些信息, 是用于定位问题的具体原因
def user_login(username, password):
# some code has check the username
if not valid_password(password) or not check_password(username, password):
logger.warn('user_login failed due to password, username %s', username)
return
# some others code
如上代码只能知道出什么错误了, 但是并不能知道当时出错误的上下文环境, 因此还是不足. 这部分弹性最大, 需要结合具体项目思考加上什么内容能定位到问题发生的原因.
# log and tips
如何输出日志? 单纯的 console.log() 并不能满足我们的需求, 可以引入框架, 常见的 log 框架有 glog,log4cpp,log4j,elf4j;
在很多 logging 框架模块 (thon logging,log4j) 中, 都有 named logger 这一概念, 这个 name 就可以是
module, filename, classname
或者 instance, 这就解决了上一章节提到了 "where the event happen" 这个问题.
(1)设置 log 的输出等级
这样可以不改程序代码, 仅仅修改 log 的输出等级, 就能够控制哪些日志输出, 哪些日志不输出. 比如我们在开发期的调试日志, 都可以设置为 DEBUG, 上线的时候设置输出等级为 INFO, 那么这些调试日志就不会被输出.
(2)设置每条日志默认包含的内容
设置每条日志默认包含哪些信息, 比如时间, 文件, 行号, 进程, 线程信息
(3)设置日志的输出目标
通过配置, 可以指定日志是输出到 stdout, 还是文件, 还是网络. 特别是在 linux 服务器上, 将日志输出到 syslog, 再使用 syslog 强大的处理, 分发功能, 配合 elk 系统进行分析, 是很多应用程序的通用做法.
# log never throw
打印日志, 是为了记录事故发生的现场, 以便发现问题, 解决问题. 那么就得保证, 打印日志这一行为本身不能引入新的问题, 比如, 不能出错抛异常. 这就好比, 处理车祸的消防车不能冲进现场一样.
# log when u think something never happen
不能认为某种情况一定不会发生, 因为程序在高并发下和网络本省的不确定性, 它还是可能发生, 如果这个地方属于关键代码, 应该就为 error 级别的错误, 也经常就是因为这种地方, 让查 bug 时候的你, 有种想骂娘的冲动.
# 总结
打日志的时候总是很随意, 至于查 bug 的时候恨不得 "手撕鬼子", 为了自己的人身安全, 建议还是好好写每一条日志吧!
不对的地方, 欢迎指正.
参考
Distributed systems for fun and profit
the-art-of-logging-advanced-message-formatting
来源: http://www.jianshu.com/p/d763218f9aaa