百度一下建设银行网站首页,wordpress留言板插件,wordpress连接数据修改,黄页88网站信息怎么删除日志系统的实现 引言最简单的日志类 demo按天日志分类和超行日志分类日志信息分级同步和异步两种写入方式 引言
日志系统是通过文件来记录项目的 调试信息#xff0c;运行状态#xff0c;访问记录#xff0c;产生的警告和错误的一个系统#xff0c;是项目中非常重要的一部… 日志系统的实现 引言最简单的日志类 demo按天日志分类和超行日志分类日志信息分级同步和异步两种写入方式 引言
日志系统是通过文件来记录项目的 调试信息运行状态访问记录产生的警告和错误的一个系统是项目中非常重要的一部分. 程序员可以通过日志文件观测项目的运行信息方便及时对项目进行调整.
最简单的日志类 demo
日志类一般使用单例模式实现
Log.h:
class Log
{
private:Log() {};~Log();
public:bool init(const char* file_name);void write_log(const char* str);static Log* getinstance();private:FILE* file;
};Log.cpp:
Log::~Log()
{if (file ! NULL){fflush(file);fclose(file);}
}Log* Log::getinstance()
{static Log instance;return instance;
}bool Log::init(const char * file_name)
{file fopen(file_name,a);if (file NULL){return false;}return true;
}void Log::write_log(const char* str)
{if (file NULL)return;fputs(str, file);
}main.cpp:
#includeLog.hint main()
{Log::getinstance()-init(log.txt);Log::getinstance()-write_log(Hello World);
}这个日志类实现了最简单的写日志的功能但是实际应用时需要在日志系统上开发出许多额外的功能来满足工作需要有些时候还要进行日志的分类操作因为你不能将所有的日志信息都塞到一个日志文件中这样会大大降低可读性接下来讲一下在这个最简单的日志类的基础上怎么添加一些新功能.
按天日志分类和超行日志分类
先说两个比较简单的 按天分类和超行分类
按天分类每一个日志按照天来分类日志前加上当前的日期作为日志的前缀 并且写日志前检查日志的创建时间如果日志创建时间不是今天那么就额外新创建一个日志更新创建时间和行数然后向新日志中写日志信息
超行分类写日志前检查本次程序写入日志的行数如果当前本次程序写入日志的行数已经到达上限那么额外创建新的日志更新创建时间然后向新日志中写日志信息
为了实现这两个小功能我们需要先向日志类中添加以下成员
程序本次启动写入日志文件的最大行数程序本次启动已经写入日志的行数日志的创建时间日志的路径名文件名创建新日志的时候命名要跟之前的命名标准一样最好是标准日志名后缀的形式这样便于标识
更新后的日志类
Log.h:
#pragma once
#includestdio.h
#includestring.h
#includestring
#includetime.h
using namespace std;class Log
{
private:Log() ;~Log();public://初始化文件路径文件最大行数bool init(const char* file_name,int split_lines 5000000);void write_log(const char* str);static Log* getinstance();private:FILE* file;char dir_name[128];//路径名char log_name[128];//日志名int m_split_lines; //日志文件最大行数(之前的日志行数不计只记录本次程序启动写入的行数long long m_count; //已经写入日志的行数int m_today; //日志的创建时间
};Log.cpp
#define _CRT_SECURE_NO_WARNINGS
#includeLog.hLog::Log()
{m_count 0;
}Log::~Log()
{if (file ! NULL){fflush(file);fclose(file);}
}Log* Log::getinstance()
{static Log instance;return instance;
}bool Log::init(const char * file_name,int split_lines)
{m_split_lines split_lines; //设置最大行数time_t t time(NULL);struct tm* sys_tm localtime(t);struct tm my_tm *sys_tm; //获取当前的时间const char* p strrchr(file_name, /);//这里需要注意下windows和linux的路径上的 斜杠符浩方向是不同的windows是\linux是 / 而且因为转义符号的原因必须是 \\char log_full_name[256] { 0 };if (p NULL) //判断是否输入了完整的路径文件名如果只输入了文件名{strcpy(log_name, file_name);snprintf(log_full_name, 255, %d_%02d_%02d_%s, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday, file_name);}else //如果输入了完整的路径名文件名{strcpy(log_name, p 1);strncpy(dir_name, file_name, p - file_name 1);//规范化命名snprintf(log_full_name,255, %s%d_%02d_%02d_%s, dir_name, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday, log_name);}m_today my_tm.tm_mday; //更新时间file fopen(log_full_name,a); //打开文件打开方式追加if (file NULL){return false;}return true;
}void Log::write_log(const char* str)
{if (file NULL)return;time_t t time(NULL); struct tm* sys_tm localtime(t); struct tm my_tm *sys_tm; //获取当前的时间用来后续跟日志的创建时间作对比m_count; //日志行数1if (m_today ! my_tm.tm_mday || m_count % m_split_lines 0) //如果创建时间当前时间或者本次写入行数达到上限{ char new_log[256] { 0 }; //新日志的文件名fflush(file);fclose(file);char time_now[16] { 0 }; //格式化当前的时间snprintf(time_now, 16, %d_%02d_%02d_, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday);if (m_today ! my_tm.tm_mday) //如果是创建时间今天{ //这里解释一下m_today在init函数被调用的时候一定会被设置成当天的时间只有init和write函数的调用不在同一天中才会出现这种情况snprintf(new_log, 255, %s%s%s, dir_name, time_now, log_name);m_today my_tm.tm_mday; //更新创建时间m_count 0; //更新日志的行数}else //如果是行数达到本次我们规定的写入上限{snprintf(new_log,255,%s%s%lld_%s, dir_name, time_now, m_count / m_split_lines,log_name);//加上版本后缀}file fopen(new_log, a);}fputs(str, file);fputs(\n, file);
}运行的结果 出现了一个以时间开头命名的日志实现了按天分类
接下来我将一次性写入行数的上限调成5看一下如果一次性写入超过了行数上限的运行结果是什么样 出现了一个后缀_1的新文件
PS:这里有一个小BUG:因为m_count是每次运行程序都会重置的一个变量所以上一次运行时可能因为输出的行数过多创建了好多新日志但是下一次运行程序时还是从第一个日志开始打印的. 而且规定行数上限并不是日志中文件行数的上限而是每次运行程序写入日志文件的行数上限所以这个功能并不完美甚至说非常鸡肋暂时还没优化好在这里仅做一个小小的演示吧.
日志信息分级
我们应该将每一条日志信息进行分类可以分为四大类 Debug: 调试中产生的信息 WARN: 调试中产生的警告信息 INFO: 项目运行时的状态信息 ERROR: 系统的错误信息
然后我们可以在日志文件中每一条日志信息的前面加上这条信息被写入的时间和其所属的分级这样会大大增加日志的可读性.
代码还是在上面代码的基础上继续改动 Log.h:
#pragma once
#includestdio.h
#includestring.h
#includestring
#includetime.h
using namespace std;class Log
{
private:Log() ;~Log();public://初始化文件路径日志缓冲区大小文件最大行数bool init(const char* file_name, int log_buf_size 8192, int split_lines 5000000);//新增了一个日志分级void write_log(int level,const char* str);static Log* getinstance();private:FILE* file;char dir_name[128];//路径名char log_name[128];//日志名int m_split_lines; //日志文件最大行数long long m_count; //日志当前的行数int m_today; //日志创建的日期记录是那一天int m_log_buf_size; //日志缓冲区的大小用来存放日志信息字符串char* m_buf; //日志信息字符串因为后续要把时间和日志分级也加进来所以开一个新的char *
};Log.cpp:
#define _CRT_SECURE_NO_WARNINGS
#includeLog.hLog::Log()
{m_count 0;
}Log::~Log()
{if (file ! NULL){fflush(file);fclose(file);}if (m_buf ! NULL){delete[] m_buf;m_buf nullptr;}
}Log* Log::getinstance()
{static Log instance;return instance;
}bool Log::init(const char * file_name, int log_buf_size , int split_lines)
{m_log_buf_size log_buf_size;m_buf new char[m_log_buf_size];memset(m_buf,\0, m_log_buf_size);// 开辟缓冲区准备存放格式化的日志字符串m_split_lines split_lines; //设置最大行数time_t t time(NULL);struct tm* sys_tm localtime(t);struct tm my_tm *sys_tm; //获取当前的时间const char* p strrchr(file_name, \\);char log_full_name[256] { 0 };if (p NULL){snprintf(log_full_name, 255, %d_%02d_%02d_%s, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday, file_name);strcpy(log_name, file_name);}else{strcpy(log_name, p 1);strncpy(dir_name, file_name, p - file_name 1);//规范化命名snprintf(log_full_name,255, %s%d_%02d_%02d_%s, dir_name, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday, log_name);}m_today my_tm.tm_mday; //更新日志的创建时间file fopen(log_full_name,a); //打开文件打开方式追加if (file NULL){return false;}return true;
}void Log::write_log(int level,const char* str)
{if (file NULL)return;time_t t time(NULL); struct tm* sys_tm localtime(t); struct tm my_tm *sys_tm; //获取当前的时间用来后续跟日志的创建时间作对比char level_s[16] { 0 }; //日志分级switch (level){case 0:strcpy(level_s, [debug]:);break;case 1:strcpy(level_s, [info]:); break;case 2:strcpy(level_s, [warn]:); break;case 3:strcpy(level_s, [erro]:); break;default:strcpy(level_s, [info]:); break;}m_count; //日志行数1if (m_today ! my_tm.tm_mday || m_count % m_split_lines 0) //如果创建时间当前时间或者行数达到上限{char new_log[256] { 0 }; //新日志的文件名fflush(file);fclose(file);char time_now[16] { 0 }; //格式化当前的时间snprintf(time_now, 16, %d_%02d_%02d_, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday);if (m_today ! my_tm.tm_mday) //如果是创建时间今天{snprintf(new_log, 255, %s%s%s, dir_name, time_now, log_name);m_today my_tm.tm_mday; //更新创建时间m_count 0; //更新日志的行数}else //如果是行数达到文件上限{snprintf(new_log,255,%s%s%lld_%s, dir_name, time_now, m_count / m_split_lines,log_name);//加上版本后缀}file fopen(new_log, a);}int n snprintf(m_buf, 48, %d-%02d-%02d %02d:%02d:%02d %s,my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday,my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec,level_s); int m snprintf(m_buf n, m_log_buf_size-n-1,%s,str);m_buf[nm] \n;m_buf[nm1] \0;fputs(m_buf, file);}main.cpp:
#includeiostream
#includeLog.hint main()
{Log::getinstance()-init(Log\\log.txt);Log::getinstance()-write_log(0,Hello World);Log::getinstance()-write_log(1,Hello World);Log::getinstance()-write_log(2,Hello World);Log::getinstance()-write_log(3,Hello World);
}运行结果 如图:日志信息前面已经加上了时间和类别分级增加了可读性
同步和异步两种写入方式
同步写入和异步写入的逻辑
图片来自公众号两猿社 我先说明一下同步和异步的特点 同步可以理解为顺序执行而异步可以理解为并行执行 比如说吃饭和烧水两件事如果先吃饭后烧水这种是同步执行 如果说一边吃饭一边烧水这种就是异步执行
那么同步执行和异步执行有什么优点又使用在什么场景之下呢
同步
当对写入顺序和实时性要求很高时例如需要确保按照特定顺序写入或写入即时生效的情况下同步写入通常更合适。在数据完整性和一致性很重要的情况下同步写入能够提供更好的保证避免数据丢失或不完整。对于一些不频繁的、关键的写入操作同步写入方式可能更容易确保操作的可靠性。
异步
当写入频率很高或写入操作消耗较多时间时使用异步写入可以显著提升系统性能和响应速度。对于写入操作对主线程影响较大容易阻塞主线程的情况下通过异步写入可以将写入操作移到独立的线程中处理减少主线程负担。在需要降低I/O操作的影响、提高系统吞吐量和并发能力的场景下异步写入方式更为适宜。
然后说一下如何实现同步和异步
同步只需要正常写入就行了 而异步我们可以借助生产者-消费者模型由子线程执行写入操作.
如果你想了解生产者-消费者模型请点击链接 生产者-消费者模型
接下来给出带有同步和异步两种写入方式的日志实现,还是在之前代码的基础上改动
封装了生产者-消费者模型的阻塞队列类
#ifndef BLOCK_QUEUE_H
#define BLOCK_QUEUE_H#include iostream
#include stdlib.h
#include pthread.h
#include sys/time.h //包含时间和定时器的头文件
#include ../lock/locker.h
using namespace std;template class T
class block_queue
{
public:block_queue(int max_size 1000){if (max_size 0){exit(-1);}m_max_size max_size;m_array new T[max_size];m_size 0;m_front -1;m_back -1;}void clear(){m_mutex.lock();m_size 0;m_front -1;m_back -1;m_mutex.unlock();}~block_queue(){m_mutex.lock();if (m_array ! NULL)delete [] m_array;m_mutex.unlock();}//判断队列是否满了bool full() {m_mutex.lock();if (m_size m_max_size){m_mutex.unlock();return true;}m_mutex.unlock();return false;}//判断队列是否为空bool empty() {m_mutex.lock();if (0 m_size){m_mutex.unlock();return true;}m_mutex.unlock();return false;}//返回队首元素bool front(T value) {m_mutex.lock();if (0 m_size){m_mutex.unlock();return false;}value m_array[m_front];m_mutex.unlock();return true;}//返回队尾元素bool back(T value) {m_mutex.lock();if (0 m_size){m_mutex.unlock();return false;}value m_array[m_back];m_mutex.unlock();return true;}int size() {int tmp 0;m_mutex.lock();tmp m_size;m_mutex.unlock();return tmp;}int max_size(){int tmp 0;m_mutex.lock();tmp m_max_size;m_mutex.unlock();return tmp;}//往队列添加元素需要将所有使用队列的线程先唤醒//当有元素push进队列,相当于生产者生产了一个元素//若当前没有线程等待条件变量,则唤醒无意义bool push(const T item){m_mutex.lock();if (m_size m_max_size) //这里没考虑生产者必须在不空的情况下需要等待的问题{m_cond.broadcast();m_mutex.unlock();return false;}m_back (m_back 1) % m_max_size;m_array[m_back] item;m_size;m_cond.broadcast();m_mutex.unlock();return true;}//pop时,如果当前队列没有元素,将会等待条件变量bool pop(T item){m_mutex.lock();while (m_size 0){if (!m_cond.wait(m_mutex.get())){m_mutex.unlock();return false;}}m_front (m_front 1) % m_max_size;item m_array[m_front];m_size--;m_mutex.unlock();return true;}
private:locker m_mutex;cond m_cond;T *m_array;int m_size;int m_max_size;int m_front;int m_back;
};
#endif
注意: 这个生产者消费者模型并没有考虑生产者必须要在不满的情况下才能生产这一情况不过这样也能凑活用先凑活看吧
Log.h:
#pragma once
#includestdio.h
#includestring.h
#includestring
#includetime.h
using namespace std;class Log
{
private:Log() ;~Log();//异步写入方法void* async_write_log(){string single_log;//要写入的日志while (m_log_queue-pop(single_log)){m_mutex.lock();//互斥锁上锁fputs(single_log.c_str(), file);m_mutex.unlock();//互斥锁解锁}}public://初始化文件路径日志缓冲区大小文件最大行数,阻塞队列长度如果阻塞队列长度为正整数表示使用异步写入否则为同步写入bool init(const char* file_name, int log_buf_size 8192, int split_lines 5000000,int max_queue_size0);//新增了一个日志分级void write_log(int level,const char* str);//公有的异步写入函数作为消费者线程的入口函数static void* flush_log_thread(void *args){Log::getinstance()-async_write_log();}static Log* getinstance();private:FILE* file;char dir_name[128];//路径名char log_name[128];//日志名int m_split_lines; //日志文件最大行数long long m_count; //日志当前的行数int m_today; //日志创建的日期记录是那一天int m_log_buf_size; //日志缓冲区的大小用来存放日志信息字符串char* m_buf; //日志信息字符串因为后续要把时间和日志分级也加进来所以开一个新的char *block_queuestring* m_log_queue; //阻塞队列封装生产者消费者模型bool m_is_async; //异步标记如果为true,表示使用异步写入方式否则是同步写入方式locker m_mutex; //互斥锁类内部封装了互斥锁用来解决多线程竞争资源问题
};Log.cpp
#define _CRT_SECURE_NO_WARNINGS
#includeLog.h
// pthread,mutex等需要在Linux下使用相关的头文件才能使用因为我是windows环境就暂时不加了.Log::Log()
{m_count 0;m_is_async false;
}Log::~Log()
{if (file ! NULL){fflush(file);fclose(file);}if (m_buf ! NULL){delete[] m_buf;m_buf nullptr;}
}Log* Log::getinstance()
{static Log instance;return instance;
}bool Log::init(const char * file_name, int log_buf_size , int split_lines,int max_queue_size)
{if (max_queue_size 1){//设置写入方式flagm_is_async true; //设置为异步写入方式//创建并设置阻塞队列长度m_log_queue new block_queuestring(max_queue_size);pthread_t tid;//flush_log_thread为回调函数,这里表示创建线程异步写日志pthread_create(tid, NULL, flush_log_thread, NULL);}m_log_buf_size log_buf_size;m_buf new char[m_log_buf_size];memset(m_buf,\0, m_log_buf_size);// 开辟缓冲区准备存放格式化的日志字符串m_split_lines split_lines; //设置最大行数time_t t time(NULL);struct tm* sys_tm localtime(t);struct tm my_tm *sys_tm; //获取当前的时间const char* p strrchr(file_name, \\);char log_full_name[256] { 0 };if (p NULL){snprintf(log_full_name, 255, %d_%02d_%02d_%s, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday, file_name);strcpy(log_name, file_name);}else{strcpy(log_name, p 1);strncpy(dir_name, file_name, p - file_name 1);//规范化命名snprintf(log_full_name,255, %s%d_%02d_%02d_%s, dir_name, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday, log_name);}m_today my_tm.tm_mday; //更新日志的创建时间file fopen(log_full_name,a); //打开文件打开方式追加if (file NULL){return false;}return true;
}void Log::write_log(int level,const char* str)
{if (file NULL)return;time_t t time(NULL); struct tm* sys_tm localtime(t); struct tm my_tm *sys_tm; //获取当前的时间用来后续跟日志的创建时间作对比char level_s[16] { 0 }; //日志分级switch (level){case 0:strcpy(level_s, [debug]:);break;case 1:strcpy(level_s, [info]:); break;case 2:strcpy(level_s, [warn]:); break;case 3:strcpy(level_s, [erro]:); break;default:strcpy(level_s, [info]:); break;}m_mutex.lock(); //互斥锁上锁m_count; //日志行数1if (m_today ! my_tm.tm_mday || m_count % m_split_lines 0) //如果创建时间当前时间或者行数达到上限{char new_log[256] { 0 }; //新日志的文件名fflush(file);fclose(file);char time_now[16] { 0 }; //格式化当前的时间snprintf(time_now, 16, %d_%02d_%02d_, my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday);if (m_today ! my_tm.tm_mday) //如果是创建时间今天{snprintf(new_log, 255, %s%s%s, dir_name, time_now, log_name);m_today my_tm.tm_mday; //更新创建时间m_count 0; //更新日志的行数}else //如果是行数达到文件上限{snprintf(new_log,255,%s%s%lld_%s, dir_name, time_now, m_count / m_split_lines,log_name);//加上版本后缀}file fopen(new_log, a);}m_mutex.unlock(); //互斥锁解锁string log_str; m_mutex.lock();//格式化int n snprintf(m_buf, 48, %d-%02d-%02d %02d:%02d:%02d %s,my_tm.tm_year 1900, my_tm.tm_mon 1, my_tm.tm_mday,my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec,level_s); int m snprintf(m_buf n, m_log_buf_size-n-1,%s,str);m_buf[nm] \n;m_buf[nm1] \0;log_str m_buf;m_mutex.unlock();//如果是异步的写入方式if (m_is_async !m_log_queue-full()) {m_log_queue-push(log_str); }else//如果是同步的写入方式{m_mutex.lock(); fputs(log_str.c_str(), file);m_mutex.unlock(); }
}日志系统先介绍到这里我介绍的日志系统还是属于功能比较稀缺实际使用上可能远远比这复杂如果想使用日志系统可以以文章介绍的为雏形继续添加新功能.
本文中代码非常可能有错误如果发现有错误烦请评论区指正我会及时修改.