c语言log_Morn:一个极简的C语言日志

986e016ebe49acffc2ad79aadb6e1c0c.png
Morn:一个C语言的基础工具和基础算法库​github.com

Morn的日志是一个极简的,几乎没有学习成本的日志。它可以实现:

  • 多种输出,包括动态文件、控制台、和用户自定义输出。
  • 日志分级,选择性的日志输出,可自定义日志级别
  • 预设日志格式,支持自定义格式
  • 根据日志文件大小,自动分割日志文件。
  • 线程安全
  • 异步日志

接口

Morn日志相关的接口极简单,只有mLogSetmLog两个接口,其中前者用于配置日志参数,后者用于输出日志。

输出日志

void mLog(int Level,const char *format,...);

这个接口简单到几乎可以不去解释。一个简单的例子:

int main()
{
    mLog(MORN_INFO, "this is a Morn log, num=%dn",1);
    return 0;
}

其运行的结果为:

this is a Morn log, num=1

日志设置

void mLogSet(int levelset);
void mLogSet(const char *filename);
void mLogSet(int levelset,const char *filename);
void mLogSet(int output,const char *filename);
void mLogSet(int levelset,int output,const char *filename);
void mLogSet(int levelset,int output,const char *filename,int64_t filesize);
void mLogSet(int levelset,int output,void (*func)(void *,int,void *),void *para);
void mLogSet(int levelset,int output,const char *filename,int64_t filesize,void (*func)(void *,int,void *),void *para);

mLogSet用来设置日志相关的参数,它包括以上7种形式。

程序中并非必须使用mLogSet函数,当没有使用mLogSet函数时,默认将日志打印在控制台上,日志级别为Info(Release版本)或Debug(Debug版本)。

mLogSet通常写在程序的开头,用以配置日志参数。也可以在程序中多次调用,用以改变日志配置。

以上mLogSet接口中:

  • levelset为设定的日志输出级别,当mLog函数中指定的日志级别大于等于此levelset时,日志才会被输出,否则被忽略。此项可设置为DFLT。
  • output为日志输出方式,包括MORN_LOG_CONSOLE(控制台)、MORN_LOG_FILE(文件)MORN_LOG_CUSTOM(用户自定义),同时也可以设置为MORN_LOG_CONSOLE & MORN_LOG_FILE表示即在控制台输出,也在文件中输出。此项可指定为DFLT,若已设置filename则默认输出到文件,若已设置func则默认按照用户自定义方式输出,否则默认输出到控制台。
  • filename为日志输出的文件名,若指定output为文件,则必须设置此项。
  • filesize:日志文件分割大小,单位为字节,只有设置了output为文件时此项才有效。若不设置此项,则所有日志将输出到同一文件中(不作分割)。否则日志文件大小超过此值时,将会新建一日志文件。
  • func:日志处理函数。它必须遵循以下形式:
    void func(char *log_data,int log_size,void *para);
  • para:即func项所需要的para。

使用

设置日志级别

此函数中的level即为日志级别。当此level大于等于mLogSet所配置的日志等级(未配置时使用默认配置)时,日志才会输出,否则忽略。

Morn预设的日志级别由低到高分别是MORN_DEBUGMORN_INFOMORN_WARNINGMORN_ERROR,他们的定义分别为:

#define MORN_DEBUG    0
#define MORN_INFO    16
#define MORN_WARNING 32
#define MORN_ERROR   48

用户除了可以使用以上四种级别外,还可以自定义日志级别。例如定义一种日志级别为NOTICE,其日志级别高于INFO低于WARNING,则可定义为

#define NOTICE (MORN_INFO+1)
mLog(NOTICE, "this is a Morn log, num=%dn",1);

使用预定义格式

Morn里使用语法糖mLogFormat预设了5种日志格式。以下例说明之:

int main()
{
    mLog(MORN_INFO, mLogFormat(1,"this is a Morn log, num=%d"),1);
    mLog(MORN_INFO, mLogFormat(2,"this is a Morn log, num=%d"),2);
    mLog(MORN_INFO, mLogFormat(3,"this is a Morn log, num=%d"),3);
    mLog(MORN_INFO, mLogFormat(4,"this is a Morn log, num=%d"),4);
    mLog(MORN_INFO, mLogFormat(5,"this is a Morn log, num=%d"),5);
    return 0;
}

此程序的输出为:

[test_log.c,line 16,function main]Info: this is a Morn log, num=1
[2020.09.13 18:03:43]Info: this is a Morn log, num=2
[thread001]Info: this is a Morn log, num=3
[2020.09.13 18:03:43 thread001]Info: this is a Morn log, num=4
[2020.09.13 18:03:43 thread001 test_log.c,line 20,function main]Info: this is a Morn log, num=5

以上可见,Morn所输出的日志格式包括:①日志所在文件、②日志所在行、③日志所在函数,③日志生产日期和时间,④日志所属线程,⑤日志级别。以上5种预设格式是以上这些信息的组合。

Morn库所有自带的日志输出(例如定时信息、异常信息),皆采用格式1。

值得一提的是mLogFormat只是一个语法糖,只有以上一种用法,以下两种用法是错误的:

int a=5;
mLog(MORN_INFO, mLogFormat(a,"this is a Morn log"));//错误用法
​
char *message = "this is a Morn log";
mLog(MORN_INFO, mLogFormat(5,message));             //错误用法
mLog(MORN_INFO, mLogFormat(5,"%s"),message);        //正确用法

如果误用mLogFormat,将会导致编译语法错误。

自定义格式

Morn认为:任何的预设格式都未必能满足用户的所有需求,Morn鼓励用户自定义日志格式。

例如,需要在日志格式中输出程序的作者,最简单的方式是:

mLog(MORN_INFO, "[%s,line %d,function %s,author:%s]%s: this is a Morn log" ,__FILE__,__LINE__,__FUNCTION__,"JingWeiZhangHuai",mLogLevel(),1);

mLog(MORN_INFO, mLogFormat(1,"author:%s. this is a Morn log"),"JingWeiZhangHuai",1);

以上两种方法皆可,但过于繁琐,Morn所鼓励的方式是仿照mLogFormat的方式定义日志格式,即:

//定义在头文件中
#define MY_FORMAT(Message) "[%s,line %d,function %s,author:%s]%s:"  Message  "n", __FILE__,__LINE__,__FUNCTION__,"JingWeiZhangHuai",mLogLevel()
​
//使用在函数中
mLog(MORN_INFO, MY_FORMAT("this is a Morn log"));

以下列出了几种可能在自定义格式时可能用到的接口:

  • 日志等级
const char *mLogLevel();        

此接口返回mLog中所输入的level。为字符串:"Debug",“Info","Warning"或"Error"。

  • 当前时间
const char *mTimeString(int64_t time_value,const char *format);

详见Morn:时间和日期。在日志中常使用mTimeString(DFLT,NULL)

  • 当前线程
int mThreadID();

返回值为当前线程编号,此编号并非系统中的线程ID,是由1开始递增的整数,如第一个线程返回1,第二个线程返回2等。

日志的多种输出

Morn日志可选择三种输出方式,分别为:

#define MORN_LOG_CONSOLE  (~1)  //输出到控制台
#define MORN_LOG_FILE     (~2)  //输出到日志文件
#define MORN_LOG_CUSTOM   (~4)  //用户自定义输出

以上三种方式可以混合使用,例如:

mLogSet(MORN_INFO);//输出到控制台
mLogSet(MORN_INFO,"./test.log");//输出到文件
mLogSet(MORN_INFO,MORN_LOG_CONSOLE&MORN_LOG_FILE,"./test.log");//既输出到控制台,也输出到文件
mLogSet(MORN_INFO,MORN_LOG_CUSTOM,my_log_func,my_log_func_para);//以用户自定义方式输出
mLogSet(MORN_INFO,MORN_LOG_CONSOLE&MORN_LOG_FILE&MORN_LOG_CUSTOM,"./test.log",DFLT,my_log_func,my_log_func_para);//同时以控制台、文件、用户自定义三种方式输出

其中用户自定义输出详见下文。

日志文件分割

当日志过多,文件过大时,往往并不希望所有的日志都保存在同一文件里,这是需要对日志文件进行分割。设置mLogSet接口中的filesize项可实现此功能。

例如:

mLogSet(DFLT,"./test.log",1024*1024);

以上表示每个日志文件的大小不超过1M字节。此时会在指定目录下生成test.log、test_1.log、test_2.log……等一系列不超过1M字节的日志文件。

日志自定义输出

Morn已提供了控制台和文件两种输出方式,但是Morn认为:无论提供多少种日志输出方式都未必能够满足所有的应用场景,最好的办法是允许用户自定义日志的输出方式。

用户自定义日志输出方式是通过在mLogSet接口中设置func和para选项来实现的。

例如,将日志通过UDP上传到另一台计算机:

void my_log_func(char *log_data,int log_size,char *udp_address)
{
    mUDPSend(udp_address,log_data,log_size);
}
...
int main()
{
    mLogSet(DFLT,MORN_LOG_CUSTOM,my_log_func,"192.168.0.123:8888");
    ...
    mLog(MORN_INFO,"this is a Morn log");
    ...
    return 0;
}

性能

为了测试Morn日志的性能,使用以下程序进行了测试,测试中将Morn日志与常用的glog、spdlog、log4cpp三种日志库进行比较:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include<iostream>
​
#include "glog/logging.h"
​
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h"
​
#include "log4cpp/Category.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/Priority.hh"
#include "log4cpp/PatternLayout.hh"
​
#include "morn_util.h"
​
int main(int argc, char** argv)
{
    char datas[100][32];
    int datai[100];
    double datad[100];
    for(int i=0;i<100;i++)
    {
        //生成随机整数
        datai[i]=mRand(DFLT,DFLT);
        //生成随机浮点数
        datad[i]=(double)mRand(-1000000,1000000)/1000000.0;
        //生成随机字符串
        int len = mRand(15,31);
        for(int j=0;j<len;j++)datas[i][j]=mRand('a','z');
        datas[i][len]=0;
    }
    
    mTimerBegin("glog");
    google::InitGoogleLogging(argv[0]);
    google::SetLogDestination(google::GLOG_INFO, "./test_log_glog.log");
    for(int n=0;n<1000000;n++)
    {
        int i=n%100;
        LOG(INFO)<<": Hello glog, "<<"datai="<<datai[i]<<", datad="<<datad[i]<<", datas="<<&(datas[i][0]);
    }
    google::ShutdownGoogleLogging();
    mTimerEnd("glog");
​
    mTimerBegin("spdlog");
    auto console2 = spdlog::basic_logger_mt(argv[0],"./test_log_spdlog.log");
    spdlog::set_pattern("[%Y.%m.%d %H:%M:%S thread%t]%l: %v");
    for(int n=0;n<1000000;n++)
    {
        int i=n%100;
        console2->info("[{} line {},function {}] Hello spdlog, datai={}, datad={}, datas={}",__FILE__,__LINE__,__FUNCTION__,datai[i],datad[i],&(datas[i][0]));
    }
    mTimerEnd("spdlog");
​
    mTimerBegin("log4cpp");
    log4cpp::FileAppender * appender = new log4cpp::FileAppender("appender","./test_log_log4cpp.log");
    log4cpp::PatternLayout* pLayout = new log4cpp::PatternLayout();
    pLayout->setConversionPattern("[%d thread%t %c]%p: %m%n");
    appender->setLayout(pLayout);
    log4cpp::Category& root =log4cpp::Category::getRoot();
    log4cpp::Category& infoCategory =root.getInstance(argv[0]);
    infoCategory.addAppender(appender);
    infoCategory.setPriority(log4cpp::Priority::INFO);
    for(int n=0;n<1000000;n++)
    {
        int i=n%100;
        infoCategory.info("[%s,line %d,function %s] Hello log4cpp, datai=%d, datad=%f, datas=%s",__FILE__,__LINE__,__FUNCTION__,datai[i],datad[i],&(datas[i][0]));
    }
    log4cpp::Category::shutdown();
    mTimerEnd("log4cpp");
​
    mTimerBegin("Morn");
    mLogSet(DFLT,DFLT,"./test_log_morn.log");
    for(int n=0;n<1000000;n++)
    {
        int i=n%100;
        mLog(MORN_INFO,mLogFormat(5,"Hello Morn, datai=%d, datad=%f, datas=%s"),datai[i],datad[i],&(datas[i][0]));
    }
    mTimerEnd("Morn");
​
    return 0;
}

以上分别测试了1000000输出条日志所需时间。四种日志的输出分别是:

  • glog
I20200913 21:14:36.839823   148 test_log2.cpp:48] : Hello glog, datai=17729, datad=-0.761655, datas=cuppeubmapohxinsmwoumohsrmfdi
  • spdlog
[2020.09.13 21:14:44 thread148]info: [test_log2.cpp line 59,function main] Hello spdlog, datai=17729, datad=-0.761655, datas=cuppeubmapohxinsmwoumohsrmfdi
  • log4cpp
[2020-09-13 21:14:45,783 thread148 .test_log2.exe]INFO: [test_log2.cpp,line 75,function main] Hello log4cpp, datai=17729, datad=-0.761655, datas=cuppeubmapohxinsmwoumohsrmfdi
  • Morn
[2020.09.13 21:15:15 thread001 test_log2.cpp,line 85,function main]Info: Hello Morn, datai=17729, datad=-0.761655, datas=cuppeubmapohxinsmwoumohsrmfdi

测试结果为:

7e3290a925935b841784439d60edbd17.png

以上可见:

类别速度(条/秒)速度(kB/s)glog1843222319spdlog82072119414log4cpp997916574Morn6197787197

Morn的速度稍慢于spdlog,但是远快于glog和log4cpp。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值