From 67ea686753ef75997149a41b30c9fcf89b097de0 Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Thu, 10 Oct 2019 15:52:37 +1100 Subject: [PATCH] log: modularise the logging infrastructure --- CMakeLists.txt | 16 ++- log/fwd.hpp | 22 ++++ log/level.cpp | 138 ++++++++++++++++++++++ log/level.hpp | 76 ++++++++++++ log/log.cpp | 267 ++----------------------------------------- log/log.hpp | 115 ++----------------- log/packet.cpp | 0 log/packet.hpp | 43 +++++++ log/scoped.cpp | 57 +++++++++ log/scoped.hpp | 52 +++++++++ log/sink/base.cpp | 9 ++ log/sink/base.hpp | 32 ++++++ log/sink/console.cpp | 122 ++++++++++++++++++++ log/sink/console.hpp | 22 ++++ log/sink/null.cpp | 0 log/sink/null.hpp | 0 log/sink/ostream.cpp | 0 log/sink/ostream.hpp | 0 log/sink/tee.cpp | 0 log/sink/tee.hpp | 0 registrar.hpp | 2 +- 21 files changed, 610 insertions(+), 363 deletions(-) create mode 100644 log/fwd.hpp create mode 100644 log/level.cpp create mode 100644 log/level.hpp create mode 100644 log/packet.cpp create mode 100644 log/packet.hpp create mode 100644 log/scoped.cpp create mode 100644 log/scoped.hpp create mode 100644 log/sink/base.cpp create mode 100644 log/sink/base.hpp create mode 100644 log/sink/console.cpp create mode 100644 log/sink/console.hpp create mode 100644 log/sink/null.cpp create mode 100644 log/sink/null.hpp create mode 100644 log/sink/ostream.cpp create mode 100644 log/sink/ostream.hpp create mode 100644 log/sink/tee.cpp create mode 100644 log/sink/tee.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ab3a277..bd331d5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,8 +423,22 @@ list ( list/sort.hpp log.hpp log/fwd.hpp - log/log.hpp + log/level.cpp + log/level.hpp log/log.cpp + log/log.hpp + log/packet.cpp + log/packet.hpp + log/scoped.cpp + log/scoped.hpp + log/sink/base.cpp + log/sink/base.hpp + log/sink/console.cpp + log/sink/console.hpp + log/sink/null.cpp + log/sink/null.hpp + log/sink/ostream.cpp + log/sink/ostream.hpp map/fixed.cpp map/fixed.hpp maths.cpp diff --git a/log/fwd.hpp b/log/fwd.hpp new file mode 100644 index 00000000..871833a8 --- /dev/null +++ b/log/fwd.hpp @@ -0,0 +1,22 @@ +/* + * 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 2019, Danny Robson + */ + +#pragma once + +namespace cruft::log { + struct packet; + + namespace sink { + class base; + + class console; + class noop; + class ostream; + class tee; + }; +} diff --git a/log/level.cpp b/log/level.cpp new file mode 100644 index 00000000..d9aaad1f --- /dev/null +++ b/log/level.cpp @@ -0,0 +1,138 @@ +/* + * 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 "level.hpp" + +#include "../debug/assert.hpp" + +#include + +#include + + +/////////////////////////////////////////////////////////////////////////////// +/// convert a string representation of a log-level into an enumeration value. +/// +/// conversion is case insensitive +/// throws std::range_error if unable to convert +cruft::log::level_t +cruft::log::to_level (std::string_view name) +{ + if (std::empty (name)) + return cruft::log::EMERGENCY; + + std::string upper (name.size (), char{}); + std::transform ( + name.cbegin (), + name.cend (), + upper.begin (), + ::toupper + ); + + static constexpr struct { + char const *name; + cruft::log::level_t value; + } VALUES[] = { + { "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 } + }; + + for (auto const [key, val]: VALUES) + if (!strcmp (upper.data (), key)) + return val; + + throw std::invalid_argument (std::string (name)); +} + + +/////////////////////////////////////////////////////////////////////////////// +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 cruft::log::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; +} diff --git a/log/level.hpp b/log/level.hpp new file mode 100644 index 00000000..e97a4593 --- /dev/null +++ b/log/level.hpp @@ -0,0 +1,76 @@ +/* + * 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-2019 Danny Robson + */ + +#pragma once + +#include + +#include + + +namespace cruft::log { + /////////////////////////////////////////////////////////////////////////// + // rfc5424 log levels. It is assumed they are contiguous to simplify array + // indexing in logging code. + enum level_t { + EMERGENCY, /** system is unusable */ + ALERT, /** action must be taken immediately */ + CRITICAL, /** critical conditions */ + ERROR, /** error conditions */ + WARNING, + WARN = WARNING, /** warning conditions */ + NOTICE, /** normal but significant condition */ + INFORMATIONAL, + INFO = INFORMATIONAL, /** informational messages */ + DEBUG /** debug-level messages */ + }; + + #define MAP_LEVEL_T(F) \ + MAP0(F, \ + EMERGENCY, \ + ALERT, \ + CRITICAL, \ + ERROR, \ + WARN, \ + NOTICE, \ + INFO, \ + DEBUG \ + ) + + constexpr auto DEFAULT_LOG_LEVEL = NOTICE; + + //------------------------------------------------------------------------- + std::string const& to_string (level_t); + level_t to_level (std::string_view); + + //------------------------------------------------------------------------- + std::ostream& + operator<< (std::ostream&, level_t); + + + /////////////////////////////////////////////////////////////////////////// + // Query or set the global logging level filter. If the severity passed to + // a logging call is less than this then the output is discarded. + // + // The initial logging level can be controlled by setting the environment + // variable LOG_LEVEL to a string corresponding to the names of the values + // in level_t. + // + // Otherwise we fall back to the hard coded default from above. + // + // As a special case, a blank value for the environment variable LOG_LEVEL + // corresponds to the maximum log level. It indicates a preference for + // minimum output, and may be changed to disable all output in future + // updates. + level_t log_level (void); + level_t log_level (level_t); + + + bool needs_break (level_t); + +} diff --git a/log/log.cpp b/log/log.cpp index 98466e78..3d926b8a 100644 --- a/log/log.cpp +++ b/log/log.cpp @@ -9,161 +9,21 @@ #include "log.hpp" -#include "../term.hpp" -#include "../time.hpp" #include "../cast.hpp" #include "../string.hpp" -#include -#include -#include -#include +#include "sink/console.hpp" + #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 -cruft::log::level_t -cruft::log::to_level (std::string_view name) +bool +cruft::log::needs_break (level_t level) { - if (std::empty (name)) - return cruft::log::EMERGENCY; + static level_t break_level; - std::string upper (name.size (), char{}); - std::transform ( - name.cbegin (), - name.cend (), - upper.begin (), - ::toupper - ); - - static constexpr struct { - char const *name; - cruft::log::level_t value; - } VALUES[] = { - { "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 } - }; - - for (auto const [key, val]: VALUES) - if (!strcmp (upper.data (), key)) - return val; - - throw std::invalid_argument (std::string (name)); -} - - -/////////////////////////////////////////////////////////////////////////////// -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 cruft::log::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) @@ -181,50 +41,12 @@ needs_break (cruft::log::level_t level) } -//----------------------------------------------------------------------------- -static -cruft::term::csi::graphics -level_colour (cruft::log::level_t level) +/////////////////////////////////////////////////////////////////////////////// +cruft::log::sink::base& +cruft::log::default_sink () { - 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; + static sink::console s_default_sink (PACKAGE_NAME); + return s_default_sink; } @@ -248,72 +70,7 @@ cruft::log::write (level_t level, char const *msg) void cruft::log::write (level_t level, std::string_view 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 + return default_sink ().write ( + packet (level, msg) ); } diff --git a/log/log.hpp b/log/log.hpp index 6d281548..d974bd87 100644 --- a/log/log.hpp +++ b/log/log.hpp @@ -8,9 +8,11 @@ #pragma once +#include "level.hpp" +#include "packet.hpp" +#include "sink/base.hpp" #include "../format.hpp" -#include #include #include @@ -22,52 +24,8 @@ namespace cruft::log { /////////////////////////////////////////////////////////////////////////// - // rfc5424 log levels. It is assumed they are contiguous to simplify array - // indexing in logging code. - enum level_t { - EMERGENCY, /** system is unusable */ - ALERT, /** action must be taken immediately */ - CRITICAL, /** critical conditions */ - ERROR, /** error conditions */ - WARNING, - WARN = WARNING, /** warning conditions */ - NOTICE, /** normal but significant condition */ - INFORMATIONAL, - INFO = INFORMATIONAL, /** informational messages */ - DEBUG /** debug-level messages */ - }; + sink::base& default_sink (void); - #define MAP_LEVEL_T(F) MAP0(F, EMERGENCY, ALERT, CRITICAL, ERROR, WARN, NOTICE, INFO, DEBUG) - - constexpr auto DEFAULT_LOG_LEVEL = NOTICE; - - //------------------------------------------------------------------------- - const std::string& to_string (level_t); - level_t to_level (std::string_view); - - //------------------------------------------------------------------------- - std::ostream& - operator<< (std::ostream&, level_t); - - - /////////////////////////////////////////////////////////////////////////// - // Query or set the global logging level filter. If the severity passed to - // a logging call is less than this then the output is discarded. - // - // The initial logging level can be controlled by setting the environment - // variable LOG_LEVEL to a string corresponding to the names of the values - // in level_t. - // - // Otherwise we fall back to the hard coded default from above. - // - // As a special case, a blank value for the environment variable LOG_LEVEL - // corresponds to the maximum log level. It indicates a preference for - // minimum output, and may be changed to disable all output in future - // updates. - level_t log_level (void); - level_t log_level (level_t); - - /////////////////////////////////////////////////////////////////////////// void write (level_t, std::string const &msg); void write (level_t, char const *msg); void write (level_t, std::string_view msg); @@ -78,29 +36,11 @@ namespace cruft::log { void write (level_t l, FormatT fmt, ArgsT &&...args) { - write ( - l, - to_string ( - format::printf ( - std::forward (fmt), - std::forward (args)... - ) - ) - ); - } - - - //------------------------------------------------------------------------- - template - void - write (level_t l, const char (&fmt)[N], Args &&...args) - { - write ( - l, - to_string ( - format::printf (fmt) ( - std::forward (args)... - ) + default_sink ().write ( + packet ( + l, + std::forward (fmt), + std::forward (args)... ) ); } @@ -126,41 +66,4 @@ namespace cruft::log { #else #define LOG_DEBUG(...) do { ; } while (0) #endif - - - /////////////////////////////////////////////////////////////////////////// - class scoped_logger { - public: - scoped_logger (level_t, std::string); - ~scoped_logger (); - - scoped_logger (scoped_logger const&) = delete; - scoped_logger& operator= (scoped_logger const&) = delete; - - scoped_logger (scoped_logger &&) = delete; - scoped_logger& operator= (scoped_logger &&) = delete; - - protected: - const level_t m_level; - const std::string m_message; - }; - - - /////////////////////////////////////////////////////////////////////////// - class scoped_timer { - public: - scoped_timer (level_t, std::string); - ~scoped_timer (); - - scoped_timer (scoped_timer const&) = delete; - scoped_timer& operator= (scoped_timer const&) = delete; - - scoped_timer (scoped_timer &&) = delete; - scoped_timer& operator= (scoped_timer &&) = delete; - - private: - const level_t m_level; - const std::string m_message; - uint64_t m_start; - }; } diff --git a/log/packet.cpp b/log/packet.cpp new file mode 100644 index 00000000..e69de29b diff --git a/log/packet.hpp b/log/packet.hpp new file mode 100644 index 00000000..a7d49645 --- /dev/null +++ b/log/packet.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include "level.hpp" +#include "../format.hpp" + +#include + +#include + + +namespace cruft::log { + struct packet { + using clock = std::chrono::high_resolution_clock; + + packet (level_t _level, std::string _message) + : level (_level) + , message (std::move (_message)) + , timestamp (clock::now ()) + { ; } + + + template + packet ( + level_t _level, + FormatT _format, + ArgsT &&..._args + ) : packet ( + _level, + cruft::format::to_string ( + cruft::format::printf ( + std::forward (_format) + ) ( + std::forward (_args)... + ) + ) + ) + { ; } + + level_t level; + std::string message; + clock::time_point timestamp; + }; +} \ No newline at end of file diff --git a/log/scoped.cpp b/log/scoped.cpp new file mode 100644 index 00000000..4a93f774 --- /dev/null +++ b/log/scoped.cpp @@ -0,0 +1,57 @@ +/* + * 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-2019 Danny Robson + */ + +#include "scoped.hpp" + +#include "../time.hpp" + +using cruft::log::scoped_logger; +using cruft::log::scoped_timer; + + +/////////////////////////////////////////////////////////////////////////////// +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 + ); +} diff --git a/log/scoped.hpp b/log/scoped.hpp new file mode 100644 index 00000000..8f355d24 --- /dev/null +++ b/log/scoped.hpp @@ -0,0 +1,52 @@ +/* + * 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-2019 Danny Robson + */ + +#pragma once + +#include "log.hpp" + +#include +#include + +namespace cruft::log { + /////////////////////////////////////////////////////////////////////////// + class scoped_logger { + public: + scoped_logger (level_t, std::string); + ~scoped_logger (); + + scoped_logger (scoped_logger const&) = delete; + scoped_logger& operator= (scoped_logger const&) = delete; + + scoped_logger (scoped_logger &&) = delete; + scoped_logger& operator= (scoped_logger &&) = delete; + + protected: + const level_t m_level; + const std::string m_message; + }; + + + /////////////////////////////////////////////////////////////////////////// + class scoped_timer { + public: + scoped_timer (level_t, std::string); + ~scoped_timer (); + + scoped_timer (scoped_timer const&) = delete; + scoped_timer& operator= (scoped_timer const&) = delete; + + scoped_timer (scoped_timer &&) = delete; + scoped_timer& operator= (scoped_timer &&) = delete; + + private: + const level_t m_level; + const std::string m_message; + uint64_t m_start; + }; +} diff --git a/log/sink/base.cpp b/log/sink/base.cpp new file mode 100644 index 00000000..bb6abc9b --- /dev/null +++ b/log/sink/base.cpp @@ -0,0 +1,9 @@ +#include "base.hpp" + +using cruft::log::sink::base; + + +/////////////////////////////////////////////////////////////////////////////// +base::base (std::string _name) + : m_name (std::move (_name)) +{ ; } diff --git a/log/sink/base.hpp b/log/sink/base.hpp new file mode 100644 index 00000000..c023e4eb --- /dev/null +++ b/log/sink/base.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "../fwd.hpp" + +#include + + +namespace cruft::log::sink { + class base { + public: + virtual ~base () = default; + + base (std::string name); + + std::string const& name (void); + + virtual void write (packet const&) = 0; + + private: + std::string m_name; + }; + + + template + class crtp : public base { + public: + template + crtp (ArgsT &&...args) + : base (std::forward (args)...) + { ; } + }; +} \ No newline at end of file diff --git a/log/sink/console.cpp b/log/sink/console.cpp new file mode 100644 index 00000000..598df484 --- /dev/null +++ b/log/sink/console.cpp @@ -0,0 +1,122 @@ +/* + * 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 2019, Danny Robson + */ + +#include "console.hpp" + +#include "../level.hpp" +#include "../packet.hpp" + +#include "../../term.hpp" +#include "../../debug/assert.hpp" +#include "../../cast.hpp" + +#include +#include + +#include +#include + +using cruft::log::sink::console; + + +/////////////////////////////////////////////////////////////////////////////// +static std::size_t +level_width (void) +{ + 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, + }; + + + 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; +} + + +/////////////////////////////////////////////////////////////////////////////// +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 (); +} + + +/////////////////////////////////////////////////////////////////////////////// +console::console (std::string name) + : crtp (std::move (name)) +{ ; } + + +/////////////////////////////////////////////////////////////////////////////// +void +console::write (packet const &val) +{ + if (val.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 (val.level) + << std::setw (cruft::cast::lossless (level_width ())) + << std::left + << val.level + << std::setw (0) + << cruft::term::csi::graphics::RESET + << "] " << val.message << std::endl; + } + + if (needs_break (val.level)) + breakpoint (); +} diff --git a/log/sink/console.hpp b/log/sink/console.hpp new file mode 100644 index 00000000..09ebefbd --- /dev/null +++ b/log/sink/console.hpp @@ -0,0 +1,22 @@ +/* + * 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 2019, Danny Robson + */ + +#pragma once + +#include "base.hpp" + +#include "ostream.hpp" + +namespace cruft::log::sink { + class console : public crtp { + public: + console (std::string name); + + virtual void write (packet const&); + }; +} \ No newline at end of file diff --git a/log/sink/null.cpp b/log/sink/null.cpp new file mode 100644 index 00000000..e69de29b diff --git a/log/sink/null.hpp b/log/sink/null.hpp new file mode 100644 index 00000000..e69de29b diff --git a/log/sink/ostream.cpp b/log/sink/ostream.cpp new file mode 100644 index 00000000..e69de29b diff --git a/log/sink/ostream.hpp b/log/sink/ostream.hpp new file mode 100644 index 00000000..e69de29b diff --git a/log/sink/tee.cpp b/log/sink/tee.cpp new file mode 100644 index 00000000..e69de29b diff --git a/log/sink/tee.hpp b/log/sink/tee.hpp new file mode 100644 index 00000000..e69de29b diff --git a/registrar.hpp b/registrar.hpp index 79a9a4a7..a434bb91 100644 --- a/registrar.hpp +++ b/registrar.hpp @@ -8,7 +8,7 @@ #pragma once -#include "log.hpp" +#include "log/log.hpp" #include "introspection.hpp" #include