第六章 zlog API
zlog 是一个用于 C 语言项目的日志库,其 API 设计是线程安全的。以下是 zlog API 的主要功能和使用方法:
6.1 初始化和结束
引入头文件
在你使用 zlog API 之前,需要先包含 zlog 的头文件:
#include "zlog.h"
函数原型
int zlog_init(const char *confpath);
int zlog_reload(const char *confpath);
void zlog_fini(void);
函数说明
zlog_init()
zlog_init()
用于初始化 zlog 库并加载配置文件。
- 参数:
confpath
是配置文件的路径。如果confpath
为NULL
,那么 zlog 会尝试从环境变量ZLOG_CONF_PATH
获取配置文件路径。如果环境变量ZLOG_CONF_PATH
也为空,那么日志将输出到标准输出(stdout)并使用内部格式。 - 返回值:初始化成功返回
0
,失败返回-1
,并且详细的错误日志会记录到由ZLOG_PROFILE_ERROR
指定的日志文件中。 - 限制:每个进程只允许第一次调用
zlog_init()
有效,后续调用将无效。
zlog_reload()
zlog_reload()
用于在运行时重新加载配置文件。
- 参数:
confpath
是新的配置文件路径。如果confpath
为NULL
,将重新加载最近一次调用zlog_init()
或zlog_reload()
使用的配置文件。 - 功能:重新计算类别规则关系,重建线程缓冲区,重置用户定义的输出函数规则。
- 返回值:重新加载成功返回
0
,失败返回-1
,并且在失败的情况下,当前内存中的配置将保持不变。因此,zlog_reload()
是一个原子操作,可以被多次调用。
zlog_fini()
zlog_fini()
用于释放所有 zlog API 的内存并关闭已经打开的文件。
- 功能:可以被多次调用,每次调用都会进行清理工作。
示例代码
以下是一个简单的示例,展示了如何使用这些 API:
#include <stdio.h>
#include "zlog.h"
int main() {
// 初始化 zlog 系统
if (zlog_init("/path/to/zlog.conf")) {
printf("zlog 初始化失败\n");
return -1;
}
// 你的应用程序代码...
// 在运行时重新加载日志配置文件
if (zlog_reload("/path/to/new/zlog.conf")) {
printf("zlog 重新加载失败\n");
}
// 应用程序结束前,释放 zlog 资源
zlog_fini();
return 0;
}
总结
- 线程安全:所有 zlog API 都是线程安全的。
- 初始化:使用
zlog_init()
初始化日志系统。 - 重新加载:使用
zlog_reload()
在运行时重新加载日志配置文件。 - 清理:使用
zlog_fini()
释放资源。
通过这些 API 函数,你可以方便地在 C 语言项目中实现灵活的日志管理。如果有更多细节需要了解,可以参考 zlog 的用户指南或相关文档。
6.2 分类操作(Category Operation)
概述
typedef struct zlog_category_s zlog_category_t;
zlog_category_t *zlog_get_category(const char *cname);
描述
zlog_get_category
是一个用于获取日志分类(category)的方法。这个方法从 zlog 的分类表(category_table)中获取一个分类,以便将来进行日志操作。如果指定的分类 cname
不存在,它将被创建。zlog 随后会根据配置中的所有规则处理这个分类。这个方法返回一个指向匹配规则的指针,该规则与 cname
对应。
规则匹配机制如下:
*
匹配所有的cname
。- 以下划线
"_"
结尾的分类字符串会匹配超级分类和子分类。例如,"aa_"
匹配cname
如"aa"
,"aa_"
,"aa_bb"
,"aa_bb_cc"
。 - 不以下划线
"_"
结尾的分类字符串严格匹配cname
。例如,"aa_bb"
仅匹配cname
为"aa_bb"
。 !
匹配没有规则匹配到的cname
。
当调用 zlog_reload()
时,每个分类的规则会自动重新计算。无需担心分类的内存释放问题,zlog_fini()
会在结束时进行清理。
返回值
- 成功时,返回
zlog_category_t
的地址。 - 失败时,返回
NULL
,并且一个详细的错误日志会记录到由ZLOG_PROFILE_ERROR
指示的日志文件中。
6.3 日志函数和宏
概述
void zlog(zlog_category_t * category,
const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, ...);
void vzlog(zlog_category_t * category,
const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, va_list args);
void hzlog(zlog_category_t * category,
const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const void *buf, size_t buflen);
描述
这三个函数是产生用户日志信息的实际日志函数,它们对应于配置文件中的 %m
条目。category
由前面描述的 zlog_get_category()
获得。
zlog()
和 vzlog()
根据类似 printf(3)
和 vprintf(3)
的格式产生输出。
vzlog()
相当于 zlog()
,只是它用 va_list
而不是可变数量的参数调用。vzlog()
调用了 va_copy
宏,调用后 args
的值不变。参见 stdarg(3)
。
hzlog()
略有不同,它的输出如下所示,是 buf
的十六进制表示形式,输出的长度是 buf_len
:
hex_buf_len=[5365]
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0000000001 23 21 20 2f 62 69 6e 2f 62 61 73 68 0a 0a 23 20 #! /bin/bash..#
0000000002 74 65 73 74 5f 68 65 78 20 2d 20 74 65 6d 70 6f test_hex - tempo
0000000003 72 61 72 79 20 77 72 61 70 70 65 72 20 73 63 72 rary wrapper scr
参数 file
和 line
通常由 __FILE__
和 __LINE__
宏填充,它们指示日志事件发生的位置。参数 func
由 __func__
或 __FUNCTION__
填充,如果编译器支持它,否则将被填充为 "<unknown>"
。
level
是一个当前级别列表中的整数,默认如下:
typedef enum {
ZLOG_LEVEL_DEBUG = 20,
ZLOG_LEVEL_INFO = 40,
ZLOG_LEVEL_NOTICE = 60,
ZLOG_LEVEL_WARN = 80,
ZLOG_LEVEL_ERROR = 100,
ZLOG_LEVEL_FATAL = 120
} zlog_level;
每个函数都有方便使用的宏。例如:
#define zlog_fatal(cat, format, args...) \
zlog(cat, __FILE__, sizeof(__FILE__)-1, \
__func__, sizeof(__func__)-1, __LINE__, \
ZLOG_LEVEL_FATAL, format, ##args)
完整的宏列表如下:
/* zlog 宏 */
#define zlog_fatal(cat, format, ...) zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_FATAL, format, ##__VA_ARGS__)
#define zlog_error(cat, format, ...) zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_ERROR, format, ##__VA_ARGS__)
#define zlog_warn(cat, format, ...) zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_WARN, format, ##__VA_ARGS__)
#define zlog_notice(cat, format, ...) zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_NOTICE, format, ##__VA_ARGS__)
#define zlog_info(cat, format, ...) zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_INFO, format, ##__VA_ARGS__)
#define zlog_debug(cat, format, ...) zlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_DEBUG, format, ##__VA_ARGS__)
/* vzlog 宏 */
#define vzlog_fatal(cat, format, args) vzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_FATAL, format, args)
#define vzlog_error(cat, format, args) vzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_ERROR, format, args)
#define vzlog_warn(cat, format, args) vzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_WARN, format, args)
#define vzlog_notice(cat, format, args) vzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_NOTICE, format, args)
#define vzlog_info(cat, format, args) vzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_INFO, format, args)
#define vzlog_debug(cat, format, args) vzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_DEBUG, format, args)
/* hzlog 宏 */
#define hzlog_fatal(cat, buf, buf_len) hzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_FATAL, buf, buf_len)
#define hzlog_error(cat, buf, buf_len) hzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_ERROR, buf, buf_len)
#define hzlog_warn(cat, buf, buf_len) hzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_WARN, buf, buf_len)
#define hzlog_notice(cat, buf, buf_len) hzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_NOTICE, buf, buf_len)
#define hzlog_info(cat, buf, buf_len) hzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_INFO, buf, buf_len)
#define hzlog_debug(cat, buf, buf_len) hzlog(cat, __FILE__, sizeof(__FILE__)-1, __func__, sizeof(__func__)-1, __LINE__, ZLOG_LEVEL_DEBUG, buf, buf_len)
返回值
这些函数不返回任何值。但是如果发生错误,则将详细的错误日志记录到由 ZLOG_PROFILE_ERROR
指示的日志文件中。
总结
这些日志函数和宏为你提供了灵活且详细的日志记录功能,通过结合使用不同的日志级别和格式,可以满足不同场景下的日志需求。在开发和调试时,可以使用更高的详细级别(如 DEBUG
),而在生产环境中,可以使用更高层次的日志级别(如 ERROR
和 FATAL
)。
6.4 MDC 操作 (MDC Operation)
概述 (SYNOPSIS)
int zlog_put_mdc(const char *key, const char *value);
char *zlog_get_mdc(const char *key);
void zlog_remove_mdc(const char *key);
void zlog_clean_mdc(void);
描述 (DESCRIPTION)
MDC(映射诊断上下文,Mapped Diagnostic Context)是一个线程的键值对映射,与分类(category)无关。
key
和value
都是字符串,每个字符串的长度都不应超过 MAXLEN_PATH
(1024)。如果输入的字符串长度超过了 MAXLEN_PATH
,输入会被截断。
需要注意的是,这个映射是绑定到线程的。因此,如果你在一个线程中设置了一个键值对,它不会影响到其他线程。
返回值 (RETURN VALUE)
zlog_put_mdc()
返回 0 表示成功,返回 -1 表示失败。zlog_get_mdc()
成功时返回指向value
的指针,失败时或键不存在时返回 NULL。- 如果发生任何错误,详细的错误日志会被记录到
ZLOG_PROFILE_ERROR
指定的日志文件中。
详细解释
在VS Code使用WSL进行C语言开发时,你可以将MDC理解为一组在特定线程中可以使用的日志上下文信息。具体的API介绍如下:
-
int zlog_put_mdc(const char *key, const char *value);
- 功能:将键值对(key-value pair)放入MDC中。
- 参数:
key
:键,字符串类型。value
:值,字符串类型。
- 返回值:成功返回0,失败返回-1。
// 示例 if (zlog_put_mdc("request_id", "12345") == 0) { // 成功放入 } else { // 处理失败情况 }
-
char *zlog_get_mdc(const char *key);
- 功能:从MDC中获取键对应的值。
- 参数:
key
:键,字符串类型。
- 返回值:成功返回值的指针,键不存在或失败时返回NULL。
// 示例 char *value = zlog_get_mdc("request_id"); if (value != NULL) { printf("Request ID: %s\n", value); } else { // 处理键不存在或错误情况 }
-
void zlog_remove_mdc(const char *key);
- 功能:从MDC中移除指定的键值对。
- 参数:
key
:键,字符串类型。
// 示例 zlog_remove_mdc("request_id");
-
void zlog_clean_mdc(void);
- 功能:清空当前线程的所有MDC键值对。
// 示例 zlog_clean_mdc();
重要注意事项
key
和value
都是字符串,长度不能超过1024字符,超出部分会被截断。- MDC是线程绑定的,即一个线程中的修改不会影响其他线程。
这样,你在编写多线程程序时可以利用MDC记录每个线程的上下文信息,从而可以在日志中区分不同线程的操作。不过要记得处理超长的字符串以及在多线程环境中正确管理这些上下文信息。
补充问题:上文中提到的MDC 是什么东西?
好的,让我们详细讲解一下MDC。
什么是MDC?
MDC(Mapped Diagnostic Context,映射诊断上下文)是一个用于在日志记录过程中关联特定上下文信息的机制。它的主要作用是为每个线程维护一个独立的键值对映射,从而方便在日志中记录和区分不同线程的上下文信息。
工作原理
MDC为每个线程维护一份独立的键值对映射,允许你在一个线程中存储、获取和删除特定的上下文信息,这些信息在记录日志时会被附加到日志条目中。例如,你可以在日志中记录请求ID、用户ID等信息,以便在日志分析时能够轻松区分来自不同请求或用户的日志条目。
主要功能
- 存储上下文信息:你可以将一些关键信息(如请求ID、用户ID等)存储在MDC中。
- 获取上下文信息:当需要记录日志时,可以从MDC中获取存储的上下文信息并添加到日志条目中。
- 删除上下文信息:执行完一项任务后,可以删除对应的上下文信息使其不影响后续任务。
- 清空上下文信息:可以一键清空当前线程的所有MDC信息。
使用举例
假设你有一个多线程的Web服务器,为了在日志中区分不同的请求,你可以在每个线程中设置请求ID。这时,MDC就很有用了。
-
设置上下文信息
if (zlog_put_mdc("request_id", "12345") == 0) { // 成功设置请求ID } else { // 处理失败情况 }
-
记录日志时获取上下文信息
char *request_id = zlog_get_mdc("request_id"); if (request_id != NULL) { printf("Logging request ID: %s\n", request_id); } else { // 没有请求ID的信息 }
-
任务结束后删除上下文信息
zlog_remove_mdc("request_id");
线程隔离
MDC 的关键特性之一是它绑定到具体的线程。这意味着在一个线程中设置的键值对对其他线程是不可见的。例如:
void thread_function() {
zlog_put_mdc("thread_id", "thread_1");
// 进行一些操作
zlog_clean_mdc();
}
int main() {
std::thread t1(thread_function);
std::thread t2(thread_function);
t1.join();
t2.join();
return 0;
}
在上面的代码中,t1
和 t2
线程各自维护自己的MDC信息,互不干扰。
优势
- 日志上下文丰富:通过MDC,可以在日志中嵌入更多的上下文信息,便于故障排查和系统监控。
- 线程隔离:每个线程独立维护自己的MDC信息,避免了线程间数据共享的问题。
总结
MDC 是一个非常有用的工具,特别是在处理复杂的多线程应用程序时。它为每个线程提供了独立的上下文环境,可以帮助开发者在日志中准确、细致地记录和区分不同上下文中的操作,从而提高系统的可维护性和可调试性。
希望这些详细的解释能帮助你更好地理解什么是MDC,以及如何在实际项目中使用它。
6.5 zlog API
说明 (DESCRIPTION)
dzlog
是一组简化的日志记录函数,这些函数省略了 zlog_category_t
类型。在内部,它使用一个默认的分类,并将该分类置于锁保护之下,因此 dzlog
也是线程安全的。省略分类意味着用户不需要创建、保存或传递 zlog_category_t
变量。然而,为了灵活性,你仍然可以通过正常的 API 获取和使用其他分类值。
我们来看一下各函数的具体作用:
dzlog_init
int dzlog_init(const char *confpath, const char *cname);
这个函数类似于 zlog_init
,但它需要一个额外的参数 cname
,用于内部默认分类的名称。dzlog_init
会初始化日志系统,并设置默认的日志分类。这个默认分类将被内部使用。
成功时返回零(0),出错时返回负一(-1),出错时详细的错误日志会记录到由 ZLOG_PROFILE_ERROR
指定的日志文件中。
dzlog_set_category
int dzlog_set_category(const char *cname);
这个函数用于更改默认分类。新的分类将替换上一个默认分类。无需担心内存释放问题,因为所有的分类分配内存会在调用 zlog_fini
时被清理干净。
成功时返回零(0),出错时返回负一(-1)。
dzlog
void dzlog(const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, ...);
这个函数用于记录日志信息,采用可变参数列表的形式。它记录日志时,会包含源文件名、函数名、行号和日志级别等信息。
vdzlog
void vdzlog(const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const char *format, va_list args);
与 dzlog
类似,但使用 va_list
类型的参数列表。这对需要传递可变参数但希望以 C 标准库的方式处理这些参数的场景非常有用。
hdzlog
void hdzlog(const char *file, size_t filelen,
const char *func, size_t funclen,
long line, int level,
const void *buf, size_t buflen);
这个函数用于记录二进制数据日志。它接受一个缓冲区及其长度,并记录这些原始的二进制数据。
宏定义 (Macros)
在 zlog.h
中定义了一些宏用于简化日志记录。这些宏将日志记录功能封装起来,使得调用更加方便。
dzlog_* 系列:
dzlog_fatal(format, ...)
dzlog_error(format, ...)
dzlog_warn(format, ...)
dzlog_notice(format, ...)
dzlog_info(format, ...)
dzlog_debug(format, ...)
它们分别对应致命错误、错误、警告、提示、信息和调试等不同级别的日志记录。
vdzlog_* 系列:
vdzlog_fatal(format, args)
vdzlog_error(format, args)
vdzlog_warn(format, args)
vdzlog_notice(format, args)
vdzlog_info(format, args)
vdzlog_debug(format, args)
与 dzlog_*
系列类似,但接受 va_list
类型的参数。
hdzlog_* 系列:
hdzlog_fatal(buf, buf_len)
hdzlog_error(buf, buf_len)
hdzlog_warn(buf, buf_len)
hdzlog_notice(buf, buf_len)
hdzlog_info(buf, buf_len)
hdzlog_debug(buf, buf_len)
用于记录二进制数据的不同级别日志。
返回值 (RETURN VALUE)
对 dzlog_init
和 dzlog_set_category
两个函数,成功时返回 0,出错时返回 -1。详细的错误日志将记录在由 ZLOG_PROFILE_ERROR
指定的日志文件中。
总结
通过 dzlog
系列 API,你可以方便地初始化日志系统,设定默认日志分类,并记录日志信息。这些函数设计简单,线程安全,适合需要高效、可靠日志记录的项目。
希望这个解释对你有所帮助。如果你有更多问题,欢迎继续讨论。
6.6 用户定义输出
原型
typedef struct zlog_msg_s {
char *buf;
size_t len;
char *path;
} zlog_msg_t;
typedef int (*zlog_record_fn)(zlog_msg_t *msg);
int zlog_set_record(const char *rname, zlog_record_fn record);
描述
zlog 允许用户定义输出函数,该输出函数与配置文件中的一种特殊规则相关联。一个典型的规则示例如下:
*.* $name, "record path %c %d"; simple
zlog_set_record()
函数用于完成这种绑定操作。带有 $rname
的规则将通过用户定义的函数 record
进行输出。回调函数的类型为 zlog_record_fn
。
struct zlog_msg_t
的成员描述如下:
path
: 该成员来源于带有$name
的规则的第二个参数,类似于文件路径,它是动态生成的。buf
和len
: 这两个成员分别表示 zlog 格式化后的日志消息及其长度。
所有 zlog_set_record()
的设置在调用 zlog_reload()
后仍然有效。
返回值
成功时,zlog_set_record()
返回 0。出错时,返回 -1,并且会记录详细的错误日志到由 ZLOG_PROFILE_ERROR
指定的日志文件中。
6.7 调试和分析
原型
void zlog_profile(void);
描述
环境变量 ZLOG_PROFILE_ERROR
指定了 zlog 的错误日志路径。
环境变量 ZLOG_PROFILE_DEBUG
指定了 zlog 的调试日志路径。
zlog_profile()
函数在运行时会将内存中的所有信息打印到 zlog 的错误日志文件中。你可以将这些信息与配置文件进行对比以找到可能的错误。