diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index dcdfef8a1..10f3b2266 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -80,7 +80,6 @@ $(package)_config_opts += -no-feature-concurrent $(package)_config_opts += -no-feature-sql $(package)_config_opts += -no-feature-statemachine $(package)_config_opts += -no-feature-syntaxhighlighter -$(package)_config_opts += -no-feature-textbrowser $(package)_config_opts += -no-feature-textodfwriter $(package)_config_opts += -no-feature-udpsocket $(package)_config_opts += -no-feature-xml diff --git a/src/init.cpp b/src/init.cpp index 72bfa6fe7..d341088df 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -324,7 +324,7 @@ void HandleSIGTERM(int) void HandleSIGHUP(int) { - fReopenDebugLog = true; + fReopenLogFiles = true; } bool static Bind(const CService &addr, unsigned int flags) { diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index d80c6a639..663687e33 100755 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -1582,6 +1582,42 @@ + + + &Error Log + + + + 3 + + + 5 + + + + + + 100 + 100 + + + + true + + + + + + + Copy Error Log contents to Clipboard + + + Copy to Clipboard + + + + + diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 52561bc35..d437701b1 100755 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -8,6 +8,12 @@ /* Milliseconds between model updates */ static const int MODEL_UPDATE_DELAY = 250; +/* Milliseconds between error log refreshes */ +static const int ERROR_LOG_UPDATE_DELAY = 2500; + +/* Initial number of debug logs to parse error log entries from */ +static const int ERROR_LOG_INITIAL_COUNT = 500; + /* AskPassphraseDialog -- Maximum passphrase length */ static const int MAX_PASSPHRASE_SIZE = 1024; diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 08baf8f84..e7e12e1a4 100755 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -12,6 +12,7 @@ #include "bantablemodel.h" #include "clientmodel.h" #include "guiutil.h" +#include "guiconstants.h" #include "platformstyle.h" #include "bantablemodel.h" @@ -28,16 +29,25 @@ #include #endif +#include #include #include #include #include #include + +#include #include +#include +#include #include #include #include +#if QT_VERSION < 0x050000 +#include +#endif + // TODO: add a scrollback limit, as there is currently none // TODO: make it possible to filter out categories (esp debug messages when implemented) // TODO: receive errors and debug messages through ClientModel @@ -290,6 +300,11 @@ RPCConsole::RPCConsole(const PlatformStyle *platformStyle, QWidget *parent) : ui->detailWidget->hide(); ui->peerHeading->setText(tr("Select a peer to view detailed information.")); + // set up timer for auto refresh for error log + errorLogTimer = new QTimer(); + connect(errorLogTimer, SIGNAL(timeout()), SLOT(errorLogRefresh())); + errorLogTimer->setInterval(ERROR_LOG_UPDATE_DELAY); + QSettings settings; consoleFontSize = settings.value(fontSizeSettingsKey, QFontInfo(QFont()).pointSize()).toInt(); clear(); @@ -300,10 +315,115 @@ RPCConsole::~RPCConsole() GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this); Q_EMIT stopExecutor(); RPCUnsetTimerInterface(rpcTimerInterface); + errorLogFile->close(); delete rpcTimerInterface; delete ui; } +void RPCConsole::errorLogInitPos() +{ + // Check if we already have the file + if (errorLogFile != NULL) { + // Get a QFile instance + errorLogFile = new QFile(QString::fromStdString(GetErrorLogPath().string())); + } + + // Try to open file + if (!errorLogFile->open(QFile::ReadOnly | QFile::Text)) + return; + + // Seek to the end of file + errorLogFile->seek(errorLogFile->size() - 1); + + // We need to move the file pos back by ERROR_LOG_INITIAL_COUNT lines + QString ch; + int lineCount = 0; + while ((lineCount < ERROR_LOG_INITIAL_COUNT) && (errorLogFile->pos() > 0)) + { + // Load the current character + ch = errorLogFile->read(1); + + // Move pos back by 2 spaces + errorLogFile->seek(errorLogFile->pos() - 2); + + // Check if we have a newline + if (ch == "\n") + lineCount++; // Count it + } + + // Move pos forward by 2 spaces + errorLogFile->seek(errorLogFile->pos() + 2); + + // Clear the textarea + ui->errorLogTextBrowser->setText(""); + + // Mark init as done + errorLogInitPosDone = true; +} + +void RPCConsole::errorLogRefresh() +{ + // Check if we are still refreshing + if (errorLogRefreshing) + return; + + // Set to refreshing + errorLogRefreshing = true; + + // Check if we have initialized debug log already + if (!errorLogInitPosDone) + errorLogInitPos(); + + // Load the stream + QTextStream in(errorLogFile); + + // Load up the lines + QString logLines = ""; + while (!in.atEnd()) { + // Load the next line + logLines += in.readLine() + "\n"; + } + + // Check if we have lines + if (logLines != "") { + // Add the new lines, purpose of duplicate moveCursor calls is + // to auto scroll to the end of the log instead of sticking to the + // top of the text area + ui->errorLogTextBrowser->moveCursor(QTextCursor::End); + ui->errorLogTextBrowser->textCursor().insertText(logLines); + ui->errorLogTextBrowser->moveCursor(QTextCursor::End); + } + + // Count the lines in the UI textarea + int uiLineCount = ui->errorLogTextBrowser->document()->lineCount(); + + // Check if lines are more than ERROR_LOG_INITIAL_COUNT + if (uiLineCount > ERROR_LOG_INITIAL_COUNT) { + // Count how many to remove + int lineCountDiff = uiLineCount - ERROR_LOG_INITIAL_COUNT; + + // Get our cursor + QTextCursor cursor = ui->errorLogTextBrowser->textCursor(); + + // REMOVE THEM + for (int i = 0; i < lineCountDiff; i++) { + cursor.movePosition(QTextCursor::Start); + cursor.select(QTextCursor::LineUnderCursor); + cursor.deleteChar(); // Remove the selected text + cursor.deleteChar(); // This is by design, this removes the \n + } + + // Replace the cursor + ui->errorLogTextBrowser->setTextCursor(cursor); + + // Move cursor back to the end + ui->errorLogTextBrowser->moveCursor(QTextCursor::End); + } + + // Mark as done + errorLogRefreshing = false; +} + bool RPCConsole::eventFilter(QObject* obj, QEvent *event) { if(event->type() == QEvent::KeyPress) // Special key handling @@ -672,6 +792,11 @@ void RPCConsole::on_tabWidget_currentChanged(int index) clearSelectedNode(); } +void RPCConsole::on_errorLogCopyClipboardButton_clicked() +{ + GUIUtil::setClipboard(ui->errorLogTextBrowser->toPlainText()); +} + void RPCConsole::on_openDebugLogfileButton_clicked() { GUIUtil::openDebugLogfile(); @@ -837,6 +962,15 @@ void RPCConsole::showEvent(QShowEvent *event) { QWidget::showEvent(event); + // Mark init as not done + errorLogInitPosDone = false; + + // Load error log initial data + errorLogRefresh(); + + // Start the error log timer + errorLogTimer->start(); + if (!clientModel || !clientModel->getPeerTableModel()) return; @@ -848,6 +982,9 @@ void RPCConsole::hideEvent(QHideEvent *event) { QWidget::hideEvent(event); + // Stop the error log timer + errorLogTimer->stop(); + if (!clientModel || !clientModel->getPeerTableModel()) return; diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index f0bca10eb..133bf1fae 100755 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -10,8 +10,11 @@ #include "net.h" -#include #include +#include +#include +#include +#include class ClientModel; class PlatformStyle; @@ -61,6 +64,8 @@ private Q_SLOTS: void on_tabWidget_currentChanged(int index); /** open the debug.log from the current datadir */ void on_openDebugLogfileButton_clicked(); + /** copy the error log text from errorLogTextBrowser */ + void on_errorLogCopyClipboardButton_clicked(); /** change the time range of the network traffic graph */ void on_sldGraphRange_valueChanged(int value); /** update traffic statistics */ @@ -106,6 +111,10 @@ public Q_SLOTS: void unbanSelectedNode(); /** set which tab has the focus (is visible) */ void setTabFocus(enum TabTypes tabType); + /** load the error log initial file pos */ + void errorLogInitPos(); + /** update the error log */ + void errorLogRefresh(); Q_SIGNALS: // For RPC command executor @@ -140,6 +149,10 @@ public Q_SLOTS: QMenu *banTableContextMenu; int consoleFontSize; QCompleter *autoCompleter; + QTimer *errorLogTimer; + QFile *errorLogFile; + bool errorLogInitPosDone = false; + bool errorLogRefreshing = false; }; #endif // NAVCOIN_QT_RPCCONSOLE_H diff --git a/src/util.cpp b/src/util.cpp index e435e4999..fc21a9775 100755 --- a/src/util.cpp +++ b/src/util.cpp @@ -122,7 +122,7 @@ string strMiscWarning; bool fLogTimestamps = DEFAULT_LOGTIMESTAMPS; bool fLogTimeMicros = DEFAULT_LOGTIMEMICROS; bool fLogIPs = DEFAULT_LOGIPS; -std::atomic fReopenDebugLog(false); +std::atomic fReopenLogFiles(false); CTranslationInterface translationInterface; /** Init OpenSSL library multithreading support */ @@ -188,51 +188,91 @@ instance_of_cinit; */ static boost::once_flag debugPrintInitFlag = BOOST_ONCE_INIT; +static boost::once_flag errorPrintInitFlag = BOOST_ONCE_INIT; /** * We use boost::call_once() to make sure mutexDebugLog and - * vMsgsBeforeOpenLog are initialized in a thread-safe manner. + * vMsgsBeforeOpenDebugLog are initialized in a thread-safe manner. * - * NOTE: fileout, mutexDebugLog and sometimes vMsgsBeforeOpenLog - * are leaked on exit. This is ugly, but will be cleaned up by - * the OS/libc. When the shutdown sequence is fully audited and + * NOTE: fileoutDebugLog, fileoutErrorLog, mutexDebugLog and sometimes + * vMsgsBeforeOpenDebugLog are leaked on exit. This is ugly, but will be cleaned + * up by the OS/libc. When the shutdown sequence is fully audited and * tested, explicit destruction of these objects can be implemented. */ -static FILE* fileout = NULL; +static FILE* fileoutDebugLog = NULL; +static FILE* fileoutErrorLog = NULL; static boost::mutex* mutexDebugLog = NULL; -static list *vMsgsBeforeOpenLog; +static boost::mutex* mutexErrorLog = NULL; +static list *vMsgsBeforeOpenDebugLog; +static list *vMsgsBeforeOpenErrorLog; static int FileWriteStr(const std::string &str, FILE *fp) { return fwrite(str.data(), 1, str.size(), fp); } +static int DebugLogWriteStr(const std::string &str) +{ + // Return the int from the write size + return FileWriteStr(str, fileoutDebugLog); // write to debug log +} + +static int ErrorLogWriteStr(const std::string &str) +{ + // Return the int from the write size + return FileWriteStr(str, fileoutErrorLog); // write to error log +} + static void DebugPrintInit() { assert(mutexDebugLog == NULL); mutexDebugLog = new boost::mutex(); - vMsgsBeforeOpenLog = new list; + vMsgsBeforeOpenDebugLog = new list; +} + +static void ErrorPrintInit() +{ + assert(mutexErrorLog == NULL); + mutexErrorLog = new boost::mutex(); + vMsgsBeforeOpenErrorLog = new list; } void OpenDebugLog() { boost::call_once(&DebugPrintInit, debugPrintInitFlag); - boost::mutex::scoped_lock scoped_lock(*mutexDebugLog); + boost::call_once(&ErrorPrintInit, errorPrintInitFlag); + boost::mutex::scoped_lock scoped_lock_debug(*mutexDebugLog); + boost::mutex::scoped_lock scoped_lock_error(*mutexErrorLog); + + assert(fileoutDebugLog == NULL); + assert(fileoutErrorLog == NULL); + assert(vMsgsBeforeOpenDebugLog); + assert(vMsgsBeforeOpenErrorLog); - assert(fileout == NULL); - assert(vMsgsBeforeOpenLog); - boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; - fileout = fopen(pathDebug.string().c_str(), "a"); - if (fileout) setbuf(fileout, NULL); // unbuffered + // Open the debug log + fileoutDebugLog = fopen(GetDebugLogPath().string().c_str(), "a"); + if (fileoutDebugLog) setbuf(fileoutDebugLog, NULL); // unbuffered + + // Open the error log + fileoutErrorLog = fopen(GetErrorLogPath().string().c_str(), "a"); + if (fileoutErrorLog) setbuf(fileoutErrorLog, NULL); // unbuffered // dump buffered messages from before we opened the log - while (!vMsgsBeforeOpenLog->empty()) { - FileWriteStr(vMsgsBeforeOpenLog->front(), fileout); - vMsgsBeforeOpenLog->pop_front(); + while (!vMsgsBeforeOpenDebugLog->empty()) { + DebugLogWriteStr(vMsgsBeforeOpenDebugLog->front()); // write to log + vMsgsBeforeOpenDebugLog->pop_front(); } - delete vMsgsBeforeOpenLog; - vMsgsBeforeOpenLog = NULL; + // dump buffered messages from before we opened the log + while (!vMsgsBeforeOpenErrorLog->empty()) { + ErrorLogWriteStr(vMsgsBeforeOpenErrorLog->front()); // write to log + vMsgsBeforeOpenErrorLog->pop_front(); + } + + delete vMsgsBeforeOpenDebugLog; + delete vMsgsBeforeOpenErrorLog; + vMsgsBeforeOpenDebugLog = NULL; + vMsgsBeforeOpenErrorLog = NULL; } bool LogAcceptCategory(const char* category) @@ -261,6 +301,7 @@ bool LogAcceptCategory(const char* category) setCategories.count(string(category)) == 0) return false; } + return true; } @@ -293,7 +334,17 @@ static std::string LogTimestampStr(const std::string &str, bool *fStartedNewLine return strStamped; } -int LogPrintStr(const std::string &str) +boost::filesystem::path GetDebugLogPath() +{ + return GetDataDir() / "debug.log"; +} + +boost::filesystem::path GetErrorLogPath() +{ + return GetDataDir() / "error.log"; +} + +int DebugLogPrintStr(const std::string &str) { int ret = 0; // Returns total number of characters written static bool fStartedNewLine = true; @@ -309,27 +360,69 @@ int LogPrintStr(const std::string &str) else if (fPrintToDebugLog) { boost::call_once(&DebugPrintInit, debugPrintInitFlag); - boost::mutex::scoped_lock scoped_lock(*mutexDebugLog); + boost::mutex::scoped_lock scoped_lock_debug(*mutexDebugLog); // buffer if we haven't opened the log yet - if (fileout == NULL) { - assert(vMsgsBeforeOpenLog); + if (fileoutDebugLog == NULL) { + assert(vMsgsBeforeOpenDebugLog); ret = strTimestamped.length(); - vMsgsBeforeOpenLog->push_back(strTimestamped); + vMsgsBeforeOpenDebugLog->push_back(strTimestamped); } else { // reopen the log file, if requested - if (fReopenDebugLog) { - fReopenDebugLog = false; - boost::filesystem::path pathDebug = GetDataDir() / "debug.log"; - if (freopen(pathDebug.string().c_str(),"a",fileout) != NULL) - setbuf(fileout, NULL); // unbuffered + if (fReopenLogFiles) { + fReopenLogFiles = false; + + // Open the log files + OpenDebugLog(); } - ret = FileWriteStr(strTimestamped, fileout); + ret = DebugLogWriteStr(strTimestamped); } } + + return ret; +} + +int ErrorLogPrintStr(const std::string &str) +{ + int ret = 0; // Returns total number of characters written + static bool fStartedNewLine = true; + + string strTimestamped = LogTimestampStr(str, &fStartedNewLine); + + if (fPrintToConsole) + { + // print to console + ret = fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout); + fflush(stdout); + } + else if (fPrintToDebugLog) + { + boost::call_once(&ErrorPrintInit, errorPrintInitFlag); + boost::mutex::scoped_lock scoped_lock_error(*mutexErrorLog); + + // buffer if we haven't opened the log yet + if (fileoutErrorLog == NULL) { + assert(vMsgsBeforeOpenErrorLog); + ret = strTimestamped.length(); + vMsgsBeforeOpenErrorLog->push_back(strTimestamped); + } + else + { + // reopen the log file, if requested + if (fReopenLogFiles) { + fReopenLogFiles = false; + + // Open the log files + OpenDebugLog(); + } + + ret = ErrorLogWriteStr(strTimestamped); + } + } + return ret; } @@ -742,19 +835,19 @@ bool TryCreateDirectory(const boost::filesystem::path& p) return false; } -void FileCommit(FILE *fileout) +void FileCommit(FILE *file) { - fflush(fileout); // harmless if redundantly called + fflush(file); // harmless if redundantly called #ifdef WIN32 - HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(fileout)); + HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); FlushFileBuffers(hFile); #else #if defined(__linux__) || defined(__NetBSD__) - fdatasync(fileno(fileout)); + fdatasync(fileno(file)); #elif defined(__APPLE__) && defined(F_FULLFSYNC) - fcntl(fileno(fileout), F_FULLFSYNC, 0); + fcntl(fileno(file), F_FULLFSYNC, 0); #else - fsync(fileno(fileout)); + fsync(fileno(file)); #endif #endif } @@ -837,11 +930,16 @@ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { } void ShrinkDebugFile() +{ + ShrinkDebugFile(GetDebugLogPath(), 10); // Shrink the debug log + ShrinkDebugFile(GetErrorLogPath(), 2); // Shrink the error log +} + +void ShrinkDebugFile(boost::filesystem::path pathLog, int maxSize) { // Scroll debug.log if it's getting too big - boost::filesystem::path pathLog = GetDataDir() / "debug.log"; FILE* file = fopen(pathLog.string().c_str(), "r"); - if (file && boost::filesystem::file_size(pathLog) > 10 * 1000000) + if (file && boost::filesystem::file_size(pathLog) > maxSize * 1000000) { // Restart the file with some of the end std::vector vch(200000,0); diff --git a/src/util.h b/src/util.h index 575c7ad5f..0a6473538 100755 --- a/src/util.h +++ b/src/util.h @@ -51,7 +51,7 @@ extern std::string strMiscWarning; extern bool fLogTimestamps; extern bool fLogTimeMicros; extern bool fLogIPs; -extern std::atomic fReopenDebugLog; +extern std::atomic fReopenLogFiles; extern CTranslationInterface translationInterface; extern const char * const NAVCOIN_CONF_FILENAME; @@ -72,8 +72,18 @@ bool SetupNetworking(); /** Return true if log accepts specified category */ bool LogAcceptCategory(const char* category); -/** Send a string to the log output */ -int LogPrintStr(const std::string &str); + +/** Returns the path to the debug.log file */ +boost::filesystem::path GetDebugLogPath(); + +/** Returns the path to the error.log file */ +boost::filesystem::path GetErrorLogPath(); + +/** Send a string to the debug log output */ +int DebugLogPrintStr(const std::string &str); + +/** Send a string to the error log output */ +int ErrorLogPrintStr(const std::string &str); #define LogPrintf(...) LogPrint(NULL, __VA_ARGS__) @@ -87,14 +97,14 @@ static inline int LogPrint(const char* category, const char* fmt, const T1& v1, } catch (std::runtime_error &e) { _log_msg_ = "Error \"" + std::string(e.what()) + "\" while formatting log message: " + fmt; } - return LogPrintStr(_log_msg_); + return DebugLogPrintStr(_log_msg_); } template bool error(const char* fmt, const T1& v1, const Args&... args) { - LogPrintStr("ERROR: " + tfm::format(fmt, v1, args...) + "\n"); + ErrorLogPrintStr("ERROR: " + tfm::format(fmt, v1, args...) + "\n"); return false; } @@ -106,11 +116,11 @@ bool error(const char* fmt, const T1& v1, const Args&... args) static inline int LogPrint(const char* category, const char* s) { if(!LogAcceptCategory(category)) return 0; - return LogPrintStr(s); + return DebugLogPrintStr(s); } static inline bool error(const char* s) { - LogPrintStr(std::string("ERROR: ") + s + "\n"); + ErrorLogPrintStr(std::string("ERROR: ") + s + "\n"); return false; } static inline bool error(std::string s) @@ -119,7 +129,7 @@ static inline bool error(std::string s) } void PrintExceptionContinue(const std::exception *pex, const char* pszThread); void ParseParameters(int argc, const char*const argv[]); -void FileCommit(FILE *fileout); +void FileCommit(FILE *file); bool TruncateFile(FILE *file, unsigned int length); int RaiseFileDescriptorLimit(int nMinFD); void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); @@ -143,6 +153,7 @@ boost::filesystem::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif void OpenDebugLog(); void ShrinkDebugFile(); +void ShrinkDebugFile(boost::filesystem::path pathLog, int maxSize); void runCommand(const std::string& strCommand); inline bool IsSwitchChar(char c)