C++ 日志实现

cpp 复制代码
/**
 * @file Logger.h
 * @author shijb (shijb@guideir.com)
 * @brief C++日志类实现
 * 支持功能:
 * 1. 日志格式:[YYYY-MM-DD
 * hh-mm-ss][tid=线程id][文件名][函数名][日志等级]日志信息
 * 2. 支持日志路径设置
 * 3. 支持日志等级(DEBUG, INFO, WARN, ERROR, FATAL)
 * 4. 支持日志回滚(按文件大小)
 * 5. 线程安全
 * @version 0.1
 * @date 2025-12-11
 *
 * @copyright Copyright (c) 2025
 *
 */

#ifndef LOGGER_H
#define LOGGER_H

#include <chrono>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <direct.h>
#include <errno.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>
#include <sys/stat.h>
#include <thread>

class Logger {
public:
  /**
   * @brief 日志等级枚举
   */
  enum class Level { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3, FATAL = 4 };

  Logger();
  ~Logger();

  /**
   * @brief 初始化日志系统
   * @param logPath 日志文件路径
   * @param level 日志等级
   * @param maxFileSize 单个日志文件最大大小(字节),0表示不限制
   * @param maxBackupFiles 最大备份文件数
   */
  void init(const std::string &logPath, Level level = Level::INFO,
            size_t maxFileSize = 10 * 1024 * 1024, int maxBackupFiles = 5);

  /**
   * @brief 设置日志等级
   * @param level 日志等级
   */
  void setLevel(Level level);

  /**
   * @brief 获取当前日志等级
   * @return Level 当前日志等级
   */
  Level getLevel() const;

  /**
   * @brief 写日志
   * @param level 日志等级
   * @param file 文件名
   * @param function 函数名
   * @param line 行号
   * @param message 日志消息
   */
  void log(Level level, const std::string &file, const std::string &function,
           int line, const std::string &message);

  /**
   * @brief 关闭日志
   */
  void shutdown();

private:
  /**
   * @brief 检查是否需要回滚日志文件
   */
  void checkRollover();

  /**
   * @brief 执行日志回滚
   */
  void doRollover();

  /**
   * @brief 获取当前时间字符串
   * @return std::string 格式化的时间字符串
   */
  std::string getCurrentTime() const;

  /**
   * @brief 获取日志等级字符串
   * @param level 日志等级
   * @return std::string 等级字符串
   */
  std::string levelToString(Level level) const;

  /**
   * @brief 获取线程ID字符串
   * @return std::string 线程ID字符串
   */
  std::string getThreadId() const;

private:
  std::ofstream logFile_; // 日志文件流
  std::string logPath_;   // 日志文件路径
  Level currentLevel_;    // 当前日志等级
  size_t maxFileSize_;    // 最大文件大小
  int maxBackupFiles_;    // 最大备份文件数
  std::mutex mutex_;      // 互斥锁,保证线程安全
  bool initialized_;      // 是否已初始化
};
#ifdef _MSC_VER
#define FUNCTION_SIGNATURE __FUNCSIG__
#else
#define FUNCTION_SIGNATURE __PRETTY_FUNCTION__
#endif
// 使用标准的函数名宏,提供更好的可移植性
#define LOG_IMPL(level, message)                                               \
  do {                                                                         \
    if (m_logger != nullptr) {                                                 \
      std::ostringstream oss;                                                  \
      oss << message;                                                          \
      m_logger->log(Logger::Level::level, __FILE__, FUNCTION_SIGNATURE,        \
                    __LINE__, oss.str());                                      \
    }                                                                          \
  } while (0)

#define LOGD(message) LOG_IMPL(DEBUG, message)
#define LOGI(message) LOG_IMPL(INFO, message)
#define LOGW(message) LOG_IMPL(WARN, message)
#define LOGE(message) LOG_IMPL(ERROR, message)
#define LOGF(message) LOG_IMPL(FATAL, message)

#define DECARLE_LOGGER_FUNC_IN_CLASS                                           \
public:                                                                        \
  void setLogger(Logger *logger) { m_logger = logger; }                        \
  Logger *getLogger() { return m_logger; }                                     \
                                                                               \
protected:                                                                     \
  Logger *m_logger = nullptr;                                                  \
                                                                               \
public:
#endif //! LOGGER_H
// 日志宏定义,方便使用
cpp 复制代码
/**
 * @file Logger.cpp
 * @brief Logger类实现
 */

#include "Logger.h"
#include <algorithm>
#include <cstring>

Logger::Logger()
    : currentLevel_(Level::INFO), maxFileSize_(10 * 1024 * 1024) // 默认10MB
      ,
      maxBackupFiles_(5), initialized_(false) {}

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

void Logger::init(const std::string &logPath, Level level, size_t maxFileSize,
                  int maxBackupFiles) {
  std::lock_guard<std::mutex> lock(mutex_);

  if (initialized_) {
    return;
  }

  logPath_ = logPath;
  currentLevel_ = level;
  maxFileSize_ = maxFileSize;
  maxBackupFiles_ = maxBackupFiles;

  // 创建日志目录
  try {
    // 提取目录路径
    std::string dirPath = logPath;
    size_t pos = dirPath.find_last_of("/\\");
    if (pos != std::string::npos) {
      dirPath = dirPath.substr(0, pos);

      // 创建目录(Windows系统)
      if (_mkdir(dirPath.c_str()) != 0) {
        // 如果目录已存在,errno为EEXIST
        if (errno != EEXIST) {
          // 检查目录是否真的存在
          struct stat info;
          if (stat(dirPath.c_str(), &info) != 0) {
            throw std::runtime_error("无法创建日志目录: " + dirPath);
          }
        }
      }
    }

    // 打开日志文件
    logFile_.open(logPath, std::ios::out | std::ios::app);
    if (!logFile_.is_open()) {
      throw std::runtime_error("无法打开日志文件: " + logPath);
    }

    initialized_ = true;

    // 写入初始化日志
    logFile_ << "[" << getCurrentTime()
             << "][Logger][init][tid=" << getThreadId()
             << "][INFO]日志系统初始化完成,日志文件: " << logPath << std::endl;
  } catch (const std::exception &e) {
    std::cerr << "日志初始化失败: " << e.what() << std::endl;
    throw;
  }
}

void Logger::setLevel(Level level) {
  std::lock_guard<std::mutex> lock(mutex_);
  currentLevel_ = level;
}

Logger::Level Logger::getLevel() const { return currentLevel_; }

void Logger::log(Level level, const std::string &file,
                 const std::string &function, int line,
                 const std::string &message) {
  // 检查日志等级
  if (static_cast<int>(level) < static_cast<int>(currentLevel_)) {
    return;
  }

  std::lock_guard<std::mutex> lock(mutex_);

  if (!initialized_ || !logFile_.is_open()) {
    // 如果未初始化,输出到控制台
    std::cout << "[" << getCurrentTime() << "] [tid=" << getThreadId() << "] ["
              << file << "] [" << function << "] [lines= " << line << "] ["
              << levelToString(level) << "]" << message << std::endl;
    return;
  }

  // 检查是否需要回滚
  checkRollover();

  // 提取文件名(去掉路径)
  std::string filename = file;
  size_t pos = filename.find_last_of("/\\");
  if (pos != std::string::npos) {
    filename = filename.substr(pos + 1);
  }

  // 写入日志
  logFile_ << "[" << getCurrentTime() << "] [tid: " << getThreadId()
           << "] [file: " << filename << "] [func: " << function
           << "] [lines: " << line << "] [" << levelToString(level) << "]"
           << message << std::endl;

  // 立即刷新,确保日志及时写入
  logFile_.flush();
}

void Logger::shutdown() {
  std::lock_guard<std::mutex> lock(mutex_);

  if (initialized_ && logFile_.is_open()) {
    logFile_ << "[" << getCurrentTime()
             << "][Logger][shutdown][tid=" << getThreadId()
             << "][INFO]日志系统关闭" << std::endl;
    logFile_.close();
  }

  initialized_ = false;
}

void Logger::checkRollover() {
  if (maxFileSize_ == 0 || !logFile_.is_open()) {
    return;
  }

  // 获取当前文件大小
  logFile_.flush();
  std::streampos pos = logFile_.tellp();

  if (pos >= static_cast<std::streampos>(maxFileSize_)) {
    doRollover();
  }
}

void Logger::doRollover() {
  if (!logFile_.is_open()) {
    return;
  }

  logFile_.close();

  try {
    // 重命名现有备份文件
    for (int i = maxBackupFiles_ - 1; i >= 0; --i) {
      std::string oldName, newName;

      if (i == 0) {
        oldName = logPath_;
        newName = logPath_ + ".1";
      } else {
        oldName = logPath_ + "." + std::to_string(i);
        newName = logPath_ + "." + std::to_string(i + 1);
      }

      // 检查文件是否存在
      struct stat buffer;
      if (stat(oldName.c_str(), &buffer) == 0) {
        if (i == maxBackupFiles_ - 1) {
          // 删除最旧的备份
          std::remove(oldName.c_str());
        } else {
          // 重命名文件
          std::rename(oldName.c_str(), newName.c_str());
        }
      }
    }

    // 重新打开日志文件
    logFile_.open(logPath_, std::ios::out | std::ios::app);
    if (!logFile_.is_open()) {
      throw std::runtime_error("无法重新打开日志文件: " + logPath_);
    }

    logFile_ << "[" << getCurrentTime()
             << "][Logger][doRollover][tid=" << getThreadId()
             << "][INFO]日志文件回滚完成" << std::endl;
  } catch (const std::exception &e) {
    std::cerr << "日志回滚失败: " << e.what() << std::endl;
    // 尝试重新打开原始文件
    logFile_.open(logPath_, std::ios::out | std::ios::app);
  }
}

std::string Logger::getCurrentTime() const {
  auto now = std::chrono::system_clock::now();
  auto time = std::chrono::system_clock::to_time_t(now);
  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
                now.time_since_epoch()) %
            1000;

  std::tm tm;
#ifdef _WIN32
  localtime_s(&tm, &time);
#else
  localtime_r(&time, &tm);
#endif

  std::ostringstream oss;
  oss << std::put_time(&tm, "%Y-%m-%d %H-%M-%S") << "." << std::setfill('0')
      << std::setw(3) << ms.count();

  return oss.str();
}

std::string Logger::levelToString(Level level) const {
  switch (level) {
  case Level::DEBUG:
    return "DEBUG";
  case Level::INFO:
    return "INFO";
  case Level::WARN:
    return "WARN";
  case Level::ERROR:
    return "ERROR";
  case Level::FATAL:
    return "FATAL";
  default:
    return "UNKNOWN";
  }
}

std::string Logger::getThreadId() const {
  std::ostringstream oss;
  oss << std::this_thread::get_id();
  return oss.str();
}
cpp 复制代码
/**
 * @file main.cpp
 * @brief 日志类测试程序
 */

#include "Logger.h"
// #include "testlog.hpp"
#include <chrono>
#include <thread>
#include <vector>

Logger *m_logger;

// 测试函数,模拟多线程日志写入
void testLogging(int threadId) {
  for (int i = 0; i < 10; ++i) {
    std::string string =
        "调试信息:" + std::to_string(threadId) + std::to_string(i);

    string = "调试信息: 线程 " + std::to_string(threadId) + " - 日志消息" +
             std::to_string(i);

    if (i % 3 == 0) {

      string = "警告: 线程 " + std::to_string(threadId) + " - 遇到特殊情况 ";
      LOGW(string);
    }

    if (i == 7) {

      string = "Error: " + std::to_string(threadId) + " - error message ";
      LOGI(string);
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(50));
  }
  std::string string = "线程 " + std::to_string(threadId) + " - 完成 ";
  LOGI(string);
}

int main() {

  m_logger = new Logger();
  std::cout << "开始测试日志系统..." << std::endl;

  // 初始化日志系统
  m_logger->init("logs/app.log", Logger::Level::DEBUG);

  std::cout << "日志系统初始化完成" << std::endl;
  std::cout << "当前日志等级: " << static_cast<int>(m_logger->getLevel())
            << std::endl;

  // 测试不同等级的日志
  LOGD("这是一条调试日志");
  LOGI("这是一条信息日志");
  LOGW("这是一条警告日志");
  LOGE("这是一条错误日志");
  LOGF("这是一条致命错误日志");

  // 测试多线程日志
  std::cout << "\n开始多线程日志测试..." << std::endl;
  std::vector<std::thread> threads;

  for (int i = 0; i < 3; ++i) {
    threads.emplace_back(testLogging, i + 1);
  }

  // 等待所有线程完成
  for (auto &t : threads) {
    t.detach();
  }

  std::cout << "多线程日志测试完成" << std::endl;

  // 测试日志等级设置
  std::cout << "\n测试日志等级设置..." << std::endl;
  m_logger->setLevel(Logger::Level::WARN);

  LOGD("这条调试日志应该不会出现");
  LOGI("这条信息日志应该不会出现");
  LOGW("这条警告日志应该出现");
  LOGE("这条错误日志应该出现");

  // 恢复日志等级
  m_logger->setLevel(Logger::Level::DEBUG);

  // 测试大文件回滚
  std::cout << "\n测试日志回滚功能..." << std::endl;
  std::cout << "写入大量日志以触发回滚..." << std::endl;

  for (int i = 0; i < 1000; ++i) {
    std::string string = "测试回滚的日志消息 " + std::to_string(i);
    LOGI(string);
  }

  // TestLog test;
  // test.test(1, 12);
  std::cout << "日志回滚测试完成" << std::endl;

  // 关闭日志系统
  m_logger->shutdown();
  std::cout << "\n日志系统已关闭" << std::endl;

  std::cout << "\n测试完成!请检查 logs/app.log 文件查看日志输出" << std::endl;
  std::cout << "日志格式: [YYYY-MM-DD "
               "hh-mm-ss][文件名][函数名][tid=线程id][日志等级]日志信息"
            << std::endl;

  return 0;
}
相关推荐
咬人喵喵2 小时前
CSS 盒子模型:万物皆是盒子
前端·css
菜鸟plus+2 小时前
Java 接口的演变
java·开发语言
李慕婉学姐2 小时前
【开题答辩过程】以《基于springboot的地铁综合服务管理系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·spring boot·后端
2401_860319522 小时前
DevUI组件库实战:从入门到企业级应用的深度探索,如何快速应用各种组件
前端·前端框架
IT空门:门主2 小时前
Spring AI的教程,持续更新......
java·人工智能·spring·spring ai
期待のcode2 小时前
Springboot配置属性绑定
java·spring boot·后端
JANGHIGH2 小时前
c++ 多线程(二)
开发语言·c++
Acc1oFl4g2 小时前
详解Java反射
java·开发语言·python
Trouvaille ~2 小时前
【Java篇】存在即不变:深刻解读String类不变的艺术
java·开发语言·javase·stringbuilder·stringbuffer·string类·字符串常量池