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;
};

#endif //LOG_INNER_HPP_

实现:

#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

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,可以使用StackTraceElement类来获取当前方法行号方法、类文件名,以及调用该方法方法信息。下面是示例代码: ``` StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); if (stackTraceElements.length >= 2) { // 获取当前方法信息 StackTraceElement currentMethod = stackTraceElements[1]; String className = currentMethod.getClassName(); String methodName = currentMethod.getMethodName(); String fileName = currentMethod.getFileName(); int lineNumber = currentMethod.getLineNumber(); System.out.println("当前方法信息:"); System.out.println("类:" + className); System.out.println("方法:" + methodName); System.out.println("文件名:" + fileName); System.out.println("行号:" + lineNumber); // 获取调用该方法方法信息 StackTraceElement callerMethod = stackTraceElements[2]; String callerClassName = callerMethod.getClassName(); String callerMethodName = callerMethod.getMethodName(); String callerFileName = callerMethod.getFileName(); int callerLineNumber = callerMethod.getLineNumber(); System.out.println("调用该方法方法信息:"); System.out.println("类:" + callerClassName); System.out.println("方法:" + callerMethodName); System.out.println("文件名:" + callerFileName); System.out.println("行号:" + callerLineNumber); } ``` 需要注意的是,获取方法信息的代码必须位于该方法内部。如果将该代码放在其他方法中,则获取到的信息可能是其他方法信息。另外,由于获取方法信息的代码本身也会被添加到堆栈跟踪中,因此需要使用`stackTraceElements[2]`来获取调用该方法方法信息

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值