遇到的坑
最后,本以为顺顺利利,结果竟出现了恼人的链接错误,大致内容为:
main.cpp.obj : error LNK2019: unresolved external symbol "public: __thiscall log4cplus::PatternLayout::PatternLayout(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (??0PatternLayout@log4cplus@@QAE@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "int __cdecl test_log4plus(void)" (?test_log4plus@@YAHXZ) main.cpp.obj : error LNK2019: unresolved external symbol "public: static class log4cplus::Logger __cdecl log4cplus::Logger::getInstance(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &)" (?getInstance@Logger@log4cplus@@SA?AV12@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@Z) referenced in function "int __cdecl test_log4plus(void)" (?test_log4plus@@YAHXZ) main.cpp.obj : error LNK2019: unresolved external symbol "class std::basic_ostringstream<char,struct std::char_traits<char>,class std::allocator<char> > & __cdecl log4cplus::detail::get_macro_body_oss(void)" (?get_macro_body_oss@detail@log4cplus@@YAAAV?$basic_ostringstream@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@XZ) referenced in function "int __cdecl test_log4plus(void)" (?test_log4plus@@YAHXZ) main.cpp.obj : error LNK2019: unresolved external symbol "void __cdecl log4cplus::detail::macro_forced_log(class log4cplus::Logger const &,int,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > const &,char const *,int,char const *)" (?macro_forced_log@detail@log4cplus@@YAXABVLogger@2@HABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@PBDH2@Z) referenced in function "int __cdecl test_log4plus(void)" (?test_log4plus@@YAHXZ) ..\build\Release\lib\mywincom.exe : fatal error LNK1120: 4 unresolved externals
猜测原因难道是调用约定不一致?还是字符集问题?
关于调用约定
microsoft的vc默认的是__cdecl方式,而windows API则是__stdcall,如果用vc开发dll给其他语言用,则应该指定__stdcall方式。
1.__cdecl
所谓的C调用规则。按从右至左的顺序压参数入栈,由调用者把参数弹出栈。切记:对于传送参数的内存栈是由调用者来维护的。返回值在EAX中因此,对于象printf这样变参数的函数必须用这种规则。编译器在编译的时候对这种调用规则的函数生成修饰名的饿时候,仅在输出函数名前加上一个下划线前缀,格式为_functionname。
2.__stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,切记:函数自己在退出时清空堆栈,返回值在EAX中。 __stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。如函数int func(int a, double b)的修饰名是_func@12。
如何解决?
在编译库时首选要明确和统一调用方式。若是stdcall则在cmake中可尝试使用以下参数:
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--add-stdcall-alias")
经验证非此问题,那么最终原因最可能是字符集问题了。难怪编译的库都带个U,说明库本身是Unicode字符集的,那么代码里必须也得使用Unicode字符集。仅代码文件格式为utf-8行吗?
答案是不行的,需要代码文件里指定:#define UNICODE这个宏定义。猜测可能是log4plus的头文件里判断了这个宏来提供不同的接口。
字符集问题
由于我的导入库lib,以及动态库dll都是正确加载的,所以不是库没正确加载的问题,也不是release加载了debug版本的问题。
最可能是当前工程项目的“字符集”设置与log4plus所用的“字符集”不一致所导致的,log4cplus是使用的Unicode 符集,当前工程需要检查下是否是使用的Unicode字符集。这里面有个概念需要搞清楚啊,文件的编码格式为utf-8不代表使用的Unicode字符集!
- Unicode 是「字符集」
- UTF-8 是「编码规则」
最后果然成功啦,这时候激动的泪儿都要出来了,这坑有点儿深啊。
在main函数所在的代码文件的最上方定义:#define UNICODE 搞定。
注意:
#define UNICODE 必须是在文件的最上方,放在log4cplus下方不行!
还有其他的一些注意事项,网上的介绍少有提及,比如:
Linking on Windows
If you are linking your application with DLL variant of log4cplus, define LOG4CPLUS_BUILD_DLL preprocessor symbol. This changes definition of LOG4CPLUS_EXPORT symbol to __declspec(dllimport).
--with-qt5
This option is disabled by default. It enables compilation of a separate shared library (liblog4cplusqt5debugappender) that implements Qt5DebugAppender. It requires Qt5 and pkg-config to be available.
Threads and signals
log4cplus is not safe to be used from asynchronous signals' handlers. This is a property of most threaded programmes in general. If you are going to use log4cplus in threaded application and if you want to use log4cplus from signal handlers then your only option is to block signals in all threads but one that will handle all signals. On POSIX platforms, this is possible using the sigwait() call. log4cplus enables this approach by blocking all signals in any threads created through its threads helpers.
还是要好好多读以下官方的readme.md文档。
地址在这里:README.md · master · mirrors / log4cplus / log4cplus · GitCode
主进程无法退出问题
构造函数调用log4cplus::initialize(),析构函数调用log4cplus::Logger::shutdown()
log4cplus::Initializer m_initializer;//构造函数调用log4cplus::initialize(),析构函数调用log4cplus::Logger::shutdown()。
还没完:当你使用了ThreadPool之后,log4cplus总是会在main函数执行之前使用static机制初始化,并创建线程池。如果你在main函数退出的时候没有调用log4cplus::deinitialize();
则线程池中的线程不会退出。导致main函数无法退出。所以要在main函数退出之前调用这句话,如果不调用,那个log4cplus::Initializer m_initializer;只负责调用shutdown,而不负责调用clearThreadPool。只有log4cplus::deinitialize();才会既clearThreadPool又shutdown。
为何必须先调用log4cplus::initialize(),文档上有个解释如下:
还需要注意的是log4cplus::Initializer m_initializer;如果放到单独封装的单例类里的构造函数中是不行的,It will initialize the library and deinitialize it at the end of the constructor. The initializer object must live throughout the use of the library.
可以单独封装一个接口,类似如下:
void close() { log4cplus::deinitialize(); }
然后在主程序main函数return之前调用该函数。
封装使用
最后介绍下封装使用。新建两个文件,logger.h和logger.cpp.
#define UNICODE #include <log4cplus/logger.h> #include <log4cplus/layout.h> #include <log4cplus/configurator.h> #include "logger.h" Logger logger = Logger::getInstance(LOG4CPLUS_TEXT("logmain")); void initLogger(bool isDebug) { if (isDebug) { PropertyConfigurator::doConfigure(LOG4CPLUS_TEXT("./log4cplus_d.conf")); } else { PropertyConfigurator::doConfigure(LOG4CPLUS_TEXT("./log4cplus.conf")); } } void shutDown(){ log4cplus::Logger::shutdown(); } void close() { log4cplus::deinitialize(); }
#pragma once #include <log4cplus/logger.h> #include <log4cplus/loggingmacros.h> using namespace log4cplus; using namespace log4cplus::helpers; // global object extern Logger logger; // define some macros for simplicity #define LOG_TRACE(logEvent) LOG4CPLUS_TRACE(logger, logEvent) #define LOG_DEBUG(logEvent) LOG4CPLUS_DEBUG(logger, logEvent) #define LOG_DEBUG_F(...) LOG4CPLUS_DEBUG_FMT(logger, __VA_ARGS__) #define LOG_INFO(logEvent) LOG4CPLUS_INFO(logger, logEvent) #define LOG_WARN(logEvent) LOG4CPLUS_WARN(logger, logEvent) #define LOG_ERROR(logEvent) LOG4CPLUS_ERROR(logger, logEvent) #define LOG_FATAL(logEvent) LOG4CPLUS_FATAL(logger, logEvent) extern void initLogger(bool isDebug); extern void shutDown(); extern void close();
日志的配置文件log4cplus.conf如下:
##debug可以如下配置 log4cplus.logger.logmain = TRACE, console log4cplus.appender.console = log4cplus::ConsoleAppender log4cplus.appender.console.layout = log4cplus::PatternLayout log4cplus.appender.console.layout.ConversionPattern = [%D{%m/%d/%y %H:%M:%S,%q} %-5p] - %m%n ##正式生成文件的可以如下配置: log4cplus.logger.logmain = INFO, file log4cplus.appender.file = log4cplus::FileAppender log4cplus.appender.file.File = ./log/myapp.log log4cplus.appender.file.MaxFileSize = 10M log4cplus.appender.file.Append = true log4cplus.appender.file.layout = log4cplus::PatternLayout log4cplus.appender.file.layout.ConversionPattern = [%D{%m/%d/%y %H:%M:%S,%q} %-5p] - %m%n
测试
int main(){ std::cout << "hello test "<< std::endl; //test_log4plus(); initLogger(true); LOG_DEBUG("this is debug"); LOG_INFO("this is info"); LOG_TRACE("this is trace"); LOG_WARN("this is warn"); LOG_ERROR("this is error"); LOG_FATAL("this is fatal"); LOG_DEBUG("this is:" << 666); LOG_DEBUG_F(LOG4CPLUS_TEXT("this is %.2f"),5.333); shutDown(); close(); return 0; }
单例封装
#ifndef MYLOGGER_H #define MYLOGGER_H #define UNICODE #include <iostream> #include <string> #include <mutex> #include <log4cplus/helpers/loglog.h> #include <log4cplus/logger.h> #include <log4cplus/initializer.h> #include <log4cplus/log4cplus.h> #include <log4cplus/fileappender.h> #include <log4cplus/consoleappender.h> #include <log4cplus/layout.h> #include <log4cplus/tchar.h> #include <log4Cplus/configurator.h> #include <log4Cplus/loggingmacros.h> #include <log4Cplus/helpers/stringhelper.h> #define MY_LOG_FILE "log.properties" using namespace log4cplus; using namespace log4cplus::helpers; //单列模式 class MyLogger { public: static MyLogger* getInstace(); //static void deleteInstance(); Logger logger; Logger console; void shutDown(); void closeLog(); private: MyLogger(); ~MyLogger(){ std::cout << "Destory singleton for log4cplus!!" <<std::endl; } static MyLogger *m_logger; static std::mutex myMutex; private: //内部类来删除对象 class Garbo{ public: Garbo(){} ~Garbo(){ if(m_logger != NULL){ delete m_logger; m_logger = nullptr; } } }; static Garbo _garbo; }; #endif // MYLOGGER_H
#include "mylogger.h" using namespace std; MyLogger *MyLogger::m_logger = NULL; mutex MyLogger::myMutex; MyLogger::Garbo MyLogger::_garbo; MyLogger *MyLogger::getInstace() { if (NULL == m_logger) { lock_guard<mutex> mg(myMutex); if (NULL == m_logger) { m_logger = new MyLogger;//在堆上建立 } } return m_logger; } MyLogger::MyLogger() { log4cplus::initialize(); PropertyConfigurator::doConfigure(LOG4CPLUS_TEXT(MY_LOG_FILE)); logger = Logger::getRoot(); console = Logger::getInstance(LOG4CPLUS_TEXT("logConsole")); cout << "Create Singleton for log4cplus!" << endl; } void MyLogger::shutDown() { log4cplus::Logger::shutdown(); } void MyLogger::closeLog() { log4cplus::deinitialize(); }
# ALL TRACE DEBUG INFO WARN ERROR FATAL OFF log4cplus.rootLogger = TRACE,SA log4cplus.logger.logConsole = TRACE,LC #For database stuff, I don't need to logging everything, it's enough printing only errors! #log4cplus.logger.DatabaseOperations=ERROR #log4cplus.additivity.file=false log4cplus.appender.LC=log4cplus::ConsoleAppender log4cplus.appender.LC.EnCoding=utf-8 log4cplus.appender.LC.layout=log4cplus::PatternLayout log4cplus.appender.LC.layout.ConversionPattern=[%D{%Y-%m-%d %H:%M:%S}] %m [%l]%n #设置日志追加到文件尾 log4cplus.appender.SA=log4cplus::TimeBasedRollingFileAppender log4cplus.appender.SA.File = Mylogger.log # 必须要先手动创建好log目录;否则无法创建文件 log4cplus.appender.SA.FilenamePattern= ./log/%d{yyyy-MM-dd_HH-mm}.log log4cplus.appender.SA.Schedule = MINUTELY log4cplus.appender.SA.CreateDirs = true log4cplus.appender.SA.MaxHistory = 9999 log4cplus.appender.SA.RollOnClose = false #设置日志文件大小 log4cplus.appender.SA.MaxFileSize = 100MB #设置生成日志最大个数 log4cplus.appender.SA.MaxBackupIndex = 100 log4cplus.appender.SA.Append = true log4cplus.appender.SA.layout=log4cplus::PatternLayout log4cplus.appender.SA.layout.ConversionPattern=[%-5p][%D{%m/%d/%y %H:%M:%S:%Q}] [%t] %c - %m [%l]%n #设置日志级别范围 log4cplus.appender.SA.filters.1=log4cplus::spi::LogLevelRangeFilter log4cplus.appender.SA.filters.1.LogLevelMin=DEBUG log4cplus.appender.SA.filters.1.LogLevelMax=FATAL log4cplus.appender.SA.filters.1.AcceptOnMatch=true log4cplus.appender.SA.filters.2=log4cplus::spi::DenyAllFilter
用于QT 项目
在qt项目中使用,把log4cplus的头文件拷入项目中,比如我的是根目录的includes文件夹。
在项目的.pro文件中,增加头文件的包含和链接库的脚本。
QT += core gui concurrent sql printsupport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 DESTDIR = $$PWD/./bin TARGET = log4cplus_test LOG4CPLUSPATH = $$PWD/includes INCLUDEPATH += -L $$LOG4CPLUSPATH \ SOURCES += \ main.cpp \ mainwindow.cpp \ logger.cpp HEADERS += \ mainwindow.h \ logger.h # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target LIBS += -L$$PWD/./bin -llog4cplusU
关于字符集,qt默认就是unicode字符集。为了保险起见,我在.pro文件中又设置了字符集如下:DEFINES += UNICODE _UNICODE
最后的最后,顺利的在qt项目中跑了起来。
引用
C++第三方日志库log4cplus的安装与使用超详解_wendy_ya的博客-CSDN博客_log4cplus
C++第三方日志库log4cplus基本语法介绍_wendy_ya的博客-CSDN博客_c++ log4cplus
C/C++ 开源日志库log4cplus的编译和使用_much0726的博客-CSDN博客_log4cplus编译
Windows C++ log4cplus编译开发配置详细步骤_make_it_simple888的博客-CSDN博客
CMake 编译 Log4cplus - 阿Hai - 博客园
Qt中第三方日志库log4cplus的基本配置和使用详解_wendy_ya的博客-CSDN博客_log4cplus配置文件
log4cplus 使用方法 配置_lx_shudong的博客-CSDN博客
Visual Studio 2019 C++使用log4cplus_mb6054360584d89的技术博客_51CTO博客
error LNK2019 【unresolved external symbol】 解决方法_lcyw的博客-CSDN博客
【CMake】构建和链接静态库和动态库 - 禅元天道 - 博客园
_cdecl与_stdcall - freden - 博客园
VS2019 中使用log4cplus,报错link2019处理_Sen-Lee的博客-CSDN博客
【log4】c++日志工具之——log4cpp - bandaoyu - 博客园
log4cplus导致主进程不能退出问题解决_荆楚闲人的博客-CSDN博客
log4cplus 使用方法 配置_lx_shudong的博客-CSDN博客
MFC log4cplus日志库的简单例子_Genven_Liang的博客-CSDN博客
我个人的log4cplus-1.2.1的编译与测试使用,CentOS环境 - 掘金
关于Log4Ccpp的引入使用,不是基础介绍,是项目中使用的方式 - 码农教程
QT 5.15.2 配置log4cplus-2.0.7_Bigfish_k的博客-CSDN博客_cmake log4cplus
c++ - How to use log4cplus in a custom singleton class - Stack Overflow