FreeSWITCH日志功能分析及apr模拟
操作系统版本:Debian 12.5_x64
FreeSWITCH版本: 1.10.11
apr库版本:apr-1.7.4 & apr-util-1.6.3
gcc版本: 12.2.0
日志功能在python等脚本里面是标准库提供的,使用起来非常方便,如果在新开发的C程序里面实现该功能,比如将系统时间、文件名称、代码行数都打印出来,该如何实现呢?
最近就遇到了这个问题,是通过参考freeswitch代码实现的。
今天整理下这方面的内容,我将从以下几个方面进行展开:
- freeswitch日志功能及相关源码分析
- 日志功能实现可行性分析
- 使用示例及运行效果
- 资源获取
一、功能说明及源码分析
在freeswitch中,可通过如下方式打印日志:
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "ENUM Reloaded\n");
日志配置文件路径:
/usr/local/freeswitch/conf/autoload_configs/logfile.conf.xml
日志文件路径:
/usr/local/freeswitch/log/freeswitch.log
日志效果:
由图可以看出,添加简单的日志代码,可将系统时间、文件名称、代码行数都打印出来。
这些功能在python等脚本里面很好实现,如果在c程序里面实现该功能,freeswitch的代码值得参考。
下面就结合上述示例,分析下freeswitch日志功能源码。
1、switch_log_printf函数分析
文件: switch_log.c
函数实现如下:
SWITCH_DECLARE(void) switch_log_printf(switch_text_channel_t channel, const char *file, const char *func, int line, const char *userdata, switch_log_level_t level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); switch_log_meta_vprintf(channel, file, func, line, userdata, level, NULL, fmt, ap); va_end(ap); }
从实现代码可以看出,switch_log_printf函数的参数列表里面有channel、file、func、line等参数,但调用时并未传入。
接下来看下调用时使用的参数。
1)SWITCH_CHANNEL_LOG宏
定义如下:
#define SWITCH_CHANNEL_LOG SWITCH_CHANNEL_ID_LOG, __FILE__, __SWITCH_FUNC__, __LINE__, NULL
从代码实现来看,这个宏把需要的参数都传入了。
2)SWITCH_LOG_INFO参数
这个参数是enum类型,定义如下:
2、switch_log_meta_vprintf函数分析
文件: switch_log.c
函数实现如下:

SWITCH_DECLARE(void) switch_log_meta_vprintf(switch_text_channel_t channel, const char *file, const char *func, int line, const char *userdata, switch_log_level_t level, cJSON **meta, const char *fmt, va_list ap) { cJSON *log_meta = NULL; char *data = NULL; char *new_fmt = NULL; int ret = 0; FILE *handle; const char *filep = (file ? switch_cut_path(file) : ""); const char *funcp = (func ? func : ""); char *content = NULL; switch_time_t now = switch_micro_time_now(); uint32_t len; #ifdef SWITCH_FUNC_IN_LOG const char *extra_fmt = "%s [%s] %s:%d %s()%c%s"; #else const char *extra_fmt = "%s [%s] %s:%d%c%s"; #endif switch_log_level_t limit_level = runtime.hard_log_level; switch_log_level_t special_level = SWITCH_LOG_UNINIT; if (meta && *meta) { log_meta = *meta; *meta = NULL; } if (limit_level == SWITCH_LOG_DISABLE) { goto end; } if (channel == SWITCH_CHANNEL_ID_SESSION && userdata) { switch_core_session_t *session = (switch_core_session_t *) userdata; special_level = session->loglevel; if (limit_level < session->loglevel) { limit_level = session->loglevel; } } if (level > 100) { if ((uint32_t) (level - 100) > runtime.debug_level) { goto end; } level = 1; } if (level > limit_level) { goto end; } switch_assert(level < SWITCH_LOG_INVALID); handle = switch_core_data_channel(channel); if (channel != SWITCH_CHANNEL_ID_LOG_CLEAN) { char date[80] = ""; //switch_size_t retsize; switch_time_exp_t tm; switch_time_exp_lt(&tm, now); switch_snprintf(date, sizeof(date), "%0.4d-%0.2d-%0.2d %0.2d:%0.2d:%0.2d.%0.6d %0.2f%%%%", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_usec, switch_core_idle_cpu()); //switch_strftime_nocheck(date, &retsize, sizeof(date), "%Y-%m-%d %T", &tm); #ifdef SWITCH_FUNC_IN_LOG len = (uint32_t) (strlen(extra_fmt) + strlen(date) + strlen(filep) + 32 + strlen(funcp) + strlen(fmt)); #else len = (uint32_t) (strlen(extra_fmt) + strlen(date) + strlen(filep) + 32 + strlen(fmt)); #endif new_fmt = malloc(len + 1); switch_assert(new_fmt); #ifdef SWITCH_FUNC_IN_LOG switch_snprintf(new_fmt, len, extra_fmt, date, switch_log_level2str(level), filep, line, funcp, 128, fmt); #else switch_snprintf(new_fmt, len, extra_fmt, date, switch_log_level2str(level), filep, line, 128, fmt); #endif fmt = new_fmt; } ret = switch_vasprintf(&data, fmt, ap); if (ret == -1) { fprintf(stderr, "Memory Error\n"); goto end; } if (channel == SWITCH_CHANNEL_ID_LOG_CLEAN) { content = data; } else { if ((content = strchr(data, 128))) { *content = ' '; } } if (channel == SWITCH_CHANNEL_ID_EVENT) { switch_event_t *event; if (switch_event_running() == SWITCH_STATUS_SUCCESS && switch_event_create(&event, SWITCH_EVENT_LOG) == SWITCH_STATUS_SUCCESS) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Log-Data", data); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Log-File", filep); switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "Log-Function", funcp); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Log-Line", "%d", line); switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Log-Level", "%d", (int) level); if (!zstr(userdata)) { switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "User-Data", userdata); } switch_event_fire(&event); data = NULL; } goto end; } if (console_mods_loaded == 0 || !do_mods) { if (handle) { int aok = 1; #ifndef WIN32 fd_set can_write; int fd; struct timeval to; fd = fileno(handle); memset(&to, 0, sizeof(to)); FD_ZERO(&can_write); FD_SET(fd, &can_write); to.tv_sec = 0; to.tv_usec = 100000; if (select(fd + 1, NULL, &can_write, NULL, &to) > 0) { aok = FD_ISSET(fd, &can_write); } else { aok = 0; } #endif if (aok) { if (COLORIZE) { #ifdef WIN32 SetConsoleTextAttribute(hStdout, COLORS[level]); WriteFile(hStdout, data, (DWORD) strlen(data), NULL, NULL); SetConsoleTextAttribute(hStdout, wOldColorAttrs); #else fprintf(handle, "%s%s%s", COLORS[level], data, SWITCH_SEQ_DEFAULT_COLOR); #endif } else { fprintf(handle, "%s", data); } } } } if (do_mods && level <= MAX_LEVEL) { switch_log_node_t *node = switch_log_node_alloc(); node->data = data; data = NULL; switch_set_string(node->file, filep); switch_set_string(node->func, funcp); node->line = line; node->level = level; node->slevel = special_level; node->content = content; node->timestamp = now; node->channel = channel; node->tags = NULL; node->meta = log_meta; log_meta = NULL; if (channel == SWITCH_CHANNEL_ID_SESSION) { switch_core_session_t *session = (switch_core_session_t *) userdata; node->userdata = userdata ? strdup(switch_core_session_get_uuid(session)) : NULL; if (session) { switch_channel_get_log_tags(switch_core_session_get_channel(session), &node->tags); } } else { node->userdata = !zstr(userdata) ? strdup(userdata) : NULL; } if (switch_queue_trypush(LOG_QUEUE, node) != SWITCH_STATUS_SUCCESS) { switch_log_node_free(&node); } } end: cJSON_Delete(log_meta); switch_safe_free(data); switch_safe_free(new_fmt); }
会使用 switch_queue_trypush 函数进行入队操作:
说明:
MAX_LEVEL的值会变,在 switch_log_bind_logger 时修改该值。
3、mod_logfile_load函数分析
文件: mod_logfile.c
freeswitch的日志是通过mod_logfile来配置的,在模块加载时,主要做以下事项:
1)解析配置文件;
2)通过switch_log_bind_logger函数来注册日志回调函数;
4、switch_log_bind_logger函数分析
文件: switch_log.c
主要用于注册回调函数,供后续流程使用。
5、log_thread函数
文件: switch_log.c
主要用于注册回调函数,供后续流程使用。
日志线程在初始化时就启动了。
6、mod_logfile_raw_write函数
文件: mod_logfile.c
功能:
1)写日志内容到文件;
2)日志文件轮转(rotate);
函数调用链如下:
mod_logfile_load => mod_logfile_logger => process_node => mod_logfile_raw_write
7、mod_logfile_rotate函数
文件: mod_logfile.c
功能:
通过修改文件名的方式,实现日志文件轮转(rotate)。
二、实现可行性分析
1、配置文件
参考freeswitch,使用xml作为配置文件,可借助libxml2来解析。
该库的GitHub地址: https://github.com/GNOME/libxml2
可以直接使用软件源安装。
debian下:
apt install libxml2-dev
依赖安装(centos7):
yum install libxml2-devel.x86_64
结论:配置功能可行,可基于libxml2实现。
2、基于队列实现日志功能
apr队列是个线程安全的FIFO队列,arp库编译、队列接口及使用示例,可参考如下文章:
可基于apr队列实现日志功能:
1)日志线程提供队列用于存储日志数据;
2)工作线程向队列中添加日志;
3)日志线程输出日志内容到文件;
结论:基于队列实现日志功能可行。
3、日志轮转
可仿照freeswitch的实现,使用apr库进行文件名称修改操作。
综上,使用apr模拟freeswitch的日志功能可行。
三、使用示例及运行效果
1、配置文件添加及解析
配置文件示例(conf.xml):
<setting> <server>192.168.137.100:5060</server> <log> <dirPath>/tmp/log</dirPath> <fileName>test.log</fileName> <rollSize>100</rollSize> MB <rollCount>5</rollCount> </log> </setting>
对应的解析代码:
完整代码可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
2、logger实现
头文件内容如下(logger.h):
完整代码可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
日志功能内容如下(logger.c):
完整代码可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
3、主程序实现
头文件内容如下(testMain.h):
#pragma once #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <time.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/resource.h> #include <sys/prctl.h> #include <arpa/inet.h> #include <apr_portable.h> #include "apr_queue.h" #include "apr_thread_pool.h" #include "apr_time.h" #include "apr_hash.h" #include "apr_thread_mutex.h" #include <pthread.h> #include <libxml/parser.h> #include <libxml/tree.h> typedef struct logger_vars_t { apr_pool_t *apr_pool; char server[256]; char log_dirname[256]; char log_filename[128]; int log_rollsize; int log_rollcount; }logger_vars_t;
主程序内容如下(testMain.c):
完整代码可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
4、编译脚本及Makefile
编译脚本内容如下(doBuild.sh):
#! /bin/bash BASEDIR=${PWD} APRDIR=${BASEDIR}/libs/apr-1.7.4 APRUTILDIR=${BASEDIR}/libs/apr-util-1.6.3 echo ${BASEDIR} #exit 0 cd ${APRDIR} ./configure --enable-static make cd ${APRUTILDIR} ./buildconf --with-apr=${APRDIR} ./configure --with-apr=${APRDIR} make cd ${BASEDIR} make testMain
Makefile文件内容如下:
CC=gcc CFLAGS=-g -gstabs+ -I/usr/include/libks -Ilibs/apr-1.7.4/include -Ilibs/apr-util-1.6.3/include -I/usr/include/libxml2/ LIBS=libs/apr-util-1.6.3/.libs/libaprutil-1.a libs/apr-1.7.4/.libs/libapr-1.a -lpthread -lxml2 # BASEDIR=${PWD} APRDIR=${BASEDIR}/libs/apr-1.7.4 APRUTILDIR=${BASEDIR}/libs/apr-util-1.6.3 all: #make apr #make apr-util make testMain apr: cd $(APRDIR) && ./configure --enable-static && make apr-util: cd $(APRUTILDIR) && ./buildconf --with-apr=$(APRDIR) && ./configure --with-apr=$(APRDIR) && make testMain: testMain.o logger.o $(CC) -o testMain testMain.o logger.o $(LIBS) clean: rm -f testMain rm -f *.o .c.o: $(CC) $(CFLAGS) -c -o $*.o $<
执行 doBuild.sh 即可编译。
5、示例效果
运行效果如下:
打包的工程文件,可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
四、其它
1、添加core dump支持
当程序crash时产生core dump文件,便于程序调试使用。
void core_setrlimits(void) { // set core dump struct rlimit rlp; memset(&rlp, 0, sizeof(rlp)); rlp.rlim_cur = 999999; rlp.rlim_max = 999999; setrlimit(RLIMIT_NOFILE, &rlp); memset(&rlp, 0, sizeof(rlp)); rlp.rlim_cur = RLIM_INFINITY; rlp.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_CPU, &rlp); setrlimit(RLIMIT_DATA, &rlp); setrlimit(RLIMIT_FSIZE, &rlp); setrlimit(RLIMIT_CORE, &rlp); return; }
在main函数中调用 core_setrlimits 函数即可。
2、日志乱码问题及队列操作注意事项
可从如下渠道获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
五、资源获取
本文涉及源码及相关文件,可从如下途径获取:
关注微信公众号(聊聊博文,文末可扫码)后回复 20250102 获取。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 上周热点回顾(2.17-2.23)
2013-01-07 dll开发及调用
2013-01-07 用dtmf实现asterisk自动拨打分机
2012-01-07 线程休眠代码(C++)
2012-01-07 VC6编译pjproject-1.12并生成python的pjsua