前言

在软件开发过程中,log往往是不太引人注意的环节,大部分的log都只是开发人员为了调试bug临时加上的。这样出来的软件,最后的log往往杂乱无章没有系统性。我们判断一个软件系统的log写的好不好,可以通过以下几点:

  • 当软件交付给其他人使用出现bug,他们是否能够凭借log对问题有一个比较准确的推断。
  • 当开发者解决bug的时候,通过更改不同的log级别是否可以快速准确的定位具体问题所在。
  • 当软件生产环境运行时,运维人员能否通过看log就大致了解系统状况和工作情况。

好的log往往各不相同,但是不好的log基本特点都是一样的:

  • 描述不清,杂乱无章,大量临时添加的log语句在其中。比如web server log里常见的一行"conntected",这往往是开发人员开发时为了验证连接是否成功加上的。而且在这个log前后往往还会出现"trying to connect", "connecting ...",以及"connected to ..."之类的重复。开发人员为了着急完成功能,往往会加入大量这样的log,但是这些log在系统中留下了杂乱的痕迹。

    我们应该尽可能准确丰富的用log表达系统行为,对于刚刚的例子,应该在刚开始连接时加入一个"debug"级别的"connecting Database/Web Admin Server/.. ...",这里不仅描述了连接url,还描述了连接的server的业务功能,这样如果有很多个连接建立时,log里可以清晰分辨,定义为"debug"级别是因为我们只需要在开发过程和调试bug时知道有这个行为即可,在生产环境中不需要反复提示。然后我们在连接的错误处理中打连接失败的log,"connection to Database/Web Admin Server .. failed, due to ..."。通常情况下,连接成功不用再打log了,因为系统会进行下一步操作。当然也可以加一个“debug”级别的“connection to Database/Web Admin Server .. established"

  • 在真正调试bug时,又需要东加一行log,西删一行log。当开发的软件出现问题时,开发人员往往发现以前写的log都用不上,解决问题需要添加很多log才行,而且还要删除很多其它的log免得log太杂乱看不清。这个其实是开发人员的经验不足,在开发过程中预计不到可能出问题的点和解决问题可能需要的条件。比如在写一个与http server以json格式通信的模块时,我们可以先用”debug“级别写一个”send [function] request to Database/Admin server", 这里用“debug”级别同样因为在生产环境时我们可以方便的关掉它,而在debug级别我们又充分描述了系统的行为。但是这样往往是不够的,我们经常会需要了解发送的具体内容进行问题诊断, 所以我们可以在后面加入一个log语句,如果有,最好用更细的级别比如"trival"之类的,记录下log("trival", "request content : " + request.body)。这样我们可以随时把log级别调到trival来详细诊断问题。等等,这样就够了么?其实还不够,如果这么写log,真出先问题的时候你会发现request content非常长,根本不能直观的来观察内容。最好的办法,我们在这里把request.body按照一定的缩进格式打印,变成JSON.stringify(request.body, null, 4),这样的log就非常直观明了了。

  • 层叠的Exception。首先明确一点,Exception是用来帮助解决未知问题的。所以它会有展开的调用栈让我们知道问题出在哪里。而如果一个已经写好的软件系统,是不应该还有未知问题出现的,如果出现了,应该作为bug尽快fix。对于一个完成的软件系统,所有的Exception都应该是被有预计的catch并且处理,然后在处理这个Exception的那一层,用简单明了的log记录下来。

      注:接下来的内容为个人见解,请每个读者独立思考合理和不合理之处。

在写log的时候,我们需要结合以下三点来考虑:

  • 这个log在系统工作过程中的出现次数
  • 这个log适合的级别(生产级别,开发级别和调试级别),对整体log的影响。
  • 这个log能表达的信息量,是否跟其它log有重复,是否可以在其它位置写表达更丰富的信息

接下来我们以一个Web系统为例子讲一下log的写法

启动

启动时候,系统需要读取一些配置文件,这个时候需要打印一些读取到的参数之类的,结合上面三点,它的出现次数一般就一次,因此对于级别没有什么要求,就写成info级别即可,表达信息时注意描述准确。

收到请求

在收到请求时,我们可以考虑打一条log表示收到,但是如果系统通讯量很大,这个log的出现次数太多,因此不适合于info级别(info一般是用在生产环境中),通常会定义成debug级别。另外,根据刚刚讲到的,我们经常会需要把收到的request内容打在log中,但是如果把内容整个打印出来会很占篇幅,如果我们是在定位问题和解决其它问题时,这个log干扰有点大。因此我们用debug级别来仅仅描述收到了request,另外再用一个更精细的级别来把请求内容打印出来。

业务逻辑

收到请求后,我们通常要开始业务逻辑,这里参考上面的办法,用”debug"来简要描述各个业务的关键点,而用更细的级别来描述每个点所涉及到的具体数据。注意,info级别因为用在生产环境中,我们通常只需要用info级别描述结果即可,不要用info去描述过程。

错误处理

错误处理有一条很基本的原则:谁处理错误谁打log。这样可以避免大量重复内容的log出现,并且按照前面所说,错误的栈信息通常是不用打印的,处理错误的函数应该能用准确的语言归纳描述错误原因和处理办法。

后记

软件系统好比一个妹子,功能,UI之类的是妹子的外表,往往会第一时间吸引人。但是用户体验(...为毛我有奇怪的联想),log这些东西是内在的品性。这些是真正能让人觉得好用(....呃)的点,也是能真正体现出设计者内功差异的地方。

好吧,你就是只看外表,不求朝朝暮暮就要一晌之欢?。。呃......

有关写log的思考的更多相关文章

  1. 关于Quartz.NET作业调度框架的一点小小的封装,实现伪AOP写LOG功能

    Quartz.NET是一个非常强大的作业调度框架,适用于各种定时执行的业务处理等,类似于WINDOWS自带的任务计划程序,其中运用Cron表达式来实现各种定时触发条件是我认为最为惊喜的地方. Quar ...

  2. 基于.Net Framework 4.0 Web API开发(3):ASP.NET Web APIs 异常的统一处理Attribute 和统一写Log 的Attribute的实现

    概述:  ASP.NET Web API 的好用使用过的都知道,没有复杂的配置文件,一个简单的ApiController加上需要的Action就能工作.但是项目,总有异常发生,本节就来谈谈API的异常 ...

  3. Java 写 Log

    . 一个最基本的例子 使用Logging框架写Log基本上就三个步骤 引入loggerg类和logger工厂类 声明logger 记录日志 下面看一个例子 //1. 引入slf4j接口的Logger和 ...

  4. PHP实现写LOG日志的代码

    这篇文章给大家介绍的内容是关于PHP实现写LOG日志的代码,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. public function write_log(){ //设置目录时间 ...

  5. Windows计划任务无法写Log的问题

    参照:https://www.cnblogs.com/jonezzz/p/10364153.html 使用WIndows计划任务去执行Exe文件时无法写Log,而Exe双击执行就能写Log,这是由于计 ...

  6. 现象:当指定logback的FileNamePattern为日期2020-01-15后,如果有线程不断的往里写log,过了零点文件不会变成下一日2020-01-16,还是会在2020-01-15里继续写 结论:写log的线程不停,文件不会按日子更换。

    logback版本:1.1.11 这个是我实验验证的,昨天我配置了一个logback,然后用两个线程不断往里写log,结果发现到了今天2020-01-16日,log文件还是昨天的logbackCfg. ...

  7. Nginx 配置日志路径(nginx.conf没有写log路径,所以debug的时候找不到日志)

    缘由:nginx.conf没有写log路径,所以debug的时候找不到日志,遂在conf文件里写入了log路径 Setp1.nginx默认日志路径: /var/log/nginx Setp2.conf ...

  8. 使用Mono Cecil 动态获取运行时数据 (Atribute形式 进行注入 用于写Log) [此文报考 xxx is declared in another module and needs to be imported的解决方法]-摘自网络

    目录 一:普通写法 二:注入定义 三:Weave函数 四:参数构造 五:业务编写 六:注入调用 7.  怎么调用别的程序集的方法示例 8. [is declared in another module ...

  9. svn强制commit写log

    https://tortoisesvn.net/docs/nightly/TortoiseSVN_en/tsvn-howto-minlogmsgsize.html Force users to ent ...

随机推荐

  1. git +vs2017 操作手册+目前工作流程图

    分支名称介绍及命名规则: 测试分支:master 线上稳定分支:master-发布分支 功能分支:命名命名规则:V版本号-开发者姓名-功能名. 紧急修复分支:命名规则:fixbug-版本号-具体问题名 ...

  2. zookeeper Java API 简单操作示例

    本文主要介绍如何在java IDE中如何应用使用客户端与zookeeper服务器通信. 首先搭建maven环境,并在pom文件中加入zookeeper引用包: <!-- https://mvnr ...

  3. Win10-64位 免安装版Mysql8下载安装运行

    今天忙活了很久去下载安装Mysql,感觉网上的那些教程怎么都对不上呢,很奇怪,不过我乱点一通至少能用了,先凑和着用吧... 记录一下, 要是不对的,以后再修改...windows10系统 2018-5 ...

  4. Powershell Get-ChildItem 筛选文件,文件处理

    使用Where-Object也可以根据其它属性来过滤. Dir | Where-Object { $_.CreationTime -gt [datetime]::Parse("May 12, ...

  5. tcp连接是基于socket通信的吗

    https://zhidao.baidu.com/question/1305788160020716299.html ------ 网络七层协议 五层模型 TCP连接 HTTP连接 socket套接字 ...

  6. 阻塞IO、非阻塞IO、同步IO、异步IO等

    https://www.cnblogs.com/zingp/p/6863170.html 阅读目录 1 基础知识回顾 2 I/O模式 3 事件驱动编程模型 4 select/poll/epoll的区别 ...

  7. Java 之综合练习

    // 练习一: 写出程序结果 interface A{} class B implements A { public String func() { return "func"; ...

  8. MySQL前后台交互登录系统设计

    1.首先我们做一个前台的注册页面 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"& ...

  9. 洛谷 P2481 [SDOI2010]代码拍卖会

    洛谷 这大概是我真正意义上的第一道黑题吧! 自己想出了一个大概,状态转移方程打错了一点点,最后还是得看题解. 一句话题意:求出有多少个\(n\)位的数,满足各个位置上的数字从左到右不下降,且被\(p\ ...

  10. time 模块,random模块,os模块

    一 :time 模块 python中,通常有几种方式来表示时间: 时间戳(timestamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量.我们运行“type(t ...