引言一日志实现方法
代码实现
LogHandlercpp 二崩溃处理
代码实现
LogHandlercpp 小结参考
引言
项目中需求一日志模块,主要实现两大功能:1.自动打印信息至日志文件;2.软件意外退出时保留信息以便跟踪问题。 本文结合了 Qt 自定义日志工具 和 让程序在崩溃时体面的退出之CallStack 提供的方法,补充实现了文章中未具体给出的管理日志文件大小和数量的功能。
环境:vs2012+Qt5.2(注:Qt5.5之后引入qInfo(),影响不大)
一、日志实现方法
基本原理是使用 qInstallMessageHandler()接管qDebug(), qWarning()等调试信息,然后将信息流存储至本地日志文件,管理日志文件。 代码在原作者基础上做了部分调整: 1.更改日志存储名称格式,用QDateTime取代QDate,以避免当日记录多条日志时的覆盖问题; 2.增加日志文件个数的判断; 3.增加日志文件大小的检测; 4.屏蔽根据修改日期保存日志机制,以免在不同日期开启软件后冲掉以前的有用log,仅凭文件大小另存log文件,然后控制文件数量。
代码实现
LogHandler.cpp
#include "LogHandler.h"
#include <stdio.h>
#include <stdlib.h>
#include <QDebug>
#include <QDateTime>
#include <QMutexLocker>
#include <QtGlobal>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTimer>
#include <QTextStream>
#include <iostream>
#define LOGLIMIT_NUM 5
#define LOGLIMIT_SIZE 500
struct LogHandlerPrivate {
LogHandlerPrivate();
~LogHandlerPrivate();
void openAndBackupLogFile();
static void messageHandler(QtMsgType type,
const QMessageLogContext &context,
const QString &msg);
void makeSureLogDirectory()
const;
void checkLogFiles();
QDir logDir;
QTimer renameLogFileTimer;
QTimer flushLogFileTimer;
QDateTime logFileCreatedDate;
static QFile *logFile;
static QTextStream *logOut;
static QMutex logMutex;
};
QMutex LogHandlerPrivate::logMutex;
QFile* LogHandlerPrivate::logFile = NULL;
QTextStream* LogHandlerPrivate::logOut = NULL;
LogHandlerPrivate::LogHandlerPrivate() {
logDir.setPath(
"Log");
QString logPath = logDir.absoluteFilePath(
"protocal.log");
logFileCreatedDate = QFileInfo(logPath).lastModified();
openAndBackupLogFile();
renameLogFileTimer.setInterval(
1000 *
60 *
5);
renameLogFileTimer.start();
QObject::connect(&renameLogFileTimer, &QTimer::timeout, [
this] {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
openAndBackupLogFile();
});
flushLogFileTimer.setInterval(
1000);
flushLogFileTimer.start();
QObject::connect(&flushLogFileTimer, &QTimer::timeout, [
this] {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
checkLogFiles();
});
}
LogHandlerPrivate::~LogHandlerPrivate() {
if (NULL != logFile) {
logFile->flush();
logFile->close();
delete logOut;
delete logFile;
logOut = NULL;
logFile = NULL;
}
}
void LogHandlerPrivate::openAndBackupLogFile() {
makeSureLogDirectory();
QString logPath = logDir.absoluteFilePath(
"protocal.log");
if (NULL == logFile) {
logFile =
new QFile(logPath);
logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ?
new QTextStream(logFile) : NULL;
if (NULL != logOut) {
logOut->setCodec(
"UTF-8");
}
if (logFileCreatedDate.isNull()) {
logFileCreatedDate = QDateTime::currentDateTime();
}
}
logDir.setFilter(QDir::Files);
logDir.setNameFilters(QStringList() <<
"*.log");
QFileInfoList logFiles = logDir.entryInfoList();
for (
int i =
0; i < logFiles.length() - LOGLIMIT_NUM; ++i)
QFile::remove(logFiles[i].absoluteFilePath());
}
void LogHandlerPrivate::makeSureLogDirectory()
const {
if (!logDir.exists()) {
logDir.mkpath(
".");
}
}
void LogHandlerPrivate::checkLogFiles() {
if (logFile->size() >
1024*LOGLIMIT_SIZE) {
logFile->flush();
logFile->close();
delete logOut;
delete logFile;
QString logPath = logDir.absoluteFilePath(
"protocal.log");
QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString(
"yyyy-MM-dd_hhmmss.log"));;
QFile::copy(logPath, newLogPath);
QFile::remove(logPath);
logFile =
new QFile(logPath);
logOut = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?
new QTextStream(logFile) : NULL;
logFileCreatedDate = QDateTime::currentDateTime();
if (NULL != logOut) {
logOut->setCodec(
"UTF-8");
}
}
}
void LogHandlerPrivate::messageHandler(QtMsgType type,
const QMessageLogContext &context,
const QString &msg) {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
QString level;
switch (type) {
case QtDebugMsg:
level =
"Debug";
break;
case QtWarningMsg:
level =
"Warning";
break;
case QtCriticalMsg:
level =
"Error";
break;
case QtFatalMsg:
level =
"Fatal";
break;
default:;
}
QByteArray localMsg = msg.toLocal8Bit();
if (NULL == LogHandlerPrivate::logOut) {
return;
}
QString fileName = context.file;
int index = fileName.lastIndexOf(QDir::separator());
fileName = fileName.mid(index +
1);
(*LogHandlerPrivate::logOut) << QString(
"%1 - [%2] (%3:%4): %5\n")
.arg(QDateTime::currentDateTime().toString(
"yyyy-MM-dd hh:mm:ss")).arg(level)
.arg(fileName).arg(context.line).arg(msg);
logOut->flush();
}
LogHandler::LogHandler() : d(NULL) {
}
LogHandler::~LogHandler() {
}
void LogHandler::installMessageHandler() {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
if (NULL == d) {
d =
new LogHandlerPrivate();
qInstallMessageHandler(LogHandlerPrivate::messageHandler);
}
}
void LogHandler::release() {
QMutexLocker locker(&LogHandlerPrivate::logMutex);
qInstallMessageHandler(
0);
delete d;
d = NULL;
}
二、崩溃处理
让程序在崩溃时体面的退出之总结博主在系列文章中做了详尽的说明。 我的应用目的是在程序崩溃时能体面退出,然后记录基本的CallStack信息到日志文件,所以只用到了前面两部分内容。在上文的基础上,用qCritical()或其他方法输出Crash信息和CallStack信息即可。
代码实现
LogHandler.cpp
LONG ApplicationCrashHandler(EXCEPTION_POINTERS *pException){
QDir DumpDir;
DumpDir.setPath(
"Log");
LPCWSTR DumpPath = (
const wchar_t*) DumpDir.absoluteFilePath(
"ProtocolTester.dmp").utf16();
CreateDumpFile(DumpPath, pException);
#ifdef _M_IX86
if (pException->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW)
{
static char TempStack[
1024 *
128];
__asm mov eax,offset TempStack[
1024 *
128];
__asm mov esp,eax;
}
#endif
CrashInfo crashinfo = GetCrashInfo(pException->ExceptionRecord);
qCritical() <<
"ErrorCode: " << crashinfo.ErrorCode << endl;
qCritical() <<
"Address: " << crashinfo.Address << endl;
qCritical() <<
"Flags: " << crashinfo.Flags << endl;
vector<CallStackInfo> arrCallStackInfo = GetCallStack(pException->ContextRecord);
qCritical() <<
"CallStack: " << endl;
for (
vector<CallStackInfo>::iterator i = arrCallStackInfo.begin(); i != arrCallStackInfo.end(); ++i)
{
CallStackInfo callstackinfo = (*i);
qCritical() << callstackinfo.MethodName <<
"() : [" << callstackinfo.ModuleName <<
"] (File: " << callstackinfo.FileName <<
" @Line " << callstackinfo.LineNumber <<
")" << endl;
}
EXCEPTION_RECORD* record = pException->ExceptionRecord;
QString errCode(QString::number(record->ExceptionCode,
16)),errAdr(QString::number((uint)record->ExceptionAddress,
16)),errMod;
QMessageBox::critical(NULL,QStringLiteral(
"Error"),QStringLiteral(
"<FONT size=4><div><b>很抱歉,程序出错了。</b><br/></div>")+
QStringLiteral(
"<div>错误代码:%1</div><div>错误地址:%2</div></FONT>").arg(errCode).arg(errAdr),
QMessageBox::Ok);
return EXCEPTION_EXECUTE_HANDLER;
}
小结
本文实现了一个轻量的Qt日志模块,功能肯定是没有log4qt或log4cxx等强大,但也基本满足了项目应用需求,想了解log4qt也可以查看DevBean豆子大神的github 最后附上相关源码:一个轻量的Qt日志模块
参考
Qt 自定义日志工具 让程序在崩溃时体面的退出之CallStack Qt 日志模块的使用 Qt5 中使用 log4qt 输出日志