C++ 自定义项目日志库,支持log分级、标签、耗时统计、显示文件名方法名行号信息等,Windows&Linux通用

一、效果展示


功能点:

  • log输出时间,精确到毫秒
  • log等级,支持 V(verbose),D(debug),I(info),W(warning),E(error)
  • log标签,可以根据标签筛选不同的log
  • log信息,自动添加文件名、方法名、行号输出,帮忙快速定位到log在工程中的位置
  • 耗时统计功能,精确到微秒

附上测试代码:
在linux下编译的话需要链接pthread,即编译命令加上 -lpthread

#include "LogUtils.h"
#include <thread>

void foo() {
    for (int i = 0; i < 5; i++) { LOGD("i: %d", i); }
}

void bar() {
    for (char c = 'a'; c < 'f'; c++) { LOGD("c: %c", c); }
}

int main(int argc, char* argv[]) {
    set_log_level(LOG_LEVEL_DEBUG);
    LOGV("test log v"); // won't print, because debug level > verbose level
    __TIC__(task);
    std::thread t1(foo);
    std::thread t2(bar);
    t1.join();
    t2.join();
    __TOC__(task); // print time elapse
    return 0;
}

二、实现

1. LogInner类

负责实际的打印工作,定义日志级别。

头文件:

#ifndef LOG_INNER_HPP_
#define LOG_INNER_HPP_

#include <stdarg.h>
#include <stdio.h>

/// @brief 日志级别
/// 级别排序 ERROR > WARNING > INFO > DEBUG > VERBOSE
typedef enum {
    LOG_LEVEL_VERBOSE,        ///< 打印所有级别日志
    LOG_LEVEL_DEBUG,          ///< 打印高于DEBUG(包含)级别日志
    LOG_LEVEL_INFO,           ///< 打印高于INFO(包含)级别日志
    LOG_LEVEL_WARN,           ///< 打印高于WARNING(包含)级别日志
    LOG_LEVEL_ERR,            ///< 打印高于ERROR(包含)级别日志
    LOG_LEVEL_CLOSE           ///< 关闭
} log_level_t;

class LogInner {

public:
    // @brief 获取LogInner单例对象
    static LogInner& getInstance();

    /// @brief 设置log级别
    /// @param [in] level 级别
    void setLogLevel(log_level_t level);

    /// @brief 自定义tag的log
    /// @param [in] tag 自定义的tag
    /// @param [in] format 字符串格式
    /// @param [in] list 待输出的可变参数
    void v(const char* tag, const char* format, va_list list) const;

    /// @brief 自定义tag的log
    /// @param [in] tag 自定义的tag
    /// @param [in] format 字符串格式
    /// @param [in] list 待输出的可变参数
    void d(const char* tag, const char* format, va_list list) const;

    /// @brief 自定义tag的log
    /// @param [in] tag 自定义的tag
    /// @param [in] format 字符串格式
    /// @param [in] list 待输出的可变参数
    void i(const char* tag, const char* format, va_list list) const;

    /// @brief 自定义tag的log
    /// @param [in] tag 自定义的tag
    /// @param [in] format 字符串格式
    /// @param [in] list 待输出的可变参数
    void w(const char* tag, const char* format, va_list list) const;

    /// @brief 自定义tag的log
    /// @param [in] tag 自定义的tag
    /// @param [in] format 字符串格式
    /// @param [in] list 待输出的可变参数
    void e(const char* tag, const char* format, va_list list) const;

private:
    LogInner();
    ~LogInner();
    LogInner(const LogInner& rhs);
    const LogInner& operator= (const LogInner& rhs);
    void output(const FILE* f, const char& l, const char* tag, const char* format, va_list list) const;

    log_level_t mLevel;
};

实现:

#include "LogInner.hpp"
#include <time.h>
#include <chrono>
#include <mutex>

using namespace std::chrono;

std::mutex mtx; // 多线程场景下打印使用,不是多线程的话关掉可以节约点性能

void LogInner::output(const FILE* f, const char& level, const char* tag, const char* format, va_list list) const {
    FILE* fp = const_cast<FILE*>(f);
    auto now = system_clock::now();
    time_t tt = system_clock::to_time_t(now);
    struct tm* timeinfo = localtime(&tt);
    mtx.lock();
    fprintf(fp, "[%02d-%02d %02d:%02d:%02d.%03d %c/%s] ",
        timeinfo->tm_mon + 1,
        timeinfo->tm_mday,
        timeinfo->tm_hour,
        timeinfo->tm_min,
        timeinfo->tm_sec,
        (int)(duration_cast<milliseconds>(now.time_since_epoch()).count() % 1000),
        level, tag);
    vfprintf(fp, format, list);
    fprintf(fp, "n");
    mtx.unlock();
}

void LogInner::v(const char* tag, const char* format, va_list list) const  {
    if(mLevel > LOG_LEVEL_VERBOSE) {
        return;
    }
    output(stdout, 'V', tag, format, list);
}

void LogInner::d(const char* tag, const char* format, va_list list) const  {
    if(mLevel > LOG_LEVEL_DEBUG) {
        return;
    }
    output(stdout, 'D', tag, format, list);
}

void LogInner::i(const char* tag, const char* format, va_list list) const  {
    if(mLevel > LOG_LEVEL_INFO) {
        return;
    }
    output(stdout, 'I', tag, format, list);
}

void LogInner::w(const char* tag, const char* format, va_list list) const  {
    if(mLevel > LOG_LEVEL_WARN) {
        return;
    }
    output(stdout, 'W', tag, format, list);
}

void LogInner::e(const char* tag, const char* format, va_list list) const  {
    if(mLevel > LOG_LEVEL_ERR) {
        return;
    }
    output(stderr, 'E', tag, format, list);
}

LogInner& LogInner::getInstance() {
    static LogInner instance;
    return instance;
}

LogInner::LogInner(): mLevel(LOG_LEVEL_VERBOSE) {
}

LogInner::~LogInner() {
}

void LogInner::setLogLevel(log_level_t level) {
    mLevel = level;
}

2. LogOuter类

负责包装 LogInner 类,可变参数的转换。

头文件:

#ifndef LOG_OUTER_H_
#define LOG_OUTER_H_

#include "LogInner.hpp"

/// @brief 打开log开关,默认LOG_LEVEL_VERBOSE
void set_log_level(log_level_t level);

/// @brief 带tag的verbose级别log
/// @param [in] tag log标识
/// @param [in] format 输出消息格式化字符串
/// @param [in] ...(可变参数)
void logv_tag(const char* tag, const char* format, ...);

/// @brief 带tag的debug级别log
/// @param [in] tag log标识
/// @param [in] format 输出消息格式化字符串
/// @param [in] ...(可变参数)
void logd_tag(const char* tag, const char* format, ...);

/// @brief 带tag的info级别log
/// @param [in] tag log标识
/// @param [in] format 输出消息格式化字符串
/// @param [in] ...(可变参数)
void logi_tag(const char* tag, const char* format, ...);

/// @brief 带tag的warning级别log
/// @param [in] tag log标识
/// @param [in] format 输出消息格式化字符串
/// @param [in] ...(可变参数)
void logw_tag(const char* tag, const char* format, ...);

/// @brief 带tag的error级别log
/// @param [in] tag log标识
/// @param [in] format 输出消息格式化字符串
/// @param [in] ...(可变参数)
void loge_tag(const char* tag, const char* format, ...);

#endif //LOG_OUTER_H_

实现:

#include "LogOuter.h"

void set_log_level(log_level_t level) {
    LogInner::getInstance().setLogLevel(level);
}

void logv_tag(const char* tag, const char* format, ...) {
    va_list list;
    va_start(list, format);
    LogInner::getInstance().v(tag, format, list);
    va_end(list);
}

void logd_tag(const char* tag, const char* format, ...) {
    va_list list;
    va_start(list, format);
    LogInner::getInstance().d(tag, format, list);
    va_end(list);
}

void logi_tag(const char* tag, const char* format, ...) {
    va_list list;
    va_start(list, format);
    LogInner::getInstance().i(tag, format, list);
    va_end(list);
}

void logw_tag(const char* tag, const char* format, ...) {
    va_list list;
    va_start(list, format);
    LogInner::getInstance().w(tag, format, list);
    va_end(list);
}

void loge_tag(const char* tag, const char* format, ...) {
    va_list list;
    va_start(list, format);
    LogInner::getInstance().e(tag, format, list);
    va_end(list);
}

3. LogUtils类

定义宏定义,方便调用(也可以直接调用LogOuter里的方法)。

#ifndef _LOG_UTILS_H_
#define _LOG_UTILS_H_

#include "LogOuter.h"
#include <string.h>
#include <chrono>

// Windows文件路径分隔符是\,Linux文件路径分隔符是/
#ifdef _MSC_VER
#define __FILENAME__ (strrchr(__FILE__, '\') + 1)
#else
#define __FILENAME__ (strrchr(__FILE__, '/') + 1)
#endif

// 可自定义标签,在cpp中自己定义这个宏就行,如若没有则使用默认标签
#ifndef LOG_TAG
#define LOG_TAG "default_tag"
#endif

#define LOG_TRACE(func, format, ...) 
    func(LOG_TAG, "[%s][%s][%d]: " format, __FILENAME__, __FUNCTION__, __LINE__, ##__VA_ARGS__)

#define LOGV(format, ...) LOG_TRACE(logv_tag, format, ##__VA_ARGS__)
#define LOGD(format, ...) LOG_TRACE(logd_tag, format, ##__VA_ARGS__)
#define LOGI(format, ...) LOG_TRACE(logi_tag, format, ##__VA_ARGS__)
#define LOGW(format, ...) LOG_TRACE(logw_tag, format, ##__VA_ARGS__)
#define LOGE(format, ...) LOG_TRACE(loge_tag, format, ##__VA_ARGS__)

#define __TIC__(V) auto time_##V##_start = std::chrono::high_resolution_clock::now()
#define __TOC__(V) auto time_##V##_end = std::chrono::high_resolution_clock::now(); 
                   auto time_##V##_span = std::chrono::duration_cast<std::chrono::duration<double>> 
                       (time_##V##_end - time_##V##_start); 
                   LOGD("%s elapse time: %.3f ms", #V, time_##V##_span.count() * 1000)

#endif // _LOG_UTILS_H_

三、后记

目前支持的功能还是有限的,但日常项目开发也足够使用了。

这里没有兼容 Android 系统了,也为了避免篇幅太长了,要做的话可以将 LogInner 类分两个实现,一个是使用 vfprintf 打印,一个是使用 Android 的 __android_log_vprint 打印,另外 Android 的 log 是自带时间信息和换行的。

关于 Android 日志,或者宏开关控制是否打印log,可以参考链接:NDK/C++ 耗时统计类TimeUtils

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>