操作系统版本: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 获取。

FreeSWITCH日志功能分析及apr模拟的更多相关文章

  1. 出现错误日志:The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path

      tomcat6出现错误日志: 信息: The APR based Apache Tomcat Native library which allows optimal performance in  ...

  2. MySQL慢日志功能分析及优化增强

    本文由  网易云发布. MySQL慢日志(slow log)是MySQL DBA及其他开发.运维人员需经常关注的一类信息.使用慢日志可找出执行时间较长或未走索引等SQL语句,为进行系统调优提供依据.本 ...

  3. freeswitch APR库

    概述 freeswitch依赖库源代码基本都可以在libs目录下找到. 在freeswitch的官方手册中,可以找到freeswitch的依赖库表格,其中freeswitch的core核心代码依赖库主 ...

  4. 日志切割logrotate和定时任务crontab详解

    1.关于日志切割 日志文件包含了关于系统中发生的事件的有用信息,在排障过程中或者系统性能分析时经常被用到.对于忙碌的服务器,日志文件大小会增长极快,服务器会很快消耗磁盘空间,这成了个问题.除此之外,处 ...

  5. freeswitch对接其它SIP设备

    这几天用到freeswitch对接其它设备方面的知识,这里整理下,也方便我以后查阅. 操作系统:debian8.5_x64 freeswitch 版本 : 1.6.8 一.freeswitch作为被叫 ...

  6. [转]Linux日志文件总管——logrotate

    FROM : https://linux.cn/article-4126-1.html 日志文件包含了关于系统中发生的事件的有用信息,在排障过程中或者系统性能分析时经常被用到.对于忙碌的服务器,日志文 ...

  7. 切割haproxy的日志

    日志的切割有以下几种方法: 1.写个定时任务,每天某个时间点把旧的日志重命名,并对服务重启使其重新打开日志并写入. 2.通过管道的方式把新产生的日志写到另外一个日志文件里. 3.通过logrotate ...

  8. tomcat并发优化之三种接收处理请求方式(BIO、NIO、APR)介绍

    原文链接:http://blog.csdn.net/xyang81/article/details/51502766 Tomcat支持三种接收请求的处理方式:BIO.NIO.APR 1>.BIO ...

  9. Linux日志文件总管——logrotate

    日志文件包含了关于系统中发生的事件的有用信息,在排障过程中或者系统性能分析时经常被用到.对于忙碌的服务器,日志文件大小会增长极快,服务器会很快消耗磁盘空间,这成了个问题.除此之外,处理一个单个的庞大日 ...

  10. Python之logging日志模块

    logging 用于便捷既然日志切线程安全的模块 vim log_test.py import logging logging.basicConfig(filename='log.log', form ...

随机推荐

  1. OKR 目标和关键成果

    OKR(Objectives and Key Results)是目标与关键成果管理法,是一套明确和跟踪目标及其完成情况的管理工具和方法.1.OKR首先是沟通工具:团队中的每个人都要写OKR,所有这些O ...

  2. ToDesk再度出手,加快云电脑高性能发展,剑指千亿级市场

    根据中国信通院发布的<云计算白皮书(2023年)>(以下简称白皮书)显示,云计算引发了软件开发部署模式的创新,成为承载各类应用的关键基础设施,为大数据.物联网.人工智能等新兴领域的发展提供 ...

  3. Halcon 快速入门教程

    文章首发于我的 github 仓库-cv算法工程师成长之路,欢迎关注我的公众号-嵌入式视觉. 本人水平有限,文章如有问题,欢迎及时指出.如果看完文章有所收获,一定要先点赞后收藏.毕竟,赠人玫瑰,手有余 ...

  4. 3D数学基础:图形和游戏开发(第二版)--读书笔记(1)

    简介: 本书是关于3D数学.三维空间的几何和代数的入门教材.它旨在告诉你如何使用数学描述三维中的物体及其位置.方向和轨迹.这不是一本关于计算机图形学.模拟,甚至计算几何的书,但是,如果读者打算研究这些 ...

  5. 2024强网杯pwn short wp

    这时2024强网杯的pwn部分的short的WP 分析以下程序的基本安全措施 *] '/home/ysly/solve/tmp/short' Arch: i386-32-little RELRO: P ...

  6. 怎样在Windows 环境下安装Git附详细步骤图

    Git下载路径:https://git-scm.com/ [步骤] 在非C盘创建一个git_install作为git的安装目录,双击安装包,按下一步默认安装即可 (后面有时间来补充各个参数意思,目前按 ...

  7. 感谢华为:iPhone 16全球价格对比:中国最便宜!比均价低1200元

    相关: https://baijiahao.baidu.com/s?id=1811582397991377070&wfr=spider&for=pc 苹果最新的iPhone 16系列已 ...

  8. apisix~限流插件的使用

    参考: https://i4t.com/19399.html https://github.com/apache/apisix/issues/9193 https://github.com/apach ...

  9. langchain_chatchat+ollama部署本地知识库,联网查询以及对数据库(Oracle)数据进行查询

    langchain_chatchat+ollama部署本地知识库,联网查询以及对数据库(Oracle)数据进行查询 涉及的内容其实挺多的,所以尽量减少篇幅 目录 langchain_chatchat+ ...

  10. 国产数据库oceanBbase,达梦,金仓与mysql数据库的性能对比 四、python读mysql写入达梦数据库

    一.说明 安装达梦的驱动 pip install dmPython==2.5.5 参数接收那里,其他数据库都是用%,达梦要用? 二.源码 #coding=utf-8 import pymysql im ...