amnezia-client/client/loghandler.cpp
Mortie 8c20a67cfa migrated the codebase to Qt6 and fixed some compatibility issues
* used a Qt6 ported version of SortFilterProxyModel
* used an updated Qt6 compatible version of QXZing
* added a flag to windows linker to avoid WinMain problem of MSVCRTD
* renamed utils.cpp to utilities.cpp for avoiding confusion with the same file name in SortFilterProxyModel
2022-08-29 12:21:09 +04:30

345 lines
7.8 KiB
C++

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "loghandler.h"
#include "constants.h"
#include "logger.h"
#include <QDate>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QMessageLogContext>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QString>
#include <QTextStream>
#ifdef MVPN_ANDROID
# include <android/log.h>
#endif
constexpr qint64 LOG_MAX_FILE_SIZE = 204800;
constexpr const char* LOG_FILENAME = "mozillavpn.txt";
namespace {
QMutex s_mutex;
QString s_location =
QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
LogHandler* s_instance = nullptr;
LogLevel qtTypeToLogLevel(QtMsgType type) {
switch (type) {
case QtDebugMsg:
return Debug;
case QtInfoMsg:
return Info;
case QtWarningMsg:
return Warning;
case QtCriticalMsg:
[[fallthrough]];
case QtFatalMsg:
return Error;
default:
return Debug;
}
}
} // namespace
// static
LogHandler* LogHandler::instance() {
QMutexLocker lock(&s_mutex);
return maybeCreate(lock);
}
// static
void LogHandler::messageQTHandler(QtMsgType type,
const QMessageLogContext& context,
const QString& message) {
QMutexLocker lock(&s_mutex);
maybeCreate(lock)->addLog(Log(qtTypeToLogLevel(type), context.file,
context.function, context.line, message),
lock);
}
// static
void LogHandler::messageHandler(LogLevel logLevel, const QStringList& modules,
const QString& className,
const QString& message) {
QMutexLocker lock(&s_mutex);
maybeCreate(lock)->addLog(Log(logLevel, modules, className, message), lock);
}
// static
LogHandler* LogHandler::maybeCreate(const QMutexLocker<QMutex>& proofOfLock) {
if (!s_instance) {
LogLevel minLogLevel = Debug; // TODO: in prod, we should log >= warning
QStringList modules;
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
if (pe.contains("MOZVPN_LEVEL")) {
QString level = pe.value("MOZVPN_LEVEL");
if (level == "info")
minLogLevel = Info;
else if (level == "warning")
minLogLevel = Warning;
else if (level == "error")
minLogLevel = Error;
}
if (pe.contains("MOZVPN_LOG")) {
QStringList parts = pe.value("MOZVPN_LOG").split(",");
for (const QString& part : parts) {
modules.append(part.trimmed());
}
}
s_instance = new LogHandler(minLogLevel, modules, proofOfLock);
}
return s_instance;
}
// static
void LogHandler::prettyOutput(QTextStream& out, const LogHandler::Log& log) {
out << "[" << log.m_dateTime.toString("dd.MM.yyyy hh:mm:ss.zzz") << "] ";
switch (log.m_logLevel) {
case Debug:
out << "Debug: ";
break;
case Info:
out << "Info: ";
break;
case Warning:
out << "Warning: ";
break;
case Error:
out << "Error: ";
break;
default:
out << "?!?: ";
break;
}
if (log.m_fromQT) {
out << log.m_message;
if (!log.m_file.isEmpty() || !log.m_function.isEmpty()) {
out << " (";
if (!log.m_file.isEmpty()) {
int pos = log.m_file.lastIndexOf("/");
out << log.m_file.right(log.m_file.length() - pos - 1);
if (log.m_line >= 0) {
out << ":" << log.m_line;
}
if (!log.m_function.isEmpty()) {
out << ", ";
}
}
if (!log.m_function.isEmpty()) {
out << log.m_function;
}
out << ")";
}
} else {
out << "(" << log.m_modules.join("|") << " - " << log.m_className << ") "
<< log.m_message;
}
out << Qt::endl;
}
// static
void LogHandler::enableDebug() {
QMutexLocker lock(&s_mutex);
maybeCreate(lock)->m_showDebug = true;
}
LogHandler::LogHandler(LogLevel minLogLevel, const QStringList& modules,
const QMutexLocker<QMutex>& proofOfLock)
: m_minLogLevel(minLogLevel), m_modules(modules) {
Q_UNUSED(proofOfLock);
#if defined(QT_DEBUG) || defined(MVPN_WASM)
m_showDebug = true;
#endif
if (!s_location.isEmpty()) {
openLogFile(proofOfLock);
}
}
void LogHandler::addLog(const Log& log, const QMutexLocker<QMutex>& proofOfLock) {
if (!matchLogLevel(log, proofOfLock)) {
return;
}
if (!matchModule(log, proofOfLock)) {
return;
}
if (m_output) {
prettyOutput(*m_output, log);
}
if ((log.m_logLevel != LogLevel::Debug) || m_showDebug) {
QTextStream out(stderr);
prettyOutput(out, log);
}
QByteArray buffer;
{
QTextStream out(&buffer);
prettyOutput(out, log);
}
emit logEntryAdded(buffer);
#if defined(MVPN_ANDROID) && defined(QT_DEBUG)
const char* str = buffer.constData();
if (str) {
__android_log_write(ANDROID_LOG_DEBUG, "mozillavpn", str);
}
#endif
}
bool LogHandler::matchModule(const Log& log,
const QMutexLocker<QMutex>& proofOfLock) const {
Q_UNUSED(proofOfLock);
// Let's include QT logs always.
if (log.m_fromQT) {
return true;
}
// If no modules has been specified, let's include all.
if (m_modules.isEmpty()) {
return true;
}
for (const QString& module : log.m_modules) {
if (m_modules.contains(module)) {
return true;
}
}
return false;
}
bool LogHandler::matchLogLevel(const Log& log,
const QMutexLocker<QMutex>& proofOfLock) const {
Q_UNUSED(proofOfLock);
return log.m_logLevel >= m_minLogLevel;
}
// static
void LogHandler::writeLogs(QTextStream& out) {
QMutexLocker lock(&s_mutex);
if (!s_instance || !s_instance->m_logFile) {
return;
}
QString logFileName = s_instance->m_logFile->fileName();
s_instance->closeLogFile(lock);
{
QFile file(logFileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return;
}
out << file.readAll();
}
s_instance->openLogFile(lock);
}
// static
void LogHandler::cleanupLogs() {
QMutexLocker lock(&s_mutex);
cleanupLogFile(lock);
}
// static
void LogHandler::cleanupLogFile(const QMutexLocker<QMutex>& proofOfLock) {
if (!s_instance || !s_instance->m_logFile) {
return;
}
QString logFileName = s_instance->m_logFile->fileName();
s_instance->closeLogFile(proofOfLock);
{
QFile file(logFileName);
file.remove();
}
s_instance->openLogFile(proofOfLock);
}
// static
void LogHandler::setLocation(const QString& path) {
QMutexLocker lock(&s_mutex);
s_location = path;
if (s_instance && s_instance->m_logFile) {
cleanupLogFile(lock);
}
}
void LogHandler::openLogFile(const QMutexLocker<QMutex>& proofOfLock) {
Q_UNUSED(proofOfLock);
Q_ASSERT(!m_logFile);
Q_ASSERT(!m_output);
QDir appDataLocation(s_location);
if (!appDataLocation.exists()) {
QDir tmp(s_location);
tmp.cdUp();
if (!tmp.exists()) {
return;
}
if (!tmp.mkdir(appDataLocation.dirName())) {
return;
}
}
QString logFileName = appDataLocation.filePath(LOG_FILENAME);
m_logFile = new QFile(logFileName);
if (m_logFile->size() > LOG_MAX_FILE_SIZE) {
m_logFile->remove();
}
if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Append |
QIODevice::Text)) {
delete m_logFile;
m_logFile = nullptr;
return;
}
m_output = new QTextStream(m_logFile);
addLog(Log(Debug, QStringList{LOG_MAIN}, "LogHandler",
QString("Log file: %1").arg(logFileName)),
proofOfLock);
}
void LogHandler::closeLogFile(const QMutexLocker<QMutex>& proofOfLock) {
Q_UNUSED(proofOfLock);
if (m_logFile) {
delete m_output;
m_output = nullptr;
delete m_logFile;
m_logFile = nullptr;
}
}