[C++]C++多线程日志库

这篇文章中的日志库比较简陋,新版本的多功能日志库辛苦移步:

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海神之光.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值