Skip to content

Commit ba31a5c

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 30ea2f8 commit ba31a5c

64 files changed

Lines changed: 1361 additions & 140 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: 37 additions & 53 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"
@@ -61,8 +62,8 @@ namespace {
6162
// inability to register a void* to get back to a class
6263
#ifdef LOG_TO_FILE
6364
QAtomicPointer<FILE> logFileFile = nullptr;
64-
QList<QByteArray>* logBuffer = new QList<QByteArray>(); // Store log messages until log file opened
65-
QMutex* logBufferMutex = new QMutex();
65+
auto logBuffer = std::make_unique<QList<QByteArray>>(); // Store log messages until log file opened
66+
auto logBufferMutex = std::make_unique<QMutex>();
6667
#endif
6768

6869
constexpr std::string_view sourceRootPath()
@@ -130,73 +131,56 @@ void logMessageHandler(QtMsgType type, const QMessageLogContext& ctxt, const QSt
130131
return;
131132
}
132133

133-
const QString file = canonicalLogFilePath(ctxt.file);
134-
const QString category =
135-
(ctxt.category != nullptr) ? QString::fromUtf8(ctxt.category) : QStringLiteral("default");
136-
if ((type == QtDebugMsg && category == QStringLiteral("tox.core")
137-
&& (file == QStringLiteral("rtp.c") || file == QStringLiteral("video.c")))
138-
|| (file == QStringLiteral("bwcontroller.c") && msg.contains("update"))) {
134+
// Time should be in UTC to save user privacy on log sharing.
135+
DebugLogModel::LogEntry entry{
136+
-1,
137+
QDateTime::currentDateTime().toUTC(),
138+
ctxt.category != nullptr ? QString::fromUtf8(ctxt.category) : QStringLiteral("default"),
139+
canonicalLogFilePath(ctxt.file),
140+
ctxt.line,
141+
type,
142+
"",
143+
};
144+
145+
if ((entry.type == QtDebugMsg && entry.category == QStringLiteral("tox.core")
146+
&& (entry.file == QStringLiteral("rtp.c") || entry.file == QStringLiteral("video.c")))
147+
|| (entry.file == QStringLiteral("bwcontroller.c") && msg.contains("update"))) {
139148
// Don't log verbose toxav messages.
140149
return;
141150
}
142151

143-
// Time should be in UTC to save user privacy on log sharing
144-
const QTime time = QDateTime::currentDateTime().toUTC().time();
145-
QString logPrefix =
146-
QStringLiteral("[%1 UTC] (%2) %3:%4 : ")
147-
.arg(time.toString("HH:mm:ss.zzz"), category, file, QString::number(ctxt.line));
148-
switch (type) {
149-
case QtDebugMsg:
150-
logPrefix += "Debug";
151-
break;
152-
case QtInfoMsg:
153-
logPrefix += "Info";
154-
break;
155-
case QtWarningMsg:
156-
logPrefix += "Warning";
157-
break;
158-
case QtCriticalMsg:
159-
logPrefix += "Critical";
160-
break;
161-
case QtFatalMsg:
162-
logPrefix += "Fatal";
163-
break;
164-
default:
165-
break;
166-
}
167-
168152
QString logMsg;
169153
for (const auto& line : msg.split('\n')) {
170-
logMsg += logPrefix + ": " + canonicalLogMessage(line) + "\n";
154+
entry.message = canonicalLogMessage(line);
155+
logMsg += DebugLogModel::render(entry);
156+
logMsg += '\n';
171157
}
172158

173-
const QByteArray LogMsgBytes = logMsg.toUtf8();
174-
fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), stderr);
159+
const QByteArray logMsgBytes = logMsg.toUtf8();
160+
fwrite(logMsgBytes.constData(), 1, logMsgBytes.size(), stderr);
175161

176162
#ifdef LOG_TO_FILE
177-
FILE* logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
163+
FILE* const logFilePtr = logFileFile.loadRelaxed(); // atomically load the file pointer
178164
if (logFilePtr == nullptr) {
179-
logBufferMutex->lock();
180-
if (logBuffer != nullptr)
181-
logBuffer->append(LogMsgBytes);
182-
183-
logBufferMutex->unlock();
184-
} else {
185-
logBufferMutex->lock();
165+
const QMutexLocker<QMutex> locker(logBufferMutex.get());
186166
if (logBuffer != nullptr) {
187-
// empty logBuffer to file
188-
for (const QByteArray& bufferedMsg : *logBuffer) {
189-
fwrite(bufferedMsg.constData(), 1, bufferedMsg.size(), logFilePtr);
190-
}
167+
logBuffer->append(logMsgBytes);
168+
}
169+
return;
170+
}
191171

192-
delete logBuffer; // no longer needed
193-
logBuffer = nullptr;
172+
const QMutexLocker<QMutex> locker(logBufferMutex.get());
173+
if (logBuffer != nullptr) {
174+
// empty logBuffer to file
175+
for (const QByteArray& bufferedMsg : *logBuffer) {
176+
fwrite(bufferedMsg.constData(), 1, bufferedMsg.size(), logFilePtr);
194177
}
195-
logBufferMutex->unlock();
196178

197-
fwrite(LogMsgBytes.constData(), 1, LogMsgBytes.size(), logFilePtr);
198-
fflush(logFilePtr);
179+
logBuffer = nullptr; // no longer needed
199180
}
181+
182+
fwrite(logMsgBytes.constData(), 1, logMsgBytes.size(), logFilePtr);
183+
fflush(logFilePtr);
200184
#endif
201185
}
202186

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: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88
#include "ui_advancedsettings.h"
99

10+
#include "src/model/debug/debuglogmodel.h"
1011
#include "src/net/toxuri.h"
1112
#include "src/persistence/profile.h"
1213
#include "src/persistence/settings.h"
1314
#include "src/widget/tool/imessageboxmanager.h"
1415
#include "src/widget/tool/recursivesignalblocker.h"
1516
#include "src/widget/translator.h"
17+
#include "util/fileutil.h"
1618
#include "util/network.h"
1719

1820
#include <QApplication>
@@ -23,6 +25,8 @@
2325
#include <QMessageBox>
2426
#include <QProcess>
2527

28+
#include <algorithm>
29+
2630
/**
2731
* @class AdvancedForm
2832
*
@@ -124,29 +128,36 @@ void AdvancedForm::on_btnCopyDebug_clicked()
124128
const QString logFileDir = settings.getPaths().getAppCacheDirPath();
125129
const QString logfile = logFileDir + "qtox.log";
126130

127-
QFile file(logfile);
128-
if (!file.exists()) {
129-
qDebug() << "No debug file found";
131+
QClipboard* clipboard = QApplication::clipboard();
132+
if (clipboard == nullptr) {
133+
qDebug() << "Unable to access clipboard";
130134
return;
131135
}
132136

133-
QClipboard* clipboard = QApplication::clipboard();
134-
if (clipboard != nullptr) {
135-
QString debugtext;
136-
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
137-
QTextStream in(&file);
138-
debugtext = in.readAll();
139-
file.close();
140-
} else {
141-
qDebug() << "Unable to open file for copying to clipboard";
142-
return;
143-
}
137+
// Maximum number of lines to copy to clipboard.
138+
const int maxDebugLogLines = bodyUI->maxLogLines->value();
139+
const QStringList lines = FileUtil::tail(logfile, maxDebugLogLines);
144140

145-
clipboard->setText(debugtext, QClipboard::Clipboard);
146-
qDebug() << "Debug log copied to clipboard";
147-
} else {
148-
qDebug() << "Unable to access clipboard";
141+
if (lines.isEmpty()) {
142+
return;
143+
}
144+
145+
// Parse the log entries and remove entries that are too old.
146+
QList<DebugLogModel::LogEntry> logEntries = DebugLogModel::parse(lines);
147+
std::reverse(logEntries.begin(), logEntries.end());
148+
const QDateTime now = QDateTime::currentDateTimeUtc();
149+
const int maxDebugLogAge =
150+
bodyUI->maxLogAge->time().hour() * 3600 + bodyUI->maxLogAge->time().minute() * 60;
151+
while (!logEntries.isEmpty() && logEntries.last().time.secsTo(now) > maxDebugLogAge) {
152+
logEntries.removeLast();
149153
}
154+
155+
QStringList debugText;
156+
std::transform(logEntries.rbegin(), logEntries.rend(), std::back_inserter(debugText),
157+
DebugLogModel::renderWithDate);
158+
159+
clipboard->setText(debugText.join('\n'), QClipboard::Clipboard);
160+
qDebug() << "Copied" << debugText.size() << "debug log entries to clipboard";
150161
}
151162

152163
void AdvancedForm::on_resetButton_clicked()

0 commit comments

Comments
 (0)