1. ホーム
  2. C++

C++はまだデバッグにprintf/coutを使っている、独自のログライブラリの書き方を学ぶ (Previous)

2022-02-22 21:53:40
<パス

記事目次


I. 前文

こんにちは、時間がなくてインターンシップ以来何も書いていなかったのですが、ふと気がつくと、インターンシップが終わってから書くことがあまりないことに気がつきました。今回のインターンシップでは、ログの重要性をより実感しています。ログを見て、サービスがクラッシュした?プログラムの動作中に呼び出された関数、受け取った値、実行された動作などが記録されているのです。我々は、すべてこの記事のタイトルを参照してください、私は能力を持っている場合、私は他の人のものを使用するのは好きではないことを欠点を持って、私は自分自身を構築するために願っていますので、今日はログライブラリをジャッキを行う、記事では、実装プロセスに焦点を当てて、あなたがソースコードを必要とするなら、あなたが得るためにgithubに行くことができます。 ロギングサービスのための軽量なC++ロギングライブラリ「FdogLog」。

<マーク 三重苦に跪け!


II. 基本機能

このロギング・ライブラリが実装すべき機能を整理してみましょう。

<ブロッククオート
  1. ロギングの最も、基本的な機能は何かというと、もちろん、ログの印刷や記録です。
  2. メッセージに含まれるべき情報は、時間?実行したユーザー?それが置かれているファイル?表示したい情報は?(カスタム表示情報は次のセクションで実装します)
  3. 情報は豊富に表示されるが、それ以外の情報はできるだけコード自身に取得させ、呼び出し側は最も重要な情報のみを設定することが重要である。
  4. 情報には重要性の階層があるので、必要な仕分けをしないと効率的ではありません。
  5. グローバルコールをできるだけ簡潔に実装する方法。
  6. ロギングライブラリがマルチスレッド環境で動作している場合に、スレッドセーフを確保する方法。(次の記事で実装します)

これらはロギング・ライブラリが持つ最も基本的な機能ですので、他に何が必要なのか考えてみてください。

<ブロッククオート
  1. ログの挙動を制御する方法。
  2. ファイルに保存する場合のファイル名の定義方法。
  3. ログが増えるとファイルがどんどん大きくなる問題の解決方法。(次回の記事で実装します)

完璧とは言えないロギング・ライブラリの機能を簡単に計画した後、次にこれらの項目についてより詳細な計画を立ててみましょう。

  1. ログの最も基本的な機能は何かというと、もちろんログを印刷したり記録したりすることです。
  2. メッセージに含まれるべき情報は、時間?実行したユーザー? それが置かれているファイル?表示したい情報?

functionという名前の関数を呼び出しているとき。

function();


どのように出力させたいか。

<ブロッククオート

と呼ばれるようになりました。
[2021-10-20 23:27:23] 呼ばれました。
[2021-10-20 23:27:23] INFO 呼び出されました。
[2021-10-20 23:27:23] INFO ルート 私は呼ばれました。
[2021-10-20 23:27:23] INFO root 17938 呼ばれました。
[2021-10-20 23:27:23] INFO root 17938 [/media/rcl/FdogIM/service.h function:8] 私は呼び出されました。

ほとんどの人が最後の出力を選ぶと思うので(それまでは最初の出力のためにcoutを多用していましたが)、ログには時間、ログレベル、実行ユーザー、プロセスID、関数が呼ばれたファイル、呼ばれたときの行数などを記載する必要があります。もちろん、すべてを出力したくないという人も必ずいるでしょうから、それは後で譲ることにします。

  1. 情報は豊富に表示されるが、それ以外の情報はできるだけコード自身に取得させ、呼び出し側は最も重要な情報のみを設定することが重要である。

  2. 情報には重要度の階層があるので、必要な仕分けをしないと効率的ではありません。

  3. グローバルコールをできるだけ簡潔にする方法。

情報には重要性の階層があり、メッセージを区別できるようにするには、共通の階層にしたがって、あります。

ERROR このメッセージが出力された後、メインシステムのコアモジュールが正常に動作していないため、正常に動作させるために修正する必要があります。
WARN このメッセージが出力された後、システムの動作に影響を与えない一般的なシステムモジュールに問題が発生しました。
INFO: このメッセージが出力された後、主にシステムの動作状態やその他の関連情報を記録します。
DEBUG。最も細かい出力、上記の様々なケースを取り除いた後、出力したいすべての関連情報をここに出力することができます。
TRACE:最も細かい出力で、上記のシナリオを削除した後に出力したい関連情報をすべて出力することができます。

階層ができたところで、グローバルに最もクリーンなコールを実現するにはどうしたらいいかというと、平たく言えば、不要なコールをすべて取り除き、最も重要なコールだけを残すということです。

#include

#include"fdoglogger.h" //add logging library header file

using namespace fdog; //namespace for the logging library

int main(){
    FdogError("error");
    FdogWarn("warning");
    FdogInfo("Info");
    FdogDebug("debug");
    FdogTrace("Trace");
    return 0;
}


情報を初期化したり、冗長な初期化関数を呼び出す必要はなく、この5つの関数っぽいものを使って出力するだけです。また、別のソースファイルであれば、やはり同じように呼び出します(ここでは、1つのパターンで実装できます。その意図は、クラスが1つの実列だけを持ち、それにアクセスするグローバルアクセスポイントを提供し、そのインスタンスはすべてのプログラムモジュールで共有されるということです。(ちょうどログの出力のようなものです).

  1. ロギングライブラリがマルチスレッド環境で動作している場合に、スレッドセーフを確保する方法。

1点目は、シングルインスタンスパターンの作成です。2つのスレッドが同時に初期化しに行った場合、シングルインスタンスが正常に作成される保証はありません。2点目は、ログをファイルに出力するため、異なるスレッドがファイルに書き込んだ場合、書き込んだデータがずれないことをどう保証するのか。第二に、ログはファイルに出力されるので、異なるスレッドがファイルに書き込んだときに、書き込まれたデータがずれないようにするにはどうすればよいでしょうか?C++のログ出力を書いているので、coutを使う必要がありますが、これはアトミックな操作ではないので、複数スレッド下では安全ではありません。

  1. ロギングの挙動を制御する方法。

ここでは、どのようなログを出力するか、ファイルや端末への入力、出力レベル、ロギングスイッチなど、ロギングの動作を設定ファイルを使って指定します。設定ファイルは、プログラム起動時に読み込まれます。(決してデッドコードを書かないようにとの注意喚起です。結果は無限大です!!)

  1. ファイルに保存する場合のファイル名の定義方法。

  2. ログが増えるとファイルが大きくなる問題の解決方法。

ログファイル名は設定ファイルで指定しますが、作成日のサフィックスを付けて作成し、何日後に新しいログファイルを作成するかは設定ファイルで設定し、設定センターでログファイルのサイズを設定している場合は、そのサイズを優先して、それを超えたら新しいファイルを作成するようにします。


III. コードの実装

1. fdoglogger.h
#ifndef FDOGLOGGER_H
#define FDOGLOGGER_H
#include

#include

#include

#include

#ifndef linux
#include

#include

#include

#include 
2. fdoglogger.cpp
#include"fdoglogger.h"

using namespace fdog;


FdogLogger * FdogLogger::singleObject = nullptr;
mutex * FdogLogger::mutex_new = new(mutex);

FdogLogger::FdogLogger(){
    initLogConfig();
}
FdogLogger::~FdogLogger(){

}

FdogLogger* FdogLogger::getInstance(){
    mutex_new->lock();
    if (singleObject == nullptr) {
        singleObject = new FdogLogger();
    }
    mutex_new->unlock();
    return singleObject;
}

void FdogLogger::initLogConfig(){

    map<string, string *> flogConfInfo;
    flogConfInfo["logSwitch"] = &this-> logger.logSwitch;
    flogConfInfo["logFileSwitch"] = &this->logger.logFileSwitch;
    flogConfInfo["logTerminalSwitch"] = &this-> logger.logTerminalSwitch;
    flogConfInfo["logName"] = &this->logger.logName;
    flogConfInfo["logFilePath"] = &this->logger.logFilePath;
    flogConfInfo["logMixSize"] = &this-> logger.logMixSize;
    flogConfInfo["logBehavior"] = &this->logger.logBehavior;
    flogConfInfo["logOverlay"] = &this->logger.logOverlay;
    flogConfInfo["logOutputLevelFile"] = &this-> logger.logOutputLevelFile;
    flogConfInfo["logOutputLevelTerminal"] = &this->logger.logOutputLevelTerminal;

    string str;
    ifstream file;
    char str_c[100]={0};
    file.open("fdoglogconf.conf");
    if(!file.is_open()){
        cout<<"File open failed \n";
    }
    while(getline(file, str)){
        if(!str.length()) {
            continue;
        }
        string str_copy = str;
        //cout<<"Get data: "<
        int j = 0;
        for(int i = 0; i < str.length(); i++){
            if(str[i]==' ')continue;
            str_copy[j] = str[i];
            j++;
        }
        str_copy.erase(j);
        if(str_copy[0]! = '#'){
            sscanf(str_copy.data(),"%[^=]",str_c);
            auto iter = flogConfInfo.find(str_c);
            if(iter!=flogConfInfo.end()){
                sscanf(str_copy.data(),"%*[^=]=%s",str_c);
                *iter->second = str_c;
            } else {
            }
        }
    }
    logger.logName = logger.logName + getLogNameTime() + ".log";

    bindFileCoutMap("5", fileType::Error);
    bindFileCoutMap("4", fileType::Warn);
    bindFileCoutMap("3", fileType::Info);
    bindFileCoutMap("2", fileType::Debug);
    bindFileCoutMap("1", fileType::Trace);

    bindTerminalCoutMap("5", terminalType::Error);
    bindTerminalCoutMap("4", terminalType::Warn);
    bindTerminalCoutMap("3", terminalType::Info);
    bindTerminalCoutMap("2", terminalType::Debug);
    bindTerminalCoutMap("1", terminalType::Trace);

    if(logger.logFileSwitch == "on"){
        if(!createFile(logger.logFilePath)){
            std::cout<<"Log work path creation failed\n";
        }
    }

    cout << "|========FdogLogger v2.0==========================|" <<endl << endl;
    cout << " LogSwitch: " << logger.logSwitch << endl;
    cout << " File output: " << < logger.logFileSwitch << endl;
    cout << " Terminal output: " << logger.logTerminalSwitch << endl;
    cout << " Log output level (file): " << logger.logOutputLevelFile << endl;    
    cout << " Log output
bool FdogLogger::getFileType(fileType fileCoutBool){
    return singleObject->fileCoutMap[fileCoutBool];
}

bool FdogLogger::getTerminalType(terminalType terminalCoutTyle){
    return singleObject->terminalCoutMap[terminalCoutTyle];
}

string FdogLogger::getLogCoutTime(){
    time_t timep;
    time (&timep);
    char tmp[64];
    strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S",localtime(&timep));
    string tmp_str = tmp;

    return SQUARE_BRACKETS_LEFT + tmp_str + SQUARE_BRACKETS_RIGHT;
}

string FdogLogger::getLogNameTime(){
    time_t timep;
    time (&timep);
    char tmp[64];
    strftime(tmp, sizeof(tmp), "%Y-%m-%d-%H:%M:%S",localtime(&timep));
    return tmp;
}

string FdogLogger::getFilePash(){
    getcwd(szbuf, sizeof(szbuf)-1);
    string szbuf_str = szbuf;
    return szbuf_str + SLASH;
}

string FdogLogger::getLogCoutProcessId(){
#ifndef linux
    return to_string(getpid());
#endif
#ifndef WIN32
// unsigned long GetPid(){
// return GetCurrentProcessId();
// }
#endif
}

string FdogLogger::getLogCoutThreadId(){
#ifndef linux
    return to_string(syscall(__NR_gettid));
#endif
#ifndef WIN32
// unsigned long GetTid(){
// return GetCurrentThreadId();
// }
#endif
}

string FdogLogger::getLogCoutUserName(){
    struct passwd *my_info;
    my_info = getpwuid(getuid());
    string name = my_info->pw_name;
    return SPACE + name + SPACE;
}

bool FdogLogger::createFile(string filePash){
    int len = filePash.length();
    if(!len){
        filePash = "log";
        if (0 ! = access(filePash.c_str(), 0)){
            if(-1 == mkdir(filePash.c_str(), 0)){
                std::cout<<"No path";
                return 0;
            }
        }
    }
    std::string filePash_cy(len,'\0');
    for(int i =0;i<len;i++){
        filePash_cy[i]=filePash[i];
        if(filePash_cy[i]=='/' || filePash_cy[i]=='\\'){
            if (-1 == access(filePash_cy.c_str(), 0)){
                if(0!=mkdir(filePash_cy.c_str(),0)){
                    std::cout<<"has path";
                    return 0;
                }
            }
        }
    }
    return 1;
}

bool FdogLogger::logFileWrite(string messages){
    ofstream file;
    file.open(logger.logFilePath + logger.logName, ::ios::app | ios::out);
    if(!file){
        cout<<"Write failed"<<<endl;
        return 0;
    }
    file << messages;
    file.close();
    return 1;
}

bool FdogLogger::bindFileCoutMap(string value1, fileType value2){
    if(logger.logOutputLevelFile.find(value1)! =std::string::npos) {
        fileCoutMap[value2] = true;
    } else {
        fileCoutMap[value2] = false;
    }
}

bool FdogLogger::bindTerminalCoutMap(string value1, terminalType value2){
    if(logger.logOutputLevelTerminal.find(value1)! =std::string::npos) {
        terminalCoutMap[value2] = true;
    } else {
        terminalCoutMap[value2] = false;
    }
}


IV. Test cases
1. fdoglogger_test.cpp
#include

#include"fdoglogger.h" //add logging library header file

using namespace fdog; //namespace for the logging library

int main(){
    FdogError("error");
    FdogWarn("warning");
    FdogInfo("Info");
    FdogDebug("debug");
    FdogTrace("Trace");
    return 0;
}

That's all I've considered for now, if there are any flaws, feel free to add them in the comment section. (For example, the file write opens and closes, which is a waste of resources, see the next post for how to optimize it).
Source code has been uploaded to github, restorestar! 
FdogLog, a lightweight C++ logging library for logging services.
#include"fdoglogger.h" using namespace fdog; FdogLogger * FdogLogger::singleObject = nullptr; mutex * FdogLogger::mutex_new = new(mutex); FdogLogger::FdogLogger(){ initLogConfig(); } FdogLogger::~FdogLogger(){ } FdogLogger* FdogLogger::getInstance(){ mutex_new->lock(); if (singleObject == nullptr) { singleObject = new FdogLogger(); } mutex_new->unlock(); return singleObject; } void FdogLogger::initLogConfig(){ map<string, string *> flogConfInfo; flogConfInfo["logSwitch"] = &this-> logger.logSwitch; flogConfInfo["logFileSwitch"] = &this->logger.logFileSwitch; flogConfInfo["logTerminalSwitch"] = &this-> logger.logTerminalSwitch; flogConfInfo["logName"] = &this->logger.logName; flogConfInfo["logFilePath"] = &this->logger.logFilePath; flogConfInfo["logMixSize"] = &this-> logger.logMixSize; flogConfInfo["logBehavior"] = &this->logger.logBehavior; flogConfInfo["logOverlay"] = &this->logger.logOverlay; flogConfInfo["logOutputLevelFile"] = &this-> logger.logOutputLevelFile; flogConfInfo["logOutputLevelTerminal"] = &this->logger.logOutputLevelTerminal; string str; ifstream file; char str_c[100]={0}; file.open("fdoglogconf.conf"); if(!file.is_open()){ cout<<"File open failed \n"; } while(getline(file, str)){ if(!str.length()) { continue; } string str_copy = str; //cout<<"Get data: "< int j = 0; for(int i = 0; i < str.length(); i++){ if(str[i]==' ')continue; str_copy[j] = str[i]; j++; } str_copy.erase(j); if(str_copy[0]! = '#'){ sscanf(str_copy.data(),"%[^=]",str_c); auto iter = flogConfInfo.find(str_c); if(iter!=flogConfInfo.end()){ sscanf(str_copy.data(),"%*[^=]=%s",str_c); *iter->second = str_c; } else { } } } logger.logName = logger.logName + getLogNameTime() + ".log"; bindFileCoutMap("5", fileType::Error); bindFileCoutMap("4", fileType::Warn); bindFileCoutMap("3", fileType::Info); bindFileCoutMap("2", fileType::Debug); bindFileCoutMap("1", fileType::Trace); bindTerminalCoutMap("5", terminalType::Error); bindTerminalCoutMap("4", terminalType::Warn); bindTerminalCoutMap("3", terminalType::Info); bindTerminalCoutMap("2", terminalType::Debug); bindTerminalCoutMap("1", terminalType::Trace); if(logger.logFileSwitch == "on"){ if(!createFile(logger.logFilePath)){ std::cout<<"Log work path creation failed\n"; } } cout << "|========FdogLogger v2.0==========================|" <<endl << endl; cout << " LogSwitch: " << logger.logSwitch << endl; cout << " File output: " << < logger.logFileSwitch << endl; cout << " Terminal output: " << logger.logTerminalSwitch << endl; cout << " Log output level (file): " << logger.logOutputLevelFile << endl; cout << " Log output bool FdogLogger::getFileType(fileType fileCoutBool){ return singleObject->fileCoutMap[fileCoutBool]; } bool FdogLogger::getTerminalType(terminalType terminalCoutTyle){ return singleObject->terminalCoutMap[terminalCoutTyle]; } string FdogLogger::getLogCoutTime(){ time_t timep; time (&timep); char tmp[64]; strftime(tmp, sizeof(tmp), "%Y-%m-%d %H:%M:%S",localtime(&timep)); string tmp_str = tmp; return SQUARE_BRACKETS_LEFT + tmp_str + SQUARE_BRACKETS_RIGHT; } string FdogLogger::getLogNameTime(){ time_t timep; time (&timep); char tmp[64]; strftime(tmp, sizeof(tmp), "%Y-%m-%d-%H:%M:%S",localtime(&timep)); return tmp; } string FdogLogger::getFilePash(){ getcwd(szbuf, sizeof(szbuf)-1); string szbuf_str = szbuf; return szbuf_str + SLASH; } string FdogLogger::getLogCoutProcessId(){ #ifndef linux return to_string(getpid()); #endif #ifndef WIN32 // unsigned long GetPid(){ // return GetCurrentProcessId(); // } #endif } string FdogLogger::getLogCoutThreadId(){ #ifndef linux return to_string(syscall(__NR_gettid)); #endif #ifndef WIN32 // unsigned long GetTid(){ // return GetCurrentThreadId(); // } #endif } string FdogLogger::getLogCoutUserName(){ struct passwd *my_info; my_info = getpwuid(getuid()); string name = my_info->pw_name; return SPACE + name + SPACE; } bool FdogLogger::createFile(string filePash){ int len = filePash.length(); if(!len){ filePash = "log"; if (0 ! = access(filePash.c_str(), 0)){ if(-1 == mkdir(filePash.c_str(), 0)){ std::cout<<"No path"; return 0; } } } std::string filePash_cy(len,'\0'); for(int i =0;i<len;i++){ filePash_cy[i]=filePash[i]; if(filePash_cy[i]=='/' || filePash_cy[i]=='\\'){ if (-1 == access(filePash_cy.c_str(), 0)){ if(0!=mkdir(filePash_cy.c_str(),0)){ std::cout<<"has path"; return 0; } } } } return 1; } bool FdogLogger::logFileWrite(string messages){ ofstream file; file.open(logger.logFilePath + logger.logName, ::ios::app | ios::out); if(!file){ cout<<"Write failed"<<<endl; return 0; } file << messages; file.close(); return 1; } bool FdogLogger::bindFileCoutMap(string value1, fileType value2){ if(logger.logOutputLevelFile.find(value1)! =std::string::npos) { fileCoutMap[value2] = true; } else { fileCoutMap[value2] = false; } } bool FdogLogger::bindTerminalCoutMap(string value1, terminalType value2){ if(logger.logOutputLevelTerminal.find(value1)! =std::string::npos) { terminalCoutMap[value2] = true; } else { terminalCoutMap[value2] = false; } }