Skip to content

Commit 0f37bf0

Browse files
authored
Add support for logging to a log file in the $XDG_STATE_DIR/flameshot (flameshot-org#4371)
(or equivelent directories on windows/mac-os) By default logging to file is enabled for all log messages. Filtering log messages written to disk by level severity and choosing a different path to save the log files is configurable via the config menu. There is also a shortcut on the about menu popup to open the configured logging directory. This shortcut is disabled if logging to file is disabled The log file implements a simple roller that saves only the two most recent sets of logs generated by flameshot. This should stop log messages from eating users' disk. We use cmake to check the version of QT6. If greater than or equal to 6.7, we set the default log directory to the standard App State directory. If not, we use the app local data directory
1 parent 37f3f7f commit 0f37bf0

File tree

14 files changed

+319
-18
lines changed

14 files changed

+319
-18
lines changed

src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,3 +539,7 @@ if (APPLE)
539539

540540

541541
endif ()
542+
543+
if(${Qt6Core_VERSION} VERSION_GREATER_EQUAL 6.7)
544+
target_compile_definitions(flameshot PRIVATE QT_STATE_DIR_SUPPORTED=1)
545+
endif()

src/config/generalconf.cpp

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// SPDX-FileCopyrightText: 2017-2019 Alejandro Sirgo Rica & Contributors
33
#include "generalconf.h"
44
#include "src/core/flameshot.h"
5+
#include "src/utils/abstractlogger.h"
56
#include "src/utils/confighandler.h"
7+
#include "src/utils/logfile.h"
68
#include <QCheckBox>
79
#include <QComboBox>
810
#include <QFile>
@@ -56,6 +58,7 @@ GeneralConf::GeneralConf(QWidget* parent)
5658
initAntialiasingPinZoom();
5759
initUndoLimit();
5860
initInsecurePixelate();
61+
initLogToFile();
5962
#ifdef ENABLE_IMGUR
6063
initCopyAndCloseAfterUpload();
6164
initUploadWithoutConfirmation();
@@ -121,6 +124,9 @@ void GeneralConf::_updateComponents(bool allowEmptySavePath)
121124
if (allowEmptySavePath || !config.savePath().isEmpty()) {
122125
m_savePath->setText(config.savePath());
123126
}
127+
m_logToFile->setChecked(config.logToFile());
128+
m_logFilePath->setText(config.logFilePath());
129+
124130
#if defined(Q_OS_LINUX) || defined(Q_OS_UNIX)
125131
m_showTray->setChecked(!config.disabledTrayIcon());
126132
#endif
@@ -244,6 +250,7 @@ void GeneralConf::resetConfiguration()
244250
if (reply == QMessageBox::Yes) {
245251
m_savePath->setText(
246252
QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
253+
m_logFilePath->setText(LogFile::defaultLogFilePath());
247254
ConfigHandler().setDefaultSettings();
248255
_updateComponents(true);
249256
}
@@ -706,6 +713,33 @@ void GeneralConf::changeSavePath()
706713
}
707714
}
708715

716+
void GeneralConf::changeLogFilePath()
717+
{
718+
QString path = ConfigHandler().logFilePath();
719+
720+
if (path.isEmpty()) {
721+
path = LogFile::defaultLogFilePath();
722+
}
723+
724+
path = QFileDialog::getExistingDirectory(
725+
this,
726+
tr("Choose a Folder"),
727+
path,
728+
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
729+
730+
if (!QFileInfo(path).isWritable()) {
731+
QMessageBox::about(
732+
this, tr("Error"), tr("Unable to write to directory."));
733+
path = QString();
734+
}
735+
736+
if (!path.isEmpty()) {
737+
m_logFilePath->setText(path);
738+
ConfigHandler().setLogFilePath(path);
739+
LogFile::resetLogFile();
740+
}
741+
}
742+
709743
void GeneralConf::initCopyPathAfterSave()
710744
{
711745
m_copyPathAfterSave = new QCheckBox(tr("Copy file path after save"), this);
@@ -880,7 +914,7 @@ void GeneralConf::initInsecurePixelate()
880914
{
881915
m_insecurePixelate = new QCheckBox(tr("Insecure Pixelate"), this);
882916
m_insecurePixelate->setToolTip(
883-
tr("Draw the pixelation effect in an insecure but more asethetic way."));
917+
tr("Draw the pixelation effect in an insecure but more aesthetic way."));
884918
m_insecurePixelate->setChecked(ConfigHandler().insecurePixelate());
885919
m_scrollAreaLayout->addWidget(m_insecurePixelate);
886920

@@ -890,6 +924,70 @@ void GeneralConf::initInsecurePixelate()
890924
&GeneralConf::setInsecurePixelate);
891925
}
892926

927+
void GeneralConf::initLogToFile()
928+
{
929+
auto* box = new QGroupBox(tr("Logging"));
930+
box->setFlat(true);
931+
m_layout->addWidget(box);
932+
933+
auto* vboxLayout = new QVBoxLayout();
934+
box->setLayout(vboxLayout);
935+
936+
m_logToFile = new QCheckBox(tr("Log to file"), this);
937+
m_logToFile->setToolTip(
938+
tr("Save all log messages produced by flameshot to a file"));
939+
m_logToFile->setChecked(ConfigHandler().logToFile());
940+
941+
connect(m_logToFile, &QCheckBox::clicked, this, &GeneralConf::setLogToFile);
942+
vboxLayout->addWidget(m_logToFile);
943+
944+
auto* pathLayout = new QHBoxLayout();
945+
946+
QString path = ConfigHandler().logFilePath();
947+
m_logFilePath = new QLineEdit(path, this);
948+
m_logFilePath->setToolTip(
949+
tr("The directory where log files will be saved"));
950+
m_logFilePath->setDisabled(true);
951+
QString foreground = this->palette().windowText().color().name();
952+
m_logFilePath->setStyleSheet(QStringLiteral("color:%1").arg(foreground));
953+
pathLayout->addWidget(m_logFilePath);
954+
955+
m_changeLogFilePathButton = new QPushButton(tr("Change..."), this);
956+
pathLayout->addWidget(m_changeLogFilePathButton);
957+
connect(m_changeLogFilePathButton,
958+
&QPushButton::clicked,
959+
this,
960+
&GeneralConf::changeLogFilePath);
961+
962+
vboxLayout->addLayout(pathLayout);
963+
964+
auto* levelLayout = new QHBoxLayout();
965+
auto* levelLabel = new QLabel(tr("Minimum Log Level"));
966+
levelLabel->setToolTip(
967+
tr("Specify the minimum severity level of log messages that "
968+
"should be saved to disk"));
969+
levelLayout->addWidget(levelLabel);
970+
971+
m_logFileLevel = new QComboBox(this);
972+
973+
m_logFileLevel->addItem(tr("Info (All Log Messages)"),
974+
AbstractLogger::Channel::Info);
975+
m_logFileLevel->addItem(tr("Warnings and Errors"),
976+
AbstractLogger::Channel::Warning);
977+
m_logFileLevel->addItem(tr("Errors"), AbstractLogger::Channel::Error);
978+
979+
auto level = ConfigHandler().value("logFileLevel").toInt();
980+
m_logFileLevel->setCurrentIndex(m_logFileLevel->findData(level));
981+
982+
connect(
983+
m_logFileLevel,
984+
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
985+
this,
986+
&GeneralConf::setLogFileLevel);
987+
levelLayout->addWidget(m_logFileLevel);
988+
vboxLayout->addLayout(levelLayout);
989+
}
990+
893991
void GeneralConf::setSelGeoHideTime(int v)
894992
{
895993
ConfigHandler().setValue("showSelectionGeometryHideTime", v);
@@ -929,4 +1027,14 @@ void GeneralConf::setReverseArrow(bool checked)
9291027
void GeneralConf::setInsecurePixelate(bool checked)
9301028
{
9311029
ConfigHandler().setInsecurePixelate(checked);
932-
}
1030+
}
1031+
1032+
void GeneralConf::setLogToFile(bool checked)
1033+
{
1034+
ConfigHandler().setLogToFile(checked);
1035+
}
1036+
1037+
void GeneralConf::setLogFileLevel(int index)
1038+
{
1039+
ConfigHandler().setValue("logFileLevel", m_logFileLevel->itemData(index));
1040+
}

src/config/generalconf.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ private slots:
5050
void undoLimit(int limit);
5151
void saveAfterCopyChanged(bool checked);
5252
void changeSavePath();
53+
void changeLogFilePath();
5354
void importConfiguration();
5455
void exportFileConfiguration();
5556
void resetConfiguration();
@@ -62,6 +63,8 @@ private slots:
6263
void setJpegQuality(int v);
6364
void setReverseArrow(bool checked);
6465
void setInsecurePixelate(bool checked);
66+
void setLogToFile(bool checked);
67+
void setLogFileLevel(int index);
6568

6669
private:
6770
const QString chooseFolder(const QString& currentPath = "");
@@ -101,6 +104,7 @@ private slots:
101104
void initJpegQuality();
102105
void initReverseArrow();
103106
void initInsecurePixelate();
107+
void initLogToFile();
104108

105109
void _updateComponents(bool allowEmptySavePath);
106110

@@ -150,4 +154,8 @@ private slots:
150154
QSpinBox* m_jpegQuality;
151155
QCheckBox* m_reverseArrow;
152156
QCheckBox* m_insecurePixelate;
157+
QCheckBox* m_logToFile;
158+
QLineEdit* m_logFilePath;
159+
QPushButton* m_changeLogFilePathButton;
160+
QComboBox* m_logFileLevel;
153161
};

src/utils/CMakeLists.txt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,20 @@ target_sources(
2525
colorutils.cpp
2626
history.cpp
2727
strfparse.cpp
28+
logfile.cpp
2829
)
2930

3031
IF (UNIX AND NOT APPLE)
31-
target_sources(
32-
flameshot
33-
PRIVATE request.h
34-
request.cpp
35-
)
32+
target_sources(
33+
flameshot
34+
PRIVATE request.h
35+
request.cpp
36+
)
3637
ENDIF()
3738

3839
IF (WIN32)
39-
target_sources(
40-
flameshot
41-
PRIVATE winlnkfileparse.cpp
42-
)
40+
target_sources(
41+
flameshot
42+
PRIVATE winlnkfileparse.cpp
43+
)
4344
ENDIF()

src/utils/abstractlogger.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
#include "abstractlogger.h"
2+
#include "confighandler.h"
3+
#include "logfile.h"
24
#include "systemnotification.h"
35
#include <cassert>
46

7+
#include <QDateTime>
58
#include <QFileInfo>
69

710
AbstractLogger::AbstractLogger(Channel channel, int targets)
811
: m_defaultChannel(channel)
912
, m_targets(targets)
10-
{
11-
if (targets & LogFile) {
12-
// TODO
13-
}
14-
}
13+
{}
1514

1615
/**
1716
* @brief Construct an AbstractLogger with output to a string.
@@ -56,8 +55,12 @@ AbstractLogger& AbstractLogger::sendMessage(const QString& msg, Channel channel)
5655
*stream << messageHeader(channel, String) << msg << "\n";
5756
}
5857
}
59-
if (m_targets & LogFile) {
60-
// TODO
58+
if (m_targets & LogFile && ConfigHandler().logToFile()) {
59+
if (channel >= ConfigHandler().logFileLevel()) {
60+
QString data;
61+
data = messageHeader(channel, LogFile) + msg + "\n";
62+
LogFile::write(&data);
63+
}
6164
}
6265
if (m_targets & Stderr) {
6366
QTextStream stream(stderr);
@@ -132,6 +135,9 @@ QString AbstractLogger::messageHeader(Channel channel, Target target)
132135
if (target == Notification) {
133136
messageChannel[0] = messageChannel[0].toUpper();
134137
return "Flameshot " + messageChannel;
138+
} else if (target == LogFile) {
139+
return QDateTime::currentDateTime().toString(Qt::ISODateWithMs) + ":[" +
140+
messageChannel + "]: ";
135141
} else {
136142
return "flameshot: " + messageChannel + ": ";
137143
}

src/utils/confighandler.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ static QMap<class QString, QSharedPointer<ValueHandler>>
138138
OPTION("jpegQuality" , BoundedInt ( 0,100,75 )),
139139
OPTION("reverseArrow" ,Bool ( false )),
140140
OPTION("insecurePixelate" ,Bool ( false )),
141+
OPTION("logToFile" ,Bool ( true )),
142+
OPTION("logFilePath" ,LoggingDir ( )),
143+
OPTION("logFileLevel" ,BoundedInt ( AbstractLogger::Channel::Info, AbstractLogger::Channel::Error, AbstractLogger::Channel::Info)),
141144
};
142145

143146
static QMap<QString, QSharedPointer<KeySequence>> recognizedShortcuts = {

src/utils/confighandler.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ class ConfigHandler : public QObject
141141
CONFIG_GETTER_SETTER(showSelectionGeometryHideTime,
142142
showSelectionGeometryHideTime,
143143
int)
144+
CONFIG_GETTER_SETTER(logToFile, setLogToFile, bool)
145+
CONFIG_GETTER_SETTER(logFilePath, setLogFilePath, QString)
146+
CONFIG_GETTER_SETTER(logFileLevel, setLogFileLevel, int)
144147

145148
// SPECIAL CASES
146149
bool startupLaunch();

src/utils/logfile.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#include "logfile.h"
2+
3+
#include "confighandler.h"
4+
5+
#include <QDir>
6+
#include <QMutexLocker>
7+
#include <QStandardPaths>
8+
9+
constexpr const char* LOG_FILE_NAME = "flameshot.log";
10+
constexpr const char* OLD_LOG_FILE_NAME = "flameshot.log.old";
11+
12+
void LogFile::write(const QString* data)
13+
{
14+
QMutexLocker locker(&m_mutex);
15+
if (nullptr == m_instance) {
16+
m_instance = new LogFile();
17+
}
18+
19+
m_instance->writeToFile(data);
20+
}
21+
22+
void LogFile::resetLogFile()
23+
{
24+
QMutexLocker locker(&m_mutex);
25+
delete m_instance;
26+
m_instance = nullptr;
27+
}
28+
29+
QString LogFile::defaultLogFilePath()
30+
{
31+
#ifdef QT_STATE_DIR_SUPPORTED
32+
const auto defaultLocation = QStandardPaths::GenericStateLocation;
33+
#else
34+
const auto defaultLocation = QStandardPaths::GenericDataLocation;
35+
#endif
36+
return QDir::toNativeSeparators(
37+
QStandardPaths::writableLocation(defaultLocation) + "/flameshot");
38+
}
39+
40+
LogFile::LogFile()
41+
{
42+
auto loggingDirectory = QDir(ConfigHandler().logFilePath());
43+
44+
if (!loggingDirectory.exists()) {
45+
loggingDirectory.mkpath(".");
46+
}
47+
48+
if (loggingDirectory.exists(OLD_LOG_FILE_NAME)) {
49+
loggingDirectory.remove(OLD_LOG_FILE_NAME);
50+
}
51+
52+
if (loggingDirectory.exists(LOG_FILE_NAME)) {
53+
loggingDirectory.rename(LOG_FILE_NAME, OLD_LOG_FILE_NAME);
54+
}
55+
56+
auto logFilePath = loggingDirectory.filePath(LOG_FILE_NAME);
57+
m_logFile = std::make_unique<QFile>(logFilePath);
58+
if (m_logFile->open(QIODeviceBase::WriteOnly | QIODeviceBase::Append)) {
59+
m_writer = std::make_unique<QTextStream>();
60+
m_writer->setDevice(m_logFile.get());
61+
}
62+
}
63+
64+
void LogFile::writeToFile(const QString* data)
65+
{
66+
if (m_writer) {
67+
*m_writer << *data;
68+
m_writer->flush();
69+
}
70+
}
71+
72+
LogFile* LogFile::m_instance = nullptr;
73+
QMutex LogFile::m_mutex;

0 commit comments

Comments
 (0)