Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Crash report content optimization #294

Merged
merged 1 commit into from
Apr 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions application/configs/dconfig/org.deepin.log.viewer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@
"permissions": "readwrite",
"visibility": "private"
},
"coredumpReportMax": {
"value": 50,
"serial": 0,
"flags": ["global"],
"name": "Coredump report max",
"name[zh_CN]": "一次上报的崩溃信息最大条数",
"description": "The maximum number of crash messages reported at once",
"permissions": "readwrite",
"visibility": "private"
},
"specialComType": {
"value": -1,
"serial": 0,
Expand Down
5 changes: 5 additions & 0 deletions application/dbusproxy/dldbushandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,8 @@ qint64 DLDBusHandler::getLineCount(const QString &filePath)
{
return m_dbus->getLineCount(filePath);
}

QString DLDBusHandler::executeCmd(const QString &cmd)
{
return m_dbus->executeCmd(cmd);
}
1 change: 1 addition & 0 deletions application/dbusproxy/dldbushandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class DLDBusHandler : public QObject
bool isFileExist(const QString &filePath);
quint64 getFileSize(const QString &filePath);
qint64 getLineCount(const QString &filePath);
QString executeCmd(const QString &cmd);
QString openLogStream(const QString &filePath);
QString readLogInStream(const QString &token);
QStringList whiteListOutPaths();
Expand Down
7 changes: 7 additions & 0 deletions application/dbusproxy/dldbusinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ public Q_SLOTS: // METHODS
return asyncCallWithArgumentList(QStringLiteral("getLineCount"), argumentList);
}

inline QDBusPendingReply<QString> executeCmd(const QString &cmd)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(cmd);
return asyncCallWithArgumentList(QStringLiteral("executeCmd"), argumentList);
}

inline QDBusPendingReply<QStringList> whiteListOutPaths()
{
QList<QVariant> argumentList;
Expand Down
2 changes: 1 addition & 1 deletion application/displaycontent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3061,7 +3061,7 @@ void DisplayContent::parseListToModel(QList<LOG_MSG_COREDUMP> iList, QStandardIt
item->setAccessibleText(QString("treeview_context_%1_%2").arg(i).arg(2));
items << item;

item = new DStandardItem(iList[i].uid);
item = new DStandardItem(iList[i].userName);
item->setData(COREDUMP_TABLE_DATA);
item->setAccessibleText(QString("treeview_context_%1_%2").arg(i).arg(3));
items << item;
Expand Down
6 changes: 6 additions & 0 deletions application/gschema/com.deepin.log.viewer.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
<summary>Coredump report time</summary>
<description>It is null by default. Record the time point of the last successful crash report, used to achieve incremental escalation</description>
</key>
<key type="i" name="coredumpreportmax">
<range min="0" max="1000" />
<default>50</default>
<summary>Coredump report max</summary>
<description>It is 50 by default.The maximum number of crash messages reported at once</description>
</key>
</schema>
</schemalist>
22 changes: 22 additions & 0 deletions application/logapplicationhelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ const QString APP_LOG_CONFIG_PATH = "/usr/share/deepin-log-viewer/deepin-log.con

const QString COREDUMP_REPORT_TIME = "coredumpReportTime";
const QString COREDUMP_REPORT_TIME_GSETTING = "coredumpreporttime";
const QString COREDUMP_REPORT_MAX = "coredumpReportMax";
const QString COREDUMP_REPORT_MAX_GSETTING = "coredumpreportmax";
const QString DCONFIG_APPID = "org.deepin.log.viewer";
const QString GSETTING_APPID = "com.deepin.log.viewer";

// 默认崩溃上报最大条数
#define MAX_COREDUMP_REPORT 50

/**
* @brief LogApplicationHelper::LogApplicationHelper 构造函数,获取日志文件路径和应用名称
* @param parent 父对象
Expand Down Expand Up @@ -774,6 +780,22 @@ void LogApplicationHelper::saveLastRerportTime(const QDateTime &date)
#endif
}

int LogApplicationHelper::getMaxReportCoredump()
{
QVariant value(MAX_COREDUMP_REPORT);

#ifdef DTKCORE_CLASS_DConfigFile
value = m_pDConfig->value(COREDUMP_REPORT_MAX);
#else
if (m_pGSettings) {
time = m_pGSettings->get(COREDUMP_REPORT_MAX_GSETTING);
}
#endif

qCWarning(logAppHelper) << "coredump report max:" << value.toInt();
return value.toInt();
}

//从应用包名转换为应用显示文本
QString LogApplicationHelper::transName(const QString &str)
{
Expand Down
2 changes: 2 additions & 0 deletions application/logapplicationhelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ class LogApplicationHelper : public QObject

QDateTime getLastReportTime();
void saveLastRerportTime(const QDateTime& date);
// 获取崩溃上报最大条数,默认为50
int getMaxReportCoredump();

private:
explicit LogApplicationHelper(QObject *parent = nullptr);
Expand Down
80 changes: 7 additions & 73 deletions application/logauththread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,6 @@

// DBUS传输文件大小阈值 100MB
#define DBUS_THRESHOLD_MAX 100
// 获取窗管崩溃时,其日志最后100行
#define KWIN_LASTLINE_NUM 100
// 窗管二进制可执行文件所在路径
const QString KWAYLAND_EXE_PATH = "/usr/bin/kwin_wayland";
const QString XWAYLAND_EXE_PATH = "/usr/bin/Xwayland";

/**
* @brief LogAuthThread::LogAuthThread 构造函数
Expand Down Expand Up @@ -1219,91 +1214,30 @@
coredumpMsg.sig = tmpList[7];
}
//获取用户名
coredumpMsg.uid = Utils::getUserNamebyUID(tmpList[5].toUInt());
coredumpMsg.uid = tmpList[5];
coredumpMsg.userName = Utils::getUserNamebyUID(tmpList[5].toUInt());
coredumpMsg.coreFile = tmpList[8];
coredumpMsg.exe = tmpList[9];
QString exePath = tmpList[9];
coredumpMsg.exe = exePath;
coredumpMsg.pid = tmpList[4];

// 解析coredump文件保存位置
if (coredumpMsg.coreFile != "missing") {
// 若coreFile状态为missing,表示文件已丢失,不继续解析文件位置
QString outInfoByte;
if (Utils::runInCmd) {
outInfoByte = DLDBusHandler::instance()->readLog(QString("coredumpctl info %1").arg(coredumpMsg.pid));
} else {
m_process->start("pkexec", QStringList() << "logViewerAuth" << QStringList() << "coredumpctl-info"
<< coredumpMsg.pid <<SharedMemoryManager::instance()->getRunnableKey());
m_process->waitForFinished(-1);
outInfoByte = m_process->readAllStandardOutput();
}
outInfoByte = DLDBusHandler::instance()->readLog(QString("coredumpctl info %1").arg(coredumpMsg.pid));

// 解析第一条堆栈信息
QStringList strList = outInfoByte.split("Stack trace of thread");
if (strList.size() > 1) {
coredumpMsg.stackInfo = "Stack trace of thread" + strList[1];
}
re.indexIn(outInfoByte);

Check warning on line 1235 in application/logauththread.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Return value of function re.indexIn() is not used.
coredumpMsg.storagePath = re.cap(0).replace("Storage: ", "");

// get maps info
if (m_parseMap) {
const QString &corePath = QDir::tempPath() + QString("/%1.dump").arg(QFileInfo(coredumpMsg.storagePath).fileName());
if (Utils::runInCmd) {
DLDBusHandler::instance()->readLog(QString("coredumpctl dump %1 -o %2").arg(coredumpMsg.pid).arg(corePath));
outInfoByte = DLDBusHandler::instance()->readLog(QString("readelf -n %1").arg(corePath));
} else {
m_process->start("pkexec", QStringList() << "logViewerAuth" << QStringList() << "coredumpctl-dump"
<< coredumpMsg.pid << corePath << SharedMemoryManager::instance()->getRunnableKey());
m_process->waitForFinished(-1);
m_process->start("pkexec", QStringList() << "logViewerAuth" << QStringList() << "readelf"
<< corePath << SharedMemoryManager::instance()->getRunnableKey());
m_process->waitForFinished(-1);
outInfoByte = m_process->readAllStandardOutput();
}
coredumpMsg.maps = outInfoByte;

// 获取二进制文件信息
m_process->start("/bin/bash", QStringList() << "-c" << QString("file %1").arg(coredumpMsg.exe));
m_process->waitForFinished(-1);
outInfoByte = m_process->readAllStandardOutput();
if (!outInfoByte.isEmpty())
coredumpMsg.binaryInfo = outInfoByte;

// 获取包名
m_process->start("/bin/bash", QStringList() << "-c" << QString("dpkg -S %1").arg(coredumpMsg.exe));
m_process->waitForFinished(-1);
outInfoByte = m_process->readAllStandardOutput();
// 获取版本号
if (!outInfoByte.isEmpty()) {
QString str = outInfoByte;
m_process->start("/bin/bash", QStringList() << "-c" << QString("dpkg-query --show %1").arg(str.split(":").first()));
m_process->waitForFinished(-1);
outInfoByte = m_process->readAllStandardOutput();
if (!outInfoByte.isEmpty()) {
coredumpMsg.packgeVersion = QString(outInfoByte).simplified();
}
}
}
} else {
coredumpMsg.storagePath = QString("coredump file is missing");
}

// 若为窗管崩溃,提取窗管最后100行日志到coredump信息中
if (coredumpMsg.exe == KWAYLAND_EXE_PATH || coredumpMsg.exe == XWAYLAND_EXE_PATH) {
// 窗管日志存放在用户家目录下,因此根据崩溃信息所属用户id获取用户家目录
uint userId = tmpList[5].toUInt();
QString userHomePath = Utils::getUserHomePathByUID(userId);
if (!userHomePath.isEmpty()) {
if (coredumpMsg.exe == KWAYLAND_EXE_PATH) {
coredumpMsg.appLog = readAppLogFromLastLines(QString("%1/.kwin-old.log").arg(userHomePath), KWIN_LASTLINE_NUM);
if (!coredumpMsg.appLog.isEmpty())
qCInfo(logAuthWork) << QString("kwin crash log:\n").arg(coredumpMsg.appLog);
} else if (coredumpMsg.exe == XWAYLAND_EXE_PATH) {
coredumpMsg.appLog = readAppLogFromLastLines(QString("%1/.xwayland.log.old").arg(userHomePath), KWIN_LASTLINE_NUM);
}
} else {
qCWarning(logAuthWork) << QString("uid:%1 homepath is empty.").arg(userId);
}
}

coredumpList.append(coredumpMsg);
//每获得600个数据就发出信号给控件加载
if (coredumpList.count() % SINGLE_READ_CNT_COREDUMP == 0) {
Expand Down
115 changes: 110 additions & 5 deletions application/logbackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ Q_LOGGING_CATEGORY(logBackend, "org.deepin.log.viewer.backend")
Q_LOGGING_CATEGORY(logBackend, "org.deepin.log.viewer.backend", QtInfoMsg)
#endif

// 获取窗管崩溃时,其日志最后100行
#define KWIN_LASTLINE_NUM 100
// 窗管二进制可执行文件所在路径
const QString KWAYLAND_EXE_PATH = "/usr/bin/kwin_wayland";
const QString XWAYLAND_EXE_PATH = "/usr/bin/Xwayland";

LogBackend *LogBackend::m_staticbackend = nullptr;

LogBackend *LogBackend::instance(QObject *parent)
Expand Down Expand Up @@ -975,18 +981,57 @@ void LogBackend::slot_coredumpFinished(int index)
executeCLIExport();
} else if (Report == m_sessionType) {
int nCount = m_currentCoredumpList.size();
QDateTime lastTime = LogApplicationHelper::instance()->getLastReportTime();
QDateTime curTime = QDateTime::currentDateTime();
if (nCount == 0) {
qCWarning(logBackend) << QString("Report coredump info failed, timeRange: '%1 ---- %2', no matching data.")
.arg(LogApplicationHelper::instance()->getLastReportTime().toString("yyyy-MM-dd hh:mm:ss"))
.arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"));
.arg(lastTime.toString("yyyy-MM-dd hh:mm:ss"))
.arg(curTime.toString("yyyy-MM-dd hh:mm:ss"));
// 此处退出码不能为-1,否则systemctl --failed服务会将其判为失败的systemd服务
qApp->exit(0);
} else {
// 统计所有崩溃重复次数
QJsonObject repeatObj;
QList<LOG_REPEAT_COREDUMP_INFO> repeatInfos = Utils::countRepeatCoredumps();
for (auto i : repeatInfos) {
repeatObj.insert(i.exePath, i.times);
}

// 更新高频崩溃应用exe路径名单(更新范围为上次上报到当前时间新产生的崩溃信息),并同步到配置文件中
QList<LOG_REPEAT_COREDUMP_INFO> repeatInfoInTimeRange = Utils::countRepeatCoredumps(lastTime.toMSecsSinceEpoch(), curTime.toMSecsSinceEpoch());
Utils::updateRepeatCoredumpExePaths(repeatInfoInTimeRange);
QStringList repeatCoredumpExePaths = Utils::getRepeatCoredumpExePaths();

// 获取最大上报条数
const int nMaxCoredumpReport = LogApplicationHelper::instance()->getMaxReportCoredump();

// 数据清洗,去重
QList<LOG_MSG_COREDUMP> afterCleanData;
for (const auto &data : m_currentCoredumpList) {
if (repeatCoredumpExePaths.indexOf(data.exe) != -1) {
auto it = std::find_if(afterCleanData.begin(), afterCleanData.end(),[&](const LOG_MSG_COREDUMP &item) {
return item.exe == data.exe;
});

// 清洗时,只保留最近的一条重复数据
if (it != afterCleanData.end())
continue;
}

afterCleanData.push_back(data);

// 限制要上报的崩溃数据条数,默认最多上报50条。若nMaxCoredumpReport <= 0,则取消最大上报条数限制
if (afterCleanData.size() > nMaxCoredumpReport && nMaxCoredumpReport > 0)
break;
}

// 仅清洗后的数据,才解析崩溃详情数据
parseCoredumpDetailInfo(afterCleanData);

// 崩溃数据转json数据
QJsonArray objList;
QDateTime latestCoredumpTime;
for (auto &data : m_currentCoredumpList) {
for (auto &data : afterCleanData) {
QDateTime coredumpTime = QDateTime::fromString(data.dateTime, "yyyy-MM-dd hh:mm:ss");
if (coredumpTime > latestCoredumpTime)
latestCoredumpTime = coredumpTime;
Expand All @@ -1006,12 +1051,14 @@ void LogBackend::slot_coredumpFinished(int index)
{"version", QCoreApplication::applicationVersion()},
{"event", "log"},
{"target", "coredump"},
{"coredumpListCount", DLDBusHandler::instance()->executeCmd("coredumpctl-list-count").toInt()},
{"repeatCoredumps", repeatObj},
{"message", objList}
};

Eventlogutils::GetInstance()->writeLogs(objCoredumpEvent);
LogApplicationHelper::instance()->saveLastRerportTime(latestCoredumpTime);
qCInfo(logBackend) << QString("Successfully reported %1 crash messages in total.").arg(m_currentCoredumpList.size());
qCInfo(logBackend) << QString("Successfully reported %1 crash messages in total.").arg(afterCleanData.size());
qApp->exit(0);
});
}
Expand Down Expand Up @@ -1361,7 +1408,7 @@ QList<LOG_MSG_COREDUMP> LogBackend::filterCoredump(const QString &iSearchStr, co
if (msg.sig.contains(iSearchStr, Qt::CaseInsensitive)
|| msg.dateTime.contains(iSearchStr, Qt::CaseInsensitive)
|| msg.coreFile.contains(iSearchStr, Qt::CaseInsensitive)
|| msg.uid.contains(iSearchStr, Qt::CaseInsensitive)
|| msg.userName.contains(iSearchStr, Qt::CaseInsensitive)
|| msg.exe.contains(iSearchStr, Qt::CaseInsensitive)) {
rsList.append(msg);
}
Expand Down Expand Up @@ -2722,3 +2769,61 @@ bool LogBackend::hasMatchedData(const LOG_FLAG &flag)

return bMatchedData;
}

void LogBackend::parseCoredumpDetailInfo(QList<LOG_MSG_COREDUMP> &list)
{
QProcess process;
for (auto &data : list) {
QDateTime coredumpTime = QDateTime::fromString(data.dateTime, "yyyy-MM-dd hh:mm:ss");
// 解析coredump文件保存位置
if (data.coreFile != "missing") {
QString outInfoByte;
// get maps info
const QString &corePath = QDir::tempPath() + QString("/%1.dump").arg(QFileInfo(data.storagePath).fileName());
DLDBusHandler::instance()->readLog(QString("coredumpctl dump %1 -o %2").arg(data.pid).arg(corePath));
outInfoByte = DLDBusHandler::instance()->readLog(QString("readelf -n %1").arg(corePath));

data.maps = outInfoByte;

// 获取二进制文件信息
process.start("/bin/bash", QStringList() << "-c" << QString("file %1").arg(data.exe));
process.waitForFinished(-1);
outInfoByte = process.readAllStandardOutput();
if (!outInfoByte.isEmpty())
data.binaryInfo = outInfoByte;

// 获取包名
process.start("/bin/bash", QStringList() << "-c" << QString("dpkg -S %1").arg(data.exe));
process.waitForFinished(-1);
outInfoByte = process.readAllStandardOutput();
// 获取版本号
if (!outInfoByte.isEmpty()) {
QString str = outInfoByte;
process.start("/bin/bash", QStringList() << "-c" << QString("dpkg-query --show %1").arg(str.split(":").first()));
process.waitForFinished(-1);
outInfoByte = process.readAllStandardOutput();
if (!outInfoByte.isEmpty()) {
data.packgeVersion = QString(outInfoByte).simplified();
}
}
}

// 若为窗管崩溃,提取窗管最后100行日志到coredump信息中
if (data.exe == KWAYLAND_EXE_PATH || data.exe == XWAYLAND_EXE_PATH) {
// 窗管日志存放在用户家目录下,因此根据崩溃信息所属用户id获取用户家目录
uint userId = data.uid.toUInt();
QString userHomePath = Utils::getUserHomePathByUID(userId);
if (!userHomePath.isEmpty()) {
if (data.exe == KWAYLAND_EXE_PATH) {
data.appLog = DLDBusHandler::instance()->readLogLinesInRange(QString("%1/.kwin-old.log").arg(userHomePath), 0, KWIN_LASTLINE_NUM).join('\n');
if (!data.appLog.isEmpty())
qCInfo(logBackend) << QString("kwin crash log:%1").arg(data.appLog);
} else if (data.exe == XWAYLAND_EXE_PATH) {
data.appLog = DLDBusHandler::instance()->readLogLinesInRange(QString("%1/.xwayland.log.old").arg(userHomePath), KWIN_LASTLINE_NUM).join('\n');
}
} else {
qCWarning(logBackend) << QString("uid:%1 homepath is empty.").arg(userId);
}
}
}
}
1 change: 1 addition & 0 deletions application/logbackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ private slots:
QString getApplogPath(const QString &appName);
QStringList getLabels(const LOG_FLAG &flag);
bool hasMatchedData(const LOG_FLAG &flag);
void parseCoredumpDetailInfo(QList<LOG_MSG_COREDUMP>& list);

private:
QStringList m_logTypes;
Expand Down
Loading
Loading