/** * Copyright - See the COPYRIGHT that is included with this distribution. * pvxs is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. */ #include #include #include // must include before epicsStdio.h to avoid clash with printf macro #include #include #include #include #include #include #include #include #include #include #include #include #include "evhelper.h" #include "utilpvt.h" typedef epicsGuard Guard; namespace pvxs { DEFINE_LOGGER(logerr, "pvxs.ev"); namespace detail { const char* log_prefix(const char* name, Level lvl) { static thread_local char prefix[64]; // YYYY-mm-ddTHH:MM:SS.FffFffFff epicsTimeStamp now; size_t N; if(epicsTimeGetCurrent(&now)) { strcpy(prefix, ""); N = strlen(prefix); } else { N = epicsTimeToStrftime(prefix, sizeof(prefix), "%Y-%m-%dT%H:%M:%S.%9f", &now); } const char *lname; switch(lvl) { case Level::Crit: lname = "CRIT"; break; case Level::Err: lname = "ERR"; break; case Level::Warn: lname = "WARN"; break; case Level::Info: lname = "INFO"; break; case Level::Debug: lname = "DEBUG"; break; default: lname = "<\?\?\?>"; break; } epicsSnprintf(prefix+N, sizeof(prefix)-N, " %s %s", lname, name); return prefix; } } // namespace detail namespace { void evlog_handler(int severity, const char *msg) { const char *sevr = "<\?\?\?>"; Level lvl = Level::Crit; switch(severity) { #define CASE(EVLVL, PLVL) case EVENT_LOG_##EVLVL : lvl = Level::PLVL; sevr = #PLVL; break CASE(DEBUG, Debug); CASE(MSG, Info); CASE(WARN, Warn); CASE(ERR, Err); #undef CASE } if(logerr.test(lvl)) errlogPrintf("libevent %s: %s\n", sevr, msg); } int name2lvl(const std::string& name) { #define CASE(LVL, Lvl) if(name==#LVL) return int(Level::Lvl) CASE(DEBUG, Debug); CASE(INFO, Info); CASE(WARN, Warn); CASE(ERR, Err); CASE(CRIT, Crit); #undef CASE return 0; } struct logger_gbl_t { epicsMutex lock; // [(pattern, level)] std::list> config; std::multimap loggers; logger_gbl_t() { event_set_log_callback(&evlog_handler); } Level init(logger *logger) { std::string name(logger->name); auto lvl = Level::Err; // see if this logger name has already been configured. auto it = loggers.find(logger->name); if(it!=loggers.end()) { lvl = it->second->lvl.load(std::memory_order_relaxed); } else { // nope for(auto& tup : config) { if(epicsStrGlobMatch(name.c_str(), tup.first.c_str())) { lvl = tup.second; break; } } } loggers.emplace(name, logger); logger->lvl.store(lvl, std::memory_order_relaxed); return lvl; } void set(const char *exp, Level lvl) { if(lvl<=Level(0)) lvl = Level(1); for(auto& tup : config) { if(tup.first==exp) { // update of existing config if(tup.second!=lvl) { tup.second = lvl; for(auto& pair : loggers) { if(epicsStrGlobMatch(pair.first.c_str(), tup.first.c_str())) { pair.second->lvl.store(lvl, std::memory_order_relaxed); } } } return; } } // new config config.emplace_back(exp, lvl); } } *logger_gbl; void logger_prepare(void *unused) { logger_gbl = new logger_gbl_t; } epicsThreadOnceId logger_once = EPICS_THREAD_ONCE_INIT; } // namespace Level logger::init() { assert(name); auto lvl = this->lvl.load(); if(lvl==Level(-1)) { // maybe we initialize if(this->lvl.compare_exchange_strong(lvl, Level::Err)) { // logger now has default config of Level::Err // we will fully initialize epicsThreadOnce(&logger_once, &logger_prepare, nullptr); assert(logger_gbl); Guard G(logger_gbl->lock); lvl = logger_gbl->init(this); } } return lvl; } void xerrlogHexPrintf(const void *buf, size_t buflen) { const uint8_t* const cbuf = static_cast(buf); // whole buffer for(size_t pos=0; pos>4)&0xf]; buf[grp][chr+1] = hex[(v>>0)&0xf]; } for(; chr<8; chr+=2) { buf[grp][chr+0] = '\0'; buf[grp][chr+1] = '\0'; } buf[grp][8] = '\0'; } errlogPrintf("%04x : %s %s %s %s\n", addr, buf[0], buf[1], buf[2], buf[3]); } } void logger_level_set(const char *name, int lvl) { epicsThreadOnce(&logger_once, &logger_prepare, nullptr); assert(logger_gbl); Guard G(logger_gbl->lock); logger_gbl->set(name, Level(lvl)); } void logger_level_clear() { epicsThreadOnce(&logger_once, &logger_prepare, nullptr); assert(logger_gbl); Guard G(logger_gbl->lock); logger_gbl->config.clear(); } void logger_config_env() { const char *env = getenv("PVXS_LOG"); if(!env || !*env) return; epicsThreadOnce(&logger_once, &logger_prepare, nullptr); Guard G(logger_gbl->lock); while(*env) { const char *sep = env; const char *eq = env; while(*sep && *sep!=',') sep++; while(*eq && *eq!='=') eq++; if(env==sep) { // empty } else if(eq < sep) { // key=VAL std::string key(env, eq-env), val(eq+1, sep-eq-1); if(key.empty() || val.empty()) { fprintf(stderr, "PVXS_LOG ignore invalid: '%s=%s'\n", key.c_str(), val.c_str()); } else if(auto lvl = name2lvl(val)) { logger_gbl->set(key.c_str(), Level(lvl)); } else { fprintf(stderr, "PVXS_LOG ignore invalid level: '%s=%s'\n", key.c_str(), val.c_str()); } } env = sep; if(*env==',') ++env; } } } // namespace pvxs namespace pvxs {namespace impl { void logger_shutdown() { epicsThreadOnce(&logger_once, &logger_prepare, nullptr); errlogFlush(); delete logger_gbl; logger_gbl = nullptr; // no resetting logger_once } }} // namespace pvxs::impl