linux c 自定义记录日志文件

 /*
 * 后台服务程序日志文件。
 * 1. 只需要调用log_init()即可
 * 2. 注意日志写的路径的权限问题
 * */

使用线程和定时器完成

一、多余日志的删除

//考虑到日志文件会越来越多,也会影响磁盘的空间占用,这里就会删除以前的日志文件,默认删除10天前的日志
//1.假定该目录只存放了日志文件,所以没有去判断文件名了
//2.获取目录中文件的修改时间,以修改时间和现在的时间作比较,则可以找出10天前的文件了。

二、日志的初始化函数,


//1.判断日志的目录是否存在,不存在则会尝试创建目录
//2.建立管道,标准输出重定向到管道的写端1.
//3.创建日志记录的线程,读取管道的数据,并写道对应的文件中
//4.为了减缓对磁盘的读写,增加一个30s的定时器,用于刷新缓存的数据到硬盘中。

三、用于记录日志的线程


//1.打开管道的读端,用fdopen,相当于增加管道的缓存,方便我后面读取一行的数据。
//循环
//1.日志的记录以行为准则,前面转换过文件描述符后,使用fgets读取一行的数据,fgets可以阻塞线程
//2.考虑到日志文件应该每天生成一个新的,也要考虑到可能机器长时间不关机的情况。用时间来判断是否需要写新的日志文件。
//3.用变量记录这次日志文件的年月日,用系统时间的年月日去比较,如果不是同一天则关闭原来的日志文件,并且
//生成新的文件名,重新打开该文件,并往新的日志文件写入日志。
//4.考虑到日志文件太多,占据了比较多的硬盘空间,设置了10天前的日志将被删除的操作。这个部分只用每次时间不同的时候做一次即可。
//5.对写入日志文件的内容加上时间标记,更方便去排查故障出现的时间。先写入时间,再写缓存读回的内容。

my_log.c


/*
 * 后台服务程序日志文件。
 * 1. 只需要调用log_init()即可
 * 2. 注意日志写的路径的权限问题
 * */
#define _GNU_SOURCE

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <signal.h>

#define DAY10_SECS (60*60*24*10)     //10天的秒数
//要考虑可能会是守护进程,这里要用绝对路径
#define LOG_PATH "/root/log"    //指定日志目录,需要绝对路径,server一般是守护进程,不指定将在根目录下。


static int pipe_read_fd;  //保存管道的文件描述符
static 	int log_fd = -1;   //用于输出的文件描述符

static void* log_thread(void* arg);



//打开一个日志文件
static FILE* log_open_file_dup2_to_stdout(char *filename)
{
	log_fd = open(filename,O_RDWR | O_APPEND | O_CREAT,0666);
	if(log_fd < 0)
	{
		perror("open log file");
		return NULL;
	}
	
	FILE* fp = fdopen(log_fd, "a");  //转换文件描述符
	//FILE* fp = fopen(filename, "a+"); 
	if(NULL == fp)
	{
		close(log_fd);
		log_fd = -1;
		perror("ERROR:fdopen ");
		return NULL;
	}

	return fp;
}


//考虑到日志文件会越来越多,也会影响磁盘的空间占用,这里就会删除以前的日志文件,默认删除10天前的日志
//1.假定该目录只存放了日志文件,所以没有去判断文件名了
//2.获取目录中文件的修改时间,以修改时间和现在的时间作比较,则可以找出10天前的文件了。
static int delete_outdate_file(void)
{
	DIR* path = NULL;
	struct dirent*ptr;
	struct stat statbuf;
	time_t t;
	char file_name[272] = {LOG_PATH};  //太小有警告
	int filename_len = strlen(LOG_PATH);

	//最后一个字符是不是/
	if(file_name[filename_len-1] != '/')
	{
		file_name[filename_len] = '/';
		filename_len += 1;
		file_name[filename_len] = '\0';		
	}	

	t = time(NULL);  //获得系统时间
	//localtime_r(&t, &tim);

	path = opendir(LOG_PATH);
	if(path==NULL)
	{
		return -1;
	}

	while(NULL!=(ptr = readdir(path)))  //循环读取目录中的文件
	{
		if(ptr->d_name[0] != '.') //跳过 . 和..
		{
			if(ptr->d_type == DT_REG)  //普通文件
			{
				snprintf(file_name+filename_len,sizeof(file_name)-filename_len,"%s",ptr->d_name);	//形成文件的绝对路径						
				if(stat(file_name, &statbuf) == 0)  //如果存在呢
				{
					if(((t - statbuf.st_mtime) > (DAY10_SECS)))  //如果文件修改时间超时了,删除文件
					{
						unlink(file_name);
					}				
				}
			}
		}	
	}
	closedir(path);
	return 0;
}



/*用定时器来处理硬盘同步信息*/
void alarm_sig_handler(int signo) {
 	printf( "debug signo = %d\n " ,signo);

 	if(signo == SIGALRM)
 	{
 		alarm(30);   //30秒触发
 		if(log_fd>=0)    //文件是正常打开了,则刷新缓存
 			syncfs(log_fd);
 		else   //文件没有被打开,关闭定时器
 			alarm(0);  //关闭定时器
 	}
}


//日志的初始化函数,
//1.判断日志的目录是否存在,不存在则会尝试创建目录
//2.建立管道,标准输出重定向到管道的写端1.
//3.创建日志记录的线程,读取管道的数据,并写道对应的文件中
//4.为了减缓对磁盘的读写,增加一个30s的定时器,用于刷新缓存的数据到硬盘中。
int log_init(void)
{
	struct stat statbuf;
	pthread_t thread;
//	int path_len = 0; 
	int pipefd[2];  //创建管道
	
	//目录是否存在?
	if(stat(LOG_PATH, &statbuf))  //检测出错
	{
		perror("stat");
		if(2 == errno)  //文件不存在,可以尝试创建文件
		{
			if(-1 == mkdir(LOG_PATH,0775))  //创建目录
			{
				perror("mkdir");
				return -1;
			}
			goto go_on;  //创建成功,继续
		}
		else //是其他错误,则终止
		{
			return -1;
		}
	}
	if((statbuf.st_mode & S_IFMT) != S_IFDIR)  //目标如果存在呢,不是目录也出错
	{
		printf("log is not dir!!! please rm log file\n");
		return -1;
	}
go_on:
	//return 0;	
	//创建管道,将标准输出到日志文件。
	if(0 !=pipe(pipefd)) 
	{
		perror("pipe");	
		return -1;
	}
		
	//输出重定向到管道(管道的写端口设置为标准输出)
	if(-1 == dup2(pipefd[1],STDOUT_FILENO))   //出错,把标准输出定位过来
	{
		close(pipefd[0]);  //关闭前面打开的文件
		close(pipefd[1]);
		perror("dup2");
		return -1;
	}

	//标准输出无缓存
	setbuf(stdout, NULL); 
		
//	close(pipefd[1]);
	pipe_read_fd = pipefd[0];  //用全局变量保存文件描述符号
	
	//创建线程记录日志
	if(0 != pthread_create(&thread, NULL, log_thread, NULL))
	{
		printf("error:pthread_create\n");
		close(pipefd[0]);
		return -1;
	}
    pthread_detach(thread);   //设置分离模式
	
    signal(SIGALRM,alarm_sig_handler);  //注册了定时函数
    alarm(30);   //30秒触发

	return 0;	
}




//用于记录日志的线程
//1.打开管道的读端,用fdopen,相当于增加管道的缓存,方便我后面读取一行的数据。
//循环
//1.日志的记录以行为准则,前面转换过文件描述符后,使用fgets读取一行的数据,fgets可以阻塞线程
//2.考虑到日志文件应该每天生成一个新的,也要考虑到可能机器长时间不关机的情况。用时间来判断是否需要写新的日志文件。
//3.用变量记录这次日志文件的年月日,用系统时间的年月日去比较,如果不是同一天则关闭原来的日志文件,并且
//生成新的文件名,重新打开该文件,并往新的日志文件写入日志。
//4.考虑到日志文件太多,占据了比较多的硬盘空间,设置了10天前的日志将被删除的操作。这个部分只用每次时间不同的时候做一次即可。
//5.对写入日志文件的内容加上时间标记,更方便去排查故障出现的时间。先写入时间,再写缓存读回的内容。
static void* log_thread(void* arg)
{	
	char buf[1024];	
	char buf_time[32];	
	FILE * fp;
	FILE* fp_write=NULL;
	time_t t;
	struct tm tim;

	int last_year=0,last_month=0,last_day=0;   //保存上次的日期
	char file_name[128] = {LOG_PATH};
	int path_len = strlen(LOG_PATH);
	
	fp = fdopen(pipe_read_fd, "r");  //打开管道,读取printf输出的内容
	if(NULL == fp)
	{
		perror("ERROR:fdopen ");
		return NULL;
	}
	
	while(1)
	{
		memset(buf,0,sizeof(buf));//数据清零
		
		if(fgets(buf,sizeof buf,fp)==NULL) //返回空指针,有问题
			continue;
	//	perror("xxxxx");
		t = time(NULL);  //获得系统时间
		localtime_r(&t, &tim);
		//根据时间生成新的日志文件
		if((last_year != (tim.tm_year+1900)) || (last_month != (tim.tm_mon+1)) || (last_day != tim.tm_mday))
		{
			if(fp_write!=NULL)
			{
				fclose(fp_write);  //关闭老的文件。
				fp_write = NULL;
                log_fd = -1;
			}
				
			//形成新的文件名
			snprintf(file_name+path_len,sizeof(file_name)-path_len,"/%4d_%02d_%02d.log",tim.tm_year+1900,tim.tm_mon+1,tim.tm_mday);
			fp_write = log_open_file_dup2_to_stdout(file_name);
			if(fp_write == NULL)
			{
				perror("open log file");
				return NULL;
			}

			//删除10天前的所有文件
			//system(cmd);
			delete_outdate_file();
		}
			
		snprintf(buf_time,sizeof(buf_time),"[%4d_%02d_%02d %02d:%02d:%02d]",tim.tm_year+1900,tim.tm_mon+1,tim.tm_mday
																			,tim.tm_hour,tim.tm_min,tim.tm_sec);			
		//write(log_fd,buf_time,strlen(buf_time));
		//write(log_fd,buf,strlen(buf));
		//使用缓存写,就不用频繁写硬盘了。
		fwrite(buf_time,1,strlen(buf_time),fp_write);
		fwrite(buf,1,strlen(buf),fp_write);
		//syncfs(log_fd);
	}	
}







my_log.h


#ifndef __MY_LOG_H__
#define __MY_LOG_H__

//打开文件,追加模式
int log_init(void);

#endif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大智兄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值