Ghi log trên C/C++

From CodeForLife
Jump to: navigation, search

Trong lập trình bất kỳ ngôn ngữ nào, ghi log là vấn đề hết sức quan trọng, đặc biệt trong việc giúp debug và tìm lỗi nghiệp vụ, lỗi ứng dụng tại thời điểm thực thi. Có nhiều thư viện log bạn có thể tích hợp vào ứng dụng như log4c nhưng đôi khi việc tích hợp thư viện ngoài là không cần thiết và gây phức tạp cho ứng dụng.

Dưới đây xin hướng dẫn bạn một cách khác đơn giản hơn nhiều để tích hợp log vào ứng dụng:

  • Chỉ cần thêm hai tệp MyLog.hMyLog.cpp ở dưới vào ứng dụng. Bạn nào muốn có thể rút gọn lại thành một file MyLog.h cũng được.
  •  Hỗ trợ chế độ ghi log ra màn hình Console hoặc ghi log ra file. Đơn giản bạn chỉ cần sửa lại giá trị của define ENABLE_LOG_FILE thành true.
  • Cho phép bật tắt thời gian trong log thông qua define ENABLE_LOG_TIME.
  • Cho phép bật tắt chế độ hiển thị tên file, tên hàm, vị trí dòng ghi log thông qua define ENABLE_LOG_FILENAME_FUNC.
  • Có thể áp dụng cho nhiều module, mỗi module tương ứng, bạn hãy xác định tên module phù hợp trong define MODULE_NAME.
  • Cho phép thay đổi log-level trong lúc thực thi thông qua biến g_log_level.
  • Hỗ trợ ghi log dữ liệu dạng binary thông qua marco LOG_HEXA và LOG_HEXA_ALL.
  • Sử dụng đơn giản thông qua marco LOG_TRACELOG_DEBUGLOG_INFOLOG_WARNLOG_ERROR. Sử dụng với tham số tương tự hàm printf trong C.

Tệp MyLog.h:

#ifndef MY_LOG_H
#define MY_LOG_H

#include <stdint.h>
#include <stdarg.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <exception>

// You can change this defines to customize
#define     ENABLE_LOG_FEATURE          true
#define     ENABLE_LOG_TIME             true
#define     ENABLE_LOG_FILENAME_FUNC    true
#define     ENABLE_LOG_FILE		false
#define     MODULE_NAME			"LogInC"

// Defines log levels
#define     LEVEL_ALL                   0
#define     LEVEL_TRACE                 1
#define     LEVEL_DEBUG                 2
#define     LEVEL_INFO                  3
#define     LEVEL_WARN                  4
#define     LEVEL_ERROR                 5

// Defines log level names
#define     LEVEL_NAME_UNKNOWN          "UNKOWN"
#define     LEVEL_NAME_ALL              "ALL"
#define     LEVEL_NAME_TRACE            "TRACE"
#define     LEVEL_NAME_DEBUG            "DEBUG"
#define     LEVEL_NAME_INFO             "INFO"
#define     LEVEL_NAME_WARN             "WARN"
#define     LEVEL_NAME_ERROR            "ERROR"

#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)

// You can change this variable in run time to change log level
extern int g_log_level;

// Log functions
void log_detail(const char* pszFilename, const char* pszFuncName, unsigned int line, int level, const char* fmt, ...);
void log_hexa(const char* pszFilename, const char* pszFuncName, unsigned int line, int level, const uint8_t* data, int length, int logLenth, const char* fmt, ...);

// Log marco
#if ENABLE_LOG_FEATURE
#define MYLOG(level, fmt, args...)                               log_detail(__FILENAME__, __FUNCTION__, __LINE__, level, fmt, ## args)
#define MYLOG_HEXA(level, data, length, fmt, args...)            log_hexa(__FILENAME__, __FUNCTION__, __LINE__, level, (const uint8_t*) data, length, 50, fmt, ## args)
#define MYLOG_HEXA_ALL(level, data, length, fmt, args...)        log_hexa(__FILENAME__, __FUNCTION__, __LINE__, level, (const uint8_t*) data, length, length, fmt, ## args)
#else
#define MYLOG(level, fmt, args...)       
#define MYLOG_HEXA(level, name, data, length)	
#define MYLOG_HEXA_ALL(level, data, length, fmt, args...)    
#endif // ENABLE_LOG_FEATURE
  
// Log marco for every levels
#define LOG_TRACE(fmt, args...)       MYLOG(LEVEL_TRACE, fmt, ## args)
#define LOG_DEBUG(fmt, args...)       MYLOG(LEVEL_DEBUG, fmt, ## args)
#define LOG_INFO(fmt, args...)        MYLOG(LEVEL_INFO, fmt, ## args)
#define LOG_WARN(fmt, args...)        MYLOG(LEVEL_WARN, fmt, ## args)
#define LOG_ERROR(fmt, args...)       MYLOG(LEVEL_ERROR, fmt, ## args)
#define LOG_HEXA(data, length, fmt, args...)        MYLOG_HEXA(LEVEL_TRACE, data, length, fmt, ## args)
#define LOG_HEXA_ALL(data, length, fmt, args...)    MYLOG_HEXA_ALL(LEVEL_TRACE, data, length, fmt, ## args)

#endif /* MY_LOG_H */

Tệp MyLog.cpp:

#include "MyLog.h"

const char* g_LevelNameUnknown = "UNKOWN";
const char* g_LevelNameAll = "ALL";
const char* g_LevelNameTrace = "TRACE";
const char* g_LevelNameDebug = "DEBUG";
const char* g_LevelNameInfo = "INFO";
const char* g_LevelNameWarn = "WARN";
const char* g_LevelNameError = "ERROR";

int g_log_level = LEVEL_DEBUG;

#if ENABLE_LOG_FILE
static inline FILE* get_log_stream() {
    static FILE* f_log_file = NULL;
    if (f_log_file == NULL) {
	char szFilename[100];
	memset(szFilename, 0, sizeof(szFilename));
	snprintf(szFile, sizeof(szFilename)-1, "Log-%s.log", MODULE_NAME);
        f_log_file = fopen("fpt_trace.log", "a+t");
    }
    return f_log_file;
}
#else
static inline FILE* get_log_stream() {
    return stdout;
}
#endif // ENABLE_LOG_TIME

static inline const char* get_level_name(int level) {
    const char* szLevelName = LEVEL_NAME_UNKNOWN;
    if (level == LEVEL_ALL) {
        szLevelName = LEVEL_NAME_ALL;
    } else if (level == LEVEL_TRACE) {
        szLevelName = LEVEL_NAME_TRACE;
    } else if (level == LEVEL_DEBUG) {
        szLevelName = LEVEL_NAME_DEBUG;
    } else if (level == LEVEL_INFO) {
        szLevelName = LEVEL_NAME_INFO;
    } else if (level == LEVEL_WARN) {
        szLevelName = LEVEL_NAME_WARN;
    } else if (level == LEVEL_ERROR) {
        szLevelName = LEVEL_NAME_ERROR;
    }
    return szLevelName;
}

void log_detail(const char* pszFilename, const char* pszFuncName, unsigned int line, int level, const char* fmt, ...) {
    if (level < g_log_level) return;
    
    try {
        // Convert level to text
        const char* szLevel = get_level_name(level);
     
        // Write log
        FILE* logOutput = get_log_stream();
#if ENABLE_LOG_TIME
        static char time_buffer[20];
        static time_t log_time;
        time(&log_time);
        strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", gmtime(&log_time));
        fprintf(logOutput, "[%s]", time_buffer);
#endif // ENABLE_LOG_TIME
      
        fprintf(logOutput, "[%s][%s]", MODULE_NAME, szLevel);
  
#if ENABLE_LOG_FILENAME_FUNC  
        fprintf(logOutput, "[%s(%d)][%s]", pszFilename, line, pszFuncName);
#endif // ENABLE_LOG_FUNC_LINE
      
        fprintf(logOutput, ": ");
        va_list args;
        va_start(args, fmt);
        vfprintf(logOutput, fmt, args);
        va_end(args);
        fprintf(logOutput, "\n");
        fflush(logOutput);
    } catch (std::exception e) {
        printf("Exception: %s\n", e.what());
    }
}

void log_hexa(const char* pszFilename, const char* pszFuncName, unsigned int line, int level, const uint8_t* data, int length, int logLenth, const char* fmt, ...) {
    if (level < g_log_level) return;

    try {
        // Convert level to text
        const char* szLevel = get_level_name(level);

        // Write log
        FILE* logOutput = get_log_stream();

        fprintf(logOutput, "[%s][%s]", MODULE_NAME, szLevel);
  
#if ENABLE_LOG_FILENAME_FUNC  
        fprintf(logOutput, "[%s(%d)][%s]", pszFilename, line, pszFuncName);
#endif // ENABLE_LOG_FUNC_LINE
    
        fprintf(logOutput, ": ");
        va_list args;
        va_start(args, fmt);
        vfprintf(logOutput, fmt, args);
        va_end(args);

        if (logLenth>length)
        {
            logLenth = length; 
        }
        for (int i=0; i < logLenth; i++)
        {
            fprintf(logOutput, "%02X ", data[i]);
        }
        if (logLenth<length)
        {
            fprintf(logOutput, "...");
        }
        fprintf(logOutput, "\n");
        fflush(logOutput);
    } catch (std::exception e) {
        printf("Exception: %s\n", e.what());
    }
}

Tệp main.cpp (Ví dụ sử dụng các marco LOG_XXXXX ở trên, cho phép thay đổi log-level thông qua tham số):

#include <stdlib.h>
#include "MyLog.h"

const char* PARAM_LOG_LEVEL = "--loglevel=";

long int rand_int() {
    return random();
}

int main(int argc, char *argv[]) {
    if (argc>=2) {
        if (strncmp(PARAM_LOG_LEVEL, argv[1], strlen(PARAM_LOG_LEVEL))==0) {
            const char* pCh = argv[1] + strlen(PARAM_LOG_LEVEL);
            if (strlen(pCh)>0) {
                int logLevel = atoi(pCh);
                g_log_level = logLevel;
            } else {
                LOG_WARN("Invalid parameter: %s", argv[1]);
            }
        }
    }
    LOG_INFO("Level of log: %d\n", g_log_level);

    LOG_INFO("The app is starting ...");

    LOG_INFO("Division by zero: %d/%d", 56, 0);

    long timestamp = 45696464364;
    double average = 4564564.78979;
    LOG_DEBUG("timestamp: %lld", timestamp);
    LOG_DEBUG("average: %lf", average);

    char data[100];
    int num = sizeof(data)/sizeof(char);
    for (int i=0; i<num; i++) {
        data[i] = (char) (rand_int() & 0xFF);
    }
    LOG_TRACE("50 first bytes in data:");
    LOG_HEXA(data, num, "\t Data (%d): ", num);
    LOG_TRACE("All bytes in data:");
    LOG_HEXA_ALL(data, num, "\t Data (%d): ", num);

    int ret = 0;
    LOG_INFO("The app finished with error code: %d", ret);

    return 0;
}

Download source code: [[File:LogInC.zip]]  (Đọc tệp README để biết cách build và chạy).

Khi chạy bạn sẽ thấy output như sau:

'''./LogInC --loglevel=0'''
<span style="color:#000000;">[2017-06-26 18:19:10][LogInC][INFO][main.cpp(22)][main]: Level of log: 0

[2017-06-26 18:19:10][LogInC][INFO][main.cpp(24)][main]: The app is starting ...
[2017-06-26 18:19:10][LogInC][INFO][main.cpp(26)][main]: Division by zero: 56/0
[2017-06-26 18:19:10][LogInC][DEBUG][main.cpp(30)][main]: timestamp: 45696464364
[2017-06-26 18:19:10][LogInC][DEBUG][main.cpp(31)][main]: average: 4564564.789790
[2017-06-26 18:19:10][LogInC][TRACE][main.cpp(38)][main]: 50 first bytes in data:
[LogInC][TRACE][main.cpp(39)][main]: 	 Data (100): 67 C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C C2 54 F8 1B E8 E7 8D 76 5A 2E 63 33 9F C9 9A 66 32 0D B7 31 58 A3 5A 25 5D 05 17 58 E9 5E D4 AB B2 ...
[2017-06-26 18:19:10][LogInC][TRACE][main.cpp(40)][main]: All bytes in data:
[LogInC][TRACE][main.cpp(41)][main]: 	 Data (100): 67 C6 69 73 51 FF 4A EC 29 CD BA AB F2 FB E3 46 7C C2 54 F8 1B E8 E7 8D 76 5A 2E 63 33 9F C9 9A 66 32 0D B7 31 58 A3 5A 25 5D 05 17 58 E9 5E D4 AB B2 CD C6 9B B4 54 11 0E 82 74 41 21 3D DC 87 70 E9 3E A1 41 E1 FC 67 3E 01 7E 97 EA DC 6B 96 8F 38 5C 2A EC B0 3B FB 32 AF 3C 54 EC 18 DB 5C 02 1A FE 43 
[2017-06-26 18:19:10][LogInC][INFO][main.cpp(44)][main]: The app finished with error code: 0