支持功能:
1.日志等级
2.日志文件路径配置
3.日志名称配置
4.日志保留个数
5.单个日志文件最大大小配置
6.日志是否写入文件配置
7.日志配置多模块共用
头文件printLog.h
#ifndef __PRINTLOG_H__
#define __PRINTLOG_H__
#define LOGMAXLEN 10240 //打印字符串的长度
#define LOGDEBUG __FILE__, __LINE__, 4
#define LOGINFO __FILE__, __LINE__, 3
#define LOGSYS __FILE__, __LINE__, 2
#define LOGWARN __FILE__, __LINE__, 1
#define LOGERROR __FILE__, __LINE__, 0
#define LOGNOLINE __FILE__, __LINE__, -1 //不打印前缀
/*
日志初始化
logCfgPath - 日志配置文件路径
module - 模块名, 没有可为NULL, 日志模块名将默认为default
成功返回0 失败-1
*/
int logInit(const char *logCfgPath, const char *module);
//打印日志
void printLog(const char *filename, int line, int lv, const char *arg, ...);
#endif
C文件
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include "cJSON.h"
#include "printLog.h"
/*日志配置文件结构体*/
typedef struct
{
char log_path[100]; //日志路径
char log_name[100]; //日志名称
int logfile_num; //日志文件个数
int logfile_size; //单日志最大大小(MB)
int log_level; //日志级别
int logfile_open; //日志文件是否打开1-开
} logCfg;
logCfg logcfg_st; //日志配置
int loginit = 0; //日志初始化标志
char LOGCFGPATH[256]; //LOG配置文件路径
//默认日志配置
char default_config[] = "{"
"\n\t\"default\": {"
"\n\t\t\"log_path\":\"./\","
"\n\t\t\"log_name\":\"default\","
"\n\t\t\"logfile_num\":1,"
"\n\t\t\"logfile_size\":1,"
"\n\t\t\"log_level\":4,"
"\n\t\t\"logfile_open\":1"
"\n\t}"
"\n}";
/*读文件返回文本内容的指针,用完需要释放(日志模块使用)*/
static char *get_logcfg_text(const char *filename)
{
FILE *fp = fopen(filename, "r");
if (!fp)
{
if (errno == 2)
{
fprintf(stderr, "fopen error: %m \n");
return 0x00;
}
else
{
fprintf(stderr, "fopen error: %m \n");
}
return NULL;
}
//将文件指针移到文件结尾处
if (fseek(fp, 0, SEEK_END))
{
fprintf(stderr, "fseek() error: %m\n");
fclose(fp);
return NULL;
}
//返回文件指针的当前位置,(字节数),失败<0
long len = ftell(fp);
if (len < 0)
{
fprintf(stderr, "ftell() error: %m\n");
fclose(fp);
return NULL;
}
//将文件指针移到文件开头处
if (fseek(fp, 0, SEEK_SET))
{
fprintf(stderr, "fseek() error: %m\n");
fclose(fp);
return NULL;
}
//申请空间保存配置文件内容
char *text_ptr = malloc(len + 1);
if (text_ptr == NULL)
{
fprintf(stderr, "malloc error: %m\n");
fclose(fp);
return NULL;
}
//每次读取1个字节,一共读取len次
fread(text_ptr, 1, len, fp);
fclose(fp);
return text_ptr;
}
int logInit(const char *logCfgPath, const char *module)
{
int ret = 0;
char *text_ptr = NULL;
cJSON *root = NULL;
loginit = 1;
memset(&logcfg_st, 0, sizeof(logcfg_st));
memset(LOGCFGPATH, 0, sizeof(LOGCFGPATH));
if (strlen(logCfgPath) > sizeof(LOGCFGPATH) - 1)
{
fprintf(stderr, "Error : logCfgPath len is too long(max size:%ld)\n",sizeof(LOGCFGPATH));
return -1;
}
strcpy(LOGCFGPATH, logCfgPath);
char module_name[128];
memset(module_name, 0, sizeof(module_name));
if (module)
{
strcpy(module_name, module);
}
else
{
strcpy(module_name, "default");
}
//配置默认值
strcpy(logcfg_st.log_path, "./");
strcpy(logcfg_st.log_name, "default");
logcfg_st.logfile_num = 1;
logcfg_st.logfile_size = 1;
logcfg_st.log_level = 4;
logcfg_st.logfile_open = 1;
if (access(LOGCFGPATH, F_OK) == -1) //日志配置文件不存在,自动生成
{
fprintf(stdout, "Warn : log cfg file not exist! auto create\n");
FILE *fp = fopen(LOGCFGPATH, "a+");
if (fp == NULL)
{
printf("fopen error : %m\n");
return -1;
}
fprintf(fp, "%s", default_config);
fclose(fp);
}
// else //文件存在读取日志配置文件
{
text_ptr = get_logcfg_text(LOGCFGPATH);
if (text_ptr == NULL)
{
fprintf(stderr, "read log cfg file failed: %m\n");
return -1;
}
//解析json
root = cJSON_Parse(text_ptr);
if (!root)
{
fprintf(stderr, "Error before: [%s]\n", cJSON_GetErrorPtr());
ret = -1;
goto end;
}
cJSON *mod = cJSON_GetObjectItem(root, module_name);
if (!mod)
{
fprintf(stderr, "Warn : module[%s] not exist, use default\n",module_name);
mod = cJSON_GetObjectItem(root, "default");
}
cJSON *item = cJSON_GetObjectItem(mod, "log_path");
if (!item)
{
fprintf(stderr, "x log_path failed, default [%s]\n", logcfg_st.log_path);
}
else
{
strncpy(logcfg_st.log_path, item->valuestring, sizeof(logcfg_st.log_path));
}
item = cJSON_GetObjectItem(mod, "log_name");
if (!item)
{
fprintf(stderr, "x log_name failed, default [%s]\n", logcfg_st.log_name);
}
else
{
strncpy(logcfg_st.log_name, item->valuestring, sizeof(logcfg_st.log_name));
}
item = cJSON_GetObjectItem(mod, "logfile_num");
if (!item)
{
fprintf(stderr, "x logfile_num failed, default [%d]\n", logcfg_st.logfile_num);
}
else
{
logcfg_st.logfile_num = item->valueint;
}
item = cJSON_GetObjectItem(mod, "logfile_size");
if (!item)
{
fprintf(stderr, "x logfile_size failed, default [%d]\n", logcfg_st.logfile_size);
}
else
{
logcfg_st.logfile_size = item->valueint;
}
item = cJSON_GetObjectItem(mod, "log_level");
if (!item)
{
fprintf(stderr, "x log_level failed, default [%d]\n", logcfg_st.log_level);
}
else
{
logcfg_st.log_level = item->valueint;
}
item = cJSON_GetObjectItem(mod, "logfile_open");
if (!item)
{
fprintf(stderr, "x logfile_open failed, default [%d]\n", logcfg_st.logfile_open);
}
else
{
logcfg_st.logfile_open = item->valueint;
}
}
fprintf(stdout, "log_path: %s\n", logcfg_st.log_path);
fprintf(stdout, "log_name: %s\n", logcfg_st.log_name);
fprintf(stdout, "logfile_num: %d\n", logcfg_st.logfile_num);
fprintf(stdout, "logfile_size: %d\n", logcfg_st.logfile_size);
fprintf(stdout, "log_level: %d\n", logcfg_st.log_level);
fprintf(stdout, "logfile_open: %d\n", logcfg_st.logfile_open);
end:
if (text_ptr)
{
free(text_ptr);
}
if (root)
{
cJSON_Delete(root);
}
return ret;
}
//日志
void printLog(const char *filename, int line, int lv, const char *arg, ...)
{
if (!loginit)
return;
if (lv > logcfg_st.log_level)
{
return;
}
char lv_str[8];
memset(lv_str, 0, sizeof(lv_str));
switch (lv)
{
case 0:
strcpy(lv_str, "ERROR");
break;
case 1:
strcpy(lv_str, "WARN");
break;
case 2:
strcpy(lv_str, "SYSTEM");
break;
case 3:
strcpy(lv_str, "INFO");
break;
case 4:
strcpy(lv_str, "DEBUG");
break;
default:
break;
}
FILE *fp = NULL;
if (logcfg_st.logfile_open)
{
char logfile[256];
memset(logfile, 0, sizeof(logfile));
sprintf(logfile, "%s/%s.log", logcfg_st.log_path, logcfg_st.log_name);
if (access(logcfg_st.log_path, F_OK) == -1)
{
fprintf(stderr, "path [%s] not exist!\n", logcfg_st.log_path);
return;
}
if (access(logfile, F_OK) == 0) //日志文件存在
{
//获取文件大小
struct stat statbuf;
int rc = stat(logfile, &statbuf);
if (rc < 0)
{
fprintf(stderr, "sys error : %m\n");
return;
}
//判断文件大小,替换文件
if (statbuf.st_size >= logcfg_st.logfile_size * 1024 * 1024) //超过大小
{
char logbakname[256];
for (int i = logcfg_st.logfile_num; i > 0; i--)
{
memset(logbakname, 0, sizeof(logbakname));
sprintf(logbakname, "%s/%s%d.log", logcfg_st.log_path, logcfg_st.log_name, i);
int isexist = access(logbakname, F_OK);
if (isexist == 0 && i == logcfg_st.logfile_num) //文件存在,且是最后一个文件,删除
{
unlink(logbakname);
}
else if (isexist == 0) //文件存在,不是最后一个,重命名
{
char logrename[256];
memset(logrename, 0, sizeof(logrename));
sprintf(logrename, "%s/%s%d.log", logcfg_st.log_path, logcfg_st.log_name, i + 1);
rename(logbakname, logrename);
}
}
memset(logbakname, 0, sizeof(logbakname));
sprintf(logbakname, "%s/%s1.log", logcfg_st.log_path, logcfg_st.log_name);
rename(logfile, logbakname);
}
}
fp = fopen(logfile, "a+");
if (fp == NULL)
{
printf("fopen error : %m\n");
return;
}
}
char str[LOGMAXLEN];
time_t timer;
time(&timer);
struct tm *stm = localtime(&timer);
struct timeval tv;
// struct timezone tz;
gettimeofday(&tv, NULL); //取毫秒
char sdate[10];
strftime(sdate, 10, "%Y%m%d", stm); //取日期
char stime_date[80];
strftime(stime_date, 80, "%G%m%d %T:", stm); //取详细时间
va_list va;
va_start(va, arg);
vsprintf(str, arg, va);
// vsnprintf(str,1000,arg,va);
if (lv == -1)
{
/* 不打印前缀 */
printf("%s", str);
if (logcfg_st.logfile_open)
fprintf(fp, "%s", str);
}
else
{
//[20210126 09:14:25:118] [DEBUG] [test.c:50] ..........test
printf("[%s%ld] [%s] [%s:%d] %s\n", stime_date, tv.tv_usec / 1000, lv_str, filename, line, str);
if (logcfg_st.logfile_open)
fprintf(fp, "[%s%ld] [%s] [%s:%d] %s\n", stime_date, tv.tv_usec / 1000, lv_str, filename, line, str);
}
if (logcfg_st.logfile_open)
fclose(fp);
va_end(va);
return;
}
Makefile
.PHONY: clean
# CC = arm-linux-gcc -std=gnu99
CC = gcc
RM = rm -f
OBJS = printLog.o cJSON.o
DYNAMIC = libprintlog.so
STATIC = libprintlog.a
all: so a
%.o: %.c
$(CC) -fpic -c $^
so:$(OBJS)
$(CC) -shared -o $(DYNAMIC) $(OBJS)
$(RM) $(OBJS)
a:
$(CC) -c printLog.c -o printLog.o
$(CC) -c cJSON.c -o cJSON.o
ar -crv $(STATIC) $(OBJS)
$(RM) $(OBJS)
clean:
$(RM) $(DYNAMIC) $(STATIC)
日志配置文件(JSON)
{
"default": { // 模块名,可以多个模块公用一个日志配置文件,未找到模块默认使用default
"log_path":"./", // 日志文件路径
"log_name":"default", // 日志名称 default.log
"logfile_num":1, // 日志文件保留个数 如2 日志文件有xxx.log xxx1.log xxx2.log
"logfile_size":1, // 每个日志文件大小(M)
"log_level":4, // 日志等级
"logfile_open":1 // 是否写入文件 1-是 0-否
},
"test": {
"log_path":"./",
"log_name":"test",
"logfile_num":1,
"logfile_size":1,
"log_level":4,
"logfile_open":1
}
}
使用cJSON解析配置文件,cjson源码
GitHub - DaveGamble/cJSON: Ultralightweight JSON parser in ANSI C