嵌入式linux下的c语言日志log模块,功能增强(二)_c语言 log模块

/**
日志打印示例。
使用:
mylog(DEBUG, "This is debug info\n");
结果:
[2018-07-22 23:37:27:172] [DEBUG] [main.cpp:5] This is debug info
默认打印当前时间(精确到毫秒)、文件名称、行号。
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <errno.h>
#include <dirent.h>
#include <stdlib.h>


#include "log.h"

//#ifndef LOGLEVEL
//#define LOGLEVEL DEBUG
//#endif

// 使用了GNU C扩展语法,只在gcc(C语言)生效,
// g++的c++版本编译不通过
static const char* s_loginfo[] = {
    [ERROR] = "ERROR",
    [WARN]  = "WARN",
    [INFO]  = "INFO",
    [DEBUG] = "DEBUG",
};


static char file_names[LOGFILE_MAXCOUNT][LOGFILE_NAMELENTH];
//记录文件名前缀(最好取自终端编号)
static char file_prifix[LOGFILE_NAMELENTH];


//linux消息队列
static int s_msg_id;
static int r_msg_id;

#define MSG_TYPE 1001

#define MAX_TEXT 1024

struct msg_st{
	long int msg_type;
	char text[MAX_TEXT];
};


static pthread_t tid;

//=============================================
static void get_timestamp(char *buffer)
{
    time_t t;
    struct tm *p;
    struct timeval tv;
    int len;
    int millsec;

    t = time(NULL);
    p = localtime(&t);

    gettimeofday(&tv, NULL);
    millsec = (int)(tv.tv_usec / 1000);

    /* 时间格式:[2011-11-15 12:47:34:888] */
    len = snprintf(buffer, 32, "[%04d-%02d-%02d %02d:%02d:%02d:%03d] ",
        p->tm_year+1900, p->tm_mon+1,
        p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec, millsec);

    buffer[len] = '\0';
}

//获取当前时间
static int get_curtime( char* outTimeStr )
{
	int ret = 0;
	time_t tTime;
	struct tm *tmTime;
	struct timeval mTime;
	time( &tTime );
	tmTime = localtime( &tTime );
	gettimeofday( &mTime, NULL );
	sprintf( outTimeStr, "%04d%02d%02d%02d%02d%02d",
			tmTime->tm_year + 1900, tmTime->tm_mon + 1,
			tmTime->tm_mday, tmTime->tm_hour,
			tmTime->tm_min, tmTime->tm_sec );
	return ret;
}


//创建文件夹
static int create_dir(const char *sPathName)  
{  
	char dirName[256];  
	strcpy(dirName, sPathName);  
	int i,len = strlen(dirName);
	for(i=1; i<len; i++)  
	{  
		if(dirName[i]=='/')  
		{  
			dirName[i] = 0; 
			if(access(dirName, 0)!=0)  
			{  
				if(mkdir(dirName, 0755)==-1)  
				{   
					fprintf(stderr,"mkdir   error\n");   
					return -1;   
				}  
			}  
			dirName[i] = '/';  

		}  
	}  

	return 0;  
} 


//获取文件大小
static unsigned long get_size(const char *path)
{
	unsigned long filesize = -1;	
	struct stat statbuff;
	if(stat(path, &statbuff) < 0){
		return filesize;
	}else{
		filesize = statbuff.st_size;
	}
	return filesize;
}

//文件是否存在,返回 1文件存在,返回0,文件不存在
static int file_exists(char *filename)
{
	return (access(filename, 0) == 0);
} 


static int read_filelist(char *basePath)
{
	DIR *dir;
	struct dirent *ptr;
	char base[1000];
	int count = 0;

	if ((dir=opendir(basePath)) == NULL)
	{
		fprintf(stderr,"Open dir error...");
		return -1;
	}

	while ((ptr=readdir(dir)) != NULL)
	{
		//printf("count=%d\n",i++);
		if(strcmp(ptr->d_name,".")==0 || strcmp(ptr->d_name,"..")==0)    ///current dir OR parrent dir
			continue;
		else if(ptr->d_type == 8)    ///file
		{
			printf("f_name:%s/%s\n",basePath,ptr->d_name);
			sprintf(file_names[count],"%s",ptr->d_name);
			count++;
			if(count > LOGFILE_MAXCOUNT -1){
				fprintf(stderr,"error!only allow files count=%d\n",LOGFILE_MAXCOUNT);
				return -2;
			}
		}
		else if(ptr->d_type == 10)    ///link file
			printf("l_name:%s/%s\n",basePath,ptr->d_name);
		else if(ptr->d_type == 4)    ///dir
		{
			printf("d_name:%s/%s\n",basePath,ptr->d_name);
			printf("this is a dir\n");
			continue;
			//是目录,继续递归读目录下面的
			//memset(base,'\0',sizeof(base));
			//strcpy(base,basePath);
			//strcat(base,"/");
			//strcat(base,ptr->d_name);
			//read_filelist(base);
		}
	}
	closedir(dir);
	return 0;
}


//处理日志文件是否保留
//原理算法:把日期转换成时间戳,然后由配置的允许保留的天数换算出一个时间范围,
//在遍历日志目录中所有的文件名,提取出日期,在这个时间范围内的保留,否则删除
//关键的地方,算出这个允许保留文件的时间范围,原理是日期转时间戳,再时间戳转日期
static int file_alives_proc()
{
	int ret = 0;
	char curtime[20];  //当前日期时间
	char deadtime[20]; //截止的历史日期


	printf("file_alives_proc:\n");
	get_curtime(curtime);
	//只取日期,省去时间
	memset(&curtime[8],0x30,6);
	printf("ailve maxdays:%d\n",LOGFILE_ALIVEDAYS);
	printf("curtime:%s\n",curtime);
	
	struct tm* tmp_time = (struct tm*)malloc(sizeof(struct tm));
	//字符串转时间
	strptime(curtime,"%Y%m%d%H%M%S",tmp_time);
	time_t t = mktime(tmp_time);
	printf("t now = %ld\n",t);

	free(tmp_time);  

	time_t t1 = t - LOGFILE_ALIVEDAYS*24*60*60;
	//再把t1转换为时间,即时间戳转时间
	
	struct tm *p; 
	p = gmtime(&t1); 
	//日期时间转字符串,由于只比较日期,因此忽略时间
	strftime(deadtime, sizeof(deadtime), "%Y%m%d000000", p); 	
	printf("deadtime:%s\n",deadtime);

	//以上获取到了curtime和deadtime,有了这个时间范围,接下来就去找这个范围的日志
	//日志文件日期在这个范围内的保留,否则删除
	for(int i = 0; i < LOGFILE_MAXCOUNT; i++ )
	{
		if(strlen(file_names[i]) > 0)
		{
			printf("file_name=%s\n",file_names[i]);
			char ftime[20];
			memset(ftime,0,20);
			memset(ftime,0x30,8);
			//关键,这个截取不能错
			memcpy(ftime,&file_names[i][strlen(LOGFILE_PREFIX)+1+strlen(file_prifix)],8);
			printf("file_time=%s\n",ftime);
			
			//开始比较 是否在日期范围内
			if(memcmp(ftime,deadtime,8) > 0)
			{
				//大于截止日期的文件保留
				printf("%s------keep alive\n",file_names[i]);
			}else{
				printf("%s----------------dead,need remove!\n",file_names[i]);
				//删除文件
				char dfname[50];
				sprintf(dfname,"%s/%s",LOGFILE_PATH,file_names[i]);
				remove(dfname);
			}
			//
		}else{
			//printf("fname=NULL\n");
		}

	}

	return ret;
}



int open_msg(void) {
	key_t msgkey;
	if ((msgkey = ftok("/tmp", 'a')) < 0) {
		printf("send ftok failed!\n");
		return -1;
	}
	printf("----msgkey is %d\n",msgkey);
	if ((s_msg_id = msgget(msgkey, IPC_CREAT | 0666)) == -1) {
		printf("msgget failed!\n");
		return -1;
	}

	printf("----s_msg_id is %d\n",s_msg_id);
	msgctl(s_msg_id,IPC_RMID,0);//先删除,否则可能满,因其生命周期同内核

	if ((s_msg_id = msgget(msgkey, IPC_CREAT | 0666)) == -1) {
		printf("msgget failed!\n");
		return -1;
	}


	r_msg_id = s_msg_id;
	return 0;
}

static int send_msg(char *buf,int len){
	struct msg_st data;
	data.msg_type = MSG_TYPE;
	strcpy(data.text,buf);
	//s_msg_id = 0;
	if(msgsnd(s_msg_id,&data,len,IPC_NOWAIT) == -1){
		printf("msgsnd failed.\n");
		perror("msgsnd");
		return -1;
	}
	return 0;
}


//若大量连续发的太快,收的太慢,会导致发送失败
static int recv_msg(void)
{
	int rsize;
	struct msg_st data;
	int msgtype = MSG_TYPE;

	char tmpfname[ 50 ];
	char tmpTime[ 14 + 1 ];
	FILE* fp;
	unsigned long filesize;

	memset(data.text,0,sizeof(data.text));
	/*接收消息队列*/    
	//阻塞接收
	rsize = msgrcv(r_msg_id,&data,sizeof(data.text),msgtype,MSG_NOERROR );    
	if(rsize == -1)
	{
		if (errno != ENOMSG) {
			perror("msgrcv");
		}
		printf("No message available for msgrcv()\n");
		return -1;
	}
	else
	{
		//printf("message received: %s\n", data.text);
		get_curtime( tmpTime );
		sprintf( tmpfname, "%s/%s%s_%8.8s.log", LOGFILE_PATH, LOGFILE_PREFIX,file_prifix, tmpTime );
		fp = fopen( tmpfname, "a" );
		if ( NULL == fp )
		{
			fprintf(stderr,"failed to open file,filename=%s\n",tmpfname);
			return -2;
		}
		else
		{
			filesize = get_size(tmpfname);
		//	printf("filesize=%u\n",filesize);
			if((filesize/1024) > LOGFILE_MAXSIZE){
				fprintf(stderr,"failed to write log,only allow maxsize=%ukb,current size=%ukb\n",LOGFILE_MAXSIZE,filesize/1024);
				fclose(fp);
				return -3 ;
			}
			//printf("%s\n",tmpfname);
			fprintf( fp, "%s", data.text );
			fclose(fp);
		}
	}
	return 0;
}

void mylog1(const char* filename, int line, enum LogLevel level, const char* fmt, ...)
{
    if(level > LOGLEVEL)
        return;

    va_list arg_list;
    char buf[1024];
	char sbuf[MAX_TEXT];

    memset(buf, 0, 1024);
	memset(sbuf,0,MAX_TEXT);

    va_start(arg_list, fmt);
    vsnprintf(buf, 1024, fmt, arg_list);
    char time[32] = {0};

    // 去掉*可能*存在的目录路径,只保留文件名
    const char* tmp = strrchr(filename, '/');
    if (!tmp) tmp = filename;
    else tmp++;
    get_timestamp(time);

	switch(level){
		case DEBUG:
			//绿色
			sprintf(sbuf,"\033[1;32m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
			break;
		case INFO:
			//蓝色
			sprintf(sbuf,"\033[1;34m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
			break;
		case ERROR:
			//红色
			sprintf(sbuf,"\033[1;31m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
			break;
		case WARN:
			//黄色
			sprintf(sbuf,"\033[1;33m%s[%s] [%s:%d] %s\n\033[0m", time, s_loginfo[level], tmp, line, buf);
			break;
	}

	printf("%s",sbuf);

#if(LOGFILE_ENABLE)
	//记录日志到文件
	send_msg(sbuf,strlen(sbuf));
#endif

    va_end(arg_list);
}


//考虑了一点儿效率,操作IO比较耗时,那就异步写入吧
//想用单独的线程,负责写日志到文件,自己实现消息队列?
//不这么做了,直接使用linux的消息队列吧

static void* thread_writelog(void* args)
{
	mylog(INFO,"thread_writelog begin...\n");
	while(1)
	{
		recv_msg();
		usleep(20*1000);							
	}
}


//参数prifix,日志文件名前缀,传终端编号
int init_log(char *prifix)
{
	int ret = 0;
	printf("init log:\n");
#if(LOGFILE_ENABLE)
	//检查目录是否存在
	ret = create_dir(LOGFILE_PATH);
	if(0 != ret){
		return ret;
	}
	printf("create dir %s\n success!\n",LOGFILE_PATH);

	ret = read_filelist(LOGFILE_PATH);
	if(0 != ret){
		printf("read_filelist err!,ret =%d\n",ret);
		//return ret;
	}

	//文件名前缀
	if(strlen(prifix) > 0){
		strcpy(file_prifix,prifix);
	}
	//处理是否保留历史记录文件
	file_alives_proc();
	//for(int i = 0; i<30;i++){
	//	printf("%s\n",file_names[i]);
	//}
	//创建消息队列
	ret = open_msg();
	if(0 != ret){
		return ret;
	}
	printf("create msg quene success!\n");

	//创建写日志文件线程
	ret = pthread_create(&tid,
			NULL,
			thread_writelog,
			NULL);
	if(0 != ret)
	{
		fprintf(stderr, "couldn't create thread_writelog, errno %d\n", ret);
	}
	else
	{
		printf("create thread_writelog success!\n");
	}
	printf("init log success!enabled log file...\n");
#else
	printf("init log success!no enable log file...\n");
#endif

	return ret;
}




//
//



/*
//使用demo:
//mylog(DEBUG,"hello world!\n");
//输出:[2019-07-26 14:31:51:882] [DEBUG] comLib.c:1257] hello world!
//
//目前只为个人使用,暂无考虑线程安全,高效率和高并发
//考虑了一点儿效率,写文件操作IO比较耗时,因此日志使用了异步写入,linux消息队列。
//因linux的消息队列,容量和长度有限制,因此若单个消息超1024byte或并发发送几千个消息
//且发送速度很快,大于了队列的接收速度,那么肯定,会发送失败
*/
#ifndef LOG_H_
#define LOG_H_

#ifdef __cplusplus 
extern "C" {
#endif

enum LogLevel
{
    ERROR = 1,
    WARN  = 2,
    INFO  = 3,
    DEBUG = 4,
};

void mylog1(const char* filename, int line, enum LogLevel level, const char* fmt, ...) __attribute__((format(printf,4,5)));

#define mylog(level, format, ...) mylog1(__FILE__, __LINE__, level, format, ## __VA_ARGS__)


//================================================================
//============日志模块操作接口,配置和使用都在下面================
//日志级别定义,小于该级别的日志方能输出
#ifndef LOGLEVEL
#define LOGLEVEL DEBUG
#endif

//目前暂只支持管理目录下的30个日志文件名,文件名长度50以内。大了不处理
#define LOGFILE_MAXCOUNT  30
#define LOGFILE_NAMELENTH 50

//===========日志文件的配置=======================================
//是否启用记录日志到文件功能
#define LOGFILE_ENABLE 1

//日志文件的路径,后面不能带"/"
#define LOGFILE_PATH "/log"
//日志文件名称的前缀
#define LOGFILE_PREFIX "log_b503_"
//日志文件存在的时间 单位(天),会自动删除当前日期-ALIVEDAYS 之前的文件
//限制日志最长保留时间不能超 LOGFILE_MAXCOUNT 天
#define LOGFILE_ALIVEDAYS 7  

//单个日志文件的限制大小 单位kb
#define LOGFILE_MAXSIZE 1*1024 
//================================================================


//日志模块初始化,若要记录日志到文件中,必须init_log且开启LOGFILE_ENABLE
//若不需要记录日志到文件功能,则不要调用init_log();
//调用了也没事,只是别开启LOGFILE_ENABLE
//参数prifix,日志文件名前缀,最好传终端唯一编号
extern int init_log(char *prifix); 

#ifdef __cplusplus 
};
#endif

#

附:

go实现的简易FTP功能,先对日志文件进行zip压缩,然后ftp至后台服务器。

package main

import (
	"archive/zip"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"

	"github.com/dutchcoders/goftp"
	"github.com/larspensjo/config"
)

var (
	ftp     *goftp.FTP
	conFile        = flag.String("ftpcfg", "/ftpcfg.ini", "config file")
	Server  string = "127.0.0.1:21"
	User    string = ""
	Pwd     string = ""
)

func checkErr(err error) {
	if err != nil {
		fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
	}
}

/**
@files:需要压缩的文件
@compreFile:压缩之后的文件
*/
func CompressZip(files []*os.File, zipfileName string) (err error) {
	zipfile, err := os.Create(zipfileName)
	if err != nil {
		return err
	}
	defer zipfile.Close()

	zw := zip.NewWriter(zipfile)
	defer zw.Close()
	for _, file := range files {
		err := compressZip(file, zw)
		if err != nil {
			return err
		}
		file.Close()
	}
	return nil
}

/**
功能:压缩文件
@file:压缩文件
@prefix:压缩文件内部的路径


为了做好运维面试路上的助攻手,特整理了上百道 **【运维技术栈面试题集锦】** ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,**小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。**

![](https://img-blog.csdnimg.cn/img_convert/7287bb49ab7f789c40e4bd0005cc3600.png)

本份面试集锦涵盖了

*   **174 道运维工程师面试题**
*   **128道k8s面试题**
*   **108道shell脚本面试题**
*   **200道Linux面试题**
*   **51道docker面试题**
*   **35道Jenkis面试题**
*   **78道MongoDB面试题**
*   **17道ansible面试题**
*   **60道dubbo面试题**
*   **53道kafka面试**
*   **18道mysql面试题**
*   **40道nginx面试题**
*   **77道redis面试题**
*   **28道zookeeper**

**总计 1000+ 道面试题, 内容 又全含金量又高**

*   **174道运维工程师面试题**

> 1、什么是运维?

> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

> 3、现在给你三百台服务器,你怎么对他们进行管理?

> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点

> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

oDB面试题**
*   **17道ansible面试题**
*   **60道dubbo面试题**
*   **53道kafka面试**
*   **18道mysql面试题**
*   **40道nginx面试题**
*   **77道redis面试题**
*   **28道zookeeper**

**总计 1000+ 道面试题, 内容 又全含金量又高**

*   **174道运维工程师面试题**

> 1、什么是运维?

> 2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

> 3、现在给你三百台服务器,你怎么对他们进行管理?

> 4、简述raid0 raid1raid5二种工作模式的工作原理及特点

> 5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

> 6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

> 7、Tomcat和Resin有什么区别,工作中你怎么选择?

> 8、什么是中间件?什么是jdk?

> 9、讲述一下Tomcat8005、8009、8080三个端口的含义?

> 10、什么叫CDN?

> 11、什么叫网站灰度发布?

> 12、简述DNS进行域名解析的过程?

> 13、RabbitMQ是什么东西?

> 14、讲一下Keepalived的工作原理?

> 15、讲述一下LVS三种模式的工作过程?

> 16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

> 17、如何重置mysql root密码?
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值