1 /*
2  * Copyright (C) 2019-2021 Ashar Khan <ashar786khan@gmail.com>
3  *
4  * This file is part of CP Editor.
5  *
6  * CP Editor is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * I will not be responsible if CP Editor behaves in unexpected way and
12  * causes your ratings to go down and or lose any important contest.
13  *
14  * Believe Software is "Software" and it isn't immune to bugs.
15  *
16  */
17 
18 #include "Core/EventLogger.hpp"
19 #include "Util/FileUtil.hpp"
20 #include "generated/portable.hpp"
21 #include "generated/version.hpp"
22 #include <QDateTime>
23 #include <QDesktopServices>
24 #include <QDir>
25 #include <QFile>
26 #include <QLibraryInfo>
27 #include <QProcess>
28 #include <QStandardPaths>
29 #include <QSysInfo>
30 #include <QUrl>
31 
32 namespace Core
33 {
34 
35 QFile Log::logFile;
36 QTextStream Log::logStream;
37 
38 const int Log::NUMBER_OF_LOGS_TO_KEEP = 50;
39 const int Log::MAXIMUM_FUNCTION_NAME_SIZE = 30;
40 const int Log::MAXIMUM_FILE_NAME_SIZE = 30;
41 const QString Log::LOG_DIR_NAME = "cpeditorLogFiles";
42 const QString Log::LOG_FILE_NAME = "cpeditor";
43 
init(int instance,bool dumptoStderr)44 void Log::init(int instance, bool dumptoStderr)
45 {
46     logStream.setDevice(&logFile);
47     if (!dumptoStderr)
48     {
49         // get the path to the log file
50         auto path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
51         LOG_ERR_IF(path.isEmpty(), "Failed to get writable temp location");
52 
53         QDir dir(path);
54         dir.mkdir(LOG_DIR_NAME);
55         if (dir.cd(LOG_DIR_NAME))
56         {
57             // keep NUMBER_OF_LOGS_TO_KEEP log files
58             auto entries = dir.entryList({LOG_FILE_NAME + "*.log"}, QDir::Files, QDir::Time);
59             for (int i = NUMBER_OF_LOGS_TO_KEEP; i < entries.length(); ++i)
60                 dir.remove(entries[i]);
61 
62             // open the log file
63             logFile.setFileName(dir.filePath(QString("%1-%2-%3.log")
64                                                  .arg(LOG_FILE_NAME)
65                                                  .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hh-mm-ss-zzz"))
66                                                  .arg(instance)));
67             logFile.open(QIODevice::WriteOnly | QFile::Text);
68             LOG_ERR_IF(!logFile.isOpen() || !logFile.isWritable(), "Failed to open file" << logFile.fileName());
69         }
70         else
71         {
72             LOG_ERR("Failed to open directory" << dir.filePath(LOG_DIR_NAME));
73         }
74     }
75     else
76     {
77         logFile.open(stderr, QIODevice::WriteOnly);
78     }
79     LOG_INFO("Event logger has been initialized successfully");
80     platformInformation();
81 }
82 
dateTimeStamp()83 QString Log::dateTimeStamp()
84 {
85     return "[" + QDateTime::currentDateTime().toString(Qt::ISODateWithMs) + "]";
86 }
87 
platformInformation()88 void Log::platformInformation()
89 {
90     LOG_INFO("Gathering system information");
91     // Check https://doc.qt.io/qt-5/qsysinfo.html to know what each identifier means
92     LOG_INFO(INFO_OF(QSysInfo::buildAbi()));
93     LOG_INFO(INFO_OF(QSysInfo::buildCpuArchitecture()));
94     LOG_INFO(INFO_OF(QSysInfo::currentCpuArchitecture()));
95     LOG_INFO(INFO_OF(QSysInfo::kernelType()));
96     LOG_INFO(INFO_OF(QSysInfo::kernelVersion()));
97     LOG_INFO(INFO_OF(QSysInfo::prettyProductName()));
98     LOG_INFO(INFO_OF(QSysInfo::productType()));
99     LOG_INFO(INFO_OF(QSysInfo::productVersion()));
100     LOG_INFO(INFO_OF(QLibraryInfo::version().toString()));
101 
102     LOG_INFO(INFO_OF(APP_VERSION));
103     LOG_INFO(INFO_OF(DISPLAY_VERSION));
104     LOG_INFO(INFO_OF(GIT_COMMIT_HASH));
105 #ifdef PORTABLE_VERSION
106     LOG_INFO("Portable Version");
107 #else
108     LOG_INFO("Setup Version");
109 #endif
110 #ifdef QT_DEBUG
111     LOG_INFO("Debug Build");
112 #else
113     LOG_INFO("Release Build");
114 #endif
115     LOG_INFO(INFO_OF(__DATE__));
116     LOG_INFO(INFO_OF(__TIME__));
117 }
118 
log(const QString & priority,QString funcName,int line,QString fileName)119 QTextStream &Log::log(const QString &priority, QString funcName, int line, QString fileName)
120 {
121     if (!logFile.isOpen() || !logFile.isWritable())
122         logFile.open(stderr, QIODevice::WriteOnly); // dump to stderr if failed to open log file
123     if (funcName.size() > MAXIMUM_FUNCTION_NAME_SIZE)
124         funcName = funcName.right(MAXIMUM_FUNCTION_NAME_SIZE);
125 
126     QFileInfo info(fileName);
127     fileName = info.fileName();
128 
129     if (fileName.size() > MAXIMUM_FILE_NAME_SIZE)
130         fileName = fileName.right(MAXIMUM_FILE_NAME_SIZE);
131 
132     return logStream << dateTimeStamp() << Qt::center << "[" << priority << "]["
133                      << qSetFieldWidth(MAXIMUM_FUNCTION_NAME_SIZE) << funcName << qSetFieldWidth(0) << "]["
134                      << qSetFieldWidth(MAXIMUM_FILE_NAME_SIZE) << fileName << qSetFieldWidth(0) << Qt::left << "]"
135                      << "(" << line << ")::";
136 }
137 
revealInFileManager()138 void Log::revealInFileManager()
139 {
140     Util::revealInFileManager(logFile.fileName()).first();
141 }
142 
clearOldLogs()143 void Log::clearOldLogs()
144 {
145     auto path = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
146     QDir dir(path);
147     if (dir.cd(LOG_DIR_NAME))
148     {
149         auto entries = dir.entryList({LOG_FILE_NAME + "*.log"}, QDir::Files);
150         for (auto const &e : entries)
151         {
152             if (e != logFile.fileName()) // clear all except the current
153             {
154                 dir.remove(e);
155                 LOG_INFO("Deleted log file:" << e);
156             }
157         }
158     }
159 }
160 
161 } // namespace Core
162