介绍
log4cxx 是一个基于 Apache log4j 的 C++ 日志框架,使用 Apache Portable Runtime(APR),在所有支持 APR 的平台都可以使用。log4cxx 是开源代码,遵守 Apache License 开源协议。
log4cxx 可以根据配置文件灵活配置日志的输出格式、输出地址、日志等级、网络配置等相关参数;
还可以给日志划分模块,并给各个模块起名字,以方便使用;
log4cxx 支持 普通日志文件、按照文件大小轮转的日志、按照日期轮转的日志、syslog日志、控制终端、smtp、socket、数据流多种方式记录日志。
最新发行版说明文档地址:https://logging.apache.org/log4cxx/1.3.1/index.html
但是,log4cxx 不能直接在 C语言 中使用,需要单独开发一个支持 C语言 的接口。本次示例包括 C语言 接口的开发和测试。
编译安装
log4cxx 代码地址:https://github.com/apache/logging-log4cxx
从代码库拉取最新的 tag 代码:git clone --branch v1.3.1-RC1 https://github.com/apache/logging-log4cxx.git

生成Makefile文件:mkdir build && cd build && cmake .. -DCMAKE_BUILD_TYPE=Release -DWITH_SOCK=ON -DLOG4CXX_ENABLE_ESMTP=ON -DLOG4CXX_ENABLE_THREADSAFE=ON

编译安装:make clean && make -j 8 && make install

动态库 和 头文件 默认安装在 /usr/local/ 目录下。首次安装后,已经打开的终端可能找不到对应的动态库,最好执行以下 ldconfig 命令,更新当前终端的链接缓存。

编写C语言接口
此处为了开发 liblog4c.so 库,给 C语言 编程提供接口。包括两个文件:log4c.cpp是对 log4cxx 的封装;log4c.h 是被调用的头文件。
这里的思路是这样的:
- 目的是生成 .so 库 和 .h 头文件,提供 链接文件 和 include 文件 给 C语言代码 使用;
- 设置一个全局变量 LoggerPtr _logger,初始化为 NULL;
- 对此全局变量只初始化一次,以此实现单例操作;
- 全局变量 _logger 只在 log4c 库内部使用,不对外显示其存在;
- C语言 调用 C++ 的库只能以函数的方式【C++不可能以宏函数的方式给C提供接口,即使提供宏接口,那么宏内部的函数一定全部都是extern “C”修饰后编译的】,所以这里必须把 C++ 的接口(例如:LOG4CXX_ERROR等)编程成函数对外提供,在 C语言 链接阶段就可以用到了。
使用 全局变量 _logger 单例的实现接口,有好处也有弊端。好处是:比较容易使用,也好理解;弊端是:在一个进程中只能实现一个 _logger 对象,失去了 log4cxx 原来的灵活性。
不过这个也可以很容易修改,如果需要多个 _logger 对象,只需要修改下面的代码,生成多个全局变量就可以。另外,还需要对每一个 _logger 对象编写对应的 日志接口函数(指的是下方代码中的宏和函数,例如:LOGGER_ERROR、logger_error等)。
两个文件内容如下:
log4c.cpp
#include <sys/prctl.h>
#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/logmanager.h>
#include <cstdarg>
#include "log4c.h"
using namespace log4cxx;
LoggerPtr _logger = NULL ;
void int_logger (const char * log_conf_file_path, const char * name) {
if ( _logger != NULL ) {
LOGGER_WARN("logger inited, do not init again!") ;
return ;
}
if (PropertyConfigurator::configure(log_conf_file_path) == spi::ConfigurationStatus::NotConfigured)
BasicConfigurator::configure();
_logger = LogManager::getLogger(name);
}
#define ARGS2MESSAGE(file, lineno, func, fmt) \
char buffer[10240]; \
memset(buffer, 0x00, sizeof(buffer) ) ; \
do { \
char tname[24] = {0}; \
memset(tname, 0x00, sizeof(tname)); \
prctl(PR_GET_NAME, tname, 0, 0, 0); \
snprintf(buffer, sizeof(buffer), "[%s:%lu] [%s:(%ld):%s] ", tname, pthread_self(), file, lineno, func); \
va_list args; \
va_start(args, fmt); \
vsnprintf(buffer+strlen(buffer), sizeof(buffer)-strlen(buffer), fmt, args); \
va_end(args); \
} while(0) \
void logger_debug(const char* file, long lineno, const char* func, const char * fmt, ...) {
ARGS2MESSAGE(file, lineno, func, fmt) ;
LOG4CXX_DEBUG(_logger, buffer);
}
void logger_info(const char* file, long lineno, const char* func, const char * fmt, ...) {
ARGS2MESSAGE(file, lineno, func, fmt) ;
LOG4CXX_INFO(_logger, buffer) ;
}
void logger_warn(const char* file, long lineno, const char* func, const char * fmt, ...) {
ARGS2MESSAGE(file, lineno, func, fmt) ;
LOG4CXX_WARN(_logger, buffer) ;
}
void c(const char* file, long lineno, const char* func, const char * fmt, ...) {
ARGS2MESSAGE(file, lineno, func, fmt) ;
LOG4CXX_ERROR(_logger, buffer) ;
}
void logger_fatal(const char* file, long lineno, const char* func, const char * fmt, ...) {
ARGS2MESSAGE(file, lineno, func, fmt) ;
LOG4CXX_FATAL(_logger, buffer) ;
}
void logger_trace(const char* file, long lineno, const char* func, const char * fmt, ...) {
ARGS2MESSAGE(file, lineno, func, fmt) ;
LOG4CXX_TRACE(_logger, buffer ) ;
}
log4c.h
#ifndef __LOG4C_H__
#define __LOG4C_H__
#ifdef __cplusplus
extern "C" {
#endif
void int_logger (const char * log_conf_file_path, const char * name) ;
void logger_debug(const char* file, long lineno, const char* func, const char * fmt, ...) ;
void logger_info(const char* file, long lineno, const char* func, const char * fmt, ...) ;
void logger_warn(const char* file, long lineno, const char* func, const char * fmt, ...) ;
void logger_error(const char* file, long lineno, const char* func, const char * fmt, ...) ;
void logger_fatal(const char* file, long lineno, const char* func, const char * fmt, ...) ;
void logger_trace(const char* file, long lineno, const char* func, const char * fmt, ...) ;
#define LOGGER_DEBUG(fmt, ...) logger_debug( __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__ )
#define LOGGER_INFO(fmt, ...) logger_info( __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__ )
#define LOGGER_WARN(fmt, ...) logger_warn( __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__ )
#define LOGGER_ERROR(fmt, ...) logger_error( __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__ )
#define LOGGER_FATAL(fmt, ...) logger_fatal( __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__ )
#define LOGGER_TRACE(fmt, ...) logger_trace( __FILE__, __LINE__, __FUNCTION__, fmt, ##__VA_ARGS__ )
#ifdef __cplusplus
} // extern "C"
#endif
#endif
测试代码和配置
这里提供了两种日志书写方式:日志文件轮转、SMTP。打印到终端输的方式,在配置文件 log.properties 中提供了相关配置 CONSOLE 配置模块,因为太简单了没写代码。
配置文件 log.properties
# 定义根 logger
log4j.rootLogger=TRACE, CONSOLE, file, smtp
# 定义 CONSOLE 命名的 logger
log4j.logger.CONSOLE=DEBUG, CONSOLE
# 设置 CONSOLE 的 additivity 为 false,防止它继承父 rootLogger 的 Appenders
log4j.additivity.CONSOLE=false
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
# 定义 file 命名的 logger
log4j.logger.file=TRACE, file
# 设置 file 的 additivity 为 false,防止它继承父 rootLogger 的 Appenders
log4j.additivity.file =false
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=file.log
log4j.appender.file.MaxFileSize=2MB
log4j.appender.file.MaxBackupIndex=3
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c %x - %m%n
# 定义 smtp 命名的 logger
log4j.logger.smtp=TRACE, smtp
# 设置 smtp 的 additivity 为 false,防止它继承父 rootLogger 的 Appenders
log4j.additivity.smtp =false
log4j.appender.smtp=org.apache.log4j.net.SMTPAppender
log4j.appender.smtp.layout=org.apache.log4j.PatternLayout
log4j.appender.smtp.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c %x - %m%n
log4j.appender.smtp.BufferSize=5
log4j.appender.smtp.SMTPPort=25
log4j.appender.smtp.From=xxx@xxx.com
log4j.appender.smtp.To=zzzz@zzz.com
log4j.appender.smtp.Subject=log4c.smtp.log
log4j.appender.smtp.Threshold=INFO
log4j.appender.smtp.SMTPDebug=false
log4j.appender.smtp.SMTPHost=mx00.zzz.com
## log4j.appender.SMTP.SMTPSSL=true
## log4j.appender.smtp.SMTPUsername=xxx@xxx.com
## log4j.appender.smtp.SMTPPassword=YYYYUUUUIIII
SMTP配置自己做了一些脱敏处理,可查看参数含义自行修改。
测试文件日志:test_file.c
#include <stdio.h>
#include "log4c.h"
int main() {
int_logger("log.properties", "file") ;
LOGGER_DEBUG("NNNNNNNNNNNNNNNNNNNNNNNNN") ;
LOGGER_ERROR("EEEEEEEEEEEEEEEEEEEEEEEEEEEE") ;
LOGGER_TRACE("TTTTTTTTTTTTTTTTT") ;
}
测试smtp日志:test_smtp.c
#include <stdio.h>
#include "log4c.h"
int main() {
int_logger("log.properties", "smtp") ;
LOGGER_TRACE("TTTTTTTTTTTTTTTTT") ;
LOGGER_DEBUG("DDDDDDDDDDDDDDDDDDDDDDDDDD") ;
LOGGER_INFO("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW7") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW6") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW5") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW4") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW3") ;
LOGGER_TRACE("TTTTTTTTTTTTTTTTT") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW2") ;
LOGGER_DEBUG("DDDDDDDDDDDDDDDDDDDDDDDDDD") ;
LOGGER_INFO("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII") ;
LOGGER_WARN("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW1") ;
LOGGER_ERROR("EEEEEEEEEEEEEEEEEEEEEEEEEEEE") ;
LOGGER_ERROR("EEEEEEEEEEEEEEEEEEEEEEEEEEEE") ;
LOGGER_TRACE("TTTTTTTTTTTTTTTTT") ;
LOGGER_DEBUG("DDDDDDDDDDDDDDDDDDDDDDDDDD") ;
LOGGER_INFO("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII") ;
LOGGER_INFO("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII") ;
LOGGER_ERROR("EEEEEEEEEEEEEEEEEEEEEEEEEEEE") ;
LOGGER_ERROR("EEEEEEEEEEEEEEEEEEEEEEEEEEEE") ;
LOGGER_TRACE("TTTTTTTTTTTTTTTTT") ;
LOGGER_DEBUG("DDDDDDDDDDDDDDDDDDDDDDDDDD") ;
LOGGER_INFO("IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII") ;
LOGGER_FATAL("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") ;
}
说明:SMTP方式写日志支持两种方式:登录账号写邮件(自己作为MUA) 和 直接推送(自己作为MTA)。
登录账号写邮件。举例说明:通过 登录 aaa@aaa.com 账号,然后用 aaa@aaa.com 账号给 bbb@bbb.com 发送日志。这里需要增加两个参数配置 log4j.appender.smtp.SMTPUsername
和 log4j.appender.smtp.SMTPPassword
;并且 log4j.appender.smtp.SMTPHost
配置的是 aaa@aaa.com 账号的登录地址。
直接推送。举例说明:使用 SMTP 协议向 bbb@bbb.com 邮箱账号中直接推送邮件。不需要登录 另外的 aaa@aaa.com 账号就可以。这里 log4j.appender.smtp.SMTPHost
配置的是 bbb.com 域名 MX记录 解析后的地址。另外对于公网邮箱可能会做一些基本的垃圾邮件拦截策略,所以需要做一些基本的 DNS 设置,至少设置自己的 公网IP 为一个合法的 MX 记录值。
关于DNS相关知识,参看往期文章:https://www.madbull.site/?p=1719
获取自己的外网IP,参看往期文章:https://www.madbull.site/?p=763
关于邮件投递和接收的相关支持,参看往期文章:https://www.madbull.site/?p=769
编译测试
Makefile文件
.PHONY: all
.PHONY: install
.PHONY: clean
all: liblog4c.so test_file test_smtp
liblog4c.so: log4c.cpp
g++ -shared -fPIC -o $@ $^ -llog4cxx
install: liblog4c.so
\cp -f liblog4c.so /usr/local/lib/
\cp -f log4c.h /usr/local/include/
test_file: test_file.c install
gcc -o $@ $< -llog4cxx -llog4c
test_smtp: test_smtp.c install
gcc -o $@ $< -llog4cxx -llog4c
clean:
\rm -f test_file.o test_file test_smtp.o test_smtp liblog4c.o liblog4c.so file.log
编译安装
首次安装后,已经打开的终端可能找不到对应的动态库,最好执行以下 ldconfig 命令,更新当前终端的链接缓存。

测试文件日志

测试SMTP日志


发表回复