Skip to content

Commit 18b68db

Browse files
committed
feat(Debug): Allow user to limit the copied log size.
We usually don't need a very long history to debug issues. This protects us from having to look at unnecessarily old logs and protects users from sharing too much of their activity history. The default of 1000 is chosen generously because it'll likely be limited by log line age first. The 1000 protects us from enormous logs.
1 parent 464939a commit 18b68db

64 files changed

Lines changed: 1366 additions & 141 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/appmanager.cpp

Lines changed: 31 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "appmanager.h"
77

88
#include "src/ipc.h"
9+
#include "src/model/debug/debuglogmodel.h"
910
#include "src/net/toxuri.h"
1011
#include "src/net/updatecheck.h"
1112
#include "src/nexus.h"
@@ -33,8 +34,8 @@ namespace {
3334
// inability to register a void* to get back to a class
3435
#ifdef LOG_TO_FILE
3536
QAtomicPointer<FILE> logFileFile = nullptr;
36-
QList<QByteArray>* logBuffer = new QList<QByteArray>(); // Store log messages until log file opened
37-
QMutex* logBufferMutex = new QMutex();
37+
auto logBuffer = std::make_unique<QList<QByteArray>>(); // Store log messages until log file opened
38+
auto logBufferMutex = std::make_unique<QMutex>();
3839
#endif
3940

4041
constexpr std::string_view sourceRootPath()
@@ -113,62 +114,47 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
113114
}
114115

115116
// Time should be in UTC to save user privacy on log sharing
116-
const QTime time = QDateTime::currentDateTime().toUTC().time();
117-
QString logPrefix =
118-
QStringLiteral("[%1 UTC] (%2) %3:%4 : ")
119-
.arg(time.toString("HH:mm:ss.zzz"), category, file, QString::number(ctxt.line));
120-
switch (type) {
121-
case QtDebugMsg:
122-
logPrefix += "Debug";
123-
break;
124-
case QtInfoMsg:
125-
logPrefix += "Info";
126-
break;
127-
case QtWarningMsg:
128-
logPrefix += "Warning";
129-
break;
130-
case QtCriticalMsg:
131-
logPrefix += "Critical";
132-
break;
133-
case QtFatalMsg:
134-
logPrefix += "Fatal";
135-
break;
136-
default:
137-
break;
138-
}
117+
const QDateTime now = QDateTime::currentDateTime().toUTC();
139118

140119
QString logMsg;
141120
for (const auto& line : msg.split('\n')) {
142-
logMsg += logPrefix + ": " + canonicalLogMessage(line) + "\n";
121+
logMsg += DebugLogModel::render({
122+
-1,
123+
now,
124+
category,
125+
file,
126+
ctxt.line,
127+
type,
128+
canonicalLogMessage(line),
129+
});
130+
logMsg += '\n';
143131
}
144132

145-
const QByteArray LogMsgBytes = logMsg.toUtf8();
146-
fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr);
133+
const QByteArray logMsgBytes = logMsg.toUtf8();
134+
fwrite(logMsgBytes.constData(), 1, logMsgBytes.size(), stderr);
147135

148136
#ifdef LOG_TO_FILE
149-
FILE* logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
137+
FILE* const logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
150138
if (logFilePtr == nullptr) {
151-
logBufferMutex->lock();
152-
if (logBuffer != nullptr)
153-
logBuffer->append(LogMsgBytes);
154-
155-
logBufferMutex->unlock();
156-
} else {
157-
logBufferMutex->lock();
139+
const QMutexLocker<QMutex> locker(logBufferMutex.get());
158140
if (logBuffer != nullptr) {
159-
// empty logBuffer to file
160-
for (const QByteArray& bufferedMsg : *logBuffer) {
161-
fwrite(bufferedMsg.constData(), 1, bufferedMsg.size(), logFilePtr);
162-
}
141+
logBuffer->append(logMsgBytes);
142+
}
143+
return;
144+
}
163145

164-
delete logBuffer; // no longer needed
165-
logBuffer = nullptr;
146+
const QMutexLocker<QMutex> locker(logBufferMutex.get());
147+
if (logBuffer != nullptr) {
148+
// empty logBuffer to file
149+
for (const QByteArray& bufferedMsg : *logBuffer) {
150+
fwrite(bufferedMsg.constData(), 1, bufferedMsg.size(), logFilePtr);
166151
}
167-
logBufferMutex->unlock();
168152

169-
fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), logFilePtr);
170-
fflush(logFilePtr);
153+
logBuffer = nullptr; // no longer needed
171154
}
155+
156+
fwrite(logMsgBytes.constData(), 1, logMsgBytes.size(), logFilePtr);
157+
fflush(logFilePtr);
172158
#endif
173159
}
174160

src/model/debug/debuglogmodel.cpp

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,15 @@
44

55
#include "debuglogmodel.h"
66

7+
#include "util/ranges.h"
8+
79
#include <QColor>
810
#include <QRegularExpression>
911

1012
namespace {
13+
const QString timeFormat = QStringLiteral("HH:mm:ss.zzz");
14+
const QString dateTimeFormat = QStringLiteral("yyyy-MM-dd HH:mm:ss.zzz");
15+
1116
QtMsgType parseMsgType(const QString& type)
1217
{
1318
if (type == "Debug") {
@@ -47,7 +52,27 @@ QString renderMsgType(QtMsgType type)
4752
return QStringLiteral("Unknown");
4853
}
4954

50-
QList<DebugLogModel::LogEntry> parse(const QStringList& logs)
55+
bool filterAccepts(DebugLogModel::Filter filter, QtMsgType type)
56+
{
57+
switch (filter) {
58+
case DebugLogModel::All:
59+
return true;
60+
case DebugLogModel::Debug:
61+
return type == QtDebugMsg;
62+
case DebugLogModel::Info:
63+
return type == QtInfoMsg;
64+
case DebugLogModel::Warning:
65+
return type == QtWarningMsg;
66+
case DebugLogModel::Critical:
67+
return type == QtCriticalMsg;
68+
case DebugLogModel::Fatal:
69+
return type == QtFatalMsg;
70+
}
71+
return false;
72+
}
73+
} // namespace
74+
75+
QList<DebugLogModel::LogEntry> DebugLogModel::parse(const QStringList& logs)
5176
{
5277
// Regex extraction of log entry
5378
// [12:35:16.634 UTC] (default) src/core/core.cpp:370 : Debug: Connected to a TCP relay
@@ -56,17 +81,29 @@ QList<DebugLogModel::LogEntry> parse(const QStringList& logs)
5681
static const QRegularExpression re(
5782
R"(\[([0-9:.]*) UTC\](?: \(([^)]*)\))? (.*?):(\d+) : ([^:]+): (.*))");
5883

84+
// Assume the last log entry is today.
85+
const QDateTime now = QDateTime::currentDateTime().toUTC();
86+
QDate lastDate = now.date();
87+
QTime lastTime = now.time();
88+
5989
QList<DebugLogModel::LogEntry> result;
60-
for (const QString& log : logs) {
90+
for (const QString& log : qtox::views::reverse(logs)) {
6191
const auto match = re.match(log);
6292
if (!match.hasMatch()) {
6393
qWarning() << "Failed to parse log entry:" << log;
6494
continue;
6595
}
6696

97+
// Reconstruct the likely date of the log entry.
98+
const QTime entryTime = QTime::fromString(match.captured(1), timeFormat);
99+
if (entryTime > lastTime) {
100+
lastDate = lastDate.addDays(-1);
101+
}
102+
lastTime = entryTime;
103+
67104
DebugLogModel::LogEntry entry;
68105
entry.index = result.size();
69-
entry.time = match.captured(1);
106+
entry.time = QDateTime{lastDate, entryTime};
70107
entry.category = match.captured(2);
71108
if (entry.category.isEmpty()) {
72109
entry.category = QStringLiteral("default");
@@ -77,36 +114,19 @@ QList<DebugLogModel::LogEntry> parse(const QStringList& logs)
77114
entry.message = match.captured(6);
78115
result.append(entry);
79116
}
117+
118+
std::reverse(result.begin(), result.end());
80119
return result;
81120
}
82121

83-
QString render(const DebugLogModel::LogEntry& entry)
122+
QString DebugLogModel::render(const DebugLogModel::LogEntry& entry, bool includeDate)
84123
{
85124
return QStringLiteral("[%1 UTC] (%2) %3:%4 : %5: %6")
86-
.arg(entry.time, entry.category, entry.file, QString::number(entry.line),
87-
renderMsgType(entry.type), entry.message);
125+
.arg(includeDate ? entry.time.toString(dateTimeFormat) : entry.time.toString(timeFormat),
126+
entry.category, entry.file, QString::number(entry.line), renderMsgType(entry.type),
127+
entry.message);
88128
}
89129

90-
bool filterAccepts(DebugLogModel::Filter filter, QtMsgType type)
91-
{
92-
switch (filter) {
93-
case DebugLogModel::All:
94-
return true;
95-
case DebugLogModel::Debug:
96-
return type == QtDebugMsg;
97-
case DebugLogModel::Info:
98-
return type == QtInfoMsg;
99-
case DebugLogModel::Warning:
100-
return type == QtWarningMsg;
101-
case DebugLogModel::Critical:
102-
return type == QtCriticalMsg;
103-
case DebugLogModel::Fatal:
104-
return type == QtFatalMsg;
105-
}
106-
return false;
107-
}
108-
} // namespace
109-
110130
DebugLogModel::DebugLogModel(QObject* parent)
111131
: QAbstractListModel(parent)
112132
{

src/model/debug/debuglogmodel.h

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#pragma once
66

77
#include <QAbstractListModel>
8+
#include <QDateTime>
89
#include <QLoggingCategory>
910

1011
class DebugLogModel : public QAbstractListModel
@@ -29,7 +30,7 @@ class DebugLogModel : public QAbstractListModel
2930
/// Index in the original log list.
3031
int index;
3132

32-
QString time;
33+
QDateTime time;
3334
QString category;
3435
QString file;
3536
int line;
@@ -52,6 +53,21 @@ class DebugLogModel : public QAbstractListModel
5253
*/
5354
int originalIndex(const QModelIndex& index) const;
5455

56+
/**
57+
* @brief Parse a list of log lines into LogEntry objects.
58+
*/
59+
static QList<LogEntry> parse(const QStringList& logs);
60+
61+
/**
62+
* @brief Render a LogEntry object into a string.
63+
*/
64+
static QString render(const LogEntry& entry, bool includeDate = false);
65+
66+
static QString renderWithDate(const LogEntry& entry)
67+
{
68+
return render(entry, true);
69+
}
70+
5571
private:
5672
void recomputeFilter();
5773

src/widget/form/debug/debuglog.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ DebugLogForm::~DebugLogForm()
9494

9595
void DebugLogForm::showEvent(QShowEvent* event)
9696
{
97-
qDebug() << "Loading logs for debug log view";
9897
debugLogModel_->reload(loadLogs(paths_));
9998

10099
GenericForm::showEvent(event);

src/widget/form/settings/advancedform.cpp

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@
77

88
#include "ui_advancedsettings.h"
99

10+
#include "src/model/debug/debuglogmodel.h"
1011
#include "src/persistence/profile.h"
1112
#include "src/persistence/settings.h"
1213
#include "src/widget/tool/imessageboxmanager.h"
1314
#include "src/widget/tool/recursivesignalblocker.h"
1415
#include "src/widget/translator.h"
16+
#include "util/fileutil.h"
1517

1618
#include <QApplication>
1719
#include <QClipboard>
@@ -20,6 +22,10 @@
2022
#include <QMessageBox>
2123
#include <QProcess>
2224

25+
#include <QtCore/qcontainerfwd.h>
26+
#include <QtCore/qlogging.h>
27+
#include <algorithm>
28+
2329
/**
2430
* @class AdvancedForm
2531
*
@@ -120,29 +126,36 @@ void AdvancedForm::on_btnCopyDebug_clicked()
120126
const QString logFileDir = settings.getPaths().getAppCacheDirPath();
121127
const QString logfile = logFileDir + "qtox.log";
122128

123-
QFile file(logfile);
124-
if (!file.exists()) {
125-
qDebug() << "No debug file found";
129+
QClipboard* clipboard = QApplication::clipboard();
130+
if (clipboard == nullptr) {
131+
qDebug() << "Unable to access clipboard";
126132
return;
127133
}
128134

129-
QClipboard* clipboard = QApplication::clipboard();
130-
if (clipboard != nullptr) {
131-
QString debugtext;
132-
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
133-
QTextStream in(&file);
134-
debugtext = in.readAll();
135-
file.close();
136-
} else {
137-
qDebug() << "Unable to open file for copying to clipboard";
138-
return;
139-
}
140-
141-
clipboard->setText(debugtext, QClipboard::Clipboard);
142-
qDebug() << "Debug log copied to clipboard";
143-
} else {
144-
qDebug() << "Unable to access clipboard";
135+
// Maximum number of lines to copy to clipboard.
136+
const int maxDebugLogLines = bodyUI->maxLogLines->value();
137+
const QStringList lines = FileUtil::tail(logfile, maxDebugLogLines);
138+
139+
if (lines.isEmpty()) {
140+
return;
141+
}
142+
143+
// Parse the log entries and remove entries that are too old.
144+
QList<DebugLogModel::LogEntry> logEntries = DebugLogModel::parse(lines);
145+
std::reverse(logEntries.begin(), logEntries.end());
146+
const QDateTime now = QDateTime::currentDateTimeUtc();
147+
const int maxDebugLogAge =
148+
bodyUI->maxLogAge->time().hour() * 3600 + bodyUI->maxLogAge->time().minute() * 60;
149+
while (!logEntries.isEmpty() && logEntries.last().time.secsTo(now) > maxDebugLogAge) {
150+
logEntries.removeLast();
145151
}
152+
153+
QStringList debugText;
154+
std::transform(logEntries.rbegin(), logEntries.rend(), std::back_inserter(debugText),
155+
DebugLogModel::renderWithDate);
156+
157+
clipboard->setText(debugText.join('\n'), QClipboard::Clipboard);
158+
qDebug() << "Copied" << debugText.size() << "debug log entries to clipboard";
146159
}
147160

148161
void AdvancedForm::on_resetButton_clicked()

0 commit comments

Comments
 (0)