这篇文章中的日志库比较简陋,新版本的多功能日志库辛苦移步:
TOMOCAT:开箱即用的 C++多功能日志库42 赞同 · 10 评论文章编辑
简介
线程安全的C++日志库。
特性
- 默认输出到控制台
- 支持配置日志保存路径和文件
- 每小时自动切割日志
- 支持DEBUG、INFO、WARN和ERROR四种级别日志输出
- 支持设置日志最大保存时长,自动清理过期日志
使用方法
编译:
$make
测试:
$make test
$./output/bin/TestLogger
[2022-04-17 17:21:28.885058][3271:c4e0d7b4e734cac][INFO ][../util/config_util/toml_helper.h:23][ParseTomlValue]parse toml succ, key:Level value:1
[2022-04-17 17:21:28.885121][3271:c4e0d7b4e734cac][INFO ][../util/config_util/toml_helper.h:23][ParseTomlValue]parse toml succ, key:Directory value:./log
[2022-04-17 17:21:28.885166][3271:c4e0d7b4e734cac][INFO ][../util/config_util/toml_helper.h:23][ParseTomlValue]parse toml succ, key:FileName value:logger.log
[2022-04-17 17:21:28.885198][3271:c4e0d7b4e734cac][INFO ][../util/config_util/toml_helper.h:23][ParseTomlValue]parse toml succ, key:RetainHours value:4
测试代码:
#include "logger.h"
int main() {
if (!logger::Logger::GetInstance()->Init("./conf/logger.conf")) {
log_error("init logger fail, print to console");
}
log_info("info message");
log_debug("debug message");
log_info("name:%s age:%d weight:%.1f", "tomocat", 26, 56.23);
log_warn("warn message");
log_error("error message");
log_error_t("err_tag", "error message with tag, type:%s length:%d", "pencil", 17);
}
配置文件:
# 日志级别, 默认打印INFO日志
# * 0: DEBUG
# * 1: INFO
# * 2: WARN
# * 3: ERROR
Level=1
# 日志存储文件夹, 默认输出到当前文件夹
Directory="./log"
# 日志文件名, 默认输出到控制台
FileName="logger.log"
# 保存小时数, 不设置则不会进行日志切割
RetainHours=4
代码
https://github.com/TOMO-CAT/CppUtil/tree/main/logger
FileAppender.h:
#pragma once
#include <string>
#include <fstream>
#include <boost/noncopyable.hpp>
#define FILE_APPENDER_BUFF_SIZE 4096
namespace logger {
class FileAppender : boost::noncopyable {
public:
/**
* @brief Construct a new File Appender object
*
* @param dir 日志保存路径
* @param file_name 日志名
* @param retain_hours 保存小时数
*/
FileAppender(std::string dir, std::string file_name, int retain_hours);
~FileAppender();
public:
/**
* @brief 必要的初始化
*
* @return RetCode 返回RetCode::OK表示成功, 其他均表示失败
*/
bool Init();
/**
* @brief 将格式化字符串写入日志文件
*
* @param fmt 带有格式控制符的字符串
* @param args 参数
*/
void Write(const char* fmt, va_list args);
void Write(const char* fmt, ...);
private:
static int64_t gen_now_hour_suffix();
static int64_t gen_hour_suffix(const struct timeval* tv);
void cut_if_need();
void delete_overdue_file(const struct timeval* tv);
private:
std::fstream file_stream_;
std::string file_dir_;
std::string file_name_;
std::string file_path_;
int retain_hours_;
int64_t last_hour_suffix_;
pthread_mutex_t write_mutex_;
static __thread char buffer_[FILE_APPENDER_BUFF_SIZE];
};
} // namespace logger
FileAppender.cpp:
#include <sys/stat.h>
#include <sys/time.h>
#include <errno.h>
#include <cstring>
#include <cstdio>
#include <cstdarg>
#include "file_appender.h"
#include "util/macro_util.h"
namespace logger {
__thread char FileAppender::buffer_[FILE_APPENDER_BUFF_SIZE];
FileAppender::FileAppender(std::string dir, std::string file_name, int retain_hours) :
file_dir_(dir), file_name_(file_name), retain_hours_(retain_hours) {
if (file_dir_.empty()) {
file_dir_ = ".";
}
file_path_ = file_dir_ + "/" + file_name_;
}
FileAppender::~FileAppender() {
if (file_stream_.is_open()) {
file_stream_.close();
}
}
bool FileAppender::Init() {
int ret = mkdir(file_dir_.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
if (ret != 0 && errno != EEXIST) {
printf2console("mkdir fail, dir:%s err:%s", file_dir_.c_str(), strerror(errno));
return false;
}
file_stream_.open(file_path_.c_str(), std::fstream::out | std::fstream::app);
last_hour_suffix_ = gen_now_hour_suffix();
pthread_mutex_init(&write_mutex_, nullptr);
return true;
}
void FileAppender::Write(const char* fmt, va_list args) {
cut_if_need();
pthread_mutex_lock(&write_mutex_);
if (file_stream_.is_open()) {
vsnprintf(buffer_, sizeof(buffer_), fmt, args);
file_stream_ << buffer_ << "\n";
file_stream_.flush();
}
pthread_mutex_unlock(&write_mutex_);
}
void FileAppender::Write(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
Write(fmt, args);
va_end(args);
}
/**
* 生成当前小时的文件suffix, 格式yyyymmddhh
* eg: 2022040214
*/
int64_t FileAppender::gen_now_hour_suffix() {
struct timeval now;
::gettimeofday(&now, nullptr);
return gen_hour_suffix(&now);
}
int64_t FileAppender::gen_hour_suffix(const struct timeval* tv) {
struct tm tm_val;
::localtime_r(&tv->tv_sec, &tm_val);
return tm_val.tm_hour + tm_val.tm_mday * 100 + (tm_val.tm_mon + 1) * 10000 + (tm_val.tm_year + 1900) * 1000000;
}
void FileAppender::cut_if_need() {
struct timeval now;
::gettimeofday(&now, nullptr);
int64_t now_hour_suffix = gen_hour_suffix(&now);
if (now_hour_suffix > last_hour_suffix_) {
pthread_mutex_lock(&write_mutex_);
if (now_hour_suffix > last_hour_suffix_) {
std::string new_file_path = file_path_ + "." + std::to_string(last_hour_suffix_); // eg: logger.log.yyyymmddhh
int ret = rename(file_path_.c_str(), new_file_path.c_str());
if (ret != 0) {
printf2console("rename fail, old_file:%s new_file:%s err:%s", file_path_.c_str(), new_file_path.c_str(), strerror(errno));
}
file_stream_.close();
file_stream_.open(file_path_.c_str(), std::fstream::out | std::fstream::app);
printf2console("cut file, last hour:%d now hour:%d file_path:%s new_file_path:%s",
last_hour_suffix_, now_hour_suffix, file_path_.c_str(), new_file_path.c_str());
last_hour_suffix_ = now_hour_suffix;
}
pthread_mutex_unlock(&write_mutex_);
delete_overdue_file(&now);
}
}
void FileAppender::delete_overdue_file(const struct timeval* tv) {
if (retain_hours_ <= 0) {
return;
}
struct timeval old_tv;
old_tv.tv_sec = tv->tv_sec - retain_hours_ * 3600;
old_tv.tv_usec = tv->tv_usec;
int64_t old_hour_suffix = gen_hour_suffix(&old_tv);
std::string old_file_path = file_path_ + "." + std::to_string(old_hour_suffix);
remove(old_file_path.c_str());
printf2console("delete old file, file_path:%s", old_file_path.c_str());
}
} // namespace logger
logger.h:
#pragma once
#include <string>
#include "file_appender.h"
namespace logger {
class Logger : boost::noncopyable {
public:
enum class Level {
DEBUG_LEVEL,
INFO_LEVEL,
WARN_LEVEL,
ERROR_LEVEL,
};
/**
* 初始化, 未初始化或初始化失败时日志会输出到控制台
*/
bool Init(const std::string& conf_path);
/**
* 获取单例
*/
static Logger* GetInstance() { return instance_; }
/**
* 根据日志级别打印日志
*/
void Log(Level log_level, const char* fmt, ...);
public:
static void set_pid(int pid);
static int get_pid();
static void set_trace_id(uint64_t trace_id = 0);
static uint64_t get_trace_id();
private:
Logger();
~Logger();
private:
static std::string gen_timestamp_prefix();
private:
static Logger* instance_;
bool is_console_output_;
FileAppender* file_appender_;
Level priority_;
static __thread uint64_t trace_id_;
static __thread int pid_;
};
} // namespace logger
// ======================================对外接口======================================
#define log_debug(fmt, args...) \
do { \
logger::Logger::GetInstance()->Log(logger::Logger::Level::DEBUG_LEVEL, "[DEBUG][%s:%d][%s]" fmt, \
__FILE__, __LINE__, __FUNCTION__, ##args); \
} while (0) \
#define log_info(fmt, args...) \
do { \
logger::Logger::GetInstance()->Log(logger::Logger::Level::INFO_LEVEL, "[INFO ][%s:%d][%s]" fmt, \
__FILE__, __LINE__, __FUNCTION__, ##args); \
} while (0) \
#define log_warn(fmt, args...) \
do { \
logger::Logger::GetInstance()->Log(logger::Logger::Level::WARN_LEVEL, "[WARN ][%s:%d][%s]" fmt, \
__FILE__, __LINE__, __FUNCTION__, ##args); \
} while (0) \
#define log_error(fmt, args...) \
do { \
logger::Logger::GetInstance()->Log(logger::Logger::Level::ERROR_LEVEL, "[ERROR][%s:%d][%s]" fmt, \
__FILE__, __LINE__, __FUNCTION__, ##args); \
} while (0) \
#define log_error_t(tag, fmt, args...) \
do { \
logger::Logger::GetInstance()->Log(logger::Logger::Level::ERROR_LEVEL, "[ERROR][%s:%d][%s][tag=%s]" fmt, \
__FILE__, __LINE__, __FUNCTION__, tag, ##args); \
} while (0) \
logger.cpp:
#include <uuid/uuid.h>
#include <sys/time.h>
#include <memory>
#include <cstdarg>
#include "logger.h"
#include "cpptoml.h"
#include "util/config_util/toml_helper.h"
namespace logger {
Logger* Logger::instance_ = new Logger();
__thread uint64_t Logger::trace_id_ = 0;
__thread int Logger::pid_ = 0;
Logger::Logger() : is_console_output_(true), file_appender_(nullptr), priority_(Level::INFO_LEVEL) {}
Logger::~Logger() {
if (file_appender_) {
delete file_appender_;
}
}
bool Logger::Init(const std::string& conf_path) {
set_pid(getpid());
set_trace_id();
std::shared_ptr<cpptoml::table> g;
try {
g = cpptoml::parse_file(conf_path);
} catch (const cpptoml::parse_exception& e) {
log_error("parse logger conf fail, path:%s err:%s", conf_path.c_str(), e.what());
return false;
}
int level;
std::string dir;
std::string file_name;
int retain_hours;
if (util::ParseTomlValue(g, "Level", level)) {
if (level >= static_cast<int>(Level::DEBUG_LEVEL) && level <= static_cast<int>(Level::ERROR_LEVEL)) {
priority_ = Level(level);
}
}
if (!util::ParseTomlValue(g, "Directory", dir)) {
dir = ".";
}
if (!util::ParseTomlValue(g, "FileName", file_name)) {
log_warn("parse FileName config fail, print to console");
return false;
}
if (!util::ParseTomlValue(g, "RetainHours", retain_hours)) {
retain_hours = 0; // don't delete overdue log file
}
file_appender_ = new FileAppender(dir, file_name, retain_hours);
if (!file_appender_->Init()) {
return false;
}
is_console_output_ = false;
return true;
}
void Logger::Log(Level log_level, const char* fmt, ...) {
if (log_level < priority_) {
return;
}
std::string new_fmt = gen_timestamp_prefix() + fmt;
va_list args;
va_start(args, fmt);
if (is_console_output_) {
vprintf((new_fmt + "\n").c_str(), args);
} else {
file_appender_->Write(new_fmt.c_str(), args);
}
va_end(args);
}
std::string Logger::gen_timestamp_prefix() {
struct timeval now;
::gettimeofday(&now, nullptr);
struct tm tm_now;
::localtime_r(&now.tv_sec, &tm_now);
char time_str[100];
snprintf(time_str, sizeof(time_str), "[%04d-%02d-%02d %02d:%02d:%02d.%06ld][%d:%lx]",
tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday, tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec, now.tv_usec,
pid_, trace_id_);
return time_str;
}
void Logger::set_pid(int pid) {
pid_ = pid;
}
int Logger::get_pid() {
return pid_;
}
void Logger::set_trace_id(uint64_t trace_id) {
if (trace_id == 0) {
uuid_t uuid;
uuid_generate(uuid);
// 将uuid解析成uint64_t数组并取第一个元素作为trace_id
uint64_t* trace_id_list = reinterpret_cast<uint64_t*>(uuid);
trace_id_ = trace_id_list[0];
} else {
trace_id_ = trace_id;
}
}
uint64_t Logger::get_trace_id() {
return trace_id_;
}
} // namespace logger