diff --git a/db/config.hh b/db/config.hh index 41956b281b..ffc541ca60 100644 --- a/db/config.hh +++ b/db/config.hh @@ -685,6 +685,8 @@ public: val(logger_log_level, string_map, /* none */, Used,\ "map of logger name to log level. Valid values are trace, debug, info, warn, error. " \ "Use --help-loggers for a list of logger names") \ + val(log_to_stdout, bool, true, Used, "Send log output to stdout") \ + val(log_to_syslog, bool, false, Used, "Send log output to syslog") \ /* done! */ #define _make_value_member(name, type, deflt, status, desc, ...) \ diff --git a/log.cc b/log.cc index 0a849b8be2..4fd7e70ca1 100644 --- a/log.cc +++ b/log.cc @@ -3,10 +3,12 @@ */ #include "log.hh" +#include "core/array_map.hh" #include #include #include #include +#include namespace logging { @@ -46,6 +48,9 @@ std::istream& operator>>(std::istream& in, log_level& level) { namespace logging { +std::atomic logger::_stdout = { true }; +std::atomic logger::_syslog = { false }; + logger::logger(sstring name) : _name(std::move(name)) { logger_registry().register_logger(this); } @@ -60,21 +65,52 @@ logger::~logger() { void logger::really_do_log(log_level level, const char* fmt, stringer** s, size_t n) { + std::ostringstream out; const char* p = fmt; while (*p != '\0') { if (*p == '{' && *(p+1) == '}') { p += 2; if (n > 0) { - (*s++)->append(std::cout); + (*s++)->append(out); --n; } else { - std::cout << "???"; + out << "???"; } } else { - std::cout << *p++; + out << *p++; } } - std::cout << "\n"; + out << "\n"; + auto msg = out.str(); + if (_stdout.load(std::memory_order_relaxed)) { + std::cout << out; + } + if (_syslog.load(std::memory_order_relaxed)) { + static array_map level_map = { + { int(log_level::debug), LOG_DEBUG }, + { int(log_level::info), LOG_INFO }, + { int(log_level::trace), LOG_DEBUG }, // no LOG_TRACE + { int(log_level::warn), LOG_WARNING }, + { int(log_level::error), LOG_ERR }, + }; + // NOTE: syslog() can block, which will stall the reactor thread. + // this should be rare (will have to fill the pipe buffer + // before syslogd can clear it) but can happen. If it does, + // we'll have to implement some internal buffering (which + // still means the problem can happen, just less frequently). + // syslog() interprets % characters, so send msg as a parameter + syslog(level_map[int(level)], "%s", msg.c_str()); + } +} + +void +logger::set_stdout_enabled(bool enabled) { + _stdout.store(enabled, std::memory_order_relaxed); +} + +void +logger::set_syslog_enabled(bool enabled) { + _syslog.store(enabled, std::memory_order_relaxed); } void diff --git a/log.hh b/log.hh index e49c788c34..34e71ca8c5 100644 --- a/log.hh +++ b/log.hh @@ -46,6 +46,8 @@ class registry; class logger { sstring _name; std::atomic _level = { log_level::warn }; + static std::atomic _stdout; + static std::atomic _syslog; private: struct stringer { // no need for virtual dtor, since not dynamically destroyed @@ -104,6 +106,8 @@ public: void set_level(log_level level) { _level.store(level, std::memory_order_relaxed); } + static void set_stdout_enabled(bool enabled); + static void set_syslog_enabled(bool enabled); }; class registry { diff --git a/main.cc b/main.cc index da6662ae77..5e48af78ea 100644 --- a/main.cc +++ b/main.cc @@ -36,13 +36,16 @@ void do_help_loggers() { } } -void apply_logger_settings(sstring default_level, db::config::string_map levels) { +void apply_logger_settings(sstring default_level, db::config::string_map levels, + bool log_to_stdout, bool log_to_syslog) { logging::logger_registry().set_all_loggers_level(boost::lexical_cast(std::string(default_level))); for (auto&& kv: levels) { auto&& k = kv.first; auto&& v = kv.second; logging::logger_registry().set_logger_level(k, boost::lexical_cast(std::string(v))); } + logging::logger::set_stdout_enabled(log_to_stdout); + logging::logger::set_syslog_enabled(log_to_syslog); } int main(int ac, char** av) { @@ -75,7 +78,8 @@ int main(int ac, char** av) { auto&& opts = app.configuration(); return read_config(opts, *cfg).then([&cfg, &db, &qp, &proxy, &ctx, &opts]() { - apply_logger_settings(cfg->default_log_level(), cfg->logger_log_level()); + apply_logger_settings(cfg->default_log_level(), cfg->logger_log_level(), + cfg->log_to_stdout(), cfg->log_to_syslog()); dht::set_global_partitioner(cfg->partitioner()); uint16_t thrift_port = cfg->rpc_port(); uint16_t cql_port = cfg->native_transport_port();