芽萌丸プログラミング部 @programming
@programming
2021/1/27 10:48 更新✏

C++でシンプルなロガーを自作

C++開発で使えるシンプルなロガーを自作してみました。 外部ライブラリに依存しませんので、以下で紹介するロガー本体 Log.h をコピペするだけで手軽に使えます。 ※基本的にLinuxでの仕様を前提としています。

目次:

仕様

ロガー本体のコード Log.h

Log.h

#ifndef LOG_H
#define LOG_H

#include <iostream>
#include <string>
#include <chrono>
#include <ctime>
#include <sys/time.h>
#include <map>

using namespace std;

/**
 * Log class
 *
 * Usage:
 *
 * #include "Log.h"
 * 
 * int main(int argc, char* argv[]){
 *   // create a new logger instance by calling the constructor:
 *   Log* log = new Log(debugLevel); // debugLevel's type is integer. 0:DEBUG(ALL), 10:INFO, 20:WARN, 30:ERROR, 99:NONE
 *   // or by passing a debug option as app's arguments: --debug=(debug|info|warn|error)
 *   // Log* log = Log::getInstanceFromArgs(argc, argv);
 * 
 *   log->debug("hoge", "foo", 123);
 *   log->info("hoge", "foo", 123);
 *   log->warn("hoge", "foo", 123);
 *   log->error("hoge", "foo", 123);
 */
class Log {
public:
    /* loglevel for console debug logs */
    int debugLevel = 99; // 99:NONE (nothing logs)

    /** constructor */
    Log(int debugLevel) {
        this->debugLevel = debugLevel;
    }

    /** creates and returns a Log instance from arguments */
    static Log* getInstanceFromArgs(int argc, char* argv[]) {
        int logLevel = 99; // 99:NONE
        //
        // parsing application's arguments:
        // 
        map<string, string> m;
        // Search each command-line argument.
        for (int count = 0; count < argc; count++) {
            string line = string(argv[count]);
            if (line.find_first_of("-") != 0) continue;
            string key = "";
            string val = "";
            int len = line.length();
            bool keyStarted = false;
            bool valStarted = false;
            for (int i = 0; i < len; i++) {
                char c = line[i];
                if (!keyStarted && c == '-') continue;
                keyStarted = true; // key part started.
                if (keyStarted && c == '=') {
                    valStarted = true; // value part started.
                    continue;
                }
                if (keyStarted && !valStarted) {
                    key.push_back(c);
                } else {
                    val.push_back(c);
                }
            }
            m[key] = val;
        }

        string debugLevel;
        if (m.find("debug") != m.end()) {
            debugLevel = m["debug"];
            if (debugLevel == "") debugLevel = "all";
        }
        if (debugLevel == "all" || debugLevel == "debug") logLevel = 0;
        else if (debugLevel == "info") logLevel = 10;
        else if (debugLevel == "warn") logLevel = 20;
        else if (debugLevel == "error") logLevel = 30;
        return new Log(logLevel);
    }

    /** parse arguments */
    map<string, string> parseArgs(int argc, char* argv[]) {
        map<string, string> m;
        // Search each command-line argument.
        for (int count = 0; count < argc; count++) {
            string line = string(argv[count]);
            if (line.find_first_of("-") != 0) continue;
            string key = "";
            string val = "";
            int len = line.length();
            bool keyStarted = false;
            bool valStarted = false;
            for (int i = 0; i < len; i++) {
                char c = line[i];
                if (!keyStarted && c == '-') continue;
                keyStarted = true; // key part started.
                if (keyStarted && c == '=') {
                    valStarted = true; // value part started.
                    continue;
                }
                if (keyStarted && !valStarted) {
                    key.push_back(c);
                } else {
                    val.push_back(c);
                }
            }
            m[key] = val;
        }
        return m;
    }

    /** Get a current ISO date string. */
    string getISODate() {
        timeval curTime;
        gettimeofday(&curTime, NULL);
        int milli = curTime.tv_usec / 1000;
        char buf[sizeof "2021-01-27T23:59:59.000Z"];
        strftime(buf, sizeof buf, "%FT%T", gmtime(&curTime.tv_sec));
        sprintf(buf, "%s.%dZ", buf, milli);
        string s = buf;
        return s;
    }

    template <typename T>
    void log_argument(T t) {
        cout << t << " ";
    }
    bool shouldLog(int debugLevel) {
        if (this->debugLevel >= 99) return false; // NONE
        if (this->debugLevel <= debugLevel) return true;
        return false;
    }
    /** debug log */
    template <typename... Args>
    void debug(Args&&... args) {
        if (!this->shouldLog(0)) return; // check global.debug flag.
        cout << this->getISODate() << " DEBUG: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)), 0)... };
        cout << endl;
    }
    /** info log */
    template <typename... Args>
    void info(Args&&... args) {
        if (!this->shouldLog(10)) return; // check global.debug flag.
        cout << this->getISODate() << " INFO : ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)), 0)... };
        cout << endl;
    }
    /** warn log */
    template <typename... Args>
    void warn(Args&&... args) {
        if (!this->shouldLog(20)) return; // check global.debug flag.
        cout << this->getISODate() << " WARN : ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)), 0)... };
        cout << endl;
    }
    /** error log */
    template <typename... Args>
    void error(Args&&... args) {
        if (!this->shouldLog(30)) return; // check global.debug flag.
        cout << this->getISODate() << " ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)), 0)... };
        cout << endl;
    }
};


#endif // LOG_H

※上記コードはコピペでそのまま使えます。

使い方

  1. #include "path/to/Log.h"でロガー本体をインクルード。
  2. Log* log = new Log(debugLevel);またはLog* log = Log::getInstanceFromArgs(argc, argv);でLogインスタンスを生成。
  3. ログ出力メソッドをコール。例: log->info("うまい棒は", "税込み", 10, "円です。");

以下はサンプルコードです。

test.cpp

#include "Log.h"

using namespace std;

int main(int argc, char* argv[]) {
    // アプリケーションの起動オプション --debug=(debug|info|warn|error) からLogインスタンス生成:
    Log* log = Log::getInstanceFromArgs(argc, argv);
    // or
    // コンストラクタを直接newしてLogインスタンス生成:
    // Log* log = new Log(10); // 引数はデバッグレベル: 0:DEBUG(ALL), 10:INFO, 20:WARN, 30:ERROR, 99:NONE

    // ログを出力:
    // TIP: ログ出力メソッドの引数には複数の文字列や数字を指定することが出来ます!
    log->debug("I'm debug log.", 123);
    log->info("I'm info log.", 123);
    log->warn("I'm warn log.", 123);
    log->error("I'm error log.", 123);
}

TIP: ロガーの使い回し
アプリケーション内でロガーを使いまわしたい場合は、一度生成したLogインスタンスをどこかグローバルなところに保持しておくと便利です。

コンパイル:

$ g++ -o a.out test.cpp Log.h -std=c++11

実行:

## 全てのログを出力:
$ ./a.out --debug
2021-01-27T00:46:34.333Z DEBUG: I'm debug log. 123 
2021-01-27T00:46:34.333Z INFO : I'm info log. 123
2021-01-27T00:46:34.333Z WARN : I'm warn log. 123 
2021-01-27T00:46:34.333Z ERROR: I'm error log. 123 

## infoログ以上を出力:
$ ./a.out --debug=info
2021-01-27T00:47:28.890Z INFO : I'm info log. 123 
2021-01-27T00:47:28.890Z WARN : I'm warn log. 123 
2021-01-27T00:47:28.890Z ERROR: I'm error log. 123 

## errorログを出力:
$ ./a.out --debug=error
2021-01-27T00:48:05.723Z ERROR: I'm error log. 123 

## errorログをlog.txtファイルへ出力
$ ./a.out --debug=error > log.txt
$ cat log.txt 
2021-01-27T00:52:26.463Z ERROR: I'm error log. 123

以上です。

参考

C++

芽萌丸プログラミング部 @programming
芽萌丸プログラミング部@programming
プログラミング関連アカウント。Web標準技術を中心に書いていきます。フロントエンドからサーバサイドまで JavaScript だけで済ませたい人たちの集いです。記事は主に @TanakaSoftwareLab が担当。
オススメ:Zattoyomiで時事ネタチェックの時間節約!