C++模塊日誌

背景

在程序中寫日誌是一件很是重要,可是很容易被開發人員忽視的地方。C++有挺多的日誌庫(如glog,log4cpp等),方便開發人員寫日誌。但通常這種log庫考慮的是應用寫log的方法。而C++做爲輪子的製造者,迫切的需求是有一個輕量級的模塊日誌,方便開發人員在庫內寫日誌。ios

在庫內寫日誌,通常咱們使用printf、std::cout等,又或者嵌入一個log模塊。當調用者須要按本身的方式寫log時,這些常見的方法顯得無能爲力。若是存在一個log模塊專爲C++的導出模塊設計,那麼它應該具備哪些功能呢?app

設計方法

設計前提

Module內寫日誌,通常有如下幾個問題須要考慮:函數

  • 不依賴具體log落地實現:如能夠自定義輸出到文件、輸出到日誌採集系統、輸出到syslog等;還可定義如須要輸出哪些log級別、插入一些traceID等;自定義輸出log的格式等。
  • 獨立:各模塊間日誌插拔式設計,互不影響,能夠選擇只記錄其中某一個或幾個模塊的log。
  • 輕量:加入log最好不須要引入太多東西(好比須要編譯,導入類等),最好是include導入就可使用。
  • 簡單而易於擴展:定義好接口以後,不須要修改源碼來實現log的多樣化;最好是一次發佈,基本不用更新。
  • 可以跨語言:輪子是給各個技術棧使用,跨語言也是一個常見要求。

設計原則

  • 只有頭文件:方便直接引入,不須要額外的編譯。
  • 接口/功能簡單靈活:定義好後,後續基本不須要改變。
  • 熱插拔:各模塊日誌獨立。

實現技巧

  • 定義2個頭文件:一個爲回調定義,方便外部接入時,不須要引入mlog的具體實現。
  • 接口:經過宏方式獲取文件、函數等信息;經過RAII構造初始化這些信息,析構調用log回調函數,進行日誌上報。
  • 熱插拔:不使用全局變量,而使用模版+靜態變量的方法,使得各Module都有本身單獨的回調函數,而且避免重複定義的問題。
  • 易用:使用stream進行日誌輸出,避免格式化出錯。

實現

Module日誌回調的定義

//mlog_def.hpp
#pragma once

/* ** 日誌回調函數原型 ** @file 日誌所在文件 ** @line 日誌所在代碼行 ** @func 日誌所在函數 ** @severity 日誌級別 ** @context 日誌內容 */
typedef void(*MLogCallBack)(const char *file, int line, const char *func, int severity, const char *content);
複製代碼

Module日誌庫實現

//mlog.hpp
#pragma once

#include <sstream>
#include <iostream>
#include <functional>
#include <string>
#include "mlog_def.hpp"

/* ** 用於在頭文件內生成全局惟一對象 */
template<typename T>
class GlobalVar {
public:
    static T VAR;
};

template<typename T> T GlobalVar<T>::VAR = nullptr;

/* ** 日誌級別 */
#define MLOG_DEBUG 0
#define MLOG_INFO 1
#define MLOG_WARN 2
#define MLOG_ERROR 3
#define MLOG_FATAL 4

/* ** mlog */
namespace mlog
{
    class LogMessage;

    /* ** 日誌回調設置函數 */
    static void SetMlogCallBack(MLogCallBack func) { GlobalVar<MLogCallBack>::VAR = func; }
}

/* ** 日誌回調生成類 */
class mlog::LogMessage
{
public:
    LogMessage(const char* file, int line, const char* func, int severity, MLogCallBack callback)
        : _file(file)
        , _line(line)
        , _func(func)
        , _severity(severity)
        , _callback(callback)
    {
    }

    ~LogMessage()
    {
        if (_callback)
        {
            std::string content = _stream.str();
            _callback(_file.c_str(), _line, _func.c_str(), _severity, content.c_str());
        }
    }

    std::ostringstream &stream() { return _stream; }

private:
    std::string _file;
    int _line;
    std::string _func;
    int _severity;

    MLogCallBack _callback;
    std::ostringstream _stream;
};

/* ** 實際使用宏 */
#define LOG_DEBUG mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_DEBUG, GlobalVar<MLogCallBack>::VAR).stream()
#define LOG_INFO mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_INFO, GlobalVar<MLogCallBack>::VAR).stream()
#define LOG_WARN mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_WARN, GlobalVar<MLogCallBack>::VAR).stream()
#define LOG_ERROR mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_ERROR, GlobalVar<MLogCallBack>::VAR).stream()
#define LOG_FATAL mlog::LogMessage(__FILE__, __LINE__, __FUNCTION__, MLOG_FATAL, GlobalVar<MLogCallBack>::VAR).stream()
複製代碼

使用

首先,Module中增長一個導出函數。ui

//mylibrary.h
#pragma once
 
#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif
 
#include <mlog/mlog_def.hpp>
MYLIBRARY_API void SetMyLibraryLogCallback(MLogCallBack logCallback);
複製代碼
//mylibrary.cpp
#include "mylibrary.h"
#include <mlog/mlog.hpp>
 
MYLIBRARY_API void SetMyLibraryLogCallback(MLogCallBack logCallback) {
    mlog::SetMlogCallBack(logCallback);
}
複製代碼

在使用Module的程序或Module中,調用該函數設置日誌回調。spa

//myapplication.cpp
#include <mlog/mlog_def.hpp>
#include "mylibrary.h"

void LogCallBack(const char *file, int line, const char *func, int severity, const char *content) {
	//日誌處理,能夠寫,也能夠幹其餘的
}
 
int main(int ,const char*[]) {
    //必要的各類初始化
    SetMyLibraryLogCallback(LogCallBack);

    //...
    return 0;
}
複製代碼
相關文章
相關標籤/搜索