#include "NGLLogger.h" #include "MurmurHash2.h" #include #include #ifdef HAVE_LOG4QT #include #include #include #include #include #include #include #include #endif #include #include #include #include namespace NGL { void LtsRollingFileAppender::setLtsLogfileName(const QString <sLogfileName) { m_ltsLogfileName = ltsLogfileName; } void LtsRollingFileAppender::activateOptions() { if (!m_ltsLogfileName.isEmpty()) setFile(m_ltsLogfileName); #ifdef HAVE_LOG4QT Log4Qt::RollingFileAppender::activateOptions(); #endif } QString LtsRollingFileAppender::m_ltsLogfileName; #ifdef HAVE_LOG4QT static Log4Qt::Appender *createLtsRollingFileAppender() { auto ret = new LtsRollingFileAppender; ret->setAppendFile(true); return ret; } #endif class CleanupHelper final { public: CleanupHelper() = default; ~CleanupHelper(); std::mutex &getLoggerMapMutex(); std::unordered_map m_loggermap; protected: std::mutex m_loggermapmutex; }; NGL::NGLLogger *NGL::NGLLogger::rootlogger_ptr = nullptr; NGL::CleanupHelper NGL::NGLLogger::s_cleanup; CleanupHelper::~CleanupHelper() { std::lock_guard lock(m_loggermapmutex); for (auto it : m_loggermap) delete it.second; m_loggermap.clear(); delete NGL::NGLLogger::rootlogger_ptr; NGL::NGLLogger::rootlogger_ptr = nullptr; } std::mutex &CleanupHelper::getLoggerMapMutex() { return m_loggermapmutex; } } NGL::NGLLogger::NGLLogger(const std::string &fullqualifiedname) : name(fullqualifiedname) , currentLevel(LogLevel::Undefined) , m_bNoSpace(true) { #ifdef HAVE_LOG4QT if (!fullqualifiedname.empty()) { logger_ptr = Log4Qt::LogManager::loggerRepository()->logger(QString::fromStdString(name)); s_cleanup.m_loggermap[MurmurHash2(fullqualifiedname.c_str(), fullqualifiedname.size())] = this; } else { logger_ptr = Log4Qt::LogManager::loggerRepository()->rootLogger(); } #endif configChanged(); } NGL::INGLLogger &NGL::NGLLogger::getNGLLogger(const char *fullqualifiedname) { std::lock_guard lock(s_cleanup.getLoggerMapMutex()); auto it = s_cleanup.m_loggermap.find(MurmurHash2(fullqualifiedname, strlen(fullqualifiedname))); if (it != s_cleanup.m_loggermap.end()) return *(*it).second; return *(new NGLLogger(fullqualifiedname)); } NGL::INGLLogger &NGL::NGLLogger::getNGLLogger(const std::string &fullqualifiedname) { std::lock_guard lock(s_cleanup.getLoggerMapMutex()); auto it = s_cleanup.m_loggermap.find(MurmurHash2(fullqualifiedname.c_str(), fullqualifiedname.size())); if (it != s_cleanup.m_loggermap.end()) return *(*it).second; return *(new NGLLogger(fullqualifiedname)); } void NGL::NGLLogger::log(LogLevel level, const INGLLogger::String &message, const char *file, int line, const char *function) { #ifdef HAVE_LOG4QT logger_ptr->logWithLocation(static_cast(level), file, line, function, QString::fromStdString(message)); #else // Stub implementation - use qDebug/qWarning/qCritical based on level Q_UNUSED(file); Q_UNUSED(line); Q_UNUSED(function); const QString msg = QString::fromStdString(message); switch (level) { case LogLevel::Fatal: case LogLevel::Error: qCritical() << msg; break; case LogLevel::Warning: qWarning() << msg; break; case LogLevel::Info: case LogLevel::Debug: case LogLevel::Trace: default: qDebug() << msg; break; } #endif } void NGL::NGLLogger::logTrace(const INGLLogger::String &message, const char *file, int line, const char *function) { if (currentLevel >= LogLevel::Trace) log(LogLevel::Trace, message, file, line, function); } void NGL::NGLLogger::logDebug(const INGLLogger::String &message, const char *file, int line, const char *function) { if (currentLevel >= LogLevel::Debug) log(LogLevel::Debug, message, file, line, function); } void NGL::NGLLogger::logInfo(const INGLLogger::String &message, const char *file, int line, const char *function) { if (currentLevel >= LogLevel::Info) log(LogLevel::Info, message, file, line, function); } void NGL::NGLLogger::logWarning(const INGLLogger::String &message, const char *file, int line, const char *function) { if (currentLevel >= LogLevel::Warning) log(LogLevel::Warning, message, file, line, function); } void NGL::NGLLogger::logError(const INGLLogger::String &message, const char *file, int line, const char *function) { if (currentLevel >= LogLevel::Error) log(LogLevel::Error, message, file, line, function); } void NGL::NGLLogger::logFatal(const INGLLogger::String &message, const char *file, int line, const char *function) { log(LogLevel::Fatal, message, file, line, function); } #ifndef LTS_VERSION_MAJOR #define LTS_VERSION_MAJOR "undefined" #endif #ifndef LTS_VERSION_MINOR #define LTS_VERSION_MINOR "undefined" #endif #ifndef LTS_VERSION_PATCH #define LTS_VERSION_PATCH "undefined" #endif #ifndef LTS_VERSION_DATE #define LTS_VERSION_DATE "undefined" #endif #ifndef JENKINS_BUILD_NUMBER #define JENKINS_BUILD_NUMBER "unknown" #endif static const char ver_major[] = LTS_VERSION_MAJOR; static const char ver_minor[] = LTS_VERSION_MINOR; static const char ver_patch[] = LTS_VERSION_PATCH; static const char ver_date[] = LTS_VERSION_DATE; static const char ver_jenkins[] = JENKINS_BUILD_NUMBER; void NGL::NGLLogger::logStartup(const char *file, int line, const char *function) { log(LogLevel::Info, QStringLiteral("Application is started (%1.%2-%3 rev%4 from %5)") .arg(QString::fromLatin1(ver_major), QString::fromLatin1(ver_minor), QString::fromLatin1(ver_jenkins), QString::fromLatin1(ver_patch), QString::fromLatin1(ver_date)).toUtf8().constData(), file, line, function); } void NGL::NGLLogger::logShutdown(const char *file, int line, const char *function) { log(LogLevel::Info, QStringLiteral("Application is shut down (%1.%2-%3 rev%4 from %5)") .arg(QString::fromLatin1(ver_major), QString::fromLatin1(ver_minor), QString::fromLatin1(ver_jenkins), QString::fromLatin1(ver_patch), QString::fromLatin1(ver_date)).toUtf8().constData(), file, line, function); } NGL::NGLLogger &NGL::NGLLogger::getNGLRootLogger() { if (!rootlogger_ptr) throw NotCreatedError(); return *rootlogger_ptr; } static QString findConfigFile(const QString &configfilename) { if (configfilename.isEmpty()) return QString(); QFileInfo fi(configfilename); if (fi.isFile() && fi.isReadable()) return fi.absoluteFilePath(); QString p = QCoreApplication::applicationDirPath() + QLatin1Char('/') + configfilename; fi.setFile(p); if (fi.isFile() && fi.isReadable()) return fi.absoluteFilePath(); return QString(); } NGL::NGLLogger &NGL::NGLLogger::createNGLRootLogger(const QString &configfilename, bool ignorealreadycreated, const QString &logOutputName, bool quiet) { if (rootlogger_ptr) { if (!ignorealreadycreated) throw AlreadyCreatedError(); } else { #ifdef HAVE_LOG4QT Log4Qt::Factory::registerAppender("Lts::RollingFileAppender", createLtsRollingFileAppender); LtsRollingFileAppender::setLtsLogfileName(logOutputName); rootlogger_ptr = new NGLLogger(std::string()); const QString filename = findConfigFile(configfilename); if (!filename.isEmpty()) { Log4Qt::PropertyConfigurator::configureAndWatch(filename); QObject::connect(Log4Qt::ConfiguratorHelper::instance(), &Log4Qt::ConfiguratorHelper::configurationFileChanged, []() { configurationFileChanged(); }); const auto l = Log4Qt::ConfiguratorHelper::instance()->configureError(); for (const auto &t : l) std::cerr << t.message().toUtf8().data() << std::endl; if (!l.isEmpty()) exit(-1); } else { if (!quiet) std::cerr << "Could not find log4cxx configuration file " << configfilename << std::endl << "Using default logging to stdout" << std::endl; Log4Qt::Properties props; props.setProperty(QLatin1String("log4j.rootLogger"), QLatin1String("WARN, stdout")); props.setProperty(QLatin1String("log4j.appender.stdout"), QLatin1String("org.apache.log4j.ConsoleAppender")); props.setProperty(QLatin1String("log4j.appender.stdout.layout"), QLatin1String("org.apache.log4j.PatternLayout")); props.setProperty(QLatin1String("log4j.appender.stdout.layout.ConversionPattern"), QLatin1String("%d %-5p %c |#| %m (%.50F:%L) %n")); Log4Qt::PropertyConfigurator::configure(props); } rootlogger_ptr->configChanged(); #else // Stub implementation - no logging initialization needed Q_UNUSED(configfilename); Q_UNUSED(ignorealreadycreated); Q_UNUSED(logOutputName); Q_UNUSED(quiet); rootlogger_ptr = new NGLLogger(std::string()); #endif } return *rootlogger_ptr; } void NGL::NGLLogger::configurationFileChanged() { for (auto ptr : s_cleanup.m_loggermap) ptr.second->configChanged(); } void NGL::NGLLogger::configChanged() { if (logger_ptr->isTraceEnabled()) currentLevel = LogLevel::Trace; else if (logger_ptr->isDebugEnabled()) currentLevel = LogLevel::Debug; else if (logger_ptr->isInfoEnabled()) currentLevel = LogLevel::Info; else if (logger_ptr->isWarnEnabled()) currentLevel = LogLevel::Warning; else if (logger_ptr->isErrorEnabled()) currentLevel = LogLevel::Error; else currentLevel = LogLevel::Fatal; } namespace NGL { namespace NGLLoggerHelper { void hexDumpHelper(NGL::INGLLogger &logger, NGL::LogLevel level, const void *a_data, const unsigned int a_size) { if (!logger.isLevelEnabled(level) || !a_data) return; unsigned char *data = (unsigned char *)a_data; QByteArray baBuf; for (unsigned int i = 0; i < a_size; i++) { if (0 == (i % 8)) baBuf = QByteArray::number(i, 16).rightJustified(8, '0').toUpper(); baBuf += " " + QByteArray::number(data[i], 16).rightJustified(2, '0').toUpper(); if (7 == (i % 8)) logger.log(level, baBuf.constData(), __FILE__, __LINE__); } if (a_size % 8) logger.log(level, baBuf.constData(), __FILE__, __LINE__); } } NGL::INGLLogger& getMemoryLogger() { try { return NGLLogger::getNGLRootLogger().getNGLLogger("memorystate"); } catch (const std::exception &ex) { fprintf(stderr, "Error retrieving logger: %s", ex.what()); abort(); } } int parseLine(char* line){ int i = strlen(line); const char* p = line; while (*p <'0' || *p > '9') p++; line[i-3] = '\0'; i = atoi(p); return i; } int getMemoryValue() { FILE* file = fopen("/proc/self/status", "r"); int result = -1; char line[128]; while (fgets(line, 128, file) != NULL){ if (strncmp(line, "VmRSS:", 6) == 0){ result = parseLine(line); break; } } fclose(file); return result; } std::string getMemoryData() { return QStringLiteral("VmRSS: %1 kB #").arg(QString::number(getMemoryValue())).toStdString(); } }