pjlib提供了一套统一的错误码,都是正数,并且屏蔽各系统的错误码。pjlib的错误在文件errno.h errno.c compat/errno.h os_core_unix.c。
错误码范围分段
/**
* PJ_ERRNO_START is where PJLIB specific error values start.
*/
#define PJ_ERRNO_START 20000
/**
* PJ_ERRNO_SPACE_SIZE is the maximum number of errors in one of
* the error/status range below.
*/
#define PJ_ERRNO_SPACE_SIZE 50000
/**
* PJ_ERRNO_START_STATUS is where PJLIB specific status codes start.
* Effectively the error in this class would be 70000 - 119000.
*/
#define PJ_ERRNO_START_STATUS (PJ_ERRNO_START + PJ_ERRNO_SPACE_SIZE)
/**
* PJ_ERRNO_START_SYS converts platform specific error codes into
* pj_status_t values.
* Effectively the error in this class would be 120000 - 169000.
*/
#define PJ_ERRNO_START_SYS (PJ_ERRNO_START_STATUS + PJ_ERRNO_SPACE_SIZE)
/**
* PJ_ERRNO_START_USER are reserved for applications that use error
* codes along with PJLIB codes.
* Effectively the error in this class would be 170000 - 219000.
*/
#define PJ_ERRNO_START_USER (PJ_ERRNO_START_SYS + PJ_ERRNO_SPACE_SIZE)
/*
* Below are list of error spaces that have been taken so far:
* - PJSIP_ERRNO_START (PJ_ERRNO_START_USER)
* - PJMEDIA_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE)
* - PJSIP_SIMPLE_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*2)
* - PJLIB_UTIL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*3)
* - PJNATH_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*4)
* - PJMEDIA_AUDIODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*5)
* - PJ_SSL_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*6)
* - PJMEDIA_VIDEODEV_ERRNO_START (PJ_ERRNO_START_USER + PJ_ERRNO_SPACE_SIZE*7)
*/
每个区间50000个,第一个区间START开头的没有定义,第二个区间STATUS是pjlib自定义的错误码,第三个区间SYS是操作系统映射的错误码,第四个区间保留给应用扩展,其中PJSIP PJMEDIA等模块在上面扩展。
映射操作系统错误码
映射原理很简单,就是操作系统错误码先转成正数(有的系统错误码是负数),再加上PJ_ERRNO_START_SYS。
/**
* @hideinitializer
* Fold a platform specific error into an pj_status_t code.
*
* @param e The platform os error code.
* @return pj_status_t
* @warning Macro implementation; the syserr argument may be evaluated
* multiple times.
*/
#if PJ_NATIVE_ERR_POSITIVE
# define PJ_STATUS_FROM_OS(e) (e == 0 ? PJ_SUCCESS : e + PJ_ERRNO_START_SYS)
#else
# define PJ_STATUS_FROM_OS(e) (e == 0 ? PJ_SUCCESS : PJ_ERRNO_START_SYS - e)
#endif
/**
* @hideinitializer
* Fold an pj_status_t code back to the native platform defined error.
*
* @param e The pj_status_t folded platform os error code.
* @return pj_os_err_type
* @warning macro implementation; the statcode argument may be evaluated
* multiple times. If the statcode was not created by
* pj_get_os_error or PJ_STATUS_FROM_OS, the results are undefined.
*/
#if PJ_NATIVE_ERR_POSITIVE
# define PJ_STATUS_TO_OS(e) (e == 0 ? PJ_SUCCESS : e - PJ_ERRNO_START_SYS)
#else
# define PJ_STATUS_TO_OS(e) (e == 0 ? PJ_SUCCESS : PJ_ERRNO_START_SYS - e)
#endif
获取和设置系统操作码,对于Linux来说,就是errno变量,在os_error_unix.c
PJ_DEF(pj_status_t) pj_get_os_error(void)
{
return PJ_STATUS_FROM_OS(errno);
}
PJ_DEF(void) pj_set_os_error(pj_status_t code)
{
errno = PJ_STATUS_TO_OS(code);
}
PJ_DEF(pj_status_t) pj_get_netos_error(void)
{
return PJ_STATUS_FROM_OS(errno);
}
PJ_DEF(void) pj_set_netos_error(pj_status_t code)
{
errno = PJ_STATUS_TO_OS(code);
}
错误码转错误字符串
错误码提示的信息过于单调,需要转成更容易分析的字符串,通过pj_strerror可以把操作码转成字符串。操作码在转字符串的过程中,分为3类,如果是pjlib区间的错误码,则使用pjlib自己实现的映射表pjlib_error,如果是操作系统相关的错误码platform_strerror,则调用系统相关的函数,如果是应用扩展的错误码,则需要先注册pj_register_strerror回调函数,应用在回调函数构造错误码对应的字符串。
PJ_DEF(pj_str_t) pj_strerror( pj_status_t statcode,
char *buf, pj_size_t bufsize )
{
int len = -1;
pj_str_t errstr;
pj_assert(buf && bufsize);
if (statcode == PJ_SUCCESS) {
len = pj_ansi_snprintf( buf, bufsize, "Success");
} else if (statcode < PJ_ERRNO_START + PJ_ERRNO_SPACE_SIZE) {
len = pj_ansi_snprintf( buf, bufsize, "Unknown error %d", statcode);
} else if (statcode < PJ_ERRNO_START_STATUS + PJ_ERRNO_SPACE_SIZE) {
len = pjlib_error(statcode, buf, bufsize);
} else if (statcode < PJ_ERRNO_START_SYS + PJ_ERRNO_SPACE_SIZE) {
len = platform_strerror(PJ_STATUS_TO_OS(statcode), buf, bufsize);
} else {
unsigned i;
/* Find user handler to get the error message. */
for (i=0; i<err_msg_hnd_cnt; ++i) {
if (IN_RANGE(statcode, err_msg_hnd[i].begin, err_msg_hnd[i].end)) {
return (*err_msg_hnd[i].strerror)(statcode, buf, bufsize);
}
}
...
}
pjlib_error就是查找全局数组映射,platform_strerror在os_error_unix.c,其本质调用strerror,应用扩展,需先注册到数组
/* Error message handler. */
static unsigned err_msg_hnd_cnt;
static struct err_msg_hnd
{
pj_status_t begin;
pj_status_t end;
pj_str_t (*strerror)(pj_status_t, char*, pj_size_t);
} err_msg_hnd[PJLIB_MAX_ERR_MSG_HANDLER];
错误输出到日志
错误最终要保存到日志,可以用宏PJ_PERROR或函数pj_perror,前者可以在编译链接的时候就优化,比如你的日志等级只有1,则比较低的等级在编译时为空,而使用pj_perror则会进行函数调用,但是函数内部实现为空。相比之下,宏PJ_PERROR更高效,看一下代码注释的描述。
* - #PJ_PERROR() macro: use this macro similar to PJ_LOG to format
* an error message and display them to the log
*
* - #pj_perror(): this function is similar to PJ_PERROR() but unlike
* #PJ_PERROR(), this function will always be included in the
* link process. Due to this reason, prefer to use #PJ_PERROR()
* if the application is concerned about the executable size.
*
不管是宏PJ_PERROR还是函数pj_perror,最终都调用pj_perror_imp,里面先调用pj_strerror把错误码转成字符串,再调用invoke_log输出到日志,最终调用的是pj_log。
static void invoke_log(const char *sender, int level, const char *format, ...)
{
va_list arg;
va_start(arg, format);
pj_log(sender, level, format, arg);
va_end(arg);
}
static void pj_perror_imp(int log_level, const char *sender,
pj_status_t status,
const char *title_fmt, va_list marker)
{
char titlebuf[PJ_PERROR_TITLE_BUF_SIZE];
char errmsg[PJ_ERR_MSG_SIZE];
int len;
/* Build the title */
len = pj_ansi_vsnprintf(titlebuf, sizeof(titlebuf), title_fmt, marker);
if (len < 0 || len >= (int)sizeof(titlebuf))
pj_ansi_strcpy(titlebuf, "Error");
/* Get the error */
pj_strerror(status, errmsg, sizeof(errmsg));
/* Send to log */
invoke_log(sender, log_level, "%s: %s", titlebuf, errmsg);
}