log4qt学习笔记
一、为什么使用log4qt?
Log4Qt 是大名鼎鼎的Apache Log4J 的Qt移植版,此日志框架的优势主要有:
- 通过修改配置文件(.conf),可以实现热更新log信息输出的目的地——控制台、文件等,可以定义每一条日志信息的级别,已决定消息日志的类型。
- 可以创建多个logger对象,以管理不同类型的日志输出。
- 统一的日志框架,线程安全,便于软件调试,特别是涉及到网络通信的部分。
- 闲谈:无论何时,能够规范高效的输出软件日志,是一名程序员必备素养,为维护代码的人考虑考虑,这个人很可能就是你自己。
二、获得log4qt
从github下载源码zip包,或使用git工具同步到本地,或fork项目到自己的github仓库,在导入到国内码云平台,可享受高速下载。
或者直接下载已经编译好的二进制库(使用vs2015编译,可供vs2015以上的编译器使用):
从我的天翼云盘分享下载。我的百度云盘分享(提取码:dq4a)下载。
如果你需要编译log4qt的源码,请参考网上其他教程,源码提供CMakeLists.txt,可以用cmake编译。若需要增加编译教程,请小伙伴们在评论区留言,我将考虑增加此部分。
三、使用log4qt
典型的输出到文件的log配置文件格式如下:
log4j.logger.HpcLog=DEBUG,HpcLog,File
log4j.appender.HpcLog=org.apache.log4j.FileAppender
log4j.appender.HpcLog=org.apache.log4j.RollingFileAppender
log4j.appender.HpcLog.File=logs/comm/hpc/hpclog.log
log4j.appender.HpcLog.MaxFileSize=6MB
log4j.appender.HpcLog.AppendFile=false
log4j.appender.HpcLog.layout=org.apache.log4j.PatternLayout
log4j.appender.HpcLog.layout.ConversionPattern=[%c][%p][%t] %d{yyyy-MM-dd_HH:mm:ss} - %m%n
典型的输出到控制台的log配置文件格式如下:
log4j.logger.SoverLog=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%c][%p][%t] %d{yyyy-MM-dd_HH:mm:ss} - %m%n
详细描述:
log4j.logger.ServerLog=DEBUG,ServerLog
"ServerLog"是logger对象的对象名称(必须保持一致),例如,定义一个与HPC(超级计算机)进行通信生成日志的logger对象:
Log4Qt::Logger *hpcCommLogger = Log4Qt::LogManager::logger("HpcLog"); //需要传入logger的名称参数
“DEBUG”是设定日志记录的最低级别,可设的值(由高到低)有OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL。例如你设置的值为INFO,那么DEBUG值将不会被输出。
log4j.appender.HpcLog=org.apache.log4j.FileAppender
“FileAppender”是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开(或者分两行写)。可设的值有:
ConsoleAppender:控制台
FileAppender:文件
DailyRollingFileAppender:每天产生一个日志文件
RollingFileAppender:文件大小到达指定尺寸的时候产生一个新的文件
WriterAppender:将日志信息以流格式发送到任意指定的地方
log4j.appender.HpcLog.File=logs/comm/hpc/hpclog.log
指定文件的日志文件的保存位置,一般在程序主进程exe路径下。不支持“./”格式的相对路径。
log4j.appender.HpcLog.MaxFileSize=6MB
指定文件的最大内存。后缀可以是KB, MB 或者GB。在日志文件到达该大小时,将会自动滚动,即将原来的内容移到“之前的文件名.1”的文件中。
log4j.appender.HpcLog.AppendFile=false
增加日志内容到指定的文件中,false则将新内容覆盖指定的文件内容,默认值是true。
log4j.appender.HpcLog.layout=org.apache.log4j.PatternLayout
“PatternLayout”配置日志的输出格式,可设的值有:
HTMLLayout:以HTML表格形式布局
PatternLayout:可以灵活地指定布局模式
SimpleLayout:包含日志信息的级别和信息字符串
TTCCLayout:包含日志产生的时间、线程、类别等等信息
log4j.appender.Console.layout.ConversionPattern=[%c][%p][%t] %d{yyyy-MM-dd_HH:mm:ss} - %m%n
配置日志输出格式,格式化符号包括(没有log4j的多):
符号 | 含义 |
---|---|
%p | 输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATA |
%d | 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS} |
%t | 输出产生该日志事件的线程名 |
%c | logger对象名称 |
%m | 输出代码中指定的具体日志信息 |
%n | 输出一个回车换行符 |
遗憾的是,log4qt不支持通过格式化输出代码所在的函数名和代码行数等关键调试信息。
我将分享一个可用的代码,以输出上述信息,期盼各路高手评点。
四、你喜欢的代码
日志操作被封装在一个头文件中。
#pragma once
#include <string>
#include <QString>
#include <QMutex>
#include <QDir>
#include <QApplication>
//log4qt
#include <log4qt/logmanager.h>
#include <log4qt/propertyconfigurator.h>
namespace SelfLogger {
#define g_SELF_LOGGER_App Logger::instance()
#define HPC_LOG_INFO g_SELF_LOGGER_App .setSrcPos(__FILE__,__FUNCTION__,__LINE__)->hpcCommInfoLog
#define HPC_LOG_DEBUG g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->hpcCommDebugLog
#define HPC_LOG_ERROR g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->hpcCommErrorLog
#define SERVER_LOG_INFO g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->serverCommInfoLog
#define SERVER_LOG_DEBUG g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->serverCommDebugLog
#define SERVER_LOG_ERROR g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->serverCommErrorLog
#define LOG_INFO g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->appInfoLog
#define LOG_DEBUG g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->appDebugLog
#define LOG_ERROR g_SELF_LOGGER_App.setSrcPos(__FILE__,__FUNCTION__,__LINE__)->appErrorLog
class Logger
{
public:
struct SLogSrcPoc
{
QString mFunctionName;
QString mLineRow;
QString mFileName;
SLogSrcPoc() :
mFunctionName(""),
mLineRow(""),
mFileName("")
{}
};
static Logger& instance()
{
static QMutex mutex;
static QScopedPointer<Logger> m_instance;
if (Q_UNLIKELY(m_instance.data() == nullptr))
{
mutex.lock();
if (m_instance.data() == nullptr)
{
m_instance.reset(new Logger());
}
mutex.unlock();
}
return *m_instance;
}
~Logger() {};
Logger* setSrcPos(const QString& fileName, const QString& funcName, int lineRow)
{
m_lock.lock();
m_sLogSrcPoc.mFileName = fileName;
m_sLogSrcPoc.mLineRow = QString::number(lineRow);
m_sLogSrcPoc.mFunctionName = funcName;
if (!m_isAlreadyConfig)
{
#ifdef _DEBUG
configureLogger(QDir::currentPath() + "/appLog4qt.conf");
#else
configureLogger(QApplication::applicationDirPath() + "/appLog4qt.conf");
#endif // DEBUG
m_isAlreadyConfig = true;
}
m_lock.unlock();
return this;
}
void serverCommInfoLog(const QString &msg)
{
m_serverCommLog->info(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void serverCommErrorLog(const QString &msg)
{
m_serverCommLog->error(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void serverCommDebugLog(const QString &msg)
{
m_serverCommLog->debug(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void hpcCommInfoLog(const QString& msg)
{
m_HpcCommLogger->info(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void hpcCommErrorLog(const QString& msg)
{
m_HpcCommLogger->error(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void hpcCommDebugLog(const QString& msg)
{
m_HpcCommLogger->debug(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void appDebugLog(const QString& msg)
{
m_appLogger->debug(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void appInfoLog(const QString& msg)
{
m_appLogger->info(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
void appErrorLog(const QString& msg)
{
m_appLogger->error(QStringLiteral("%1 ->(%2->%3->%4)")
.arg(msg)
.arg(m_sLogSrcPoc.mFileName)
.arg(m_sLogSrcPoc.mFunctionName)
.arg(m_sLogSrcPoc.mLineRow)
);
}
private:
bool configureLogger(const QString& confFilePath)
{
return Log4Qt::PropertyConfigurator::configure(confFilePath);
}
private:
Logger() {
m_serverCommLog = Log4Qt::LogManager::logger("ServerLog");
m_appLogger = Log4Qt::LogManager::logger("DebugLog");
m_HpcCommLogger = Log4Qt::LogManager::logger("HpcLog");
};
SLogSrcPoc m_sLogSrcPoc;
Log4Qt::Logger *m_serverCommLog; //与本地进程通信日志
Log4Qt::Logger *m_appLogger; //主程序日志
Log4Qt::Logger *m_HpcCommLogger; //与hpc通信日志
QMutex m_lock;
bool m_isAlreadyConfig = false;
};
}
使用说明:
- 先确保log输出的文件夹存在,不存在则创建,一般在main函数中进行检查。
- 在需要打印日志的地方引入上述头文件,用定义好的宏打印日志内容。
例如:
LOG_DEBUG("Hello China!")
LOG_INFO("Hello World!")
LOG_ERROR(QStringLiteral("你好,中国!"))