C 定制Log

前言:

一个 C 工程,在大多数时候我们需要定制打印函数,而不再仅仅使用printf。

对于一个工程,常见的需求有:

  1. 可以控制 打印级别;
  2. 控制分模块打印;
  3. 在打印头添加 __FILE__、__FUNCTION__、__LINE__ 等信息;
  4. 在打印头添加 时间信息;
  5. 日志直接输出到文件;
  6. 通过配置文件初始化打印级别 。。。

 

对于整个Log模块,我们可以使用面向对象的思想,使用 log.c 和 log.h 这两个文件来负责关于 Log 的所有工作。

以下,我们来看看怎么实现这样的定制功能:

1、控制打印级别

控制打印级别有很多方法,介绍一下其中的2种

// log.h

#define LOG_LEVEL 3
#if (LOG_LEVEL < 0 || LOG_LEVEL > 5)
#error "LOG_LEVEL should be between 0 and 5(include 0 and 5)"
#endif

/**************************App debug output control*******************************/
#define LOG_LEVEL LOG_LEVEL
#define log_p(...)
#define log_i(...)
#define log_d(...)
#define log_w(...)
#define log_e(...)

#if (LOG_LEVEL > 0)
#undef log_p
#define log_p(...) printf("[LOG_PRINT] "__VA_ARGS__)
    #if (LOG_LEVEL > 1)
    #undef log_i
    #define log_i(...) printf("[LOG_INFO] "__VA_ARGS__)
        #if (LOG_LEVEL > 2)
        #undef log_d
        #define log_d(...) printf("[LOG_DEBUG] "__VA_ARGS__)
            #if (LOG_LEVEL > 3)
            #undef log_w
            #define log_w(...) printf("[LOG_WARNING] "__VA_ARGS__)
                #if (LOG_LEVEL > 4)
                #undef log_e
                #define log_e(...) printf("[LOG_ERROR] "__VA_ARGS__)
                #endif
            #endif
        #endif
    #endif
#endif

这个方法实现了,控制大于某个级别才会输出,而且在每句打印开头都加了级别说明。

此方法只需要 log.h 就能实现控制输出级别了,这里在设置 LOG_LEVEL 的时候,用了一种宏检测技术,当值超过我们的限定的时候,#error 会中止编译。

这里通过 LOG_LEVEL 来控制打印级别,首先定义所有的打印函数为空,但是对于大于 LOG_LEVEL 的打印级别会被重新定义。

"..."是 C 语言中的变长参数表,后面使用宏 __VA_ARGS__ 来接收,相当于在每个打印前加了相应的级别说明。

不足:控制级别只能是从低到高,不可跳级设置;得使用多个不同的函数。

// log.h
#ifndef __LOG_H__
#define __LOG_H__

#include <stdio.h>

typedef enum
{
      LOG_INFO  = 0x01,
      LOG_DEBUG = 0x02,
      LOG_WARNING = 0x04,
      LOG_ERROR = 0x08,

      LOG_ALL   = 0xff
}LOG_TAG;

void set_level(const unsigned char level);                 //设置打印级别
void print(const unsigned char level,const char *, ...);   //带级别的打印

#endif
// log.c
#include "log.h"
#include <stdarg.h>
static unsigned char g_level = 0;

void set_level(const unsigned char level)
{
      g_level = level;
}

void print(const unsigned char level,const char *va_alist, ...)
{
      if(!(level & g_level))
            return ;

      va_list ap;

      switch (level & g_level)
      {
            case LOG_INFO:
                  printf("[INFO] ");
                  break;

            case LOG_DEBUG:
                  printf("[DEBUG] ");
                  break;

            case LOG_WARNING:
                  printf("[WARNING] ");
                  break;

            case LOG_ERROR:
                  printf("[ERROR] ");
                  break;

            default:
                  return ;
      }

      va_start(ap, va_alist);
      vfprintf(stdout, va_alist, ap);
      va_end(ap);
      return ;
}

这种方法实现了,可配置任意输出级别而不限于大小,带有打印级别说明。

此方法使用单个函数实现了级别控制,打印级别作为参数传入。

LOG_TAG 的数据类型为一个 8bit 的 unsigned char 型,每一个级别对应一个位。所以在可以直接通过 位与 的结果来判断是否设置为打印。 

另外,在对 "..." 的解析中,使用 va_list 来解析。va_start 使 ap 指向可变参数表中的第一个参数;vfprintf 将打印的字符输出到标准输出;va_end 将 ap 赋0,使它不指向内存的变量。

不足:级别数有限,此处最高7级,依据数据位数而定。

 

2、分模块输出

分模块的原理和分级别的原理基本相同,可以将两者结合起来,实现更细分的控制。

// log.h
#ifndef __LOG_H__
#define __LOG_H__

#include <stdio.h>

typedef unsigned int uint;
typedef unsigned char uchar;

typedef enum
{
      LOG_M_MAIN = 0,
      LOG_M_INIT,
      LOG_M_SEVER,
      LOG_M_MEDIA,
      LOG_M_LED,

      LOG_M_MAX
}LOG_MODULE;

typedef enum
{
      LOG_T_INFO  = 0x01,
      LOG_T_DEBUG = 0x02,
      LOG_T_WARNING = 0x04,
      LOG_T_ERROR = 0x08,

      LOG_T_ALL   = 0xff
}LOG_TAG;

void log_set_level(const uint mod, const uchar level);             //设置打印级别
void log_p(const uint mod, const uchar level,const char *, ...);   //带级别的打印

#endif
// log.c
#include "log.h"
#include <stdarg.h>

static uchar g_log_modu_str[][20] =
{
      "M_MAIN",
      "M_INIT",
      "M_SEVER",
      "M_MEDIA",
      "M_LED",
      "M_MAX",
};

static uchar g_level[LOG_M_MAX] = {0};

#ifdef USE_SUB_MODULE
void log_set_level(const uint mod, const uchar level)
{
      if (mod >= LOG_M_MAX)
      {
            return ;
      }

      g_level[mod] = level;
      return ;
}

void log_p(const uint mod, const uchar level,const char *va_alist, ...)
{
      if (mod >= LOG_M_MAX || !(g_level[mod] & level))
      {
            return ;
      }

      va_list args;
      uchar buf[128] = {0};

      snprintf(buf, sizeof(buf), "[%s]", g_log_modu_str[mod]);
      switch (level & g_level[mod])
      {
            case LOG_T_INFO:
                  strcat(buf, "[T_INFO] ");
                  break;

            case LOG_T_DEBUG:
                  strcat(buf, "[T_DEBUG] ");
                  break;

            case LOG_T_WARNING:
                  strcat(buf, "[T_WARNING] ");
                  break;

            case LOG_T_ERROR:
                  strcat(buf, "[T_ERROR] ");
                  break;

            default:
                  return ;
      }

      fprintf(stdout, buf);

      va_start(args, va_alist);
      vfprintf(stdout, va_alist, args);
      va_end(args);

      fflush(stdout);

      return ;
}

此方法,除了设置级别外,还可以分模块输出。

 

3、在打印头添加 __FILE__、__FUNCTION__、__LINE__ 等信息

// log.h

#ifdef USE_LOG_MODULE
      #define log_fl(MOD, LEVEL, FORMAT, ARGS...) \
            log_p(MOD,LEVEL,"[%s][%s][%d] "FORMAT, __FILE__, __FUNCTION__, __LINE__, ##ARGS )
#else
      #define log_fl(LEVEL, FORMAT, ARGS...) \
            log_p(LEVEL,"[%s][%s][%d] "FORMAT, __FILE__, __FUNCTION__, __LINE__, ##ARGS )
#endif

在前面的基础上,如果要加上 文件名、函数名、行数 等信息的话,只需要在头文件声明一个宏即可,将信息分别对应着输入项。

对于前面的分模块和不分模块的打印,用 USE_LOG_MODULE 来区分。

 

4、在打印头添加 时间信息

时间大致有 墙上时钟时间、用户UTC时间、系统CPU时间。

这里以用户UTC时间为列:

// log.c

#include <sys/time.h>
#include <time.h>

#ifdef USE_LOG_TIME
      struct timeval tmval;
      struct tm tm_now = {0};
      time_t t_now = 0;

      gettimeofday(&tmval, NULL);
      t_now = tmval.tv_sec;
      localtime_r( &t_now, &tm_now);

      snprintf( buf, sizeof(buf)-1, "[%02d:%02d:%02d.%03d]",
          tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, tmval.tv_usec/1000 );
      fputs(buf, stderr);
#endif

 

5、日志直接输出到文件

即将原来的输出到终端,转为存储到文件。

// log.c
#define LOG_FILE_NAME "log.txt"

int file_write(char* buf, size_t buf_len, char* file_name, char* type)
{
      if ( buf == NULL || buf_len <= 0 ||\
       file_name == NULL || file_name[0] == 0)
      {
            return 0;
      }

      FILE *fd = fopen(file_name, type);
      if (fd == NULL)
      {
            perror("fopen");
            return 0;
      }

      fwrite(buf, 1, buf_len, fd);
      fclose(fd);
      return 1;
}

int log_write_file(char* buf, size_t buf_len, char* file_name)
{
      return file_write(buf, buf_len, file_name, "a+");
}

void log_to_file(const uint mod, const uchar level,const char *va_alist, ...)
{
      if (mod >= LOG_M_MAX || !(g_level[mod] & level))
      {
            return ;
      }

      va_list args;
      char buf[128] = {0};

      snprintf(buf, sizeof(buf), "[%s]", g_log_modu_str[mod]);
      switch (level & g_level[mod])
      {
            case LOG_T_INFO:
                  strcat(buf, "[T_INFO] ");
                  break;

            case LOG_T_DEBUG:
                  strcat(buf, "[T_DEBUG] ");
                  break;

            case LOG_T_WARNING:
                  strcat(buf, "[T_WARNING] ");
                  break;

            case LOG_T_ERROR:
                  strcat(buf, "[T_ERROR] ");
                  break;

            default:
                  return ;
      }

      log_write_file(buf, strlen(buf), LOG_FILE_NAME);

      va_start(args, va_alist);
      memset(buf, 0, sizeof(buf));
      vsnprintf(buf, sizeof(buf), va_alist, args);
      log_write_file(buf, strlen(buf), LOG_FILE_NAME);
      va_end(args);

      return ;
}

 

6、通过配置文件初始化打印

以带有模块的打印为例,配置文件命名为 log.ini:

M_MAIN = sfs
M_INIT =
M_SEVER = 8
M_MEDIA = 1
M_LED = 43
M_MAX = 2

格式是,模块名 = 等级

这个是测试用的文件,里面包含了一些错误配置,包括 等级为英文和等级非法的,缺省等级的。

// log.c

#define LOG_INI_FILE "log.ini"

void log_init(void)
{
      FILE *fd = fopen(LOG_INI_FILE, "r");
      if (fd == NULL)
      {
            perror("fopen LOG_INI_FILE");
            return ;
      }

      int level = 0;
      char buf[128] = {0};
      size_t read_len = 0, i, j;

      while (!feof(fd))
      {
            fgets(buf, sizeof(buf), fd);
            read_len = strlen(buf);

            for(i = 0; i <= LOG_M_MAX; i++)
            {
                  if(strstr(buf, g_log_modu_str[i]))
                  {
                        for(j = 0; j < read_len; j++)
                        {
                              if(buf[j] == '=')
                              {
                                    j++;
                                    if(j < read_len)
                                    {
                                          level = atoi(buf+j);
                                    }
                                    else
                                    {
                                          break;
                                    }

                                    switch(level)
                                    {
                                          case LOG_T_INFO:
                                          case LOG_T_DEBUG:
                                          case LOG_T_WARNING:
                                          case LOG_T_ERROR:
                                          case LOG_T_ALL:
                                                if(i == LOG_M_MAX)      
                                                {
                                                      for(i = 0; i < LOG_M_MAX; i++)
                                                      {
                                                            log_set_level(i, level);
                                                      }

                                                      return ;
                                                }
                                                log_set_level(i, level);
                                                break;
                                          default:
                                                break;
                                    }
                                    break;
                              }
                        }
                        break;
                  }
            }
            memset(buf, 0, sizeof(buf));
      }

      return ;
}

可对每个模块设置自己的等级;除此之外,当 M_MAX有值,使用这个值设置所有模块。

 

总结:

好用的、格式标准的 Log 对于一个工程来说至关重要,定制化 Log 是必须的,上面的代码还比较粗糙,但是万变不离其宗,以上是 Log 的思想和初步实现,供各位参考。

转载于:https://my.oschina.net/neverdead/blog/1587319

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值