diff --git a/client/client_settings.hpp b/client/client_settings.hpp index 5dfabc0f..d7a54f12 100644 --- a/client/client_settings.hpp +++ b/client/client_settings.hpp @@ -49,10 +49,10 @@ struct ClientSettings SharingMode sharing_mode{SharingMode::shared}; }; - struct LoggingSettings + struct Logging { - bool debug{false}; - std::string debug_logfile{""}; + std::string sink{""}; + std::string filter{"*:info"}; }; size_t instance{1}; @@ -60,7 +60,7 @@ struct ClientSettings ServerSettings server; PlayerSettings player; - LoggingSettings logging; + Logging logging; }; #endif diff --git a/client/snapclient.cpp b/client/snapclient.cpp index e7de3b37..f70008c3 100644 --- a/client/snapclient.cpp +++ b/client/snapclient.cpp @@ -94,7 +94,10 @@ int main(int argc, char** argv) OptionParser op("Allowed options"); auto helpSwitch = op.add("", "help", "produce help message"); auto groffSwitch = op.add("", "groff", "produce groff message"); - auto debugOption = op.add, Attribute::hidden>("", "debug", "enable debug logging", ""); // TODO: &settings.logging.debug); + op.add>("", "logsink", "log sink [null,system,stdout,stderr,file:]", settings.logging.sink, &settings.logging.sink); + auto logfilterOption = op.add>( + "", "logfilter", "log filter :[,:]* with tag = * or and level = [trace,debug,info,notice,warning,error,fatal]", + settings.logging.filter); auto versionSwitch = op.add("v", "version", "show version number"); #if defined(HAS_ALSA) || defined(WINDOWS) auto listSwitch = op.add("l", "list", "list PCM devices"); @@ -175,18 +178,42 @@ int main(int argc, char** argv) // XXX: Only one metadata option must be set - // TODO: AixLog::Log::init("snapclient", AixLog::Severity::trace, AixLog::Type::special); - if (debugOption->is_set()) + settings.logging.filter = logfilterOption->value(); + if (logfilterOption->is_set()) { - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); - if (!debugOption->value().empty()) - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, debugOption->value(), - "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); + for (size_t n = 1; n < logfilterOption->count(); ++n) + settings.logging.filter += "," + logfilterOption->value(n); } - else + + if (settings.logging.sink.empty()) + { + settings.logging.sink = "stdout"; +#ifdef HAS_DAEMON + if (daemonOption->is_set()) + settings.logging.sink = "system"; +#endif + } + AixLog::Filter logfilter; + auto filters = utils::string::split(settings.logging.filter, ','); + for (const auto& filter : filters) + logfilter.add_filter(filter); + + string logformat = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"; + if (settings.logging.sink.find("file:") != string::npos) { - AixLog::Log::instance().add_logsink(AixLog::Severity::info, "%Y-%m-%d %H-%M-%S [#severity] (#tag_func)"); + string logfile = settings.logging.sink.substr(settings.logging.sink.find(":") + 1); + AixLog::Log::init(logfilter, logfile, logformat); } + else if (settings.logging.sink == "stdout") + AixLog::Log::init(logfilter, logformat); + else if (settings.logging.sink == "stderr") + AixLog::Log::init(logfilter, logformat); + else if (settings.logging.sink == "system") + AixLog::Log::init("snapclient", logfilter); + else if (settings.logging.sink == "null") + AixLog::Log::init(); + else + throw SnapException("Invalid log sink: " + settings.logging.sink); #ifdef HAS_DAEMON std::unique_ptr daemon; @@ -311,7 +338,7 @@ int main(int argc, char** argv) } catch (const std::exception& e) { - LOG(ERROR) << "Exception: " << e.what() << std::endl; + LOG(FATAL) << "Exception: " << e.what() << std::endl; exitcode = EXIT_FAILURE; } diff --git a/client/stream.cpp b/client/stream.cpp index 22db37f7..f307514e 100644 --- a/client/stream.cpp +++ b/client/stream.cpp @@ -480,8 +480,8 @@ bool Stream::getPlayerChunk(void* outputBuffer, const cs::usec& outputBufferDacT lastUpdate_ = now; median_ = buffer_.median(); shortMedian_ = shortBuffer_.median(); - LOG(INFO, LOG_TAG) << "Chunk: " << age.count() / 100 << "\t" << miniBuffer_.median() / 100 << "\t" << shortMedian_ / 100 << "\t" << median_ / 100 - << "\t" << buffer_.size() << "\t" << cs::duration(outputBufferDacTime) << "\t" << frame_delta_ << "\n"; + LOG(DEBUG, "Stats") << "Chunk: " << age.count() / 100 << "\t" << miniBuffer_.median() / 100 << "\t" << shortMedian_ / 100 << "\t" << median_ / 100 + << "\t" << buffer_.size() << "\t" << cs::duration(outputBufferDacTime) << "\t" << frame_delta_ << "\n"; frame_delta_ = 0; } return (abs(cs::duration(age)) < 500); diff --git a/common/aixlog.hpp b/common/aixlog.hpp index 8e8d4d94..852655f8 100644 --- a/common/aixlog.hpp +++ b/common/aixlog.hpp @@ -3,7 +3,7 @@ / _\ ( )( \/ )( ) / \ / __) / \ )( ) ( / (_/\( O )( (_ \ \_/\_/(__)(_/\_)\____/ \__/ \___/ - version 1.3.0 + version 1.4.0 https://github.com/badaix/aixlog This file is part of aixlog @@ -175,6 +175,53 @@ enum class Severity : std::int8_t }; +static Severity to_severity(std::string severity, Severity def = Severity::info) +{ + std::transform(severity.begin(), severity.end(), severity.begin(), [](unsigned char c) { return std::tolower(c); }); + if (severity == "trace") + return Severity::trace; + else if (severity == "debug") + return Severity::debug; + else if (severity == "info") + return Severity::info; + else if (severity == "notice") + return Severity::notice; + else if (severity == "warning") + return Severity::warning; + else if (severity == "error") + return Severity::error; + else if (severity == "fatal") + return Severity::fatal; + else + return def; +} + + +static std::string to_string(Severity logSeverity) +{ + switch (logSeverity) + { + case Severity::trace: + return "Trace"; + case Severity::debug: + return "Debug"; + case Severity::info: + return "Info"; + case Severity::notice: + return "Notice"; + case Severity::warning: + return "Warn"; + case Severity::error: + return "Error"; + case Severity::fatal: + return "Fatal"; + default: + std::stringstream ss; + ss << static_cast(logSeverity); + return ss.str(); + } +} + /** * @brief * Color constants used for console colors @@ -330,6 +377,10 @@ struct Tag { } + Tag(const char* text) : text(text), is_null_(false) + { + } + Tag(const std::string& text) : text(text), is_null_(false) { } @@ -345,6 +396,11 @@ struct Tag return !is_null_; } + bool operator<(const Tag& other) const + { + return (text < other.text); + } + std::string text; private: @@ -404,6 +460,59 @@ struct Metadata Timestamp timestamp; }; + +class Filter +{ +public: + Filter() + { + } + + Filter(Severity severity) + { + add_filter(severity); + } + + bool match(const Metadata& metadata) const + { + if (tag_filter_.empty()) + return true; + + auto iter = tag_filter_.find(metadata.tag); + if (iter != tag_filter_.end()) + return (metadata.severity >= iter->second); + + iter = tag_filter_.find("*"); + if (iter != tag_filter_.end()) + return (metadata.severity >= iter->second); + + return false; + } + + void add_filter(const Tag& tag, Severity severity) + { + tag_filter_[tag] = severity; + } + + void add_filter(Severity severity) + { + tag_filter_["*"] = severity; + } + + void add_filter(const std::string& filter) + { + auto pos = filter.find(":"); + if (pos != std::string::npos) + add_filter(filter.substr(0, pos), to_severity(filter.substr(pos + 1))); + else + add_filter(to_severity(filter)); + } + +private: + std::map tag_filter_; +}; + + /** * @brief * Abstract log sink @@ -412,7 +521,7 @@ struct Metadata */ struct Sink { - Sink(Severity severity) : severity(severity) + Sink(const Filter& filter) : filter(filter) { } @@ -420,7 +529,7 @@ struct Sink virtual void log(const Metadata& metadata, const std::string& message) = 0; - Severity severity; + Filter filter; }; /// ostream operators << for the meta data structs @@ -490,31 +599,6 @@ class Log : public std::basic_streambuf> log_sinks_.erase(std::remove(log_sinks_.begin(), log_sinks_.end(), sink), log_sinks_.end()); } - static std::string to_string(Severity logSeverity) - { - switch (logSeverity) - { - case Severity::trace: - return "Trace"; - case Severity::debug: - return "Debug"; - case Severity::info: - return "Info"; - case Severity::notice: - return "Notice"; - case Severity::warning: - return "Warn"; - case Severity::error: - return "Error"; - case Severity::fatal: - return "Fatal"; - default: - std::stringstream ss; - ss << logSeverity; - return ss.str(); - } - } - protected: Log() noexcept : last_buffer_(nullptr) { @@ -536,7 +620,7 @@ class Log : public std::basic_streambuf> { for (const auto& sink : log_sinks_) { - if (metadata_.severity >= sink->severity) + if (sink->filter.match(metadata_)) sink->log(metadata_, get_stream().str()); } } @@ -591,6 +675,24 @@ class Log : public std::basic_streambuf> std::recursive_mutex mutex_; }; +/** + * @brief + * Null log sink + * + * Discards all log messages + */ +struct SinkNull : public Sink +{ + SinkNull() : Sink(Filter()) + { + } + + void log(const Metadata& metadata, const std::string& message) override + { + } +}; + + /** * @brief * Abstract log sink with support for formatting log message @@ -607,7 +709,7 @@ class Log : public std::basic_streambuf> */ struct SinkFormat : public Sink { - SinkFormat(Severity severity, const std::string& format) : Sink(severity), format_(format) + SinkFormat(const Filter& filter, const std::string& format) : Sink(filter), format_(format) { } @@ -627,13 +729,13 @@ struct SinkFormat : public Sink size_t pos = result.find("#severity"); if (pos != std::string::npos) - result.replace(pos, 9, Log::to_string(metadata.severity)); + result.replace(pos, 9, to_string(metadata.severity)); pos = result.find("#color_severity"); if (pos != std::string::npos) { std::stringstream ss; - ss << TextColor(Color::RED) << Log::to_string(metadata.severity) << TextColor(Color::NONE); + ss << TextColor(Color::RED) << to_string(metadata.severity) << TextColor(Color::NONE); result.replace(pos, 15, ss.str()); } @@ -673,7 +775,7 @@ struct SinkFormat : public Sink */ struct SinkCout : public SinkFormat { - SinkCout(Severity severity, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") : SinkFormat(severity, format) + SinkCout(const Filter& filter, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") : SinkFormat(filter, format) { } @@ -689,7 +791,7 @@ struct SinkCout : public SinkFormat */ struct SinkCerr : public SinkFormat { - SinkCerr(Severity severity, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") : SinkFormat(severity, format) + SinkCerr(const Filter& filter, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") : SinkFormat(filter, format) { } @@ -705,8 +807,8 @@ struct SinkCerr : public SinkFormat */ struct SinkFile : public SinkFormat { - SinkFile(Severity severity, const std::string& filename, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") - : SinkFormat(severity, format) + SinkFile(const Filter& filter, const std::string& filename, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") + : SinkFormat(filter, format) { ofs.open(filename.c_str(), std::ofstream::out | std::ofstream::trunc); } @@ -734,7 +836,7 @@ struct SinkFile : public SinkFormat */ struct SinkOutputDebugString : public Sink { - SinkOutputDebugString(Severity severity) : Sink(severity) + SinkOutputDebugString(const Filter& filter) : Sink(filter) { } @@ -753,7 +855,7 @@ struct SinkOutputDebugString : public Sink */ struct SinkUnifiedLogging : public Sink { - SinkUnifiedLogging(Severity severity) : Sink(severity) + SinkUnifiedLogging(const Filter& filter) : Sink(filter) { } @@ -793,7 +895,7 @@ struct SinkUnifiedLogging : public Sink */ struct SinkSyslog : public Sink { - SinkSyslog(const char* ident, Severity severity) : Sink(severity) + SinkSyslog(const char* ident, const Filter& filter) : Sink(filter) { openlog(ident, LOG_PID, LOG_USER); } @@ -842,7 +944,7 @@ struct SinkSyslog : public Sink */ struct SinkAndroid : public Sink { - SinkAndroid(const std::string& ident, Severity severity) : Sink(severity), ident_(ident) + SinkAndroid(const std::string& ident, const Filter& filter) : Sink(filter), ident_(ident) { } @@ -899,7 +1001,7 @@ struct SinkAndroid : public Sink */ struct SinkEventLog : public Sink { - SinkEventLog(const std::string& ident, Severity severity) : Sink(severity) + SinkEventLog(const std::string& ident, const Filter& filter) : Sink(filter) { std::wstring wide = std::wstring(ident.begin(), ident.end()); // stijnvdb: RegisterEventSource expands to RegisterEventSourceW which takes wchar_t event_log = RegisterEventSource(NULL, wide.c_str()); @@ -951,16 +1053,16 @@ struct SinkEventLog : public Sink */ struct SinkNative : public Sink { - SinkNative(const std::string& ident, Severity severity) : Sink(severity), log_sink_(nullptr), ident_(ident) + SinkNative(const std::string& ident, const Filter& filter) : Sink(filter), log_sink_(nullptr), ident_(ident) { #ifdef __ANDROID__ - log_sink_ = std::make_shared(ident_, severity); + log_sink_ = std::make_shared(ident_, filter); #elif HAS_APPLE_UNIFIED_LOG_ - log_sink_ = std::make_shared(severity); + log_sink_ = std::make_shared(filter); #elif _WIN32 - log_sink_ = std::make_shared(ident, severity); + log_sink_ = std::make_shared(ident, filter); #elif HAS_SYSLOG_ - log_sink_ = std::make_shared(ident_.c_str(), severity); + log_sink_ = std::make_shared(ident_.c_str(), filter); #else /// will not throw or something. Use "get_logger()" to check for success log_sink_ = nullptr; @@ -995,7 +1097,7 @@ struct SinkCallback : public Sink { using callback_fun = std::function; - SinkCallback(Severity severity, callback_fun callback) : Sink(severity), callback_(callback) + SinkCallback(const Filter& filter, callback_fun callback) : Sink(filter), callback_(callback) { } @@ -1033,7 +1135,7 @@ static std::ostream& operator<<(std::ostream& os, const Severity& log_severity) } else { - os << Log::to_string(log_severity); + os << to_string(log_severity); } return os; } diff --git a/server/etc/snapserver.conf b/server/etc/snapserver.conf index 421e2600..2a932212 100644 --- a/server/etc/snapserver.conf +++ b/server/etc/snapserver.conf @@ -146,10 +146,12 @@ stream = pipe:///tmp/snapfifo?name=default # [logging] -# enable debug logging -#debug = false +# log sink [null,system,stdout,stderr,file:] +# when left empty: if running as daemon "system" else "stdout" +#sink = -# log file name for the debug logs (debug must be enabled) -#debug_logfile = +# log filter :[,:]* +# with tag = * or and level = [trace,debug,info,notice,warning,error,fatal] +#filter = *:info # ############################################################################### diff --git a/server/server_settings.hpp b/server/server_settings.hpp index 9b5cd2ba..c437b056 100644 --- a/server/server_settings.hpp +++ b/server/server_settings.hpp @@ -51,16 +51,16 @@ struct ServerSettings std::vector bind_to_address{{"0.0.0.0"}}; }; - struct LoggingSettings + struct Logging { - bool debug{false}; - std::string debug_logfile{""}; + std::string sink{""}; + std::string filter{"*:info"}; }; HttpSettings http; TcpSettings tcp; StreamSettings stream; - LoggingSettings logging; + Logging logging; }; #endif diff --git a/server/snapserver.cpp b/server/snapserver.cpp index 1d9bc461..685fc451 100644 --- a/server/snapserver.cpp +++ b/server/snapserver.cpp @@ -70,9 +70,11 @@ int main(int argc, char* argv[]) // debug settings OptionParser conf(""); - conf.add>("", "logging.debug", "enable debug logging", settings.logging.debug, &settings.logging.debug); - conf.add>("", "logging.debug_logfile", "log file name for the debug logs (debug must be enabled)", settings.logging.debug_logfile, - &settings.logging.debug_logfile); + op.add>("", "logging.sink", "log sink [null,system,stdout,stderr,file:]", settings.logging.sink, &settings.logging.sink); + auto logfilterOption = op.add>( + "", "logging.filter", + "log filter :[,:]* with tag = * or and level = [trace,debug,info,notice,warning,error,fatal]", + settings.logging.filter); // stream settings conf.add>("", "stream.port", "Server port", settings.stream.port, &settings.stream.port); @@ -182,18 +184,42 @@ int main(int argc, char* argv[]) exit(EXIT_SUCCESS); } - // TODO: AixLog::Log::init("snapserver", AixLog::Severity::trace, AixLog::Type::special); - if (settings.logging.debug) + settings.logging.filter = logfilterOption->value(); + if (logfilterOption->is_set()) { - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); - if (!settings.logging.debug_logfile.empty()) - AixLog::Log::instance().add_logsink(AixLog::Severity::trace, settings.logging.debug_logfile, - "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"); + for (size_t n = 1; n < logfilterOption->count(); ++n) + settings.logging.filter += "," + logfilterOption->value(n); } - else + + if (settings.logging.sink.empty()) + { + settings.logging.sink = "stdout"; +#ifdef HAS_DAEMON + if (daemonOption->is_set()) + settings.logging.sink = "system"; +#endif + } + AixLog::Filter logfilter; + auto filters = utils::string::split(settings.logging.filter, ','); + for (const auto& filter : filters) + logfilter.add_filter(filter); + + string logformat = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)"; + if (settings.logging.sink.find("file:") != string::npos) { - AixLog::Log::instance().add_logsink(AixLog::Severity::info, "%Y-%m-%d %H-%M-%S [#severity] (#tag_func)"); + string logfile = settings.logging.sink.substr(settings.logging.sink.find(":") + 1); + AixLog::Log::init(logfilter, logfile, logformat); } + else if (settings.logging.sink == "stdout") + AixLog::Log::init(logfilter, logformat); + else if (settings.logging.sink == "stderr") + AixLog::Log::init(logfilter, logformat); + else if (settings.logging.sink == "system") + AixLog::Log::init("snapclient", logfilter); + else if (settings.logging.sink == "null") + AixLog::Log::init(); + else + throw SnapException("Invalid log sink: " + settings.logging.sink); for (const auto& opt : conf.unknown_options()) LOG(WARNING) << "unknown configuration option: " << opt << "\n";