当前位置: 首页 > news >正文

日志系统 on Linux C/C++

目录

  • 概述
  • C 版本
    • 功能说明
    • 使用建议
  • C++ 版本
    • 功能特点

概述

在项目开发中,日志系统是一个很重要的部分,很多的问题都可以从日志中分析出来,所以在开发中拥有一个好的日志系统是非常值得去做的事情。
简单日志系统的要求:
1.日志等级,INFO, WARNING, DEBUG, ERROR;
2.可输出到文件,也可输出到控制台;
3.日志文件的size 最大为20MB(可调整),文件太大,可能会打不开;
4.日志仅保留30 days,日志太多可能没办法存储下来;

C 版本

功能说明

  1. 日志级别控制:
    • 四种日志级别:DEBUG、INFO、WARNING、ERROR
    • 通过log_set_level()设置当前日志级别
  2. 输出目标控制:
    • 通过宏OUTPUT_TO_CONSOLE和OUTPUT_TO_FILE控制输出目标
    • 可以单独启用/禁用控制台或文件输出
  3. 日志文件管理:
    • 日志文件以log_time方式命名(如app_20230101_120000.log)
    • 当日志文件超过20MB时自动轮转
    • 第一个日志文件在初始化时生成
  4. 日志清理:
    • log_cleanup_old_files()函数删除30天前的日志文件

使用建议

  1. 在程序启动时调用log_init()初始化日志系统
  2. 使用log_set_level()设置适当的日志级别
  3. 通过修改宏定义来控制输出目标
  4. 定期调用log_cleanup_old_files()清理旧日志(如每天一次)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <dirent.h>
#include <unistd.h>// 日志级别定义
typedef enum {LOG_DEBUG = 0,LOG_INFO,LOG_WARNING,LOG_ERROR
} LogLevel;// 输出目标控制宏
#define OUTPUT_TO_CONSOLE 1
#define OUTPUT_TO_FILE    1// 全局变量
static LogLevel current_level = LOG_INFO;
static FILE *log_file = NULL;
static char current_log_path[256] = {0};
static const char *log_dir = "./logs";
static const char *log_prefix = "app";// 设置日志级别
void log_set_level(LogLevel level) {current_level = level;
}// 初始化日志系统
int log_init() {// 创建日志目录mkdir(log_dir, 0755);// 生成初始日志文件名time_t now = time(NULL);struct tm *tm = localtime(&now);snprintf(current_log_path, sizeof(current_log_path), "%s/%s_%04d%02d%02d_%02d%02d%02d.log",log_dir, log_prefix,tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,tm->tm_hour, tm->tm_min, tm->tm_sec);// 打开日志文件log_file = fopen(current_log_path, "a");if (!log_file) {perror("Failed to open log file");return -1;}return 0;
}// 检查并执行日志轮转
static void check_rotate_log() {if (!log_file) return;// 检查文件大小fflush(log_file);struct stat st;if (stat(current_log_path, &st) == 0 && st.st_size > 20 * 1024 * 1024) {// 关闭当前文件fclose(log_file);// 生成新文件名time_t now = time(NULL);struct tm *tm = localtime(&now);snprintf(current_log_path, sizeof(current_log_path), "%s/%s_%04d%02d%02d_%02d%02d%02d.log",log_dir, log_prefix,tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,tm->tm_hour, tm->tm_min, tm->tm_sec);// 重新打开文件log_file = fopen(current_log_path, "a");if (!log_file) {perror("Failed to rotate log file");}}
}// 清理30天前的日志文件
void log_cleanup_old_files() {time_t threshold = time(NULL) - 30 * 24 * 3600;DIR *dir = opendir(log_dir);if (!dir) return;struct dirent *entry;while ((entry = readdir(dir)) != NULL) {if (strstr(entry->d_name, ".log") && strstr(entry->d_name, log_prefix)) {char filepath[512];snprintf(filepath, sizeof(filepath), "%s/%s", log_dir, entry->d_name);struct stat st;if (stat(filepath, &st) == 0 && st.st_mtime < threshold) {remove(filepath);}}}closedir(dir);
}// 写入日志
void log_write(LogLevel level, const char *format, ...) {if (level < current_level) return;// 获取当前时间time_t now = time(NULL);struct tm *tm = localtime(&now);char timestamp[20];strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);// 日志级别字符串const char *level_str;switch (level) {case LOG_DEBUG: level_str = "DEBUG"; break;case LOG_INFO: level_str = "INFO"; break;case LOG_WARNING: level_str = "WARN"; break;case LOG_ERROR: level_str = "ERROR"; break;default: level_str = "UNKNOWN"; break;}// 格式化日志消息va_list args;va_start(args, format);char message[1024] = {0};vsnprintf(message, sizeof(message), format, args);va_end(args);// 输出到控制台#if OUTPUT_TO_CONSOLEfprintf(stdout, "[%s] [%s] %s\n", timestamp, level_str, message);fflush(stdout);#endif// 输出到文件#if OUTPUT_TO_FILEif (log_file) {fprintf(log_file, "[%s] [%s] %s\n", timestamp, level_str, message);fflush(log_file);check_rotate_log();}#endif
}// 日志宏
#define LOGD(...) log_write(LOG_DEBUG, __VA_ARGS__)
#define LOGI(...)  log_write(LOG_INFO, __VA_ARGS__)
#define LOGW(...)  log_write(LOG_WARNING, __VA_ARGS__)
#define LOGE(...) log_write(LOG_ERROR, __VA_ARGS__)// 示例用法
int main() {if (log_init() != 0) {fprintf(stderr, "Failed to initialize log system\n");return 1;}log_set_level(LOG_DEBUG);  // 设置日志级别// 测试日志输出LOGD("This is a debug message");LOGI("This is an info message");LOGW("This is a warning message");LOGE("This is an error message");// 测试日志清理log_cleanup_old_files();// 关闭日志文件if (log_file) {fclose(log_file);}return 0;
}

C++ 版本

功能特点

  1. 线程安全:使用互斥锁保证多线程安全
  2. 单例模式:全局唯一的日志实例
  3. 灵活的初始化:
    • 可指定日志目录和前缀
    • 可单独控制控制台和文件输出
  4. 自动轮转:当日志文件超过20MB时自动创建新文件
  5. 日志清理:可清理指定天数前的旧日志文件
  6. 格式化输出:支持printf风格的格式化输出
#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <mutex>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <vector>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstdarg>enum class LogLevel {DEBUG,INFO,WARNING,ERROR
};class Logger {
public:// 获取单例实例static Logger& instance() {static Logger instance;return instance;}// 设置日志级别void set_level(LogLevel level) {std::lock_guard<std::mutex> lock(mutex_);level_ = level;}// 初始化日志系统bool init(const std::string& log_dir = "./logs", const std::string& log_prefix = "app",bool console_output = true,bool file_output = true) {std::lock_guard<std::mutex> lock(mutex_);log_dir_ = log_dir;log_prefix_ = log_prefix;console_output_ = console_output;file_output_ = file_output;// 创建日志目录if (mkdir(log_dir_.c_str(), 0755) != 0 && errno != EEXIST) {std::cerr << "Failed to create log directory: " << log_dir_ << std::endl;return false;}// 生成初始日志文件名if (file_output_) {rotate_file();}return true;}// 清理旧日志文件void cleanup_old_files(int days_to_keep = 30) {std::lock_guard<std::mutex> lock(mutex_);time_t threshold = time(nullptr) - days_to_keep * 24 * 3600;DIR* dir = opendir(log_dir_.c_str());if (!dir) return;struct dirent* entry;while ((entry = readdir(dir)) != nullptr) {std::string filename = entry->d_name;if (filename.find(log_prefix_) == 0 && filename.find(".log") != std::string::npos) {std::string filepath = log_dir_ + "/" + filename;struct stat st;if (stat(filepath.c_str(), &st) == 0 && st.st_mtime < threshold) {remove(filepath.c_str());}}}closedir(dir);}// 日志写入函数void log(LogLevel level, const std::string& format, ...) {if (level < level_) return;std::lock_guard<std::mutex> lock(mutex_);// 格式化时间auto now = std::time(nullptr);auto tm = *std::localtime(&now);std::ostringstream timestamp;timestamp << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");// 日志级别字符串std::string level_str;switch (level) {case LogLevel::DEBUG:   level_str = "DEBUG"; break;case LogLevel::INFO:    level_str = "INFO"; break;case LogLevel::WARNING: level_str = "WARN"; break;case LogLevel::ERROR:   level_str = "ERROR"; break;}// 格式化消息va_list args;va_start(args, format);char message[1024] = {0};vsnprintf(message, sizeof(message), format, args);va_end(args);// 完整的日志行std::ostringstream log_line;log_line << "[" << timestamp.str() << "] [" << level_str << "] " << message << "\n";std::string log_str = log_line.str();// 输出到控制台if (console_output_) {std::cout << log_str;std::cout.flush();}// 输出到文件if (file_output_ && file_) {*file_ << log_str;file_->flush();// 检查是否需要轮转check_rotate();}}private:Logger() = default;Logger(const Logger&) = delete;Logger& operator=(const Logger&) = delete;// 检查并执行日志轮转void check_rotate() {if (!file_) return;// 获取当前文件大小file_->seekp(0, std::ios::end);auto size = file_->tellp();if (size > 20 * 1024 * 1024) { // 20MBrotate_file();}}// 轮转日志文件void rotate_file() {if (file_) {file_->close();}// 生成新文件名auto now = std::time(nullptr);auto tm = *std::localtime(&now);std::ostringstream filename;filename << log_dir_ << "/" << log_prefix_ << "_"<< std::put_time(&tm, "%Y%m%d_%H%M%S") << ".log";current_log_path_ = filename.str();file_.reset(new std::ofstream(current_log_path_, std::ios::app));if (!file_->is_open()) {std::cerr << "Failed to open log file: " << current_log_path_ << std::endl;file_.reset();}}// 成员变量LogLevel level_ = LogLevel::INFO;std::string log_dir_;std::string log_prefix_;std::string current_log_path_;std::unique_ptr<std::ofstream> file_;bool console_output_ = true;bool file_output_ = true;std::mutex mutex_;
};// 日志宏
#define LOGD(...)   Logger::instance().log(LogLevel::DEBUG, __VA_ARGS__)
#define LOGI(...)    Logger::instance().log(LogLevel::INFO, __VA_ARGS__)
#define LOGW(...) Logger::instance().log(LogLevel::WARNING, __VA_ARGS__)
#define LOGE(...)   Logger::instance().log(LogLevel::ERROR, __VA_ARGS__)// 示例用法
int main() {// 初始化日志系统Logger::instance().init("./logs", "myapp", true, true);Logger::instance().set_level(LogLevel::DEBUG);// 测试日志输出LOGD("This is a debug message: %d", 42);LOGI("This is an info message: %s", "Hello");LOGW("This is a warning message: %.2f", 3.14);LOGE("This is an error message");// 测试日志清理Logger::instance().cleanup_old_files(30);return 0;
}
http://www.dtcms.com/a/276862.html

相关文章:

  • UE5多人MOBA+GAS 21、给升龙添加连段攻击,从角色的按下事件中传递事件给GA
  • Action-Agnostic Point-Level Supervision for Temporal Action Detection
  • 一扇门铃,万向感应——用 eventfd 实现零延迟通信
  • QCustomPlot绘图保存成PDF文件
  • 网络安全的基本练习
  • 北京-4年功能测试2年空窗-报培训班学测开-第四十九天
  • 行测速算之假设分配法
  • ROS2中的QoS(Quality of Service)详解
  • v-show和v-if的区别
  • 算法复杂度分析:大O表示法详解
  • 婚后才明白,原来结婚真需要一点冲动!
  • 编程与数学 03-001 计算机组成原理 04_非数值数据表示与校验码
  • 解码冯・诺依曼:操作系统是如何为进程 “铺路” 的?
  • 002_Claude模型与定价
  • java进阶(二)+学习笔记
  • Qt 3D模块加载复杂模型
  • Cesium初探-CallbackProperty
  • 开发语言中关于面向对象和面向过程的笔记
  • 打造你的专属智能生活:鸿蒙系统自定义场景开发全流程详解
  • VISUALBERT:一个简单且高效的视觉与语言基线模型
  • 微信小程序案例 - 本地生活(首页)
  • 代码随想录|图论|15并查集理论基础
  • 算法学习笔记:18.拉斯维加斯算法 ——从原理到实战,涵盖 LeetCode 与考研 408 例题
  • RFCOMM协议详解:串口仿真与TCP/IP协议栈移植技术——面试高频考点与真题解析
  • 1.2.3_2 TCP/IP模型
  • Java小白-设计模式
  • 动态规划理论基础,LeetCode 509. 斐波那契数
  • 012_PDF处理与文档分析
  • jenkins使用Jenkinsfile部署springboot+docker项目
  • 011_视觉能力与图像处理