diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 59fb4d81ab..c03366ecda 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -21,6 +21,16 @@ if (APPLE) target_link_libraries(${TARGET_NAME} ${FRAMEWORK_IOKIT} ${CORE_FOUNDATION} ${OpenGL}) endif() +if (UNIX AND NOT APPLE) + find_package(Journald) + + if (${JOURNALD_FOUND}) + target_link_libraries(${TARGET_NAME} ${JOURNALD_LIBRARIES}) + target_include_directories(${TARGET_NAME} PRIVATE ${JOURNALD_INCLUDE_DIR}) + target_compile_definitions(${TARGET_NAME} PUBLIC HAS_JOURNALD) + endif() +endif() + target_zlib() target_nsight() target_json() diff --git a/libraries/shared/src/LogHandler.cpp b/libraries/shared/src/LogHandler.cpp index f214613bfa..2149350e64 100644 --- a/libraries/shared/src/LogHandler.cpp +++ b/libraries/shared/src/LogHandler.cpp @@ -31,6 +31,10 @@ #include #include +#ifdef HAS_JOURNALD +#include +#endif + QRecursiveMutex LogHandler::_mutex; LogHandler& LogHandler::getInstance() { @@ -51,6 +55,10 @@ LogHandler::LogHandler() { } #endif +#ifdef HAS_JOURNALD + _useJournald = true; +#endif + auto optionList = logOptions.split(","); for (auto option : optionList) { @@ -68,6 +76,8 @@ LogHandler::LogHandler() { _shouldDisplayMilliseconds = true; } else if (option == "keep_repeats") { _keepRepeats = true; + } else if (option == "nojournald") { + _useJournald = false; } else if (option != "") { fprintf(stdout, "Unrecognized option in VIRCADIA_LOG_OPTIONS: '%s'\n", option.toUtf8().constData()); } @@ -142,6 +152,13 @@ void LogHandler::setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds) { _shouldDisplayMilliseconds = shouldDisplayMilliseconds; } +bool LogHandler::isJournaldAvailable() const { +#ifdef HAS_JOURNALD + return true; +#else + return false; +#endif +} void LogHandler::flushRepeatedMessages() { QMutexLocker lock(&_mutex); @@ -163,6 +180,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont if (message.isEmpty()) { return QString(); } + QMutexLocker lock(&_mutex); // log prefix is in the following format @@ -196,25 +214,73 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont } } + // This is returned from this function and wanted by the LogEntityServer, + // so we have to have it even when using journald. QString logMessage = QString("%1 %2\n").arg(prefixString, message.split('\n').join('\n' + prefixString + " ")); - const char* color = ""; - const char* resetColor = ""; - - if (_useColor) { - color = colorForLogType(type); - resetColor = colorReset(); - } - - if (_keepRepeats || _previousMessage != message) { - if (_repeatCount > 0) { - fprintf(stdout, "[Previous message was repeated %i times]\n", _repeatCount); + if ( _useJournald ) { +#ifdef HAS_JOURNALD + int priority = LOG_NOTICE; + switch(type) { + case LogMsgType::LogFatal: priority = LOG_EMERG; break; + case LogMsgType::LogCritical: priority = LOG_CRIT; break; + case LogMsgType::LogWarning: priority = LOG_WARNING; break; + case LogMsgType::LogInfo: priority = LOG_INFO; break; + case LogMsgType::LogDebug: priority = LOG_DEBUG; break; + case LogMsgType::LogSuppressed: priority = LOG_DEBUG; break; + default: + fprintf(stderr, "Unrecognized log type: %i", (int)type); } - fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor); - _repeatCount = 0; + size_t threadID = (size_t)QThread::currentThreadId(); + QString sd_file = QString("CODE_FILE=%1").arg(context.file); + QString sd_line = QString("CODE_LINE=%1").arg(context.line); + + int retval = sd_journal_send_with_location(sd_file.toUtf8().constData(), + sd_line.toUtf8().constData(), + context.function == NULL ? "(unknown)" : context.function, + "MESSAGE=%s", message.toUtf8().constData(), + "PRIORITY=%i", priority, + "CATEGORY=%s", context.category, + "TID=%i", threadID, + NULL); + + if ( retval != 0 ) { + fprintf(stderr, "Failed to log message, error %i: ", retval); + fprintf(stderr, "file=%s, line=%i, func=%s, prio=%i, msg=%s\n", + context.file, + context.line, + context.function, + priority, + message.toUtf8().constData() + ); + } +#endif } else { - _repeatCount++; + const char* color = ""; + const char* resetColor = ""; + + if (_useColor) { + color = colorForLogType(type); + resetColor = colorReset(); + } + + if (_keepRepeats || _previousMessage != message) { + if (_repeatCount > 0) { + fprintf(stdout, "[Previous message was repeated %i times]\n", _repeatCount); + } + + fprintf(stdout, "%s%s%s", color, qPrintable(logMessage), resetColor); + _repeatCount = 0; + } else { + _repeatCount++; + } + + _previousMessage = message; + #ifdef Q_OS_WIN + // On windows, this will output log lines into the Visual Studio "output" tab + OutputDebugStringA(qPrintable(logMessage)); + #endif } if ( !_breakMessages.empty() ) { @@ -229,6 +295,7 @@ QString LogHandler::printMessage(LogMsgType type, const QMessageLogContext& cont // On windows, this will output log lines into the Visual Studio "output" tab OutputDebugStringA(qPrintable(logMessage)); #endif + return logMessage; } @@ -275,4 +342,4 @@ void LogHandler::printRepeatedMessage(int messageID, LogMsgType type, const QMes void LogHandler::breakOnMessage(const char *message) { QMutexLocker lock(&_mutex); LogHandler::getInstance()._breakMessages.append(QString::fromUtf8(message)); -} \ No newline at end of file +} diff --git a/libraries/shared/src/LogHandler.h b/libraries/shared/src/LogHandler.h index 899e387288..54862e8e09 100644 --- a/libraries/shared/src/LogHandler.h +++ b/libraries/shared/src/LogHandler.h @@ -31,24 +31,82 @@ enum LogMsgType { LogSuppressed = 100 }; -/// Handles custom message handling and sending of stats/logs to Logstash instance +/// + +/** + * @brief Handles custom message handling and sending of stats/logs to Logstash instance + * + */ class LogHandler : public QObject { Q_OBJECT public: + /** + * @brief Returns the one instance of the LogHandler object + * + * @return LogHandler& + */ static LogHandler& getInstance(); - /// sets the target name to output via the verboseMessageHandler, called once before logging begins - /// \param targetName the desired target name to output in logs + /** + * @brief Set the Target Name to output via the verboseMessageHandler + * + * Called once before logging begins + * + * @param targetName the desired target name to output in logs + */ void setTargetName(const QString& targetName); + /** + * @brief Set whether to output the process ID + * + * @note This has no effect when logging with journald, the PID is always logged + * + * @param shouldOutputProcessID Whether to output the PID + */ void setShouldOutputProcessID(bool shouldOutputProcessID); + + /** + * @brief Set whether to output the thread ID + * + * @param shouldOutputThreadID + */ void setShouldOutputThreadID(bool shouldOutputThreadID); + + /** + * @brief Set whether to display timestamps with milliseconds + * + * @param shouldDisplayMilliseconds + */ void setShouldDisplayMilliseconds(bool shouldDisplayMilliseconds); + /** + * @brief Set whether to use Journald, if it's available + * + * @param shouldUseJournald Whether to use journald + */ + void setShouldUseJournald(bool shouldUseJournald); + + /** + * @brief Whether Journald is available on this version/system. + * + * Support is available depending on compile options and only on Linux. + * + * @return true Journald is available + * @return false Journald is not available + */ + bool isJournaldAvailable() const; + QString printMessage(LogMsgType type, const QMessageLogContext& context, const QString &message); - /// a qtMessageHandler that can be hooked up to a target that links to Qt - /// prints various process, message type, and time information + /** + * @brief A qtMessageHandler that can be hooked up to a target that links to Qt + * + * Prints various process, message type, and time information + * + * @param type Log message type + * @param context Context of the log message (source file, line, function) + * @param message Log message + */ static void verboseMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); int newRepeatedMessageID(); @@ -89,6 +147,7 @@ private: bool _shouldDisplayMilliseconds { false }; bool _useColor { false }; bool _keepRepeats { false }; + bool _useJournald { false }; QString _previousMessage; int _repeatCount { 0 };