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

CLion实现log日志系统

  1. 日志存储:文本文件,文件后缀为 .log

  2. 日志内容:时间、级别、文件、行号、内容

  3. 日志级别:debug < info < warn < error < fatal

  4. 日志翻滚:当日志的大小超过设置值,那么日志内容将保存到新文件

注:生成的文件在cmake-build-debug

Logger.h

#pragma once
#include <string>
#include <fstream>
namespace yazi {namespace utility {#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__)//__FILE__和__LINE__是编译器内置的预定义宏,用来获取当前正在被编译的源文件的名称和行号。//fatal(format,...) 支持两种调用方式,都没问题://无可变参数:fatal("纯文本日志");(靠 ## 消除逗号)//有可变参数:fatal("带占位符的日志:%d", 参数);(靠 ## 保留逗号)//这也是为什么 C++ 可变参数宏几乎都会用 ##__VA_ARGS__ 的原因 —— 兼容两种场景,避免语法错误。class Logger {public:enum Level{DEBUG=0, INFO, WARN, ERROR, FATAL,LEVEL_COUNT};//enum是枚举类型,给第一个设为0,其他依次递增。最后的LEVEL_COUNT值为5,刚好可以代表枚举类型的数量。static Logger * instance();//返回值是 Logger*(指向 Logger 类唯一实例的指针)。void open(const std::string & filename);//打开日志文件void close();//关闭日志文件void log(Level level,const char * file,int line,const char * format,...);//写日志void level(Level level) {//设置日志级别m_level = level;}void max(int bytes) {//设置日志文件最大字节数m_max = bytes;}private:Logger();//构造函数私有化,禁止外部创建实例~Logger();//析构函数私有化,禁止外部销毁实例void rotate();//日志轮转private:std::string m_filename;//日志文件名std::ofstream m_fout;//文件流Level m_level;//日志级别int m_max;//日志文件最大字节数int m_len;//当前日志文件大小static const char * s_level[LEVEL_COUNT];//日志级别字符串static Logger * m_instance;//声明静态实例指针};}
}

Logger.cpp

#include "Logger.h"#include <cstdarg>
#include <cstring>
#include <iostream>
using namespace yazi::utility;
#include<ctime>
const char * Logger::s_level[LEVEL_COUNT] = {"DEBUG","INFO","WARN","ERROR","FATAL"
};
//这行代码定义的 s_level 数组是枚举 Level 和字符串名称之间的 “映射表”,
//作用是将抽象的枚举值(整数)转换为人类可读的日志级别字符串(如 "DEBUG"),是日志格式化输出的关键。
Logger * Logger::m_instance = nullptr;//初始化静态指针为 nullptrLogger::Logger():m_level(DEBUG),m_max(0),m_len(0){}
Logger::~Logger() {close();
}
// 获取单例实例的函数
Logger * Logger::instance() {if(m_instance == nullptr) {// 第一次调用时,实例为空m_instance = new Logger();// 才创建实例}return m_instance;
}
/*
*   你的 Logger 类是懒汉式单例,实现步骤可概括为:1、私有化构造 / 析构函数,禁止外部创建实例;2、用静态指针 m_instance 存储唯一实例地址;3、初始化为 nullptr,实现 “按需创建”;4、通过 instance() 函数控制,确保只创建一次实例并返回。**/
void Logger::open(const std::string & filename) {m_filename = filename;//保存文件名m_fout.open(filename,std::ios::app);// 打开文件(追加模式)if(m_fout.fail()) {throw std::logic_error("open file failed " +  filename);}m_fout.seekp(0,std::ios::end);//// 移动文件指针到末尾m_len = m_fout.tellp();// // 获取当前文件大小(字节数)//tellp():返回当前写指针的位置(距离文件开头的字节数),这个值刚好等于文件的当前大小(因为指针在末尾)
}void Logger::close() {m_fout.close();
}void Logger::log(Level level,const char * file,int line,const char *format,...) {if(m_level > level) {return;}if(m_fout.fail()) {throw std::logic_error("open file failed" + m_filename);}time_t ticks = time(nullptr);//获取当前系统时间(秒级,从1970-01-01 00:00:00开始计算)struct tm * ptm = localtime(&ticks);//把秒级时间转换为“本地时间结构体”(包含年、月、日、时、分、秒)//struct tm 是 “时间分解器”,把秒数转成年月日时分秒;char timestamp[32];memset(timestamp,0,sizeof(timestamp));strftime(timestamp,sizeof(timestamp),"%Y-%m-%d %H:%M:%S",ptm);//strftime 是 “时间格式化器”,按规则把 struct tm 转成字符串;const char * fmt = "%s %s %s:%d ";int size = snprintf(nullptr,0,fmt,timestamp,s_level[level],file,line);//不能直接用 level 代替 s_level[level],因为 level 是枚举值(整数),//而日志需要输出人类能看懂的级别名称(比如 "DEBUG"、"FATAL")//snprintf(目标缓冲区, 最大长度, 格式化字符串, 参数1, 参数2, ...);/*一、snprintf返回值:1、如果成功:返回「格式化后字符串的总长度」(不包含最后的 \0),不管是否写满缓冲区。2、如果失败:返回负数(比如格式化出错)。二、这行代码的特殊用法:nullptr + 0 组合当我们给 snprintf 传前两个参数为 nullptr(空指针,没有实际缓冲区)和 0(最大长度为 0)时,snprintf 会做两件事:1、不写入任何数据:因为没有缓冲区(nullptr),且最大长度为 0,无法存储任何字符。2、依然计算长度:按照 fmt 格式和后面的参数(timestamp、s_level[level] 等),计算出 “如果正常写入,最终字符串的总长度”,并把这个长度作为返回值返回。
*/if(size>0) {char * buffer = new char[size+1];snprintf(buffer,size+1,fmt,timestamp,s_level[level],file,line);buffer[size] = '\0';//std::cout<<buffer<<std::endl;m_fout<<buffer;m_len += size;delete [] buffer;}va_list arg_ptr;va_start(arg_ptr,format);size = vsnprintf(nullptr,0,format,arg_ptr);va_end(arg_ptr);/**1. va_list arg_ptr; —— 定义 “可变参数列表” 变量va_list 不是普通类型,而是 C 标准库定义的一个 “类型别名”(本质是一个指针或结构体),作用是 “存储可变参数的地址”,相当于一个 “容器”,用来装 ... 里的所有参数。arg_ptr 就是这个容器的名字,后续会通过它来访问 ... 中的参数。2. va_start(arg_ptr, format); —— 初始化容器,指向第一个可变参数作用:告诉编译器 “从哪个位置开始,后面的就是可变参数”,并让 arg_ptr 指向第一个可变参数的地址。关键:第二个参数 format 是 可变参数前面的最后一个固定参数(log 函数中,... 前面的固定参数依次是 level、file、line、format,最后一个固定参数就是 format)。编译器通过 format 的地址,就能找到它后面第一个可变参数的地址,然后把这个地址存到 arg_ptr 里。举个例子:如果调用 log(DEBUG, "main.cpp", 10, "用户 %s 年龄 %d", "张三", 25),那么:format 是 "用户 %s 年龄 %d"(固定参数的最后一个)。va_start 会让 arg_ptr 指向第一个可变参数 "张三" 的地址。3. size = vsnprintf(nullptr, 0, format, arg_ptr); —— 用可变参数计算字符串长度这行就是利用前面初始化好的 arg_ptr(装着可变参数的容器),调用 vsnprintf 计算 “格式化后的字符串长度”。细节:nullptr + 0:表示 “不实际存储字符串,只计算长度”(前面讲过的 vsnprintf 特殊用法)。format:用户传入的格式化字符串(比如 "用户 %s 年龄 %d")。arg_ptr:传给 vsnprintf,让它能读取到 ... 中的参数("张三"、25)。最终:size 会得到格式化后的字符串总长度(比如 "用户 张三 年龄 25" 的长度是 16)。4. va_end(arg_ptr); —— 释放容器,避免内存问题作用:“清理”arg_ptr 这个容器,告诉编译器 “可变参数已经处理完了”,释放相关的临时资源(比如避免野指针)。必须调用:这是 C 标准的要求,只要用了 va_start 初始化 va_list,就必须用 va_end 收尾,否则可能导致内存泄漏或程序异常。*/if(size>0) {char * content = new char[size+1];va_start(arg_ptr,format);vsnprintf(content,size+1,format,arg_ptr);va_end(arg_ptr);m_fout<<content;m_len += size;delete [] content;}m_fout<<std::endl;m_fout.flush();/**强制让 m_fout 把当前缓冲区中所有未写入文件的数据,立刻同步到磁盘文件,清空缓冲区。举个例子:如果你的日志是 debug("程序开始运行"),执行 m_fout << "程序开始运行" 后,数据可能还在内存缓冲区里;调用 flush() 后,这行日志才会真正写到 app.log(或你指定的日志文件)中。*///std::cout<<timestamp<<std::endl;if(m_max > 0 && m_len > m_max) {rotate();}
}
void Logger::rotate() {//轮转日志文件close();time_t ticks = time(nullptr);//获取当前系统时间(秒级,从1970-01-01 00:00:00开始计算)struct tm * ptm = localtime(&ticks);//把秒级时间转换为“本地时间结构体”(包含年、月、日、时、分、秒)//struct tm 是 “时间分解器”,把秒数转成年月日时分秒;char timestamp[32];//定义一个字符数组存储时间戳字符串memset(timestamp,0,sizeof(timestamp));//把数组初始化为全0(避免残留垃圾数据)strftime(timestamp,sizeof(timestamp),"%Y-%m-%d_%H-%M-%S",ptm);//格式化时间:按 "%Y-%m-%d_%H-%M-%S" 格式把时间结构体转成字符串//strftime 是 “时间格式化器”,按规则把 struct tm 转成字符串;std::string new_filename = m_filename + "." + timestamp;if(rename(m_filename.c_str(),new_filename.c_str())!=0) {//rename(old_name, new_name):C 标准库函数,用于重命名文件(把 old_name 改成 new_name)。//m_filename.c_str() → 把 C++ 字符串 m_filename 转成 C 风格字符串(rename 要求)throw std::logic_error("rename file failed: " + std::string(strerror(errno)));}open(m_filename);//重命名完成后,旧的 app.log 已经变成备份文件,需要重新创建一个新的 app.log 继续写日志;//调用 open(m_filename) 会以 “追加模式” 创建新的 app.log(如果文件不存在则创建,存在则继续追加),//并初始化 m_fout(文件流)和 m_len(文件大小,重置为 0)。}

main.cpp

#include <iostream>
#include "utility/Logger.h"
using namespace yazi::utility;
int main() {Logger::instance()->open("./test.log");Logger::instance()->max(1024);//Logger::instance()->level(Logger::DEBUG);Logger::instance()->log(Logger::DEBUG, __FILE__, __LINE__, "hello world");Logger::instance()->log(Logger::INFO, __FILE__, __LINE__, "name is %s,age is %d ", "yazi", 18);debug("name is %s,age is %d ", "slim", 19);info("name is %s,age is %d ", "tom", 20);warn("name is %s,age is %d ", "jack", 21);error("name is %s,age is %d ", "ciic", 22);fatal("name is %s,age is %d ", "yob", 23);return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.29)
project(Logger)set(CMAKE_CXX_STANDARD 20)add_executable(Logger main.cpputility/Logger.cpputility/Logger.h
)

http://www.dtcms.com/a/432278.html

相关文章:

  • Linux-03_01(Linux实用操作)
  • [温习C/C++]C++刷题技巧—字符串查找find、find_if、find_first_of和find_last_of
  • 网站空间可以自己买吗wordpress4.9免登陆发布接口
  • 网站建设的行业资讯广西钦州有人帮做网站的公司吗
  • wordpress调用文件上传网络优化工作应该怎么做
  • 网站托管服务国家信息企业网查询
  • 解决 Windows 11 “找不到 gpedit.msc” 问题的方法
  • 网站建设饱和了吗最好的wordpress博客主题
  • TCP粘包和拆包问题
  • C#基础04-基础语法
  • 网站建设期间工作软文营销方法有哪些
  • 网站登陆注册怎么做宁波seo整站优化软件
  • 网站在互联网营销中的作用互联网相关网站
  • Easyx使用(中篇)
  • 省品牌建设联合会网站平面设计图片创意手绘
  • 山西教育学会网站建设网站搭建推广优化
  • 移动端开发平台海南百度推广seo
  • 网站建设与维护的国家定价标准淄博专业网站建设公司
  • 网站设计用什么字体网站商城系统建设方案
  • 广东省农业农村厅彭彬湛江网站优化快速排名
  • 亚马逊品牌注册网站建设建设银行怎么从网站上改手机号码
  • 上海建站模板源码个人简历模板免费下
  • 现在c 做网站用什么软件vps安装wordpress
  • 中国移动深圳有限公司门户网站seo怎么做优化排名
  • 外国购物网站设计风格wordpress头部导航栏代码
  • 互联网金融p2p网站建设模板万装网装修平台
  • 做外单都有什么网站网站建公司简介
  • 正规网站制作全包企业网站如何去做优化
  • 太仓网站制作网站建设网站首页栏目设置
  • aspnet网站开发教程数据库常州快速建站模板