C++ 日志宏定义

2017-10-28

项目中,无论客户端还是服务端,日志都是必不可少的,一般的日志格式具备下面几个要素:

时间,日志等级,源码文件,源码行数,日志字符串格式化内容。


1. 日志示例

  • 示例代码:
1
LOG_ERR("check file failed, task id = %d, error = %d", iTaskID, iErrCode);
  • 内容:
1
[2017-10-28 19:40:01][ERROR][uploadclient.cpp][380] check file failed, task id = 6, error = 23

2. 日志宏定义

字符串格式化数据如何作为参数传递,研究了不少时间~ 为啥要将日志函数定义为宏呢,主要是因为 __FILE__ 和 __LINE__ 这两个参数,只有通过宏,才能正确记录哪个文件,哪一行的日志。


2.1. windows

1
2
3
4
5
#define LOG_TRACE(x, ...)     LogTrace(__FILE__, __LINE__, x, ##__VA_ARGS__);
#define LOG_DEBUG(x, ...)     LogDebug(__FILE__, __LINE__, x, ##__VA_ARGS__);
#define LOG_INFO(x, ...)      LogInfo(__FILE__, __LINE__, x, ##__VA_ARGS__);
#define LOG_IMPORTANT(x, ...) LogImportant(__FILE__, __LINE__, x, ##__VA_ARGS__);
#define LOG_ERR(x, ...)       LogError(__FILE__, __LINE__, x, ##__VA_ARGS__);  
  • 函数
1
2
3
4
5
6
void LogData(LPCTSTR pFile, int iLine, int iType, LPCTSTR lpInfo);
void LogTrace(LPCTSTR pFile, int iLine, LPCTSTR lpszFormat, ...);
void LogDebug(LPCTSTR pFile, int iLine, LPCTSTR lpszFormat, ...);
void LogInfo(LPCTSTR pFile, int iLine, LPCTSTR lpszFormat, ...);
void LogImportant(LPCTSTR pFile, int iLine, LPCTSTR lpszFormat, ...);
void LogError(LPCTSTR pFile, int iLine, LPCTSTR lpszFormat, ...);

2.2. Linux

  • log4cplus
1
2
3
4
5
6
#define LOG4_FATAL(args...) LOG4CPLUS_FATAL_FMT(GetLogger(), ##args)
#define LOG4_ERROR(args...) LOG4CPLUS_ERROR_FMT(GetLogger(), ##args)
#define LOG4_WARN(args...)  LOG4CPLUS_WARN_FMT(GetLogger(), ##args)
#define LOG4_INFO(args...)  LOG4CPLUS_INFO_FMT(GetLogger(), ##args)
#define LOG4_DEBUG(args...) LOG4CPLUS_DEBUG_FMT(GetLogger(), ##args)
#define LOG4_TRACE(args...) LOG4CPLUS_TRACE_FMT(GetLogger(), ##args)
1
2
3
4
5
6
7
8
9
10
11
12
13
#define LOG_FORMAT(level, args...)                                           \
    if (m_logger != nullptr) {                                               \
        m_logger->log_data(__FILE__, __LINE__, __FUNCTION__, level, ##args); \
    }

#define LOG_EMERG(args...) LOG_FORMAT((Log::LL_EMERG), ##args)
#define LOG_ALERT(args...) LOG_FORMAT((Log::LL_ALERT), ##args)
#define LOG_CRIT(args...) LOG_FORMAT((Log::LL_CRIT), ##args)
#define LOG_ERROR(args...) LOG_FORMAT((Log::LL_ERR), ##args)
#define LOG_WARN(args...) LOG_FORMAT((Log::LL_WARNING), ##args)
#define LOG_NOTICE(args...) LOG_FORMAT((Log::LL_NOTICE), ##args)
#define LOG_INFO(args...) LOG_FORMAT((Log::LL_INFO), ##args)
#define LOG_DEBUG(args...) LOG_FORMAT((Log::LL_DEBUG), ##args)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Log {
   public:
    enum {
        LL_EMERG = 0, /* system is unusable */
        LL_ALERT,     /* action must be taken immediately */
        LL_CRIT,      /* critical conditions */
        LL_ERR,       /* error conditions */
        LL_WARNING,   /* warning conditions */
        LL_NOTICE,    /* normal but significant condition */
        LL_INFO,      /* informational */
        LL_DEBUG,     /* debug-level messages */
        LL_COUNT
    };

    Log();
    virtual ~Log() {}

   public:
    bool set_level(int level);
    bool set_level(const char* level);
    bool set_log_path(const char* path);
    bool log_data(const char* file_name, int file_line, const char* func_name, int level, const char* fmt, ...);

   private:
    bool log_raw(const char* file_name, int file_line, const char* func_name, int level, const char* msg);

   private:
    int m_cur_level;
    std::string m_path;
};

3. 跨平台

跨平台日志处理,详细请参考 zookeeper-client-c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* zookeeper_log.h */
#ifndef ZK_LOG_H_
#define ZK_LOG_H_

#include <zookeeper.h>

#ifdef __cplusplus
extern "C" {
#endif

extern ZOOAPI ZooLogLevel logLevel;
#define LOGCALLBACK(_zh) zoo_get_log_callback(_zh)
#define LOGSTREAM NULL

#define LOG_ERROR(_cb, ...) if(logLevel>=ZOO_LOG_LEVEL_ERROR) \
    log_message(_cb, ZOO_LOG_LEVEL_ERROR, __LINE__, __func__, __VA_ARGS__)
#define LOG_WARN(_cb, ...) if(logLevel>=ZOO_LOG_LEVEL_WARN) \
    log_message(_cb, ZOO_LOG_LEVEL_WARN, __LINE__, __func__, __VA_ARGS__)
#define LOG_INFO(_cb, ...) if(logLevel>=ZOO_LOG_LEVEL_INFO) \
    log_message(_cb, ZOO_LOG_LEVEL_INFO, __LINE__, __func__, __VA_ARGS__)
#define LOG_DEBUG(_cb, ...) if(logLevel==ZOO_LOG_LEVEL_DEBUG) \
    log_message(_cb, ZOO_LOG_LEVEL_DEBUG, __LINE__, __func__, __VA_ARGS__)

ZOOAPI void log_message(log_callback_fn callback, ZooLogLevel curLevel,
    int line, const char* funcName, const char* format, ...);

FILE* zoo_get_log_stream();

#ifdef __cplusplus
}
#endif

#endif /*ZK_LOG_H_*/

4. fprintf 线程安全吗

因为 redis 的日志实现 是用 fprintf 将文件内容序列化的,不知道对于多线程它是否安全,查了一下 glibc 的实现源码,发现有上锁和解锁的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* https://github.com/lattera/glibc/blob/master/stdio-common/fprintf.c */

/* Write formatted output to STREAM from the format string FORMAT.  */
/* VARARGS2 */
int
__fprintf (FILE *stream, const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = vfprintf (stream, format, arg);
  va_end (arg);

  return done;
}
ldbl_hidden_def (__fprintf, fprintf)
ldbl_strong_alias (__fprintf, fprintf)

/* We define the function with the real name here.  But deep down in
   libio the original function _IO_fprintf is also needed.  So make
   an alias.  */
ldbl_weak_alias (__fprintf, _IO_fprintf)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* https://github.com/lattera/glibc/blob/master/stdio-common/vfprintf.c */

/* The function itself.  */
int
vfprintf (FILE *s, const CHAR_T *format, va_list ap)
{
    ...
  /* Lock stream.  */
#ifdef USE_IN_LIBIO
  __libc_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);
#else
  __libc_cleanup_region_start ((void (*) (void *)) &__funlockfile, s);
  __flockfile (s);
#endif
    ...
all_done:
  /* Unlock the stream.  */
  __libc_cleanup_region_end (1);

  return done;
}

5. 参考