Libevent通过va_list实现日志功能

Libevent提供一个记录错误和警告信息的日志功能。默认是直接将上述信息输出到标准错误,同时也可以通过回调函数提供自己的日志函数覆盖默认的功能。

1、C语言实现可变参数

<stdarg.h>中包含一组宏定义,它们对如何遍历参数进行了定义。依据下面的几个函数,可以写出高效率的日志功能。


va_list//宏定义,用于声明变量,该变量将依次引用各参数。

void va_start (va_list ap, paramN);//ap前面定义的参数指针,param是函数列表最后一个有名参数。

type va_arg (va_list ap, type);//该函数返回一个参数,并将ap指向下一个参数,使用type类型名决定返回对象的类型、指针移动步长。

va_end//确保清理了ap对应的内存

int vsnprintf (char * s, size_t n, const char * format, va_list arg );
/*
按照指定格式将可变参数写入字符串缓存区。
s: Pointer to a buffer where the resulting C-string is stored.The buffer should have a size of at least n characters.

n: Maximum number of bytes to be used in the buffer.The generated string has a length of at most n-1, leaving space for the additional terminating null character.函数自动在转换完成的字符串后面加上终止字符\0。size_t is an unsigned integral type.

format: C string that contains a format string that follows the same specifications as format in printf (see printf for details).
arg: A value identifying a variable arguments list initialized with va_start.
*/

简单demo:

#include <stdio.h>
#include <stdarg.h>

void PrintFError ( const char * format, ... )
{
  char buffer[256];//缓存
  va_list args;//ap变量
  va_start (args, format);//初始化ap
  vsnprintf (buffer , 256 , format , args);//按照format将可变参数列表转换成对应字符串
  printf("%s" , buffer);//输出字符串
  va_end (args);//清理ap
}

int main ()
{
   FILE * pFile;
   char szFileName[]="myfile.txt";

   pFile = fopen (szFileName,"r");
   if (pFile == NULL)
     PrintFError ("Error opening %s %d \n",szFileName , 555);
   else
   {
     fclose (pFile);
   }
   return 0;
}

这里写图片描述

2、libevent实现日志功能

void event_err(int eval, const char *fmt, ...);
void event_warn(const char *fmt, ...);
void event_sock_err(int eval, evutil_socket_t sock, const char *fmt, ...) ;
void event_sock_warn(evutil_socket_t sock, const char *fmt, ...);
void event_errx(int eval, const char *fmt, ...);
void event_warnx(const char *fmt, ...);
void event_msgx(const char *fmt, ...);
void _event_debugx(const char *fmt, ...);

上述这些函数都定义在<log-internal.h>,说明这些函数只能被libevent内部调用,用于将一些不合理的情况打印出来,告知用户。
这些函数的实现非常简单。例如event_err。其具体实现如下。

/*
eval:表示错误码
fmt:固定格式
*/
void
event_err(int eval, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    _warn_helper(_EVENT_LOG_ERR, strerror(errno), fmt, ap);
    va_end(ap);
    event_exit(eval);
}


/*
供event_err等上述函数内部调用的函数。
severity:日志类别
    EVENT_LOG_DEBUG        调试日志
    #define EVENT_LOG_MSG  消息日志
    #define EVENT_LOG_WARN 警告日志
    #define EVENT_LOG_ERR  错误日志
errstr:额外的错误信息,也就是一行字符串,用于提示操作人员
fmt:固定格式
ap:可变参数变量   
*/
static void
_warn_helper(int severity, const char *errstr, const char *fmt, va_list ap)
{
    char buf[1024];
    size_t len;
    //如果有可变参数,把参数格式化到缓冲区
    if (fmt != NULL)
        evutil_vsnprintf(buf, sizeof(buf), fmt, ap);//调用vsnprintf将可变参数写入buf
    else
        buf[0] = '\0';

    //如果有额外信息描述,信息追加到可变参数的后面
    if (errstr) {
        len = strlen(buf);
        if (len < sizeof(buf) - 3) {//-3 为了多存三个字符 冒号 空格 和\0
            evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);//存储
        }
    }

    event_log(severity, buf);//缓冲区作为一条日志,输出
}

/*
log_fn是函数指针,可以由用户配置的回调函数,当此函数赋值时候,对应的错误处理将执行此回调函数。
*/
static void
event_log(int severity, const char *msg)
{
    if (log_fn)//调用用户自定义回调函数
        log_fn(severity, msg);
    else {
        const char *severity_str;//字符常量串指针,用于指向字符串常量
        switch (severity) {
        case _EVENT_LOG_DEBUG:
            severity_str = "debug";
            break;
        case _EVENT_LOG_MSG:
            severity_str = "msg";
            break;
        case _EVENT_LOG_WARN:
            severity_str = "warn";
            break;
        case _EVENT_LOG_ERR:
            severity_str = "err";
            break;
        default:
            severity_str = "???";
            break;
        }
        (void)fprintf(stderr, "[%s] %s\n", severity_str, msg);//输出到标准错误终端
    }
}

通过上述分析函数调用过程依次是:event_err-> _warn_helper->event_log将错误信息输出到终端显示,如果用户设置了对应的错误回调函数,那么将执行回调函数,用于可以在回调函数里面存储对应的错误信息,生成以后的日志文件。

将函数分离出来测试:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <event.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <errno.h>
int
evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);
int
evutil_snprintf(char *buf, size_t buflen, const char *format, ...)
{
    int r;
    va_list ap;
    va_start(ap, format);
    r = evutil_vsnprintf(buf, buflen, format, ap);
    va_end(ap);
    return r;
}
int
evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap)
{
    int r;
    if (!buflen)
        return 0;
    r = vsnprintf(buf, buflen, format, ap);//转换,最多存储buflen-1个字符,最后存储\0
    return r;
}
static void
event_log(int severity, const char *msg)
{
        const char *severity_str;//字符常量串指针,用于指向字符串常量
        switch (severity) {
        case _EVENT_LOG_DEBUG:
            severity_str = "debug";
            break;
        case _EVENT_LOG_MSG:
            severity_str = "msg";
            break;
        case _EVENT_LOG_WARN:
            severity_str = "warn";
            break;
        case _EVENT_LOG_ERR:
            severity_str = "err";
            break;
        default:
            severity_str = "???";
            break;
        }
        (void)fprintf(stderr, "[%s] %s\n", severity_str, msg);//输出到标准错误终端
}


static void
_warn_helper(int severity, const char *errstr, const char *fmt, va_list ap)
{
    char buf[1024];
    size_t len;
    //如果有可变参数,把参数格式化到缓冲区
    if (fmt != NULL)
        evutil_vsnprintf(buf, sizeof(buf), fmt, ap);//将变参数写入buf
    else
        buf[0] = '\0';

    //如果有额外信息描述,信息追加到可变参数的后面
    if (errstr) {
        len = strlen(buf);//已经存储字符长度
        if (len < sizeof(buf) - 3) {//-3 为了多存三个字符 冒号 空格 和\0,否则出现问题
            evutil_snprintf(buf + len, sizeof(buf) - len, ": %s", errstr);
        }
    }
    event_log(severity, buf);//缓冲区作为一条日志,输出
}

void
event_err(int eval, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    _warn_helper(_EVENT_LOG_ERR, "this is extra info", fmt, ap);
    va_end(ap);
    exit(eval);
}

void event_log_callback(int severity, const char *msg)
{
    printf("%s" , msg);
}

int main(void)
{
    event_err(0, "%s: failed to register rpc %s %d",
        __func__ , "wangjun" ,12);
    return 0;
}

这里写图片描述
在此,libevent的日志功能分析完毕。这是一个非常简陋的日志功能。

3、自定义回调日志函数例子

#include <event2/event.h>
#include <stdio.h>

static void discard_cb(int severity, const char *msg)
{
    /* This callback does nothing. */
}

static FILE *logfile = NULL;
static void write_to_file_cb(int severity, const char *msg)
{
    const char *s;
    if (!logfile)
        return;
    switch (severity) {
        case _EVENT_LOG_DEBUG: s = "debug"; break;
        case _EVENT_LOG_MSG:   s = "msg";   break;
        case _EVENT_LOG_WARN:  s = "warn";  break;
        case _EVENT_LOG_ERR:   s = "error"; break;
        default:               s = "?";     break; /* never reached */
    }
    fprintf(logfile, "[%s] %s\n", s, msg);
}

/* Turn off all logging from Libevent. */
void suppress_logging(void)//设置没有日志功能
{
    event_set_log_callback(discard_cb);
}

/* Redirect all Libevent log messages to the C stdio file 'f'. */
void set_logfile(FILE *f)//设置将日志写入文件,f为对应的文件描述符符
{
    logfile = f;
    event_set_log_callback(write_to_file_cb);
}

注意:在回调函数里面不要调用libevent内部的函数,否则会出现难以发现的bug。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有时需要偏执狂

请我喝咖啡

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

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

打赏作者

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

抵扣说明:

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

余额充值