小優(yōu)智能科技有限公司成立于2015年底,是一家專注于高精度3D機(jī)器視覺(jué)模組研發(fā)、生產(chǎn)及銷售的高科技企業(yè)。
公司自主研發(fā)的3D機(jī)器視覺(jué)模組采用激光/DLP白光編碼光柵結(jié)構(gòu)光+雙工業(yè)相機(jī)方案,還原物體三維信息,廣泛應(yīng)用于消費(fèi)電子領(lǐng)域、工業(yè)領(lǐng)域和安防領(lǐng)域,具有精度高、速度快、成本低的優(yōu)勢(shì)。
基于Qt開發(fā)的日志庫(kù)QsLog的使用
概述
C++下的日志庫(kù)有很多,如log4cpp、Easylogging++等,Qt下也有l(wèi)og4qt。
這里介紹QsLog,它是一個(gè)基于Qt的輕量級(jí)開源日志庫(kù)。
git地址:https://github.com/victronenergy/QsLog
源代碼結(jié)構(gòu)如下圖:
特征:
六個(gè)日志級(jí)別(從跟蹤到致命)
運(yùn)行時(shí)可配置的日志級(jí)別閾值。
關(guān)閉日志記錄時(shí)的最小開銷。
支持多個(gè)目標(biāo),附帶文件和調(diào)試目標(biāo)。
線程安全
支持現(xiàn)成的常見Qt類型的日志記錄。
小依賴:直接把它放到你的項(xiàng)目中。
一、QsLog使用方式
1. 源碼集成
在你的工程中,直接包含QsLog.pri文件,進(jìn)行源碼集成。
當(dāng)然你也可以包含QsLog.pri后,編譯為xx.dll,在應(yīng)用工程中去調(diào)用xx.dll。
2. 動(dòng)態(tài)庫(kù)集成
編譯QsLogSharedLibrary.pro,生成動(dòng)態(tài)鏈接庫(kù)QsLog2.dll,在你的工程中進(jìn)行調(diào)用。
二、日志級(jí)別
支持六個(gè)日志級(jí)別,優(yōu)先級(jí)從低到高依次為:Trace、Debug、Info、Warn、Error、Fatal、Off。如下:
enum Level
{
TraceLevel = 0,
DebugLevel,
InfoLevel,
WarnLevel,
ErrorLevel,
FatalLevel,
OffLevel
};
Trace:跟蹤,最低等級(jí)的,用于打開所有日志記錄。
Debug:調(diào)試,打印一些細(xì)粒度調(diào)試運(yùn)行信息。
Info:信息,打印粗粒度信息,突出強(qiáng)調(diào)程序的運(yùn)行過(guò)程。打印一些感興趣或者重要的信息,可用于環(huán)境中輸出程序運(yùn)行的一些重要信息,但不能濫用,避免打印過(guò)多日志。
Warn:警告,表明會(huì)出現(xiàn)潛在錯(cuò)誤的情形,有些信息不是錯(cuò)誤信息,但是也要給程序員的一些提示。
Error:錯(cuò)誤,指出雖然發(fā)生錯(cuò)誤,但仍然不影響系統(tǒng)的繼續(xù)運(yùn)行。打印錯(cuò)誤和異常信息。
Fatal:致命的,發(fā)生嚴(yán)重錯(cuò)誤,將導(dǎo)致應(yīng)用程序的退出。這個(gè)級(jí)別比較高,程序直接停止運(yùn)行了。
Off:最高等級(jí)的,用于關(guān)閉所有日志記錄。
可以通過(guò)setLoggingLevel()設(shè)置記錄日志的級(jí)別。
void setLoggingLevel(Level newLevel)
一般我們可以將日志級(jí)別保存到配置文件,以便程序發(fā)布后,可通過(guò)修改配置來(lái)改變記錄日志級(jí)別。
三、日志輸出目的地
QsLog的使用很簡(jiǎn)單,在我們自己的工程中直接include它的QsLog.pri文件,然后源文件中包含QsLog.h就可以使用了。日志打印輸出目的地可以有4種,分別是:
1. 輸出到文件;
2. 輸出到控制臺(tái)stdout;
3. 輸出到函數(shù);
4. 輸出到QObject。
并且可以添加任意多個(gè)目的地址,比如輸出到文件的同時(shí),還要輸出到控制臺(tái)進(jìn)行顯示,以方便查看調(diào)試打印信息。
1. 輸出到文件(支持文件分割)
// 測(cè)試文件為目的地
void test_output_file()
{
// 初始化日志機(jī)制
Logger& logger = Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel);
// 添加文件為目的地
const QString sLogPath(QDir(QApplication::applicationDirPath()).filePath("log.txt"));
DestinationPtr fileDestination(DestinationFactory::MakeFileDestination(
sLogPath, EnableLogRotation, MaxSizeBytes(512*1024), MaxOldLogCount(5)));
logger.addDestination(fileDestination);
// 打印日志
QLOG_TRACE() << "1-trace msg";
QLOG_DEBUG() << "2-debug msg";
QLOG_INFO() << "3-info msg";
QLOG_WARN() << "4-warn msg";
QLOG_ERROR() << "5-error msg";
QLOG_FATAL() << "6-fatal msg";
QsLogging::Logger::destroyInstance();
}
代碼很簡(jiǎn)單,setLoggingLevel()設(shè)置記錄的日志級(jí)別;
目前為TraceLevel,則日志級(jí)別比TraceLevel高的都會(huì)輸出到文件;
若為ErrorLevel,則只有"5-error msg"和"6-fatal msg"這2條會(huì)輸出。
然后設(shè)置文件名和輸出目的地。其中目的地是由DestinationFactory::MakeFileDestination()函數(shù)進(jìn)行構(gòu)造的,其原型為:
static DestinationPtr MakeFileDestination(
const QString& filePath,
LogRotationOption rotation = DisableLogRotation,
const MaxSizeBytes &sizeInBytesToRotateAfter = MaxSizeBytes(),
const MaxOldLogCount &oldLogsToKeep = MaxOldLogCount());
函數(shù)參數(shù)含義:
filePath: 日志文件名
rotation: 取值DisableLogRotation和EnableLogRotation,
前者表示禁止日志文件分割,即日志始終往一個(gè)文件中寫入。
后者表示啟用日志文件分割,此時(shí)sizeInBytesToRotateAfter和oldLogsToKeep參數(shù)才有意義。
sizeInBytesToRotateAfter: 每個(gè)日志文件的字節(jié)數(shù)大小限制,即到達(dá)此大小后,自動(dòng)新建文件,在新文件中進(jìn)行寫入。
oldLogsToKeep: 舊日志文件保留(備份)個(gè)數(shù),超過(guò)此數(shù)量,自動(dòng)刪除最久遠(yuǎn)文件,備份文件最多支持10個(gè)。
若為2,則如下三個(gè)文件中內(nèi)容,按照時(shí)間先后順序排列為:log.txt.2->log.txt.1->log.txt,在log.txt中為最新日志,log.txt.1為次新,log.txt.2為最久遠(yuǎn)日志。
若此時(shí)log.txt超過(guò)sizeInBytesToRotateAfter限制,則會(huì)發(fā)生log.txt.2被刪除,log.txt.1被改名log.txt.2,log.txt被改名log.txt.1,新建log.txt。
運(yùn)行效果
2. 輸出到控制臺(tái)stdout
// 測(cè)試stdout為目的地
void test_output_stdout()
{
// 初始化日志機(jī)制
Logger& logger = Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel);
// 添加stdout為目的地
DestinationPtr debugDestination(DestinationFactory::MakeDebugOutputDestination());
logger.addDestination(debugDestination);
// 打印日志
QLOG_TRACE() << "1-trace msg";
QLOG_DEBUG() << "2-debug msg";
QLOG_INFO() << "3-info msg";
QLOG_WARN() << "4-warn msg";
QLOG_ERROR() << "5-error msg";
QLOG_FATAL() << "6-fatal msg";
QsLogging::Logger::destroyInstance();
}
運(yùn)行效果:
3. 輸出到處理函數(shù)
void logFunction(const QString &message, QsLogging::Level level)
{
qDebug()<<"From log function: "<<qPrintable(message)<<" " << static_cast<int>(level);
}
// 測(cè)試函數(shù)為目的地
void test_output_function()
{
// 初始化日志機(jī)制
Logger& logger = Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel);
// 添加函數(shù)為目的地
DestinationPtr functorDestination(DestinationFactory::MakeFunctorDestination(&logFunction));
logger.addDestination(functorDestination);
// 打印日志
QLOG_TRACE() << "1-trace msg";
QLOG_DEBUG() << "2-debug msg";
QLOG_INFO() << "3-info msg";
QLOG_WARN() << "4-warn msg";
QLOG_ERROR() << "5-error msg";
QLOG_FATAL() << "6-fatal msg";
QsLogging::Logger::destroyInstance();
}
輸出到函數(shù),該函數(shù)需要定義為如下類型:
typedef void (*LogFunction)(const QString &message, Level level);
運(yùn)行效果:
4. 輸出到QTextEdit控件
除了上面的輸出方式,還可以輸出到一個(gè)QObject對(duì)象上,主要是通過(guò)信號(hào)槽機(jī)制,將打印日志發(fā)送到QObject的槽函數(shù)進(jìn)行處理。
void MainWindow::writeLog(const QString &message, int level)
{
ui->textEdit->append(message + " " + QString::number(level));
}
// 測(cè)試QObject為目的地
void test_output_qobject(MainWindow* window)
{
// 初始化日志機(jī)制
Logger& logger = Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel);
// 添加QObject為目的地
DestinationPtr objectDestination(DestinationFactory::MakeFunctorDestination(window, SLOT(writeLog(QString,int))));
logger.addDestination(objectDestination);
// 打印日志
QLOG_TRACE() << "1-trace msg";
QLOG_DEBUG() << "2-debug msg";
QLOG_INFO() << "3-info msg";
QLOG_WARN() << "4-warn msg";
QLOG_ERROR() << "5-error msg";
QLOG_FATAL() << "6-fatal msg";
QsLogging::Logger::destroyInstance();
}
輸出到QObject時(shí),需要定義其槽函數(shù),為如下類型:
void xxxx(const QString &message, int level)
寫本例子時(shí),發(fā)現(xiàn)TRACE信息不能輸出到QObject,是因?yàn)镼sLogDestFunctor.cpp文件中,write函數(shù)有個(gè)bug,如下:
void QsLogging::FunctorDestination::write(const QString &message, QsLogging::Level level)
{
if (mLogFunction)
mLogFunction(message, level);
if (level > QsLogging::TraceLevel)
emit logMessageReady(message, static_cast<int>(level));
}
應(yīng)將>改為>=,修改后即可解決,如下:
void QsLogging::FunctorDestination::write(const QString &message, QsLogging::Level level)
{
if (mLogFunction)
mLogFunction(message, level);
if (level >= QsLogging::TraceLevel)
emit logMessageReady(message, static_cast<int>(level));
}
運(yùn)行效果:
四、打印源文件名稱和行號(hào)
在QsLog.pri文件中
DEFINES += QS_LOG_LINE_NUMBERS
打開此宏定義,重新編譯,即可打印帶源文件名稱和行號(hào)的日志。如下:
運(yùn)行效果
五、禁止日志記錄
禁用日志記錄,有時(shí)候關(guān)閉日志記錄是有必要的。可以通過(guò)3種方式實(shí)現(xiàn):
全局地,在編譯時(shí),通過(guò)在QsLog.pri文件,打開DEFINES += QS_LOG_DISABLE宏定義。
全局地,在運(yùn)行時(shí),通過(guò)將日志級(jí)別設(shè)置為關(guān)閉,即setLoggingLevel(QsLogging::OffLevel)。
在編譯時(shí),通過(guò)在目標(biāo)文件中包含QsLogDisableForThisFile.h,為每個(gè)文件創(chuàng)建一個(gè)。
六、線程安全
使用日志宏進(jìn)行打印日志是線程安全的,日志宏如下:
QLOG_TRACE() << "1-trace msg";
QLOG_DEBUG() << "2-debug msg";
QLOG_INFO() << "3-info msg";
QLOG_WARN() << "4-warn msg";
QLOG_ERROR() << "5-error msg";
QLOG_FATAL() << "6-fatal msg";
這在前面我們已經(jīng)使用過(guò)了。
如setLoggingLevel()、addDestination()函數(shù)不是線程安全的。
七、日志的異步打印
所謂的異步打印,其實(shí)就是單獨(dú)開一個(gè)線程來(lái)專門寫日志。
在QsLog.pri文件中
DEFINES += QS_LOG_SEPARATE_THREAD
打開此宏定義,重新編譯,日志內(nèi)容就會(huì)在單獨(dú)的線程中排隊(duì)并寫入。