common/logger.cpp (318 lines of code) (raw):

#include "logger.h" #include <algorithm> #include <stdarg.h> #include <stdio.h> #include <syslog.h> #include <fstream> #include <sstream> #include <iomanip> #include <stdexcept> #include "schema.h" #include "select.h" #include "dbconnector.h" #include "subscriberstatetable.h" #include "producerstatetable.h" using namespace swss; #define MUTEX std::lock_guard<std::mutex> _lock(getInstance().m_mutex); void swss::err_exit(const char *fn, int ln, int e, const char *fmt, ...) { va_list ap; char buff[1024]; size_t len; va_start(ap, fmt); snprintf(buff, sizeof(buff), "%s::%d err(%d/%s): ", fn, ln, e, strerror(e)); len = strlen(buff); vsnprintf(buff+len, sizeof(buff)-len, fmt, ap); va_end(ap); SWSS_LOG_ERROR("Aborting: %s", buff); abort(); } Logger::~Logger() { terminateSettingThread(); } void Logger::terminateSettingThread() { // can't be executed under mutex, since it can cause deadlock if (m_settingThread) { m_stopEvent->notify(); m_settingThread->join(); m_settingThread = nullptr; m_stopEvent = nullptr; } } void Logger::restartSettingThread() { terminateSettingThread(); m_stopEvent = std::make_unique<SelectableEvent>(0); m_settingThread.reset(new std::thread(&Logger::settingThread, this)); } const Logger::PriorityStringMap Logger::priorityStringMap = { { "EMERG", SWSS_EMERG }, { "ALERT", SWSS_ALERT }, { "CRIT", SWSS_CRIT }, { "ERROR", SWSS_ERROR }, { "WARN", SWSS_WARN }, { "NOTICE", SWSS_NOTICE }, { "INFO", SWSS_INFO }, { "DEBUG", SWSS_DEBUG } }; void Logger::swssPrioNotify(const std::string& component, const std::string& prioStr) { auto& logger = getInstance(); if (priorityStringMap.find(prioStr) == priorityStringMap.end()) { SWSS_LOG_ERROR("Invalid loglevel. Setting to NOTICE. %s", prioStr.c_str()); logger.m_minPrio = SWSS_NOTICE; } else { SWSS_LOG_DEBUG("Changing logger minPrio to %s", prioStr.c_str()); logger.m_minPrio = priorityStringMap.at(prioStr); } } const Logger::OutputStringMap Logger::outputStringMap = { { "SYSLOG", SWSS_SYSLOG }, { "STDOUT", SWSS_STDOUT }, { "STDERR", SWSS_STDERR } }; void Logger::swssOutputNotify(const std::string& component, const std::string& outputStr) { auto& logger = getInstance(); if (outputStringMap.find(outputStr) == outputStringMap.end()) { SWSS_LOG_ERROR("Invalid logoutput. Setting to SYSLOG. %s", outputStr.c_str()); logger.m_output = SWSS_SYSLOG; } else { logger.m_output = outputStringMap.at(outputStr); } } void Logger::linkToDbWithOutput( const std::string& dbName, const PriorityChangeNotify& prioNotify, const std::string& defPrio, const OutputChangeNotify& outputNotify, const std::string& defOutput) { auto& logger = getInstance(); // Initialize internal DB with observer logger.m_settingChangeObservers.insert(std::make_pair(dbName, std::make_pair(prioNotify, outputNotify))); DBConnector db("CONFIG_DB", 0); swss::Table table(&db, CFG_LOGGER_TABLE_NAME); SWSS_LOG_DEBUG("Component %s register to logger", dbName.c_str()); std::string prio, output; bool doUpdate = false; if(!table.hget(dbName, DAEMON_LOGLEVEL, prio)) { prio = defPrio; doUpdate = true; } if(!table.hget(dbName, DAEMON_LOGOUTPUT, output)) { output = defOutput; doUpdate = true; } if (doUpdate) { FieldValueTuple fvLevel(DAEMON_LOGLEVEL, prio); FieldValueTuple fvOutput(DAEMON_LOGOUTPUT, output); std::vector<FieldValueTuple>fieldValues = { fvLevel, fvOutput }; SWSS_LOG_DEBUG("Set %s loglevel to %s", dbName.c_str() , prio.c_str()); table.set(dbName, fieldValues); } logger.m_currentPrios.set(dbName, prio); logger.m_currentOutputs.set(dbName, output); prioNotify(dbName, prio); outputNotify(dbName, output); } void Logger::linkToDb(const std::string& dbName, const PriorityChangeNotify& prioNotify, const std::string& defPrio) { linkToDbWithOutput(dbName, prioNotify, defPrio, swssOutputNotify, "SYSLOG"); } void Logger::linkToDbNative(const std::string& dbName, const char * defPrio) { linkToDb(dbName, swssPrioNotify, defPrio); getInstance().restartSettingThread(); } void Logger::restartLogger() { getInstance().restartSettingThread(); } Logger& Logger::getInstance() { static Logger m_logger; return m_logger; } void Logger::setMinPrio(Priority prio) { getInstance().m_minPrio = prio; } Logger::Priority Logger::getMinPrio() { return getInstance().m_minPrio; } void Logger::settingThread() { Select select; DBConnector db("CONFIG_DB", 0); std::map<std::string, std::shared_ptr<SubscriberStateTable>> selectables; auto table = std::make_shared<SubscriberStateTable>(&db, CFG_LOGGER_TABLE_NAME); selectables.emplace(CFG_LOGGER_TABLE_NAME, table); select.addSelectable(table.get()); select.addSelectable(m_stopEvent.get()); while (1) { Selectable *selectable = nullptr; /* TODO Resolve latency caused by timeout at initialization. */ int ret = select.select(&selectable, 1000); // Timeout if there is no data in 1000 ms. if (ret == Select::ERROR) { SWSS_LOG_NOTICE("%s select error %s", __PRETTY_FUNCTION__, strerror(errno)); continue; } if (ret == Select::TIMEOUT) { SWSS_LOG_DEBUG("%s select timeout", __PRETTY_FUNCTION__); continue; } if (selectable == m_stopEvent.get()) { break; } KeyOpFieldsValuesTuple koValues; SubscriberStateTable *subscriberStateTable = NULL; subscriberStateTable = dynamic_cast<SubscriberStateTable *>(selectable); if (subscriberStateTable == NULL) { SWSS_LOG_ERROR("dynamic_cast returned NULL"); break; } subscriberStateTable->pop(koValues); std::string key = kfvKey(koValues), op = kfvOp(koValues); if (op != SET_COMMAND || !m_settingChangeObservers.contains(key)) { continue; } const auto& values = kfvFieldsValues(koValues); for (auto& i : values) { auto& field = fvField(i); auto& value = fvValue(i); if ((field == DAEMON_LOGLEVEL) && (value != m_currentPrios.get(key))) { m_currentPrios.set(key, value); m_settingChangeObservers.get(key).first(key, value); } else if ((field == DAEMON_LOGOUTPUT) && (value != m_currentOutputs.get(key))) { m_currentOutputs.set(key, value); m_settingChangeObservers.get(key).second(key, value); } } } } void Logger::write(Priority prio, const char *fmt, ...) { if (prio > m_minPrio) return; // TODO // + add thread id using std::thread::id this_id = std::this_thread::get_id(); // + add timestmap with millisecond resolution va_list ap; va_start(ap, fmt); if (m_output == SWSS_SYSLOG) { vsyslog(prio, fmt, ap); } else { std::stringstream ss; ss << std::setw(6) << std::right << priorityToString(prio); ss << fmt << std::endl; MUTEX; if (m_output == SWSS_STDOUT) { vprintf(ss.str().c_str(), ap); } else { vfprintf(stderr, ss.str().c_str(), ap); } } va_end(ap); } void Logger::wthrow(Priority prio, const char *fmt, ...) { char buffer[0x1000]; va_list ap; va_start(ap, fmt); if (m_output == SWSS_SYSLOG) { vsyslog(prio, fmt, ap); } else { std::stringstream ss; ss << std::setw(6) << std::right << priorityToString(prio); ss << fmt << std::endl; MUTEX; if (m_output == SWSS_STDOUT) { vprintf(ss.str().c_str(), ap); } else { vfprintf(stderr, ss.str().c_str(), ap); } } va_end(ap); va_start(ap, fmt); vsnprintf(buffer, 0x1000, fmt, ap); va_end(ap); throw std::runtime_error(buffer); } std::string Logger::priorityToString(Logger::Priority prio) { for (const auto& i : priorityStringMap) { if (i.second == prio) { return i.first; } } return "UNKNOWN"; } std::string Logger::outputToString(Logger::Output output) { for (const auto& i : outputStringMap) { if (i.second == output) { return i.first; } } return "UNKNOWN"; } Logger::ScopeLogger::ScopeLogger(int line, const char *fun) : m_line(line), m_fun(fun) { swss::Logger::getInstance().write(swss::Logger::SWSS_DEBUG, ":> %s: enter", m_fun); } Logger::ScopeLogger::~ScopeLogger() { swss::Logger::getInstance().write(swss::Logger::SWSS_DEBUG, ":< %s: exit", m_fun); } Logger::ScopeTimer::ScopeTimer(int line, const char *fun, const char *fmt, ...) : m_line(line), m_fun(fun) { char buffer[0x1000]; va_list ap; va_start(ap, fmt); vsnprintf(buffer, 0x1000, fmt, ap); va_end(ap); m_msg = buffer; m_start = std::chrono::high_resolution_clock::now(); } Logger::ScopeTimer::~ScopeTimer() { auto end = std::chrono::high_resolution_clock::now(); double duration = std::chrono::duration_cast<second_t>(end - m_start).count(); Logger::getInstance().write(swss::Logger::SWSS_NOTICE, ":- %s: %s took %lf sec", m_fun, m_msg.c_str(), duration); }