Linux C语言实现日志模块demo

supersimple / 2025-01-15 / 原文

有时候在调试编写程序的时候日志打印模块是必不可少的,今天我们在Linux下用C语言实现一个模块化的日志demo小玩具

一、一共有以下几个文件:

image

1). 模块功能的代码实现c文件  --log.c
2). 可供引用的头文件   -- log.h 自行创建include文件夹放下面即可
3). makefile文件--Makefile
4). 功能测试文件--test.c
5). 日志配置模板文件--log.conf.simple
6). 日志配置文件--log.conf 拷贝的log.conf.simple
7). lib文件夹 存放编译出的lib库,静态动态两种链接方式都有
8). logout文件夹,默认存放日志输出文件位置
9). 2024-xx-xx.log 日志文件,里面存放日志,没有会根据日期创建
  1. 模块功能的代码实现c文件 --log.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>

#include "include/log.h"

/* 存储日志信息记录的结构体变量 */
LOGSET logsetting;
LOG loging;

/* 日志级别对应的字符串 */
const static char LogLevelText[4][10]={" INFO  "," DEBUG ","WARNING"," ERROR "};
/* 获取日期 */
static char * getdate(char *date);
/* 获取时间 */
static void settime(void);
/* 获取日志级别*/
static unsigned char getcode(char *path);
/* 读取日志配置文件内容 log.conf */
static unsigned char ReadConfig(char *path);
/* 获取日志配置 */
static LOGSET *getlogset(void);
/* 打印日志 */
static void PrintfLog(char * fromat,va_list args);
/* 日志信息初始化*/
static int initlog(unsigned char loglevel);

static unsigned char getcode(char *path){
    unsigned char code=255;
    if(strcmp("INFO",path)==0)
        code = INFO_LEVEL;
    else if(strcmp("WARNING",path)==0)
        code = WARNING_LEVEL;
    else if(strcmp("ERROR",path)==0)
        code = ERROR_LEVEL;
    else if(strcmp("ALL",path)==0)
        code = ALL_LEVEL;
    else if(strcmp("DEBUG",path)==0)
        code = DEBUG_LEVEL;
    else
        code = DEFAULT_LEVEL;

    return code;
}

static unsigned char ReadConfig(char *path){
    char value[MAXFILEPATH] = {0x0};
    char data[MAXFILENAME] = {0x0};
    char file_line[MAXFILENAME] = {0x0};

    FILE *fpath=fopen(path,"r");
    if(fpath==NULL)
    {
        perror("LOG conf file read error! ");
        return -1;
    }
    getdate(data);
    strcat(data,".log");
    while (fgets(file_line, sizeof(file_line), fpath) != NULL) {
        memset(value,0,sizeof(value));
        
        if (sscanf(file_line, "path = %s\n", value) == 1) 
        {
            strcat(value,"/");
            strcat(value,data);
            memcpy(logsetting.filepath,value,strlen(value));
        } 
        else if (sscanf(file_line, "level = %s\n",value) == 1) 
        {
            logsetting.loglevel=getcode(value);
        }
        
    }
    
    fclose(fpath);
    return 0;
}
/*
*日志设置信息
* */
static LOGSET *getlogset(){
    char path[MAXFILEPATH]={0x0};
    getcwd(path,sizeof(path));
    strcat(path, CONFNAME);
    if(access(path,F_OK)==0)
    {
        ReadConfig(path);
    }
    else
    {
        logsetting.loglevel = DEFAULT_LEVEL;
        logsetting.maxfilelen = MAXFILELEN;
    }
    return &logsetting;
}

/*
*获取日期
* */
static char * getdate(char *date){
    time_t timer=time(NULL);
    strftime(date,11,"%Y-%m-%d",localtime(&timer));
    return date;
}

/*
*获取时间
* */
static void settime(){
    time_t timer=time(NULL);
    strftime(loging.logtime, DATE_LEN, "%Y-%m-%d %H:%M:%S", localtime(&timer));
}

/*
*不定参打印
* */
static void PrintfLog(char * fromat,va_list args){
    int d;
    char c, *s;
    //写入日志级别,日志时间
    fprintf(loging.logfile,"[%s][%s]: ",LogLevelText[loging.loglevel-1],loging.logtime);

    while(*fromat)
    {
        switch(*fromat){
            case 's':{
                s = va_arg(args, char *);
                fprintf(loging.logfile,"%s",s);
                break;}
            case 'd':{
                d = va_arg(args, int);
                fprintf(loging.logfile,"%d",d);
                break;}
            case 'c':{
                c = (char)va_arg(args, int);
                fprintf(loging.logfile,"%c",c);
                break;}
            
            default:{
                if(*fromat!='%'&&*fromat!='\n')
                    fprintf(loging.logfile,"%c",*fromat);
                break;}
        }
        fromat++;
    }
    fprintf(loging.logfile,"%s","\n");
}

static int initlog(unsigned char loglevel){
    char strdate[30]={0x0};
    LOGSET *logsetting;
    //获取日志配置信息
    if((logsetting=getlogset())==NULL){
        perror("Get Log Set Fail!");
        return -1;
    }

    if(loglevel > logsetting->loglevel)
    {
        return -1;
    }
    memset(&loging,0,sizeof(LOG));

    //获取日志时间
    settime();
    if(strlen(logsetting->filepath)==0){
        char *path=getenv("PWD");
        memcpy(logsetting->filepath,path,strlen(path));

        getdate(strdate);
        strcat(strdate,".log");
        strcat(logsetting->filepath,"/");
        strcat(logsetting->filepath,strdate);
    }
    memcpy(loging.filepath,logsetting->filepath,MAXFILEPATH);
    loging.loglevel = loglevel;
    //打开日志文件
    if(loging.logfile==NULL)
        loging.logfile=fopen(loging.filepath, "a+");
    if(loging.logfile==NULL){
        perror("Open Log File Fail!");
        return -1;
    }
    
    return 0;
}

/*
*日志写入
* */
int LogWrite(unsigned char loglevel,char *fromat,...)
{
    int  rtv = -1;
    va_list args;

    //初始化日志
    if(initlog(loglevel) != 0)
    {
        return rtv;
    }

    //打印日志信息
    va_start(args,fromat);
    PrintfLog(fromat,args);
    va_end(args);

    //文件刷出
    fflush(loging.logfile);
    //日志关闭
    if(loging.logfile!=NULL)
        fclose(loging.logfile);
    loging.logfile=NULL;
    rtv = 0;
    
    return rtv;
}
  1. 可供引用的头文件 -- log.h

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <stdarg.h>

#ifndef LOG_H
#define LOG_H

#define INFO_LEVEL 		0x01
#define DEBUG_LEVEL 	0x02
#define WARNING_LEVEL 	0x03
#define ERROR_LEVEL 	0x04
#define ALL_LEVEL 		0x05
#define DEFAULT_LEVEL	INFO_LEVEL

#define INFO 		INFO_LEVEL 	
#define DEBUG		DEBUG_LEVEL 
#define WARNING		WARNING_LEVEL
#define ERROR		ERROR_LEVEL 
#define ALL			ALL_LEVEL 	
#define DEFAULT		DEFAULT_LEVEL

#define MAXFILELEN 		4096
#define MAXFILEPATH 	512
#define MAXFILENAME 	50
#define DATE_LEN 		20
#define CONFNAME "/log.conf"

typedef struct log{
	char logtime[20];
	unsigned char loglevel;
	char filepath[MAXFILEPATH];
	FILE *logfile;
}LOG;

typedef struct logseting{
	char filepath[MAXFILEPATH];
	unsigned int maxfilelen;
	unsigned char loglevel;
}LOGSET;

int LogWrite(unsigned char loglevel,char *fromat,...);

#endif
  1. makefile

#
# MakeFile
#

# 编译器 gcc
CC=gcc
# log库目录
LOGLIBDIR=$(PWD)/lib
# 编译参数
CFLAGS=-Wall -std=c99 -g -O0 
LIBFLAGS=-L $(LOGLIBDIR) -llog 
# 日志模块文件
LOGSRC=log.c
LOGOBJ=$(LOGSRC:.c=.o)
# 编出动态库,默认使用动态库编译
LIB=liblog.so
# 编出静态库
SLIB=liblog-s.a
# 测试程序
OBJS=test
# 测试程序文件
SOURCEFILE=test.c

.PHONY:all
all:$(OBJS)

$(OBJS):$(LIB) $(SOURCEFILE)
	$(CC) $(SOURCEFILE) $(LIBFLAGS) $(CFLAGS) -o $@
	@cp log.conf.simple log.conf
	@echo "compile finish"

$(LIB):$(LOGOBJ)
	$(CC) -shared $(CFLAGS) -o  $@  $^ 
	@cp $(LIB) $(LOGLIBDIR) -R

$(SLIB):$(LOGOBJ)
	ar -crv $(SLIB) $(LOGOBJ)
	@cp $(SLIB) $(LOGLIBDIR) -R

$(LOGOBJ):$(LOGSRC)
	$(CC) -g -O0 -c -fPIC $^

.PHONY:static
static:$(SLIB)
	$(CC) $(SOURCEFILE) -L $(LOGLIBDIR) -llog-s $(CFLAGS) -o $(OBJS)

.PHONY:clean
clean:
	rm -fr $(LOGOBJ) $(LIB) $(SLIB) $(OBJS) 
  1. 功能测试文件--test.c

/*
 * 日志模块小玩具测试
 **/

#include "include/log.h"

int main(int argv,char**argc)
{
	LogWrite(DEBUG,"%s","H.e.l.l.o W.o.r.l.d!");
	sleep(1);
	LogWrite(INFO,"%s","Hello World!");
	sleep(3);
	LogWrite(WARNING,"%s","H e l l o W o r l d!");
	sleep(5);
	LogWrite(ERROR,"%s","Hallo World!");
	return 0;
}
  1. 日志配置模板文件--log.conf.simple

###
### log 模块的配置文件
### 

# log文件存放目录
path = ./logout  

# 日志打印级别,打印类型小于配置类型方可打印输出
# 选项          级别
# INFO           1 
# DEBUG          2
# WARNING        3
# ERROR          4
# ALL            5
level = ALL

二、 编译运行效果图

  1. 编译以动态库方式链接(默认方式)
    image

  2. 编译以静态库方式链接
    image

  3. 运行以及查看结果
    image
    (注:在test.c 中设置了sleep休眠方便观察日志顺序,并不是卡顿,可以取消休眠)

三、 TODO LIST

  1. 此demo未考虑高并发多进程/线程情况,后续需要添加锁
  2. 配置参数较少仅有日志文件目录选择与log显示级别
  3. 性能较为低下