3

I need to use log4cxx for a C++ project. However I fail to understand the basic setup of this library. Here is my minimal attempt:

$ cat logger.cpp 
#include <log4cxx/logger.h>
#include <log4cxx/propertyconfigurator.h>
#include <log4cxx/helpers/properties.h>

static log4cxx::LoggerPtr ptr;

int main()
{
  log4cxx::helpers::Properties prop;
  prop.setProperty("log4j.rootLogger","DEBUG, A1");
  prop.setProperty("log4j.appender.A1","org.apache.log4j.ConsoleAppender");
  prop.setProperty("log4j.appender.A1.layout","org.apache.log4j.PatternLayout");
  prop.setProperty("log4j.appender.A1.layout.ConversionPattern","%d{ABSOLUTE} %-5p [%c] %m%n");
  log4cxx::PropertyConfigurator::configure(prop);

  ptr = log4cxx::Logger::getLogger("API");
  LOG4CXX_INFO(ptr , "test_info");
  return 0;
}

I then compile it using:

$ g++ -g -o logger logger.cpp -llog4cxx

It fails with:

$ gdb ./logger
[...]
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff5f69dc9 in apr_pool_create_ex () from /lib64/libapr-1.so.0
Missing separate debuginfos, use: debuginfo-install apr-1.5.1-3.fc21.x86_64 apr-util-1.5.4-1.fc21.x86_64 cyrus-sasl-lib-2.1.26-19.fc21.x86_64 expat-2.1.0-10.fc21.x86_64 libdb-5.3.28-9.fc21.x86_64 libgcc-4.9.2-6.fc21.x86_64 libstdc++-4.9.2-6.fc21.x86_64 libuuid-2.25.2-3.fc21.x86_64 log4cxx-0.10.0-17.fc21.x86_64 nspr-4.10.8-1.fc21.x86_64 nss-3.19.1-1.0.fc21.x86_64 nss-softokn-freebl-3.19.1-1.0.fc21.x86_64 nss-util-3.19.1-1.0.fc21.x86_64 openldap-2.4.40-3.fc21.x86_64 zlib-1.2.8-7.fc21.x86_64
(gdb) bt
#0  0x00007ffff5f69dc9 in apr_pool_create_ex () from /lib64/libapr-1.so.0
#1  0x00007ffff7b26b58 in log4cxx::helpers::Pool::Pool() () from /lib64/liblog4cxx.so.10
#2  0x00007ffff7ae06ea in log4cxx::helpers::MutexException::formatMessage(int) () from /lib64/liblog4cxx.so.10
#3  0x00007ffff7ae0786 in log4cxx::helpers::MutexException::MutexException(int) () from /lib64/liblog4cxx.so.10
#4  0x00007ffff7b4a310 in log4cxx::helpers::synchronized::synchronized(log4cxx::helpers::Mutex const&) () from /lib64/liblog4cxx.so.10
#5  0x00007ffff7b5d9c8 in log4cxx::WriterAppender::close() () from /lib64/liblog4cxx.so.10
#6  0x00007ffff7ac979c in log4cxx::ConsoleAppender::~ConsoleAppender() () from /lib64/liblog4cxx.so.10
#7  0x00007ffff7ac98b9 in log4cxx::ConsoleAppender::~ConsoleAppender() () from /lib64/liblog4cxx.so.10
#8  0x00007ffff7aba247 in log4cxx::helpers::AppenderAttachableImpl::~AppenderAttachableImpl() () from /lib64/liblog4cxx.so.10
#9  0x00007ffff7b0494c in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#10 0x00007ffff7b388b4 in log4cxx::spi::RootLogger::~RootLogger() () from /lib64/liblog4cxx.so.10
#11 0x00007ffff7b0429a in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#12 0x00007ffff7b04429 in log4cxx::Logger::~Logger() () from /lib64/liblog4cxx.so.10
#13 0x0000000000401d6e in log4cxx::helpers::ObjectPtrT<log4cxx::Logger>::~ObjectPtrT (this=0x6031a0 <ptr>, __in_chrg=<optimized out>) at /usr/include/log4cxx/helpers/objectptr.h:100
#14 0x00007ffff6e38392 in __run_exit_handlers () from /lib64/libc.so.6
#15 0x00007ffff6e383e5 in exit () from /lib64/libc.so.6
#16 0x00007ffff6e1efe7 in __libc_start_main () from /lib64/libc.so.6
#17 0x0000000000401599 in _start ()

What is so fundamentally wrong to using a global variable for the logger ? It does make sense to me to have a singleton pattern here.

System is: Fedora 21 with:

$ rpm -qv log4cxx
log4cxx-0.10.0-17.fc21.x86_64

and:

$ g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.9.2/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.9.2-20150212/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 4.9.2 20150212 (Red Hat 4.9.2-6) (GCC) 
4

1 回答 1

1

更小的尝试将以完全相同的方式崩溃:

#include <log4cxx/logger.h>
#include <log4cxx/basicconfigurator.h>

static log4cxx::LoggerPtr ptr;

int main()
{
  log4cxx::BasicConfigurator::configure();

  ptr = log4cxx::Logger::getLogger("API");
  return 0;
}

这种愚蠢崩溃的最终原因是根深蒂固的,与 log4cxx 中的基本设计缺陷有关(从错误的尝试和哲学开始,使 C++ 代码“看起来、感觉和工作都像 Java”)。

最接近的原因是全局 LoggerPtr 持有的 Logger 的析构函数ptr调用得太晚了。到那时,APR 库(log4cxx 代码库所依赖的)将被解除,因此synchronized::synchronized构造函数调用(堆栈跟踪中的#4)必然会失败 - 之后某种崩溃变得不可避免。为什么代码必须跳过这样的障碍来释放资源本身就是一个传奇,不值得在这里讨论。

因此,问题与静态反初始化的顺序有关:APR 库“过早”被解除,因为它实际上是通过静态单例初始化的,为时已晚(在示例代码中,当 LoggerPtr 实际上是给定一个记录器来保存)。

示例代码可以通过在退出之前添加此语句来“修复” main()(例如,在 之前return 0;):

ptr = 0;

这将具有在 APR 库仍然“活动”时调用复杂资源释放序列的效果。

更通用的“解决方案”将涉及适当控制 APR 库的生命周期。log4cxx 代码库有散布在各处的静态 Meyers 单例,包括一个用于包装对 APR 库的调用apr_initialize()apr_terminate()用于 APR 库的调用,但是众所周知的“静态初始化顺序惨败”使得很难将该特定单例安排为“最旧的”。(它必须从每个其他单例的构造函数中调用。)因此,实际的“答案”是通过额外的调用来保持 APR 库永远活着,apr_initialize()例如,早期main()-与匹配平衡apr_terminate(),并在程序终止时泄漏该特定资源。

请注意,该错误可以通过其他方式触发。而且,事实上,这个错误成为后续版本的阻碍,这可能是整个 log4cxx 项目在十年前基本上死掉的原因。

于 2019-11-11T16:19:41.930 回答