通过代码打印一条log信息一点也不难,我们从写第一行C语言代码就学会了,如:


printf("hello world !");


但是在实际的项目开发中,却并不是这么简单,如果一个上万行代码的项目全部都用这种最简单的方式来输出log,会带来很多的问题,比如:


(1) 软件发布的时候,太多的log输出会带来很多问题,例如影响系统性能、泄露不必要的信息等等,所以我们必须考虑,代码是否能轻易打开和关闭log输出?


(2) 代码是否可以做到只关闭一般的log信息输出,但依然保留一些“错误信息”的输出?


(3) 如何判断代码输出的某一条log信息是来自哪个文件、哪一行、哪个函数?


(4) 调试代码的过程中能否分模块地查看log信息?


这就是真实的项目开发与自己写demo的不同,我们需要站在产品的角度考虑更多的问题,并使我们的代码写得更加“专业”。这也是我写博文的目的,希望能给“初学者”带来一些“项目实战”中的编程经验,而这些经验仅仅从课本是得不到的。


言归正传,我们来解决一下上面的问题,一起构建一个稍微“专业”一点的log系统吧。


任何一种编程语言或者开发平台,都有自己的Log输出接口,比如C语言的printf,C++中的cout,Shell脚本中的echo,Android的"Log类",真正的项目开发中,我们都应该基于这些基本的输出接口进行一层“封装”,以构建一个可以控制的Log系统,这里,我以C语言为例,给出一份典型的通过宏定义方式控制的Log系统代码,希望大家能够通过阅读这份代码,更清楚地理解这样一套Log系统该如何实现。


下面是一份 syslog.h 文件,对C语言的printf函数进行了封装,其他的模块代码可以通过包含该头文件,使用 “LOGD,LOGI,LOGE”这三个宏,来分别打印“调试信息”,“普通信息”和“错误信息”。


(注:本文件可以在博文最后的附件中下载,也可以到我的Github下载:地址


/*
 *  COPYRIGHT NOTICE  
 *  Copyright (C) 2015, Jhuster, All Rights Reserved
 *  Author:  Jhuster(lujun.hust@gmail.com)
 *  
 *  https://github.com/Jhuster
 *   
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.  
 */
#ifndef _SYS_LOG_
#define _SYS_LOG_

#include <stdio.h>

#define SYS_LOG_LEVEL_CLOSE 0
#define SYS_LOG_LEVEL_ERROR 1
#define SYS_LOG_LEVEL_INFO  2
#define SYS_LOG_LEVEL_DEBUG 3

//#define SYS_LOG_LEVEL SYS_LOG_LEVEL_ERROR
//#define SYS_LOG_DETAIL

#ifndef SYS_LOG_TAG
#define SYS_LOG_TAG "SYSLOG"
#endif 

#ifndef SYS_LOG_LEVEL
#define SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#endif

#ifndef SYS_LOG_DETAIL
#define PRINT(format,...) printf("%s: "format,SYS_LOG_TAG,##__VA_ARGS__);
#else
#define PRINT(format,...) printf("%s %s: [%s,%s,%d]: %s: " format,__DATE__,__TIME__,__FILE__,__FUNCTION__,__LINE__,SYS_LOG_TAG,##__VA_ARGS__); 
#endif

#if SYS_LOG_LEVEL >= SYS_LOG_LEVEL_DEBUG
#define LOGD(format,...) PRINT(format,##__VA_ARGS__)
#else
#define LOGD(format,...) 
#endif

#if SYS_LOG_LEVEL >= SYS_LOG_LEVEL_INFO
#define LOGI(format,...) PRINT(format,##__VA_ARGS__)
#else
#define LOGI(format,...) 
#endif

#if SYS_LOG_LEVEL >= SYS_LOG_LEVEL_ERROR
#define LOGE(format,...) PRINT(format,##__VA_ARGS__)
#else
#define LOGE(format,...) 
#endif

#endif //_SYS_LOG_


这份基于C语言的Log系统主要有如下特性:


(1) 支持开启/关闭Log信息


通过 SYS_LOG_LEVEL 宏的定义,可以控制Log输出开启和关闭,当该值定义为 SYS_LOG_LEVEL_CLOSE时,将关闭所有的Log输出。


(2) 支持多种Log输出级别


通过 SYS_LOG_LEVEL 宏的定义,可以修改输出的Log级别,对于Release版本,可以将 SYS_LOG_LEVEL 定义为 SYS_LOG_LEVEL_ERROR,这样,除了 LOGE 打印出的错误信息外,其他的Log信息都不会输出。


(3) 支持输出Log信息所在的具体位置


当开启了 SYS_LOG_DETAIL 宏之后,输出的Log信息中将包含详细的信息,包括:Log产生的日期时间,文件名,函数名,行数等信息。


(4) 支持通过TAG标签分类不同模块的Log信息


不同的模块可以定义不同的 SYS_LOG_TAG,这样,在系统调试的时候,可以通过 grep TAG 的方式过滤其他模块的Log信息,更加专注地分析本模块的问题。


使用方法:


//main.c

#define SYS_LOG_TAG "ticktick"  //定义本模块的TAG

#include "syslog.h"

void main() {

    LOGD("hello world\n"); //打印调试信息,可以通过修改"syslog.h"的"SYS_LOG_LEVEL"来控制是否输出
    
    LOGE("This is an error message!\n"); //打印错误信息
}


具体代码中宏定义的原理我就不详细论述了,我更希望从“思想”上告诉大家,开始编写真实的实战项目前,请先定义好一套“专业”的Log系统,这样,会为自己将来的调试和产品发布带来无尽的好处。代码中有不清楚的地方欢迎留言讨论或者来信lujun.hust@gmail.com交流,或者关注我的新浪微博 @卢_俊 获取最新的文章和资讯。。