| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "logs.h"
- #include "platform/platform_specific.h"
- #include "core/crash_reports.h"
- #include "core/launcher.h"
- #include "mtproto/facade.h"
- namespace {
- std::atomic<int> ThreadCounter/* = 0*/;
- thread_local bool WritingEntryFlag/* = false*/;
- class WritingEntryScope final {
- public:
- WritingEntryScope() {
- WritingEntryFlag = true;
- }
- ~WritingEntryScope() {
- WritingEntryFlag = false;
- }
- };
- } // namespace
- enum LogDataType {
- LogDataMain,
- LogDataDebug,
- LogDataTcp,
- LogDataMtp,
- LogDataCount
- };
- QMutex *_logsMutex(LogDataType type, bool clear = false) {
- static QMutex *LogsMutexes = 0;
- if (clear) {
- delete[] LogsMutexes;
- LogsMutexes = 0;
- } else if (!LogsMutexes) {
- LogsMutexes = new QMutex[LogDataCount];
- }
- return &LogsMutexes[type];
- }
- QString _logsFilePath(LogDataType type, const QString &postfix = QString()) {
- QString path(cWorkingDir());
- switch (type) {
- case LogDataMain: path += u"log"_q + postfix + u".txt"_q; break;
- case LogDataDebug: path += u"DebugLogs/log"_q + postfix + u".txt"_q; break;
- case LogDataTcp: path += u"DebugLogs/tcp"_q + postfix + u".txt"_q; break;
- case LogDataMtp: path += u"DebugLogs/mtp"_q + postfix + u".txt"_q; break;
- }
- return path;
- }
- int32 LogsStartIndexChosen = -1;
- QString _logsEntryStart() {
- static thread_local auto threadId = ThreadCounter++;
- static auto index = 0;
- const auto tm = QDateTime::currentDateTime();
- return QString("[%1 %2-%3]").arg(tm.toString("hh:mm:ss.zzz"), QString("%1").arg(threadId, 2, 10, QChar('0'))).arg(++index, 7, 10, QChar('0'));
- }
- class LogsDataFields {
- public:
- LogsDataFields() {
- for (int32 i = 0; i < LogDataCount; ++i) {
- files[i].reset(new QFile());
- }
- }
- bool openMain() {
- return reopen(LogDataMain, 0, u"start"_q);
- }
- void closeMain() {
- QMutexLocker lock(_logsMutex(LogDataMain));
- WritingEntryScope scope;
- const auto file = files[LogDataMain].get();
- if (file && file->isOpen()) {
- file->close();
- }
- }
- bool instanceChecked() {
- return reopen(LogDataMain, 0, QString());
- }
- QString full() {
- const auto file = files[LogDataMain].get();
- if (!file || !file->isOpen()) {
- return QString();
- }
- QFile out(file->fileName());
- if (out.open(QIODevice::ReadOnly)) {
- return QString::fromUtf8(out.readAll());
- }
- return QString();
- }
- void write(LogDataType type, const QString &msg) {
- QMutexLocker lock(_logsMutex(type));
- WritingEntryScope scope;
- if (type != LogDataMain) {
- reopenDebug();
- }
- const auto file = files[type].get();
- if (!file || !file->isOpen()) {
- return;
- }
- file->write(msg.toUtf8());
- file->flush();
- }
- private:
- std::unique_ptr<QFile> files[LogDataCount];
- int32 part = -1;
- bool reopen(LogDataType type, int32 dayIndex, const QString &postfix) {
- if (files[type] && files[type]->isOpen()) {
- if (type == LogDataMain) {
- if (!postfix.isEmpty()) {
- return true;
- }
- } else {
- files[type]->close();
- }
- }
- auto mode = QIODevice::WriteOnly | QIODevice::Text;
- if (type == LogDataMain) { // we can call LOG() in LogDataMain reopen - mutex not locked
- if (postfix.isEmpty()) { // instance checked, need to move to log.txt
- Assert(!files[type]->fileName().isEmpty()); // one of log_startXX.txt should've been opened already
- auto to = std::make_unique<QFile>(_logsFilePath(type, postfix));
- if (to->exists() && !to->remove()) {
- LOG(("Could not delete '%1' file to start new logging: %2").arg(to->fileName(), to->errorString()));
- return false;
- }
- if (auto from = QFile(files[type]->fileName()); !from.copy(to->fileName())) { // don't close files[type] yet
- LOG(("Could not copy '%1' to '%2' to start new logging: %3").arg(files[type]->fileName(), to->fileName(), from.errorString()));
- return false;
- }
- if (to->open(mode | QIODevice::Append)) {
- std::swap(files[type], to);
- LOG(("Moved logging from '%1' to '%2'!").arg(to->fileName(), files[type]->fileName()));
- to->remove();
- LogsStartIndexChosen = -1;
- QDir working(cWorkingDir()); // delete all other log_startXX.txt that we can
- QStringList oldlogs = working.entryList(QStringList("log_start*.txt"), QDir::Files);
- for (QStringList::const_iterator i = oldlogs.cbegin(), e = oldlogs.cend(); i != e; ++i) {
- QString oldlog = cWorkingDir() + *i, oldlogend = i->mid(u"log_start"_q.size());
- if (oldlogend.size() == 1 + u".txt"_q.size() && oldlogend.at(0).isDigit() && base::StringViewMid(oldlogend, 1) == u".txt"_q) {
- bool removed = QFile(oldlog).remove();
- LOG(("Old start log '%1' found, deleted: %2").arg(*i, Logs::b(removed)));
- }
- }
- return true;
- }
- LOG(("Could not open '%1' file to start new logging: %2").arg(to->fileName(), to->errorString()));
- return false;
- } else {
- bool found = false;
- int32 oldest = -1; // find not existing log_startX.txt or pick the oldest one (by lastModified)
- QDateTime oldestLastModified;
- for (int32 i = 0; i < 10; ++i) {
- QString trying = _logsFilePath(type, u"_start%1"_q.arg(i));
- files[type]->setFileName(trying);
- if (!files[type]->exists()) {
- LogsStartIndexChosen = i;
- found = true;
- break;
- }
- QDateTime lastModified = QFileInfo(trying).lastModified();
- if (oldest < 0 || lastModified < oldestLastModified) {
- oldestLastModified = lastModified;
- oldest = i;
- }
- }
- if (!found) {
- files[type]->setFileName(_logsFilePath(type, u"_start%1"_q.arg(oldest)));
- LogsStartIndexChosen = oldest;
- }
- }
- } else {
- files[type]->setFileName(_logsFilePath(type, postfix));
- if (files[type]->exists()) {
- if (files[type]->open(QIODevice::ReadOnly | QIODevice::Text)) {
- if (QString::fromUtf8(files[type]->readLine()).toInt() == dayIndex) {
- mode |= QIODevice::Append;
- }
- files[type]->close();
- }
- } else {
- QDir().mkdir(cWorkingDir() + u"DebugLogs"_q);
- }
- }
- if (files[type]->open(mode)) {
- if (type != LogDataMain) {
- files[type]->write(((mode & QIODevice::Append)
- ? qsl("\
- ----------------------------------------------------------------\n\
- NEW LOGGING INSTANCE STARTED!!!\n\
- ----------------------------------------------------------------\n")
- : qsl("%1\n").arg(dayIndex)).toUtf8());
- files[type]->flush();
- }
- return true;
- } else if (type != LogDataMain) {
- LOG(("Could not open debug log '%1': %2").arg(files[type]->fileName(), files[type]->errorString()));
- }
- return false;
- }
- void reopenDebug() {
- time_t t = time(NULL);
- struct tm tm;
- mylocaltime(&tm, &t);
- static const int switchEach = 15; // minutes
- int32 newPart = (tm.tm_min + tm.tm_hour * 60) / switchEach;
- if (newPart == part) return;
- part = newPart;
- int32 dayIndex = (tm.tm_year + 1900) * 10000 + (tm.tm_mon + 1) * 100 + tm.tm_mday;
- QString postfix = QString("_%4_%5").arg((part * switchEach) / 60, 2, 10, QChar('0')).arg((part * switchEach) % 60, 2, 10, QChar('0'));
- reopen(LogDataDebug, dayIndex, postfix);
- reopen(LogDataTcp, dayIndex, postfix);
- reopen(LogDataMtp, dayIndex, postfix);
- }
- };
- LogsDataFields *LogsData = 0;
- using LogsInMemoryList = QList<QPair<LogDataType, QString>>;
- LogsInMemoryList *LogsInMemory = 0;
- LogsInMemoryList *DeletedLogsInMemory = SharedMemoryLocation<LogsInMemoryList, 0>();
- QString LogsBeforeSingleInstanceChecked; // LogsInMemory already dumped in LogsData, but LogsData is about to be deleted
- void _logsWrite(LogDataType type, const QString &msg) {
- if (LogsData && (type == LogDataMain || LogsStartIndexChosen < 0)) {
- if (type == LogDataMain || Logs::DebugEnabled()) {
- LogsData->write(type, msg);
- }
- } else if (LogsInMemory != DeletedLogsInMemory) {
- if (!LogsInMemory) {
- LogsInMemory = new LogsInMemoryList;
- }
- LogsInMemory->push_back(qMakePair(type, msg));
- } else if (!LogsBeforeSingleInstanceChecked.isEmpty() && type == LogDataMain) {
- LogsBeforeSingleInstanceChecked += msg;
- }
- }
- namespace Logs {
- namespace {
- bool DebugModeEnabled = false;
- [[maybe_unused]] void MoveOldDataFiles(const QString &wasDir) {
- if (wasDir.isEmpty()) {
- return;
- }
- QFile data(wasDir + "data"), dataConfig(wasDir + "data_config"), tdataConfig(wasDir + "tdata/config");
- if (data.exists() && dataConfig.exists() && !QFileInfo::exists(cWorkingDir() + "data") && !QFileInfo::exists(cWorkingDir() + "data_config")) { // move to home dir
- LOG(("Copying data to home dir '%1' from '%2'").arg(cWorkingDir(), wasDir));
- if (data.copy(cWorkingDir() + "data")) {
- LOG(("Copied 'data' to home dir"));
- if (dataConfig.copy(cWorkingDir() + "data_config")) {
- LOG(("Copied 'data_config' to home dir"));
- bool tdataGood = true;
- if (tdataConfig.exists()) {
- tdataGood = false;
- QDir().mkpath(cWorkingDir() + "tdata");
- if (tdataConfig.copy(cWorkingDir() + "tdata/config")) {
- LOG(("Copied 'tdata/config' to home dir"));
- tdataGood = true;
- } else {
- LOG(("Copied 'data' and 'data_config', but could not copy 'tdata/config'!"));
- }
- }
- if (tdataGood) {
- if (data.remove()) {
- LOG(("Removed 'data'"));
- } else {
- LOG(("Could not remove 'data'"));
- }
- if (dataConfig.remove()) {
- LOG(("Removed 'data_config'"));
- } else {
- LOG(("Could not remove 'data_config'"));
- }
- if (!tdataConfig.exists() || tdataConfig.remove()) {
- LOG(("Removed 'tdata/config'"));
- } else {
- LOG(("Could not remove 'tdata/config'"));
- }
- QDir().rmdir(wasDir + "tdata");
- }
- } else {
- LOG(("Copied 'data', but could not copy 'data_config'!!"));
- }
- } else {
- LOG(("Could not copy 'data'!"));
- }
- }
- }
- } // namespace
- void SetDebugEnabled(bool enabled) {
- DebugModeEnabled = enabled;
- }
- bool DebugEnabled() {
- return DebugModeEnabled;
- }
- bool WritingEntry() {
- return WritingEntryFlag;
- }
- void start() {
- Assert(LogsData == nullptr);
- auto &launcher = Core::Launcher::Instance();
- if (!launcher.checkPortableVersionFolder()) {
- return;
- }
- LogsData = new LogsDataFields();
- if (cWorkingDir().isEmpty()) {
- #if (!defined Q_OS_WIN && !defined _DEBUG) || defined Q_OS_WINRT || defined OS_WIN_STORE || defined OS_MAC_STORE
- cForceWorkingDir(psAppDataPath());
- #else // (!Q_OS_WIN && !_DEBUG) || Q_OS_WINRT || OS_WIN_STORE || OS_MAC_STORE
- cForceWorkingDir(cExeDir());
- if (!LogsData->openMain()) {
- cForceWorkingDir(psAppDataPath());
- }
- #endif // (!Q_OS_WIN && !_DEBUG) || Q_OS_WINRT || OS_WIN_STORE || OS_MAC_STORE
- }
- if (launcher.validateCustomWorkingDir()) {
- delete LogsData;
- LogsData = new LogsDataFields();
- }
- // WinRT build requires the working dir to stay the same for plugin loading.
- #ifndef Q_OS_WINRT
- QDir::setCurrent(cWorkingDir());
- #endif // !Q_OS_WINRT
- QDir().mkpath(cWorkingDir() + u"tdata"_q);
- launcher.workingFolderReady();
- CrashReports::StartCatching();
- if (!LogsData->openMain()) {
- delete LogsData;
- LogsData = nullptr;
- }
- LOG(("Launched version: %1, install beta: %2, alpha: %3, debug mode: %4"
- ).arg(AppVersion
- ).arg(Logs::b(cInstallBetaVersion())
- ).arg(cAlphaVersion()
- ).arg(Logs::b(DebugEnabled())));
- LOG(("Executable dir: %1, name: %2").arg(cExeDir(), cExeName()));
- LOG(("Initial working dir: %1").arg(launcher.initialWorkingDir()));
- LOG(("Working dir: %1").arg(cWorkingDir()));
- LOG(("Command line: %1").arg(launcher.arguments().join(' ')));
- if (!LogsData) {
- LOG(("FATAL: Could not open '%1' for writing log!"
- ).arg(_logsFilePath(LogDataMain, u"_startXX"_q)));
- return;
- }
- #ifdef Q_OS_WIN
- if (cWorkingDir() == psAppDataPath()) { // fix old "Telegram Win (Unofficial)" version
- MoveOldDataFiles(psAppDataPathOld());
- }
- #elif !defined Q_OS_MAC && !defined _DEBUG // fix first version
- MoveOldDataFiles(launcher.initialWorkingDir());
- #endif
- if (LogsInMemory) {
- Assert(LogsInMemory != DeletedLogsInMemory);
- LogsInMemoryList list = *LogsInMemory;
- for (LogsInMemoryList::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) {
- if (i->first == LogDataMain) {
- _logsWrite(i->first, i->second);
- }
- }
- }
- LOG(("Logs started"));
- }
- void finish() {
- delete LogsData;
- LogsData = 0;
- if (LogsInMemory && LogsInMemory != DeletedLogsInMemory) {
- delete LogsInMemory;
- }
- LogsInMemory = DeletedLogsInMemory;
- _logsMutex(LogDataMain, true);
- CrashReports::FinishCatching();
- }
- bool started() {
- return LogsData != 0;
- }
- bool instanceChecked() {
- if (!LogsData) return false;
- if (!LogsData->instanceChecked()) {
- LogsBeforeSingleInstanceChecked = Logs::full();
- delete LogsData;
- LogsData = 0;
- LOG(("FATAL: Could not move logging to '%1'!").arg(_logsFilePath(LogDataMain)));
- return false;
- }
- if (LogsInMemory) {
- Assert(LogsInMemory != DeletedLogsInMemory);
- LogsInMemoryList list = *LogsInMemory;
- for (LogsInMemoryList::const_iterator i = list.cbegin(), e = list.cend(); i != e; ++i) {
- if (i->first != LogDataMain) {
- _logsWrite(i->first, i->second);
- }
- }
- }
- if (LogsInMemory) {
- Assert(LogsInMemory != DeletedLogsInMemory);
- delete LogsInMemory;
- }
- LogsInMemory = DeletedLogsInMemory;
- DEBUG_LOG(("Debug logs started."));
- LogsBeforeSingleInstanceChecked.clear();
- return true;
- }
- void multipleInstances() {
- if (LogsInMemory) {
- Assert(LogsInMemory != DeletedLogsInMemory);
- delete LogsInMemory;
- }
- LogsInMemory = DeletedLogsInMemory;
- if (Logs::DebugEnabled()) {
- LOG(("WARNING: debug logs are not written in multiple instances mode!"));
- }
- LogsBeforeSingleInstanceChecked.clear();
- }
- void closeMain() {
- LOG(("Explicitly closing main log and finishing crash handlers."));
- if (LogsData) {
- LogsData->closeMain();
- }
- }
- void writeMain(const QString &v) {
- time_t t = time(NULL);
- struct tm tm;
- mylocaltime(&tm, &t);
- const auto msg = QString("[%1.%2.%3 %4:%5:%6] %7\n"
- ).arg(tm.tm_year + 1900
- ).arg(tm.tm_mon + 1, 2, 10, QChar('0')
- ).arg(tm.tm_mday, 2, 10, QChar('0')
- ).arg(tm.tm_hour, 2, 10, QChar('0')
- ).arg(tm.tm_min, 2, 10, QChar('0')
- ).arg(tm.tm_sec, 2, 10, QChar('0')
- ).arg(v);
- _logsWrite(LogDataMain, msg);
- writeDebug(v);
- }
- void writeDebug(const QString &v) {
- const auto msg = QString("%1 %2\n").arg(_logsEntryStart(), v);
- _logsWrite(LogDataDebug, msg);
- #ifdef Q_OS_WIN
- //OutputDebugString(reinterpret_cast<const wchar_t *>(msg.utf16()));
- #elif defined Q_OS_MAC
- //objc_outputDebugString(msg);
- #elif defined _DEBUG
- //std::cout << msg.toUtf8().constData();
- #endif
- }
- void writeTcp(const QString &v) {
- const auto msg = QString("%1 %2\n").arg(_logsEntryStart(), v);
- _logsWrite(LogDataTcp, msg);
- }
- void writeMtp(int32 dc, const QString &v) {
- const auto expanded = [&] {
- const auto bare = MTP::isTemporaryDcId(dc)
- ? MTP::getRealIdFromTemporaryDcId(dc)
- : MTP::BareDcId(dc);
- const auto base = (MTP::isTemporaryDcId(dc) ? "temporary_" : "")
- + QString::number(bare);
- const auto shift = MTP::GetDcIdShift(dc);
- if (shift == 0) {
- return base + "_main";
- } else if (shift == MTP::kExportDcShift) {
- return base + "_export";
- } else if (shift == MTP::kExportMediaDcShift) {
- return base + "_export_download";
- } else if (shift == MTP::kConfigDcShift) {
- return base + "_config_enumeration";
- } else if (shift == MTP::kLogoutDcShift) {
- return base + "_logout_guest";
- } else if (shift == MTP::kUpdaterDcShift) {
- return base + "_download_update";
- } else if (shift == MTP::kGroupCallStreamDcShift) {
- return base + "_stream";
- } else if (MTP::isDownloadDcId(dc)) {
- const auto index = shift - MTP::kBaseDownloadDcShift;
- return base + "_download" + QString::number(index);
- } else if (MTP::isUploadDcId(dc)) {
- const auto index = shift - MTP::kBaseUploadDcShift;
- return base + "_upload" + QString::number(index);
- } else if (shift >= MTP::kDestroyKeyStartDcShift) {
- const auto index = shift - MTP::kDestroyKeyStartDcShift;
- return base + "_key_destroyer" + QString::number(index);
- }
- return base + "_unknown" + QString::number(shift);
- }();
- const auto msg = _logsEntryStart()
- + u" (dc:%1) "_q.arg(expanded)
- + v
- + '\n';
- _logsWrite(LogDataMtp, msg);
- }
- QString full() {
- if (LogsData) {
- return LogsData->full();
- }
- if (!LogsInMemory || LogsInMemory == DeletedLogsInMemory) {
- return LogsBeforeSingleInstanceChecked;
- }
- int32 size = LogsBeforeSingleInstanceChecked.size();
- for (LogsInMemoryList::const_iterator i = LogsInMemory->cbegin(), e = LogsInMemory->cend(); i != e; ++i) {
- if (i->first == LogDataMain) {
- size += i->second.size();
- }
- }
- QString result;
- result.reserve(size);
- if (!LogsBeforeSingleInstanceChecked.isEmpty()) {
- result.append(LogsBeforeSingleInstanceChecked);
- }
- for (LogsInMemoryList::const_iterator i = LogsInMemory->cbegin(), e = LogsInMemory->cend(); i != e; ++i) {
- if (i->first == LogDataMain) {
- result += i->second;
- }
- }
- return result;
- }
- } // namespace Logs
|