| 特性 | 實現方式 |
| 分級日志 | INFO/ERROR/SYSTEM三級,分文件存儲 |
| 按日期分目錄 | 根目錄/YYYYMMDD/下存放當日日志 |
| 自動分卷 | 單文件超過512KB自動覆蓋重建 |
| 變長參數 | va_list支持printf風格格式化 |
| 編譯開關 | _LOG_WRITE_STATE_宏控制是否輸出 |
| 線程安全 | 文件操作加互斥鎖(代碼中需補充) |
核心數據結構
#define MAX_LOGTEXT_LEN 2048
#define MAX_FILE_PATH 255
#define MAX_LOG_FILE_SIZE (512 * 1024)
#define LOG_TYPE_INFO 0
#define LOG_TYPE_ERROR 1
#define LOG_TYPE_SYSTEM 2
#pragma pack(push, 1)
typedef struct tagLOG_DATA {
char strDate[11]; // YYYY-MM-DD
char strTime[9]; // HH:MM:SS
unsigned int iType; // 日志類型
char strText[MAX_LOGTEXT_LEN]; // 日志內容
} LOG_DATA, *LPLOG_DATA;
#pragma pack(pop)
const char g_LogRootPath[] = "C://My_APPLOG"; // 根目錄
關鍵函數流程
1. 入口:Write_Log(變長參數格式化)
void Write_Log(unsigned int uiLogType, char *pstrFmt, ...) {
#if _LOG_WRITE_STATE_
LOG_DATA data;
memset(&data, 0, sizeof(data));
// 格式化內容
va_list v1;
va_start(v1, pstrFmt);
_vsnprintf(data.strText, MAX_LOGTEXT_LEN, pstrFmt, v1);
va_end(v1);
data.iType = uiLogType;
// 獲取當前時間
time_t curTime = time(NULL);
struct tm *mt = localtime(&curTime);
strftime(data.strDate, sizeof(data.strDate), "%Y-%m-%d", mt);
strftime(data.strTime, sizeof(data.strTime), "%H:%M:%S", mt);
Write_Log_Text(&data);
#endif
}
2. 路徑構建:GetLogPath → GetLogFileName
// 構建:根目錄/YYYYMMDD/
int GetLogPath(char *pStrPath) {
time_t curTime = time(NULL);
struct tm *mt = localtime(&curTime);
sprintf(pStrPath, "%s//%d%02d%02d", g_LogRootPath,
mt->tm_year+1900, mt->tm_mon+1, mt->tm_mday);
return Create_LogDir(pStrPath); // 遞歸創建多級目錄
}
// 根據類型生成文件名:app_info.log / app_error.log / app_system.log
int GetLogFileName(int iLogType, const char *pStrPath, char *pStrName) {
char szLogName[MAX_FILE_PATH];
switch (iLogType) {
case LOG_TYPE_INFO: sprintf(szLogName, "%s//app_info.log", pStrPath); break;
case LOG_TYPE_ERROR: sprintf(szLogName, "%s//app_error.log", pStrPath); break;
case LOG_TYPE_SYSTEM: sprintf(szLogName, "%s//app_system.log", pStrPath); break;
}
// 文件存在且超過大小限制 → 重建覆蓋
if (IsFileExist(szLogName) && GetFileLenth(szLogName) + 256 >= MAX_LOG_FILE_SIZE) {
Create_LogFile(szLogName, 0);
} else if (!IsFileExist(szLogName)) {
Create_LogFile(szLogName, 0);
}
sprintf(pStrName, "%s", szLogName);
return LOG_SUCCESS;
}
3. Windows API 文件/目錄操作
// 遞歸創建多級目錄
int Create_LogDir(const char *pStrPath) {
char szSub[MAX_FILE_PATH];
char *pSub = NULL;
int iLen = 0, bFind = 0;
while (1) {
pSub = strchr(pStrPath + iLen, '/');
if (!pSub) { // 最後一級
int iRet = CreateDirectory(pStrPath, NULL);
if (!iRet && GetLastError() == ERROR_ALREADY_EXISTS) return LOG_SUCCESS;
return iRet ? LOG_SUCCESS : LOG_FAILED;
}
if (bFind) {
strncpy(szSub, pStrPath, pSub - pStrPath);
CreateDirectory(szSub, NULL); // 忽略已存在錯誤
} else bFind = 1;
iLen = pSub - pStrPath + 1;
}
}
// 創建/截斷文件
int Create_LogFile(const char *pStrFile, int iPos) {
HANDLE hd = CreateFile(pStrFile, GENERIC_READ|GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hd == INVALID_HANDLE_VALUE) return LOG_FAILED;
SetFilePointer(hd, iPos, NULL, FILE_BEGIN);
SetEndOfFile(hd);
CloseHandle(hd);
return LOG_SUCCESS;
}
4. 寫入日志內容
int Write_Log_Text(LPLOG_DATA lpLogData) {
char szFilePath[MAX_FILE_PATH], szFileName[MAX_LOG_FILE_NAME_LEN];
char szLogText[MAX_LOGTEXT_LEN];
GetLogPath(szFilePath);
GetLogFileName(lpLogData->iType, szFilePath, szFileName);
FILE *pFile = fopen(szFileName, "a+");
if (!pFile) return LOG_FAILED;
sprintf(szLogText, "%s %s %s\n", lpLogData->strDate, lpLogData->strTime, lpLogData->strText);
fwrite(szLogText, 1, strlen(szLogText), pFile);
fclose(pFile);
return LOG_SUCCESS;
}
使用示例
int main() {
Write_Log(LOG_TYPE_SYSTEM, "Program begin.");
// 業務邏輯...
FILE *f = fopen("config.ini", "r");
if (!f) {
Write_Log(LOG_TYPE_ERROR, "Open config.ini failed: %d", GetLastError());
} else {
Write_Log(LOG_TYPE_INFO, "Config loaded, size=%d", file_size);
fclose(f);
}
Write_Log(LOG_TYPE_SYSTEM, "Program end.");
return 0;
}
生成日志示例:
2024-01-15 10:30:45 Program begin.
2024-01-15 10:30:46 Config loaded, size=1024
2024-01-15 10:30:47 Program end.
代碼中待改進項(作者自註)
| 問題 | 改進方案 |
time()精度只到秒 | 用GetLocalTime(ms)或QueryPerformanceCounter(μs) |
| 非線程安全 | 文件操作加CRITICAL_SECTION或std::mutex |
| 僅支持Windows | 抽象文件/目錄/時間接口,提供Linux實現 |
| 缺文件名/行號 | 宏封裝:LOG_INFO(fmt, ...)自動傳__FILE__,__LINE__ |
| 同步寫入阻塞業務線程 | 異步日志:生產者-消費者隊列+後台寫入線程 |
| 無日志級別過濾 | 運行時配置最小級別,編譯期#if LOG_LEVEL <= INFO |
現代化重構建議(C++17+)
class Logger {
std::filesystem::path root_;
std::mutex mtx_;
std::ofstream info_, error_, system_;
size_t max_size_;
public:
enum Level { INFO, ERROR, SYSTEM };
Logger(std::filesystem::path root, size_t max_size = 512*1024)
: root_(root), max_size_(max_size) {}
template
void log(Level level, const char* file, int line, const char* fmt, Args&&... args) {
std::lock_guard lock(mtx_);
auto now = std::chrono::system_clock::now();
auto date = fmt::format("{:%Y-%m-%d}", now);
auto time = fmt::format("{:%H:%M:%S}", now);
auto& stream = get_stream(level, date);
stream << date << ' ' << time << ' '
<< '[' << level_name(level) << ']'
<< ' ' << fmt::format(fmt, std::forward(args)...)
<< " (" << file << ':' << line << ")\n";
if (stream.tellp() > max_size_) rotate(level, date);
}
// 便捷宏
#define LOG_INFO(...) logger.log(Logger::INFO, __FILE__, __LINE__, __VA_ARGS__)
// ...
};
總結
本日志系統雖屬「輪子級」實現,但完整展示了:分級存儲、日期分目錄、大小分卷、變長參數格式化、Windows API文件操作、目錄遞歸創建等工程實用技術。對於無法引入spdlog、glog、Boost.Log等成熟庫的老項目或嵌入式環境,此代碼可作為輕量級替代方案直接使用或改造。
- 上一篇文章: 用 C 語言打造圖形化電子時鐘:從零實作完整原始碼解析
- 下一篇文章: GCC頭文件搜索路徑機制詳解:-I、環境變量、系統默認目錄優先級
Copyright ©
程式碼之家 All Rights Reserved