简单有效的c++ log系统(带注释)

logger.h

#pragma once
#include <string>
#include <fstream> // ofstream

namespace utility {
// __FILE__:当前文件名  __LINE__:当前行号
//__VA_ARGS__就是...值的复制
//当可变参数的个数为0时,##可把前面多余的","去掉,防止编译出错
#define debug(format, ...) \
    Logger::instance()->log(Logger::DEBUG, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define info(format, ...) \
    Logger::instance()->log(Logger::INFO, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define warn(format, ...) \
    Logger::instance()->log(Logger::WARN, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define error(format, ...) \
    Logger::instance()->log(Logger::ERROR, __FILE__, __LINE__, format, ##__VA_ARGS__)

#define fatal(format, ...) \
    Logger::instance()->log(Logger::FATAL, __FILE__, __LINE__, format, ##__VA_ARGS__)

class Logger
{
public:
    enum Level {
        DEBUG = 0,
        INFO,
        WARN,
        ERROR,
        FATAL,
        LEVEL_COUNT
    };

    static Logger* instance(); // 单例模式
    void open(const std::string &filename); // 打开文件
    void close(); // 关闭文件,一定记得关闭文件(所以关联析构函数)

    //产生log,format和... 记录多参数log
    // log等级,文件名,所在行,log信息
    void log(Level level, const char *file, int line, const char *format, ...);

    // 设置单个文件最大大小
    void max(int bytes);

    // 设置最低报错限制
    void level(int level);
private:
    Logger(); // 初始化数据
    ~Logger(); // 关闭文件
    void rotate();
    
private:
    std::string m_filename; // 存log文件名
    std::ofstream m_font; // ofstream是从内存到硬盘,ifstream是从硬盘到内存
    int m_max; // 单个文件最大存储长度
    int m_len; // 当前文件长度?
    int m_level; // 限制最低报错(低于此level不考虑)
    static const char *s_level[LEVEL_COUNT]; // 预存level等级的字符串数组
    static Logger *m_instance; // 存储单例
};
}

logger.cpp

#include "logger.h"
#include <time.h> //time_t localtime
#include <stdarg.h> //va_list
using namespace utility;

const char* Logger::s_level[LEVEL_COUNT] = {
    "DEBUG",
    "INFO",
    "WARN",
    "ERROR",
    "FATAL"
};

Logger *Logger::m_instance = nullptr;

// 初始化
Logger::Logger() : m_max(0), m_len(0), m_level(DEBUG) {}

Logger::~Logger() {
    close();
}

Logger* Logger::instance() { //生成、调用单例
    if(m_instance == nullptr) {
        m_instance = new Logger();
    }
    return m_instance;
}

void Logger::open(const std::string &filename) {
    m_filename = filename;
    m_font.open(filename.c_str(), std::ios::app);//app 以追加方式打开文件
    if(m_font.fail()) {
        throw std::logic_error("open log file failed: " + filename);
    }
    m_font.seekp(0, std::ios::end);//把文件的写指针从文件结尾向后移0个字节
    m_len = m_font.tellp();// tellp()获取当前的流位置,使用seekp()来重定位流位置。
}

void Logger::close() {
    m_font.close();
}

void Logger::log(Level level, const char *file, int line, const char *format, ...) {
    if(level < m_level) { // 排除限制等级以下的log信息
        return;
    }
    if(m_font.fail()) {
        throw std::logic_error("open log file failed: " + m_filename);
    }
    
    time_t ticks = time(nullptr);
    struct tm* ptm = localtime(&ticks);//localtime 返回指向 tm 结构的指针,该结构带有被填充的时间信息
    char timestamp[32]; // 记录log时间戳,最好为4倍数方便对齐
    memset(timestamp, 0, sizeof(timestamp)); // 初始化为0
    strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", ptm); //根据 format 中定义的格式化规则,格式化结构 timeptr 表示的时间,并把它存储在 timestamp 中
    
    int len = 0;
    const char * fmt = "%s %s %s: %d";
    // len获取当前生成的部分log字符串长度
    len = snprintf(nullptr, 0, fmt, timestamp, s_level[level], file, line);//设将可变参数(...)按照 format 格式化成字符串,并将字符串复制到 str 中
    if (len > 0) { 
        char *buffer = new char[len + 1];//snprintf生成的字符串会在结尾加“\0”
        snprintf(buffer, len + 1, fmt, timestamp, s_level[level], file, line);
        buffer[len] = 0;
        m_font << buffer;
        delete[] buffer; // new 配 delete
        m_len += len; // 更新当前文件长度
    }

    va_list arg_ptr; // 获取不定个数的参数
    va_start(arg_ptr, format);
    // len获取当前生成的部分log字符串长度
    len = vsnprintf(nullptr, 0, format, arg_ptr);
    va_end(arg_ptr);
    if (len > 0) {
        char * content = new char[len + 1];
        va_start(arg_ptr, format);
        vsnprintf(content, len + 1, format, arg_ptr);
        va_end(arg_ptr);
        content[len] = 0;
        m_font << content;
        delete [] content;
        m_len += len;
    }

    m_font << "\n"; // 单条log结尾换行
    m_font.flush(); // 将内存数据写入硬盘

    if(m_max > 0 && m_len >= m_max) { // 若超出文件最大范围,新生成文件
        rotate();
    }
}

void Logger::max(int bytes) {
    m_max = bytes;
}

void Logger::level(int level) {
    m_level = level;
}

void Logger::rotate() {
    close(); // 先关闭当前文件
    time_t ticks = time(NULL);
    struct tm* ptm = localtime(&ticks);
    char timestamp[32];
    memset(timestamp, 0, sizeof(timestamp));
    strftime(timestamp, sizeof(timestamp), ".%Y-%m-%d_%H-%M-%S", ptm);
    std::string filename = m_filename + timestamp; // 设置新的文件名为 最初文件名 + 时间戳
    if (rename(m_filename.c_str(), filename.c_str()) != 0) {
        throw std::logic_error("rename log file failed: " + std::string(strerror(errno)));
    }
    open(m_filename); // 开启文件(更新)
}

main.cpp

#include <iostream>
#include <string>
using namespace std;

#include "Logger.h"
using namespace utility;


int main() {
    // 初始化日志对象
    Logger::instance()->open("./main.log");

    debug("name=%s age=%d", "jack", 18);

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值