前言
此博客记录对于TinyWebServer项目的学习,并根据自己的理解做出些许更改。 原项目地址:https://github.com/qinguoyi/TinyWebServer
Log
日志系统是用来存储程序运行中的通知信息、warning、error等。 首要任务就是格式化输出字符串到一个文件,还应当记录消息产生的时间,此系统选择使用按天作为文件名。 以消费者-生产者模式,使用阻塞队列实现线程异步处理Log消息。 还实现了主线程同步处理Log消息。
Log.h
从头文件可以清楚看出Log类具有的功能。
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <pthread.h>
#include "block_queue.h"
using namespace std;
class Log
{
public:
//C++11以后,使用局部变量懒汉不用加锁
static Log *get_instance();
//异步写的线程
static void *flush_log_thread(void *args)
{
Log::get_instance()->async_write_log();
}
//可选择的参数有日志文件、日志缓冲区大小、最大行数以及最长日志条队列
bool init(const char *file_name, int close_log, int log_buf_size = 8192, int split_lines = 5000000, int max_queue_size = 0);
//写入消息
void write_log(int level, const char *format, ...);
//刷新缓冲区
void flush(void);
private:
Log();
virtual ~Log();
void *async_write_log();
private:
char dir_name[128]; //路径名
char log_name[128]; //log文件名
int m_split_lines; //日志最大行数
int m_log_buf_size; //日志缓冲区大小
long long m_count; //日志行数记录
int m_today; //因为按天分类,记录当前时间是那一天
FILE *m_fp; //打开log的文件指针
char *m_buf;
block_queue<string> *m_log_queue; //阻塞队列
bool m_is_async; //是否同步标志位
locker m_mutex;
int m_close_log; //关闭日志
};
//使用宏定义,便于调用, 使用##__VA_ARGS__,支持format后面可有0到多个参数
#define LOG_DEBUG(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_INFO(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_WARN(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_ERROR(format, ...) if(0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#endif
init
//异步需要设置阻塞队列的长度,同步不需要设置
bool Log::init(const char *file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size)
{
//如果设置了max_queue_size,则设置为异步
if (max_queue_size >= 1)
{
m_is_async = true;
m_log_queue = new block_queue<string>(max_queue_size);
pthread_t tid;
//flush_log_thread为回调函数,这里表示创建线程异步写日志
pthread_create(&tid, NULL, flush_log_thread, NULL);
}
m_close_log = close_log;
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)
{
//没有'/',直接用时间+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;
m_fp = fopen(log_full_name, "a");
if (m_fp == NULL)
{
return false;
}
return true;
}
write_log实现
这里主要是用到对字符操作的知识,因为要格式化输出,这里简单介绍两个函数: snprintf()函数用于实现将多个参数格式化输入到一个字符串; 例如
int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %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, now.tv_usec, s);
vsnprintf()函数同样用于实现将多个参数格式化输入到一个字符串,使用va_list结构体获取指定参数后面的参数 例如
int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst);
fomat可以为写入的格式,valst为格式对应的参数,valst也可以为空,不包含参数。 两个函数的返回值都是后面的字符串的长度,字符串长度大于写入size的时候会被截断写入,但是返回值依旧是字符串的长度而不是写入的长度。
void Log::write_log(int level, const char *format, ...)
{
struct timeval now = {0, 0};
gettimeofday(&now, NULL);
time_t t = now.tv_sec;
struct tm *sys_tm = localtime(&t);
struct tm my_tm = *sys_tm;
char s[16] = {0};
switch (level)
{
case 0:
strcpy(s, "[debug]:");
break;
case 1:
strcpy(s, "[info]:");
break;
case 2:
strcpy(s, "[warn]:");
break;
case 3:
strcpy(s, "[erro]:");
break;
default:
strcpy(s, "[info]:");
break;
}
//写入一个log,对m_count++, m_split_lines最大行数
m_mutex.lock();
m_count++;
//日期改变(不是同一天),或者达到最大行,新建log文件
if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //everyday log
{
char new_log[256] = {0};
fflush(m_fp);
fclose(m_fp);
char tail[16] = {0};
snprintf(tail, 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, tail, log_name);
m_today = my_tm.tm_mday;
m_count = 0;
}
else
{
snprintf(new_log, 255, "%s%s%s.%lld", dir_name, tail, log_name, m_count / m_split_lines);
}
m_fp = fopen(new_log, "a");
}
m_mutex.unlock();
va_list valst;
//获取可变参数,传入的format后的参数
va_start(valst, format);
string log_str;
m_mutex.lock();
//写入的具体时间内容格式
int n = snprintf(m_buf, 48, "%d-%02d-%02d %02d:%02d:%02d.%06ld %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, now.tv_usec, s);
//这里m是输入字符串的长度,vsnprintf最后一位默认为'\0'
int m = vsnprintf(m_buf + n, m_log_buf_size - 1, format, valst);
//这里m_log_buf_size如果小于输入n+m,会发生数组越界
//m_buf[n + m] = '\n';
//m_buf[n + m + 1] = '\0';
//修改如下
if(n + m + 1 > m_log_buf_size - 1)
{
m_buf[m_log_buf_size - 2] = '\n';
m_buf[m_log_buf_size - 1] = '\0';
}
else{
m_buf[n + m] = '\n';
m_buf[n + m + 1] = '\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(), m_fp);
m_mutex.unlock();
}
va_end(valst);
}
其中异步模式,是将处理好的字符串放到了阻塞队列中,等待处理线程进行处理,处理线程只需要取出该字符串,将其写入文件
void* Log::async_write_log()
{
string single_log;
//从阻塞队列中取出一个日志string,写入文件流
while (m_log_queue->pop(single_log))
{
m_mutex.lock();
//写入文件
fputs(single_log.c_str(), m_fp);
m_mutex.unlock();
}
}
测试
同步模式
#include "log.h"
int main()
{
int m_close_log = 0;
Log::get_instance()->init("log_test", 0, 60);
LOG_DEBUG("debug test");
LOG_INFO("%d, %s\n", 22, "abc");
return 0;
}
异步模式
设置两个写入线程,分别写入INFO和WARN
#include "log.h"
#include <unistd.h>
int m_close_log = 0;
static int count = 0;
locker mutex;
void* log_info(void *arg)
{
while (1)
{
usleep(1000);
mutex.lock();
LOG_INFO("INFO: %d", ++count);
mutex.unlock();
}
}
void* log_warn(void *arg)
{
while (1)
{
usleep(1000);
mutex.lock();
LOG_WARN("WARN: %d", ++count);
mutex.unlock();
}
}
int main()
{
Log::get_instance()->init("../log_info", 0, 60, 800, 20);
pthread_t info, warn;
pthread_create(&info, NULL, log_info, NULL);
pthread_create(&warn, NULL, log_warn, NULL);
sleep(1);
pthread_cancel(info);
pthread_cancel(warn);
return 0;
}
因为最大行设置的为800,所以log信息被保存为两个文件,内容如下图所示。
|