Ghi log trên C/C++
From CodeForLife
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.h và MyLog.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_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_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