使用 Pantheios C API 为 C 程序添加日志记录






4.89/5 (12投票s)
一篇关于如何从 C 编译单元使用 Pantheios 日志记录 API 库的教程,以及对 C 和 C++ API 所提供功能的比较。
引言
除了提供 C++ API 外,Pantheios
日志记录 API 库还提供了一个 C API,用于在 C 编译单元中进行日志记录。本文提供了一个关于如何使用 C API 为 C 程序添加日志记录的快速教程,并对 C 和 C++ API 之间的一些差异进行了对比。本教程不会涵盖配置和链接 Pantheios 等问题;读者最好先参考 关于构建和使用 Pantheios 的入门文章。
1: 初始化
使用 Pantheios
C API 首先需要通过 pantheios_init()
和 pantheios_uninit()
函数进行显式初始化。对于每次返回值 >= 0 的 pantheios_init()
调用,都必须调用一次 pantheios_uninit()
。负返回值表示初始化失败,在这种情况下,唯一可以调用的函数是 pantheios_getInitErrorString()
。下面的示例显示了这两种代码路径。
#include <pantheios/pantheios.h>
#include <stdio.h>
#include <stdlib.h>
extern const char PANTHEIOS_FE_PROCESS_IDENTITY[] = "pantheios-C";
int main(int argc, char** argv)
{
int panres = pantheios_init();
if(panres < 0)
{
fprintf(stderr, "Failed to initialise the Pantheios libraries: %s\n",
pantheios_getInitErrorString(panres));
return EXIT_FAILURE;
}
else
{
/* . . . rest of program */
pantheios_uninit();
return EXIT_SUCCESS;
}
}
显式初始化的要求与 C++ API 不同,后者在包含 pantheios/pantheios.hpp 时会自动进行初始化。
(如果您的链接单元包含一个或多个包含 pantheios/pantheios.hpp 的 C++ 编译单元,并且您的链接单元不是 DLL,并且您没有定义 PANTHEIOS_NO_AUTO_INIT
,那么您可以在主 C 源文件中省略显式初始化。但是,最好还是这样做:这在使用 C 语言的 Pantheios
时是惯用的,而且您以后可能会删除或用 C 重写 C++ 编译单元,然后发现您的程序神秘地无法运行,甚至无法告知原因!)
2: 使用 C API
Pantheios C API 基于 printf()
函数族。这对 API 的语法、健壮性以及通用性和可扩展性都有影响。
API 中的主要日志记录函数是 pantheios_logprintf()
。(名称较长是为了避免 C 全局命名空间中的任何名称冲突,因为很可能存在其他 logprintf()
函数。)pan_sev_t
是一个 typedef,定义了一个 32 位有符号整数。
PANTHEIOS_CALL(int) pantheios_logprintf(pan_sev_t severity
, char const* format
, ...);
在语法上,格式字符串和参数的指定与 printf()
完全相同,例如:
int i = 10;
double d = 9.9;
pantheios_logprintf(PANTHEIOS_SEV_NOTICE, "i=%d, d=%G", i, d);
唯一的区别是 pantheios_logprintf()
将严重级别作为其第一个参数,并且不需要在格式字符串中指定回车符('\n'
)。
Pantheios
C API 中还有另外两个函数:pantheios_logvprintf()
和 pantheios_logputs()
。
PANTHEIOS_CALL(int) pantheios_logvprintf( pan_sev_t severity
, char const* format
, va_list args);
PANTHEIOS_CALL(void) pantheios_logputs( pan_sev_t severity
, char const* message);
前者像 vprintf()
一样,接收一个参数数组。后者是 puts()
的日志记录类似物,它直接通过 Pantheios
核心将单个 C 风格 字符串
处理并输出到 后端。因此,当程序出现意外行为时,建议使用它,作为在终止前写入日志的一种“尽力而为”的尝试。(注意:与在这种情况下使用的任何函数一样,不保证成功。)
3: 参数类型
C API 的 printf()
类语法的第二个后果是其对 printf()
可理解的类型的限制:整数、浮点类型和 C 风格 字符串
。这与 Pantheios
C++ API 形成鲜明对比,后者开箱即用就能理解大量类型,并且是无限可扩展的。
这也意味着 C API 不是类型安全的。将整数传递给 pantheios_logprintf()
而它期望的是 C 风格 字符串
,与 printf()
一样,同样可能导致进程崩溃。同样,这与 C++ API 形成对比,后者是 100% 类型安全的。
4: 日志记录自定义类型
与 C++ API 不同,C API 不提供记录自定义类型的任何帮助。考虑以下示例,其中在函数进入时记录一个 IPv4 地址参数:
int connect_to_peer(struct in_addr const* addr)
{
pantheios_logprintf(PANTHEIOS_SEV_DEBUG
, "connect_to_peer(%u.%u.%u.%u)"
, (NULL == addr) ? 0 : ((addr->s_addr & 0x000000ff) >> 0)
, (NULL == addr) ? 0 : ((addr->s_addr & 0x0000ff00) >> 8)
, (NULL == addr) ? 0 : ((addr->s_addr & 0x00ff0000) >> 16)
, (NULL == addr) ? 0 : ((addr->s_addr & 0xff000000) >> 24));
. . .
这涉及大量繁琐的样板代码,并且必须在每次必须记录 in_addr
实例的地方(小心地)重复。与 C++ API 相比,后者理解 in_addr
类型以及许多其他类型,并且可以轻松扩展以处理您希望的任何类型。
int connect_to_peer(struct in_addr const* addr)
{
pantheios::log_DEBUG("connect_to_peer(", addr, ")");
. . .
对于 C 语言来说,一种提供更高健壮性和应用程序代码透明度的替代方法是使用辅助转换器函数,如下所示:
char const* convert_addr(char* buff, size_t cchBuff, struct in_addr const* addr);
int connect_to_peer(struct in_addr const* addr)
{
char buff[16]; /* space enough for IPv4 */
pantheios_logprintf(PANTHEIOS_SEV_DEBUG
, "connect_to_peer(%s)"
, convert_addr(&buff[0], STLSOFT_NUM_ELEMENTS(buff), addr));
. . .
缺点是,无论是否启用了调试级别的日志记录,都会进行转换。对于之前的显式形式,在检查严重级别后才进行转换。
可下载的项目包含了这两种方法的实现,以及主程序,以说明它们之间的区别。
摘要
我们已经了解了如何使用 Pantheios
C API,如何初始化它(包括报告初始化错误),如何记录基本类型,以及如何记录自定义类型。
我们已经看到,在使用 C API 记录自定义类型时,您被迫在效率、重用性和表达能力之间做出妥协。使用 C++ API 则无需这种妥协——它 100% 类型安全,并且仅在需要时执行转换。当然,Pantheios
团队的建议是,如果您可以使用 C++ API(在 C++ 编译单元中),则优先使用它;当您不能使用时,C API 提供了 Pantheios
的许多优点,但并非全部。
这是使用 Pantheios
配合 be.WindowsConsole
后端(带或不带回调功能)的简要介绍。
Pantheios
的世界还有很多内容,在未来的文章中,我将解释更多功能,并涵盖最佳实践,并讨论 Pantheios
如何提供 100% 的类型安全性以及 无与伦比的性能。
欢迎在 SourceForge 上的 Pantheios 项目站点 的论坛上发布您的问题。
历史
- 2008年6月20日:初始版本