附上测试代码:
在linux下编译的话需要链接pthread,即编译命令加上 -lpthread
。
#include "LogUtils.h"
#include
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;
}
负责实际的打印工作,定义日志级别。
头文件:
#ifndef LOG_INNER_HPP_
#define LOG_INNER_HPP_
#include
#include
/// @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
#include
#include
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;
}
负责包装 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);
}
定义宏定义,方便调用(也可以直接调用LogOuter里的方法)。
#ifndef _LOG_UTILS_H_
#define _LOG_UTILS_H_
#include "LogOuter.h"
#include
#include
// 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