/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright 2012-2016 Danny Robson */ #include "log.hpp" #include "term.hpp" #include "time.hpp" #include "cast.hpp" #include #include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// static constexpr cruft::log::level_t ALL_LEVELS[] = { cruft::log::EMERGENCY, cruft::log::ALERT, cruft::log::CRITICAL, cruft::log::ERROR, cruft::log::WARN, cruft::log::NOTICE, cruft::log::INFO, cruft::log::DEBUG, }; /////////////////////////////////////////////////////////////////////////////// /// convert a string representation of a log-level into an enumeration value. /// /// conversion is case insensitive /// throws std::range_error if unable to convert static cruft::log::level_t to_level (std::string name) { if (std::empty (name)) return cruft::log::EMERGENCY; static const std::map NAME_LEVELS = { { "EMERGENCY", cruft::log::EMERGENCY }, { "ALERT", cruft::log::ALERT }, { "CRITICAL", cruft::log::CRITICAL }, { "ERROR", cruft::log::ERROR }, { "WARN", cruft::log::WARN }, { "WARNING", cruft::log::WARN }, { "NOTICE", cruft::log::NOTICE }, { "INFO", cruft::log::INFO }, { "INFORMATIONAL", cruft::log::INFO }, { "DEBUG", cruft::log::DEBUG } }; std::transform (name.cbegin (), name.cend (), name.begin (), ::toupper); auto pos = NAME_LEVELS.find (name); if (pos == NAME_LEVELS.end ()) throw std::range_error (name); return pos->second; } /////////////////////////////////////////////////////////////////////////////// const std::string& cruft::log::to_string (level_t l) { switch (l) { #define CASE(L) \ case cruft::log::L: { \ static const std::string STR = #L; \ return STR; \ } MAP_LEVEL_T(CASE) #undef CASE } unreachable (); } //----------------------------------------------------------------------------- std::ostream& cruft::log::operator<< (std::ostream& os, level_t l) { return os << to_string (l); } /////////////////////////////////////////////////////////////////////////////// // Determine what the value for LOG_LEVEL should be at the beginning of // execution given the system environment. // // Note that the LOG macros _cannot_ be used from within this function as it // will likely result in infinite recursion. static cruft::log::level_t initial_log_level (void) { const char *env = getenv ("LOG_LEVEL"); if (!env) return cruft::log::DEFAULT_LOG_LEVEL; try { return to_level (env); } catch (...) { std::clog << "Invalid environment LOG_LEVEL: '" << env << "'\n"; return cruft::log::DEFAULT_LOG_LEVEL; } } //----------------------------------------------------------------------------- // We shouldn't ever actually get to use the default value, but we set it to // the most verbose option just in case we've made a mistake elsewhere. static bool s_log_level_done; static cruft::log::level_t s_log_level_value; //----------------------------------------------------------------------------- cruft::log::level_t cruft::log::log_level (level_t _level) { s_log_level_value = _level; s_log_level_done = true; return s_log_level_value; } //----------------------------------------------------------------------------- cruft::log::level_t cruft::log::log_level (void) { if (!s_log_level_done) { s_log_level_value = initial_log_level (); s_log_level_done = true; } return s_log_level_value; } //----------------------------------------------------------------------------- static bool needs_break (cruft::log::level_t level) { static cruft::log::level_t break_level; static bool has_level = [&] (void) { const char *env = getenv ("BREAK_LEVEL"); if (!env) return false; try { break_level = to_level (env); return true; } catch (...) { return false; } } (); return has_level && level <= break_level; } //----------------------------------------------------------------------------- static cruft::term::csi::graphics level_colour (cruft::log::level_t level) { using cruft::term::csi::graphics; switch (level) { case cruft::log::EMERGENCY: case cruft::log::ALERT: case cruft::log::CRITICAL: case cruft::log::ERROR: return graphics (graphics::FOREGROUND, graphics::RED); case cruft::log::WARNING: return graphics (graphics::FOREGROUND, graphics::YELLOW); case cruft::log::NOTICE: case cruft::log::INFORMATIONAL: return graphics (graphics::FOREGROUND, graphics::GREEN); case cruft::log::DEBUG: return graphics (graphics::FOREGROUND, graphics::WHITE); } unreachable (); } //----------------------------------------------------------------------------- static size_t level_width (void) { static size_t width = [] { size_t hi = 0; for (auto i: ALL_LEVELS) hi = cruft::max (to_string (i).size (), hi); return hi; } (); return width; } /////////////////////////////////////////////////////////////////////////////// void cruft::log::write (level_t level, const std::string &msg) { if (level <= log_level ()) { static const size_t time_len = strlen("YYYY-mm-dd HHMMhSS") + 1; std::string time_string (time_len - 1, '\0'); time_t unix_time = time (nullptr); if (0 == strftime (&time_string[0], time_len, "%Y-%m-%d %H%Mh%S", localtime (&unix_time))) { warn ("failed to log time"); return; } std::clog << time_string << " [" << level_colour (level) << std::setw (cruft::cast::lossless (level_width ())) << std::left << level << std::setw (0) << cruft::term::csi::graphics::RESET << "] " << msg << std::endl; } if (needs_break (level)) breakpoint (); } /////////////////////////////////////////////////////////////////////////////// cruft::log::scoped_logger::scoped_logger ( level_t _level, std::string _message ): m_level (_level), m_message (std::move (_message)) { ; } //----------------------------------------------------------------------------- cruft::log::scoped_logger::~scoped_logger () { write (m_level, m_message); } /////////////////////////////////////////////////////////////////////////////// cruft::log::scoped_timer::scoped_timer ( cruft::log::level_t _level, std::string _message ): m_level (_level), m_message (std::move (_message)), m_start (cruft::nanoseconds ()) { ; } //----------------------------------------------------------------------------- cruft::log::scoped_timer::~scoped_timer () { auto finish = cruft::nanoseconds (); auto duration = finish - m_start; write ( m_level, "%fs, %s", float (duration) / 1'000'000'000.f, m_message ); }