基於c++的日志文件實現


概述

所有的商業軟件或線上系統都具有日志功能,因為日志信息提供了系統啟動以來的重要的操作或狀態遷移記錄,是追蹤各種異常錯誤的第一手資料。絕大部分系統的日志模塊會自動保留歷史日志文件,即:日志文件大小達到約定上限時,自動轉儲到一個新的歷史文件,當前文件清空並繼續記錄新的日志信息,例如:假設當前日志文件名為test.log, 當它的大小到達上限(例如10MB)時,就把其文件內容轉儲到新文件test.log.1, 然后test.log清空並繼續記錄新信息。根據配置不同,我們可以保留1到N份歷史日志文件。當歷史日志文件數目達到上限時,我們可以采用round-robin策略(或其他策略)依據文件生成時間依次覆蓋老的文件。本文嘗試用C++實現上述日志文件功能,其功能歸納總結如下:

1) 日志文件提供接口讓用戶配置日志文件名、日志大小上限、歷史日志文件數目上限;

2) 日志文件提供Append()接口,讓用戶向文件追加日志消息;

3) 日志文件在執行Append()接口過程中,自動檢測當前日志文件大小:如果追加當前消息后,文件大小超過約定上限,則記錄當前消息前,將已有消息轉儲到歷史文件並保證歷史日志文件數不超過約定上限;否者,直接記錄當前消息;

4) 日志文件提供接口讓用戶配置是否對歷史日志文件進行壓縮;

實現

LogFile類實現了上述功能,先看一下其接口定義:

 1 #ifndef _LOGFILE_H
 2 #define _LOGFILE_H
 3 
 4 #include <fstream>
 5 #include <iostream>
 6 
 7 
 8 class LogFile {
 9 public:
10     LogFile(const LogFile &) = delete;
11     LogFile& operator=(const LogFile &) = delete;
12     LogFile(const std::string&, double, unsigned int, bool);
13 
14     ~LogFile();
15     void Append(std::string &&msg);
16 
17 private:
18     void Rotate();
19     double GetFileSize();
20     std::string NextHistoryFile();
21 
22 private:
23     std::ofstream ofs_;
24     std::string file_name_;
25     double cur_size_;
26     double max_size_;
27     unsigned int max_file_num_;
28 bool compress_;
28 }; 29 30 #endif //_LOGFILE_H

 

LogFile成員變量說明

ofs_: c++ std::ofstream類型對象,通過其操作符"<<"把消息寫入日志文件

file_name_: 日志文件名;

cur_size_: 實時記錄當前日志文件大小,避免每次執行Append()操作時調用系統函數獲取文件大小;

max_size_: 用戶指定的日志文件上限;

max_file_num_: 用戶指定的最大歷史文件數;

 

LogFile成員函數說明

LogFile(const std::string&, double, unsigned int): LogFile類的唯一構造函數,可以指定日志文件名,文件大小上限,歷史文件數量上限,其實現也非常簡單: 

LogFile::LogFile(const std::string& file_name,
                 double max_size,
                 unsigned int max_file_num,
bool compress) : file_name_(file_name), max_size_(max_size), max_file_num_(max_file_num),
compress_(compress) { assert(max_file_num_
> 0); cur_size_ = GetFileSize(); ofs_.open(file_name_, std::ofstream::out|std::ofstream::app); }

 

~LogFile(): 析構函數,主要功能是關閉在構造函數中打開的ofstream對象.

LogFile::~LogFile()
{
    if (ofs_.is_open()) {
        ofs_.close();
    }
}

 

void Append(std::string &&msg):LogFile類最重要的接口,讓用戶向日志文件追加新的消息。其實現邏輯為:在寫文件前,先檢查當前日志文件大小加上當前消息的長度之和是否會超過創建LogFile對象時指定的日志文件大小上限:如果超過,就將當前文件內容轉存到歷史文件,並清空當前日志文件(通過Rotate函數),然后,繼續把當前消息寫入日志文件。 有一點值得說明:獲取當前日志文件大小不是通過調用系統函數,而是通過類成員變量cur_size_(構造函數調用一次系統函數為cur_size_賦初值,之后,每次執行Append(), cur_size_都累加消息長度,從而實時追蹤日志文件長度), 這樣避免了每次執行Append()時都調用系統函數檢查文件長度,從而提高了效率。

void LogFile::Append(std::string&& msg)
{
    double msg_size = (double)msg.size();
    if (cur_size_+ msg_size >= max_size_) {
        Rotate();
    }

    ofs_ << std::forward<std::string>(msg) << std::endl;
    cur_size_ += msg_size;
}


void Rotate(): 轉儲函數,當前日志文件內容被轉存到某個歷史日志文件,當前日志文件被清空並被重新打開

//
// Compress file "old_file" and save data into file "new_file", the
// compression is based on zlib.
//
void LogFile::compress_file(const char *old_file, const char *new_file)
{
    gzFile gf = gzopen(new_file, "wb");
    if (gf == NULL) {
        std::cout << "gzopen() failed" << std::endl;
        return;
    }

    const int BUF_LEN = 500;
    char buf[BUF_LEN];
    size_t reads = 0;

    FILE *fd = fopen(old_file, "rb");
    if (fd == NULL) {
        std::cout << "fopen(" << old_file << ") failed" << std::endl;
    }

    while((reads = fread(buf, 1, BUF_LEN, fd)) > 0) {
        if (ferror(fd)) {
            std::cout << "fread() failed!" << std::endl;
            break;
        }
        gzwrite(gf, buf, reads);
    }

    fclose(fd);
    gzclose(gf);
}

//
// Save existing log messages into history file and empty log file.
//
// Note: call this function only when log file reaches maximum size.
//
void LogFile::Rotate()
{
    if (ofs_.is_open()) {
        ofs_.close();
    }

    std::string history_file = NextHistoryFile();
    if (compress_) {
        compress_file(file_name_.c_str(), history_file.c_str());
    } else {
        std::rename(file_name_.c_str(), history_file.c_str());
    }
    ofs_.open(file_name_, std::ofstream::out|std::ofstream::trunc);
    cur_size_ = 0;
}
     

 

std::string NextHistoryFile(): 返回下一個歷史日志文件名以用於轉存日志文件信息

std::string LogFile::NextHistoryFile()
{
    static int next_file_no = 0;
    int file_num = (next_file_no++) % max_file_num_;
    return file_name_ + "." + std::to_string(file_num);
}

 

double LogFile::GetFileSize(): 返回當前日志文件大小(bytes),當前實現只針對Linux

double LogFile::GetFileSize()
{
    struct stat statbuf;
    if (stat(file_name_.c_str(), &statbuf) == 0) {
        return (double)statbuf.st_size;
    } else {
        perror("Faild to get log file size");
        return 0;
    }
}

測試

針對上述LogFile的實現,我們可以進行如下測試:

#include "logfile.h"


int main(void)
{
    LogFile lf("testlogfile.log", 1024, 8, false);
    for (auto i = 0; i < 200; i++) {
        lf.Append(std::to_string(i) + ": this is a very very very very very very very very very very very very very very vvery very very very very ery long log");
    }
}

測試結果如下:

stephenw@stephenw-devbox1:/local/project/logcpp$ ls -lart testlogfile.log*
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.4
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.3
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.2
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.1
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.7
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.6
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.5
-rw-rw-r-- 1 stephenw stephenw 992 6月  27 23:02 testlogfile.log.0
-rw-rw-r-- 1 stephenw stephenw 124 6月  27 23:02 testlogfile.log

stephenw@stephenw-devbox1:/local/project/logcpp$ tail -n5 testlogfile.log.0 testlogfile.log.1 testlogfile.log.2
==> testlogfile.log.0 <==
194: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
195: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
196: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
197: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
198: this is a very very very very very very very very very very very very very very vvery very very very very ery long log

==> testlogfile.log.1 <==
138: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
139: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
140: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
141: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
142: this is a very very very very very very very very very very very very very very vvery very very very very ery long log

==> testlogfile.log.2 <==
146: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
147: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
148: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
149: this is a very very very very very very very very very very very very very very vvery very very very very ery long log
150: this is a very very very very very very very very very very very very very very vvery very very very very ery long log

 

 總結

 本文通過一個簡單得c++類實現了日志文件主要功能,但仍然有許多提升空間,例如各種異常的處理(文件無法打開,寫文件失敗等),多線程以及跨平台的支持等,但仍不失為一個好的起點,稍加改造即可應用於生產環境。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM