mirror of
https://github.com/badaix/snapcast
synced 2025-02-22 23:24:29 +01:00
1251 lines
32 KiB
C++
1251 lines
32 KiB
C++
/***
|
|
__ __ _ _ __ __ ___
|
|
/ _\ ( )( \/ )( ) / \ / __)
|
|
/ \ )( ) ( / (_/\( O )( (_ \
|
|
\_/\_/(__)(_/\_)\____/ \__/ \___/
|
|
version 1.5.1
|
|
https://github.com/badaix/aixlog
|
|
|
|
This file is part of aixlog
|
|
Copyright (C) 2017-2025 Johannes Pohl
|
|
|
|
This software may be modified and distributed under the terms
|
|
of the MIT license. See the LICENSE file for details.
|
|
***/
|
|
|
|
/// inspired by "eater":
|
|
/// https://stackoverflow.com/questions/2638654/redirect-c-stdclog-to-syslog-on-unix
|
|
|
|
#ifndef AIX_LOG_HPP
|
|
#define AIX_LOG_HPP
|
|
|
|
#ifndef _WIN32
|
|
#define HAS_SYSLOG_ 1
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
|
|
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1012
|
|
#define HAS_APPLE_UNIFIED_LOG_ 1
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <chrono>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#ifdef __ANDROID__
|
|
#include <android/log.h>
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#include <Windows.h>
|
|
// ERROR macro is defined in Windows header
|
|
// To avoid conflict between these macro and declaration of ERROR / DEBUG in SEVERITY enum
|
|
// We save macro and undef it
|
|
#pragma push_macro("ERROR")
|
|
#pragma push_macro("DEBUG")
|
|
#undef ERROR
|
|
#undef DEBUG
|
|
#endif
|
|
|
|
#ifdef HAS_APPLE_UNIFIED_LOG_
|
|
#include <os/log.h>
|
|
#endif
|
|
|
|
#ifdef HAS_SYSLOG_
|
|
#include <syslog.h>
|
|
#endif
|
|
|
|
#ifdef __ANDROID__
|
|
// fix for bug "Android NDK __func__ definition is inconsistent with glibc and C++99"
|
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=631489
|
|
#ifdef __GNUC__
|
|
#define AIXLOG_INTERNAL__FUNC __FUNCTION__
|
|
#else
|
|
#define AIXLOG_INTERNAL__FUNC __func__
|
|
#endif
|
|
#else
|
|
#define AIXLOG_INTERNAL__FUNC __func__
|
|
#endif
|
|
|
|
/// Internal helper macros (exposed, but shouldn't be used directly)
|
|
#define AIXLOG_INTERNAL__LOG_SEVERITY(SEVERITY_) std::clog << static_cast<AixLog::Severity>(SEVERITY_) << TAG()
|
|
#define AIXLOG_INTERNAL__LOG_SEVERITY_TAG(SEVERITY_, TAG_) std::clog << static_cast<AixLog::Severity>(SEVERITY_) << TAG(TAG_)
|
|
|
|
#define AIXLOG_INTERNAL__ONE_COLOR(FG_) AixLog::Color::FG_
|
|
#define AIXLOG_INTERNAL__TWO_COLOR(FG_, BG_) AixLog::TextColor(AixLog::Color::FG_, AixLog::Color::BG_)
|
|
|
|
// https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros
|
|
#define AIXLOG_INTERNAL__VAR_PARM(PARAM1_, PARAM2_, FUNC_, ...) FUNC_
|
|
#define AIXLOG_INTERNAL__LOG_MACRO_CHOOSER(...) AIXLOG_INTERNAL__VAR_PARM(__VA_ARGS__, AIXLOG_INTERNAL__LOG_SEVERITY_TAG, AIXLOG_INTERNAL__LOG_SEVERITY, )
|
|
#define AIXLOG_INTERNAL__COLOR_MACRO_CHOOSER(...) AIXLOG_INTERNAL__VAR_PARM(__VA_ARGS__, AIXLOG_INTERNAL__TWO_COLOR, AIXLOG_INTERNAL__ONE_COLOR, )
|
|
|
|
/// External logger macros
|
|
// usage: LOG(SEVERITY) or LOG(SEVERITY, TAG)
|
|
// e.g.: LOG(NOTICE) or LOG(NOTICE, "my tag")
|
|
#ifndef WIN32
|
|
#define LOG(...) AIXLOG_INTERNAL__LOG_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) << TIMESTAMP << FUNC
|
|
#endif
|
|
|
|
// usage: COLOR(TEXT_COLOR, BACKGROUND_COLOR) or COLOR(TEXT_COLOR)
|
|
// e.g.: COLOR(yellow, blue) or COLOR(red)
|
|
#define COLOR(...) AIXLOG_INTERNAL__COLOR_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
|
|
|
|
#define FUNC AixLog::Function(AIXLOG_INTERNAL__FUNC, __FILE__, __LINE__)
|
|
#define TAG AixLog::Tag
|
|
#define COND AixLog::Conditional
|
|
#define TIMESTAMP AixLog::Timestamp(std::chrono::system_clock::now())
|
|
|
|
|
|
// stijnvdb: sorry! :) LOG(SEV, "tag") was not working for Windows and I couldn't figure out how to fix it for windows without potentially breaking everything
|
|
// else...
|
|
// https://stackoverflow.com/questions/3046889/optional-parameters-with-c-macros (Jason Deng)
|
|
#ifdef WIN32
|
|
#define LOG_2(severity, tag) AIXLOG_INTERNAL__LOG_SEVERITY_TAG(severity, tag)
|
|
#define LOG_1(severity) AIXLOG_INTERNAL__LOG_SEVERITY(severity)
|
|
#define LOG_0() LOG_1(0)
|
|
|
|
#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
|
|
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
|
|
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, LOG_2, LOG_1, FUNC_, ...))
|
|
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(__VA_ARGS__())
|
|
#define LOG(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__) << TIMESTAMP << FUNC
|
|
#endif
|
|
|
|
/**
|
|
* @brief
|
|
* Severity of the log message
|
|
*/
|
|
enum SEVERITY
|
|
{
|
|
TRACE = 0,
|
|
DEBUG = 1,
|
|
INFO = 2,
|
|
NOTICE = 3,
|
|
WARNING = 4,
|
|
ERROR = 5,
|
|
FATAL = 6
|
|
};
|
|
|
|
namespace AixLog
|
|
{
|
|
|
|
/**
|
|
* @brief
|
|
* Severity of the log message
|
|
*
|
|
* Mandatory parameter for the LOG macro
|
|
*/
|
|
enum class Severity : std::int8_t
|
|
{
|
|
// Mapping table from AixLog to other loggers. Boost is just for information.
|
|
// https://chromium.googlesource.com/chromium/mini_chromium/+/master/base/logging.cc
|
|
//
|
|
// Aixlog Boost Syslog Android macOS EventLog Syslog Desc
|
|
//
|
|
// trace trace DEBUG VERBOSE DEBUG INFORMATION
|
|
// debug debug DEBUG DEBUG DEBUG INFORMATION debug-level message
|
|
// info info INFO INFO INFO SUCCESS informational message
|
|
// notice NOTICE INFO INFO SUCCESS normal, but significant, condition
|
|
// warning warning WARNING WARN DEFAULT WARNING warning conditions
|
|
// error error ERROR ERROR ERROR ERROR error conditions
|
|
// fatal fatal CRIT FATAL FAULT ERROR critical conditions
|
|
// ALERT action must be taken immediately
|
|
// EMERG system is unusable
|
|
|
|
trace = SEVERITY::TRACE,
|
|
debug = SEVERITY::DEBUG,
|
|
info = SEVERITY::INFO,
|
|
notice = SEVERITY::NOTICE,
|
|
warning = SEVERITY::WARNING,
|
|
error = SEVERITY::ERROR,
|
|
fatal = SEVERITY::FATAL
|
|
};
|
|
|
|
|
|
static Severity to_severity(std::string severity, Severity def = Severity::info)
|
|
{
|
|
std::transform(severity.begin(), severity.end(), severity.begin(), [](unsigned char c) { return static_cast<char>(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<int>(logSeverity);
|
|
return ss.str();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* Color constants used for console colors
|
|
*/
|
|
enum class Color
|
|
{
|
|
none = 0,
|
|
NONE = 0,
|
|
black = 1,
|
|
BLACK = 1,
|
|
red = 2,
|
|
RED = 2,
|
|
green = 3,
|
|
GREEN = 3,
|
|
yellow = 4,
|
|
YELLOW = 4,
|
|
blue = 5,
|
|
BLUE = 5,
|
|
magenta = 6,
|
|
MAGENTA = 6,
|
|
cyan = 7,
|
|
CYAN = 7,
|
|
white = 8,
|
|
WHITE = 8
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Encapsulation of foreground and background color
|
|
*/
|
|
struct TextColor
|
|
{
|
|
TextColor(Color foreground = Color::none, Color background = Color::none) : foreground(foreground), background(background)
|
|
{
|
|
}
|
|
|
|
Color foreground;
|
|
Color background;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* For Conditional logging of a log line
|
|
*/
|
|
struct Conditional
|
|
{
|
|
using EvalFunc = std::function<bool()>;
|
|
|
|
Conditional() : func_([](void) { return true; })
|
|
{
|
|
}
|
|
|
|
Conditional(EvalFunc func) : func_(std::move(func))
|
|
{
|
|
}
|
|
|
|
Conditional(bool value) : func_([value](void) { return value; })
|
|
{
|
|
}
|
|
|
|
virtual ~Conditional() = default;
|
|
|
|
virtual bool is_true() const
|
|
{
|
|
return func_();
|
|
}
|
|
|
|
protected:
|
|
EvalFunc func_;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Timestamp of a log line
|
|
*
|
|
* to_string will convert the time stamp into a string, using the strftime syntax
|
|
*/
|
|
struct Timestamp
|
|
{
|
|
using time_point_sys_clock = std::chrono::time_point<std::chrono::system_clock>;
|
|
|
|
Timestamp(std::nullptr_t) : is_null_(true)
|
|
{
|
|
}
|
|
|
|
Timestamp() : Timestamp(nullptr)
|
|
{
|
|
}
|
|
|
|
Timestamp(const time_point_sys_clock& time_point) : time_point(time_point), is_null_(false)
|
|
{
|
|
}
|
|
|
|
Timestamp(time_point_sys_clock&& time_point) : time_point(time_point), is_null_(false)
|
|
{
|
|
}
|
|
|
|
virtual ~Timestamp() = default;
|
|
|
|
explicit operator bool() const
|
|
{
|
|
return !is_null_;
|
|
}
|
|
|
|
/// strftime format + proprietary "#ms" for milliseconds
|
|
std::string to_string(const std::string& format = "%Y-%m-%d %H-%M-%S.#ms") const
|
|
{
|
|
std::time_t now_c = std::chrono::system_clock::to_time_t(time_point);
|
|
struct ::tm now_tm = localtime_xp(now_c);
|
|
char buffer[256];
|
|
strftime(buffer, sizeof buffer, format.c_str(), &now_tm);
|
|
std::string result(buffer);
|
|
size_t pos = result.find("#ms");
|
|
if (pos != std::string::npos)
|
|
{
|
|
int ms_part = std::chrono::time_point_cast<std::chrono::milliseconds>(time_point).time_since_epoch().count() % 1000;
|
|
char ms_str[4];
|
|
if (snprintf(ms_str, 4, "%03d", ms_part) >= 0)
|
|
result.replace(pos, 3, ms_str);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
time_point_sys_clock time_point;
|
|
|
|
private:
|
|
bool is_null_;
|
|
|
|
inline std::tm localtime_xp(std::time_t timer) const
|
|
{
|
|
std::tm bt;
|
|
#if defined(__unix__)
|
|
localtime_r(&timer, &bt);
|
|
#elif defined(_MSC_VER)
|
|
localtime_s(&bt, &timer);
|
|
#else
|
|
static std::mutex mtx;
|
|
std::lock_guard<std::mutex> lock(mtx);
|
|
bt = *std::localtime(&timer);
|
|
#endif
|
|
return bt;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Tag (string) for log line
|
|
*/
|
|
struct Tag
|
|
{
|
|
Tag(std::nullptr_t) : is_null_(true)
|
|
{
|
|
}
|
|
|
|
Tag() : Tag(nullptr)
|
|
{
|
|
}
|
|
|
|
Tag(const char* text) : text(text), is_null_(false)
|
|
{
|
|
}
|
|
|
|
Tag(const std::string& text) : text(text), is_null_(false)
|
|
{
|
|
}
|
|
|
|
Tag(std::string&& text) : text(std::move(text)), is_null_(false)
|
|
{
|
|
}
|
|
|
|
virtual ~Tag() = default;
|
|
|
|
explicit operator bool() const
|
|
{
|
|
return !is_null_;
|
|
}
|
|
|
|
bool operator<(const Tag& other) const
|
|
{
|
|
return (text < other.text);
|
|
}
|
|
|
|
std::string text;
|
|
|
|
private:
|
|
bool is_null_;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Capture function, file and line number of the log line
|
|
*/
|
|
struct Function
|
|
{
|
|
Function(const std::string& name, const std::string& file, size_t line) : name(name), file(file), line(line), is_null_(false)
|
|
{
|
|
}
|
|
|
|
Function(std::string&& name, std::string&& file, size_t line) : name(std::move(name)), file(std::move(file)), line(line), is_null_(false)
|
|
{
|
|
}
|
|
|
|
Function(std::nullptr_t) : line(0), is_null_(true)
|
|
{
|
|
}
|
|
|
|
Function() : Function(nullptr)
|
|
{
|
|
}
|
|
|
|
virtual ~Function() = default;
|
|
|
|
explicit operator bool() const
|
|
{
|
|
return !is_null_;
|
|
}
|
|
|
|
std::string name;
|
|
std::string file;
|
|
size_t line;
|
|
|
|
private:
|
|
bool is_null_;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Collection of a log line's meta data
|
|
*/
|
|
struct Metadata
|
|
{
|
|
Metadata() : severity(Severity::trace), tag(nullptr), function(nullptr), timestamp(nullptr)
|
|
{
|
|
}
|
|
|
|
Severity severity;
|
|
Tag tag;
|
|
Function function;
|
|
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, Severity> tag_filter_;
|
|
};
|
|
|
|
|
|
/**
|
|
* @brief
|
|
* Abstract log sink
|
|
*
|
|
* All log sinks must inherit from this Sink
|
|
*/
|
|
struct Sink
|
|
{
|
|
Sink(Filter filter) : filter(std::move(filter))
|
|
{
|
|
}
|
|
|
|
virtual ~Sink() = default;
|
|
|
|
virtual void log(const Metadata& metadata, const std::string& message) = 0;
|
|
|
|
Filter filter;
|
|
};
|
|
|
|
/// ostream operators << for the meta data structs
|
|
static std::ostream& operator<<(std::ostream& os, const Severity& log_severity);
|
|
static std::ostream& operator<<(std::ostream& os, const Timestamp& timestamp);
|
|
static std::ostream& operator<<(std::ostream& os, const Tag& tag);
|
|
static std::ostream& operator<<(std::ostream& os, const Function& function);
|
|
static std::ostream& operator<<(std::ostream& os, const Conditional& conditional);
|
|
static std::ostream& operator<<(std::ostream& os, const Color& color);
|
|
static std::ostream& operator<<(std::ostream& os, const TextColor& text_color);
|
|
|
|
using log_sink_ptr = std::shared_ptr<Sink>;
|
|
|
|
/**
|
|
* @brief
|
|
* Main Logger class with "Log::init"
|
|
*
|
|
* Don't use it directly, but call once "Log::init" with your log sink instances.
|
|
* The Log class will simply redirect clog to itself (as a streambuf) and
|
|
* forward whatever went to clog to the log sink instances
|
|
*/
|
|
class Log : public std::basic_streambuf<char, std::char_traits<char>>
|
|
{
|
|
public:
|
|
static Log& instance()
|
|
{
|
|
static Log instance_;
|
|
return instance_;
|
|
}
|
|
|
|
/// Without "init" every LOG(X) will simply go to clog
|
|
static void init(const std::vector<log_sink_ptr>& log_sinks = {})
|
|
{
|
|
Log::instance().log_sinks_.clear();
|
|
|
|
for (const auto& sink : log_sinks)
|
|
Log::instance().add_logsink(sink);
|
|
}
|
|
|
|
template <typename T, typename... Ts>
|
|
static std::shared_ptr<T> init(Ts&&... params)
|
|
{
|
|
std::shared_ptr<T> sink = Log::instance().add_logsink<T>(std::forward<Ts>(params)...);
|
|
init({sink});
|
|
return sink;
|
|
}
|
|
|
|
template <typename T, typename... Ts>
|
|
std::shared_ptr<T> add_logsink(Ts&&... params)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
|
static_assert(std::is_base_of<Sink, typename std::decay<T>::type>::value, "type T must be a Sink");
|
|
std::shared_ptr<T> sink = std::make_shared<T>(std::forward<Ts>(params)...);
|
|
log_sinks_.push_back(sink);
|
|
return sink;
|
|
}
|
|
|
|
void add_logsink(const log_sink_ptr& sink)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
|
log_sinks_.push_back(sink);
|
|
}
|
|
|
|
void remove_logsink(const log_sink_ptr& sink)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
|
log_sinks_.erase(std::remove(log_sinks_.begin(), log_sinks_.end(), sink), log_sinks_.end());
|
|
}
|
|
|
|
protected:
|
|
Log() noexcept : last_buffer_(nullptr), do_log_(true)
|
|
{
|
|
std::clog.rdbuf(this);
|
|
std::clog << Severity() << Tag() << Function() << Conditional() << AixLog::Color::NONE << std::flush;
|
|
}
|
|
|
|
virtual ~Log()
|
|
{
|
|
sync();
|
|
}
|
|
|
|
int sync() override
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
|
if (!get_stream().str().empty())
|
|
{
|
|
if (do_log_)
|
|
{
|
|
for (const auto& sink : log_sinks_)
|
|
{
|
|
if (sink->filter.match(metadata_))
|
|
sink->log(metadata_, get_stream().str());
|
|
}
|
|
}
|
|
get_stream().str("");
|
|
get_stream().clear();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int overflow(int c) override
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(mutex_);
|
|
if (c != EOF)
|
|
{
|
|
if (c == '\n')
|
|
sync();
|
|
else if (do_log_)
|
|
get_stream() << static_cast<char>(c);
|
|
}
|
|
else
|
|
{
|
|
sync();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
private:
|
|
friend std::ostream& operator<<(std::ostream& os, const Severity& log_severity);
|
|
friend std::ostream& operator<<(std::ostream& os, const Timestamp& timestamp);
|
|
friend std::ostream& operator<<(std::ostream& os, const Tag& tag);
|
|
friend std::ostream& operator<<(std::ostream& os, const Function& function);
|
|
friend std::ostream& operator<<(std::ostream& os, const Conditional& conditional);
|
|
|
|
std::stringstream& get_stream()
|
|
{
|
|
auto id = std::this_thread::get_id();
|
|
if ((last_buffer_ == nullptr) || (last_id_ != id))
|
|
{
|
|
last_id_ = id;
|
|
last_buffer_ = &(buffer_[id]);
|
|
}
|
|
return *last_buffer_;
|
|
}
|
|
|
|
/// one buffer per thread to avoid mixed log lines
|
|
std::map<std::thread::id, std::stringstream> buffer_;
|
|
/// the last thread id
|
|
std::thread::id last_id_;
|
|
/// the last buffer
|
|
std::stringstream* last_buffer_ = nullptr;
|
|
Metadata metadata_;
|
|
bool do_log_;
|
|
std::vector<log_sink_ptr> log_sinks_;
|
|
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
|
|
*
|
|
* "format" in the c'tor defines a log pattern.
|
|
* For every log message, these placeholders will be substituded:
|
|
* - strftime syntax is used to format the logging time stamp (%Y, %m, %d, ...)
|
|
* - #ms: milliseconds part of the logging time stamp with leading zeros
|
|
* - #severity: log severity
|
|
* - #tag_func: the log tag. If empty, the function
|
|
* - #tag: the log tag
|
|
* - #function: the function
|
|
* - #message: the log message
|
|
*/
|
|
struct SinkFormat : public Sink
|
|
{
|
|
SinkFormat(const Filter& filter, const std::string& format) : Sink(filter), format_(format)
|
|
{
|
|
}
|
|
|
|
virtual void set_format(const std::string& format)
|
|
{
|
|
format_ = format;
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override = 0;
|
|
|
|
protected:
|
|
virtual void do_log(std::ostream& stream, const Metadata& metadata, const std::string& message) const
|
|
{
|
|
std::string result = format_;
|
|
if (metadata.timestamp)
|
|
result = metadata.timestamp.to_string(result);
|
|
|
|
size_t pos = result.find("#severity");
|
|
if (pos != std::string::npos)
|
|
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) << to_string(metadata.severity) << TextColor(Color::NONE);
|
|
result.replace(pos, 15, ss.str());
|
|
}
|
|
|
|
pos = result.find("#tag_func");
|
|
if (pos != std::string::npos)
|
|
result.replace(pos, 9, metadata.tag ? metadata.tag.text : (metadata.function ? metadata.function.name : "log"));
|
|
|
|
pos = result.find("#tag");
|
|
if (pos != std::string::npos)
|
|
result.replace(pos, 4, metadata.tag ? metadata.tag.text : "");
|
|
|
|
pos = result.find("#function");
|
|
if (pos != std::string::npos)
|
|
result.replace(pos, 9, metadata.function ? metadata.function.name : "");
|
|
|
|
pos = result.find("#message");
|
|
if (pos != std::string::npos)
|
|
{
|
|
result.replace(pos, 8, message);
|
|
stream << result << std::endl;
|
|
}
|
|
else
|
|
{
|
|
if (result.empty() || (result.back() == ' '))
|
|
stream << result << message << std::endl;
|
|
else
|
|
stream << result << " " << message << std::endl;
|
|
}
|
|
}
|
|
|
|
std::string format_;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Formatted logging to cout
|
|
*/
|
|
struct SinkCout : public SinkFormat
|
|
{
|
|
SinkCout(const Filter& filter, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") : SinkFormat(filter, format)
|
|
{
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
do_log(std::cout, metadata, message);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Formatted logging to cerr
|
|
*/
|
|
struct SinkCerr : public SinkFormat
|
|
{
|
|
SinkCerr(const Filter& filter, const std::string& format = "%Y-%m-%d %H-%M-%S.#ms [#severity] (#tag_func)") : SinkFormat(filter, format)
|
|
{
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
do_log(std::cerr, metadata, message);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Formatted logging to file
|
|
*/
|
|
struct SinkFile : public SinkFormat
|
|
{
|
|
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);
|
|
}
|
|
|
|
~SinkFile() override
|
|
{
|
|
ofs.close();
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
do_log(ofs, metadata, message);
|
|
}
|
|
|
|
protected:
|
|
mutable std::ofstream ofs;
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* @brief
|
|
* Windows: Logging to OutputDebugString
|
|
*
|
|
* Not tested due to unavailability of Windows
|
|
*/
|
|
struct SinkOutputDebugString : public Sink
|
|
{
|
|
SinkOutputDebugString(const Filter& filter) : Sink(filter)
|
|
{
|
|
}
|
|
|
|
void log(const Metadata& /*metadata*/, const std::string& message) override
|
|
{
|
|
#ifdef UNICODE
|
|
std::wstring wide = std::wstring(message.begin(), message.end());
|
|
OutputDebugString(wide.c_str());
|
|
#else
|
|
OutputDebugString(message.c_str());
|
|
#endif
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef HAS_APPLE_UNIFIED_LOG_
|
|
/**
|
|
* @brief
|
|
* macOS: Logging to Apples system logger
|
|
*/
|
|
struct SinkUnifiedLogging : public Sink
|
|
{
|
|
SinkUnifiedLogging(const Filter& filter) : Sink(filter)
|
|
{
|
|
}
|
|
|
|
os_log_type_t get_os_log_type(Severity severity) const
|
|
{
|
|
// https://developer.apple.com/documentation/os/os_log_type_t?language=objc
|
|
switch (severity)
|
|
{
|
|
case Severity::trace:
|
|
case Severity::debug:
|
|
return OS_LOG_TYPE_DEBUG;
|
|
case Severity::info:
|
|
case Severity::notice:
|
|
return OS_LOG_TYPE_INFO;
|
|
case Severity::warning:
|
|
return OS_LOG_TYPE_DEFAULT;
|
|
case Severity::error:
|
|
return OS_LOG_TYPE_ERROR;
|
|
case Severity::fatal:
|
|
return OS_LOG_TYPE_FAULT;
|
|
default:
|
|
return OS_LOG_TYPE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
os_log_with_type(OS_LOG_DEFAULT, get_os_log_type(metadata.severity), "%{public}s", message.c_str());
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef HAS_SYSLOG_
|
|
/**
|
|
* @brief
|
|
* UNIX: Logging to syslog
|
|
*/
|
|
struct SinkSyslog : public Sink
|
|
{
|
|
SinkSyslog(const char* ident, const Filter& filter) : Sink(filter)
|
|
{
|
|
openlog(ident, LOG_PID, LOG_USER);
|
|
}
|
|
|
|
~SinkSyslog() override
|
|
{
|
|
closelog();
|
|
}
|
|
|
|
int get_syslog_priority(Severity severity) const
|
|
{
|
|
// http://unix.superglobalmegacorp.com/Net2/newsrc/sys/syslog.h.html
|
|
switch (severity)
|
|
{
|
|
case Severity::trace:
|
|
case Severity::debug:
|
|
return LOG_DEBUG;
|
|
case Severity::info:
|
|
return LOG_INFO;
|
|
case Severity::notice:
|
|
return LOG_NOTICE;
|
|
case Severity::warning:
|
|
return LOG_WARNING;
|
|
case Severity::error:
|
|
return LOG_ERR;
|
|
case Severity::fatal:
|
|
return LOG_CRIT;
|
|
default:
|
|
return LOG_INFO;
|
|
}
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
syslog(get_syslog_priority(metadata.severity), "(%s) %s", metadata.tag.text.c_str(), message.c_str());
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#ifdef __ANDROID__
|
|
/**
|
|
* @brief
|
|
* Android: Logging to android log
|
|
*
|
|
* Use logcat to read the logs
|
|
*/
|
|
struct SinkAndroid : public Sink
|
|
{
|
|
SinkAndroid(const std::string& ident, const Filter& filter) : Sink(filter), ident_(ident)
|
|
{
|
|
}
|
|
|
|
android_LogPriority get_android_prio(Severity severity) const
|
|
{
|
|
// https://developer.android.com/ndk/reference/log_8h.html
|
|
switch (severity)
|
|
{
|
|
case Severity::trace:
|
|
return ANDROID_LOG_VERBOSE;
|
|
case Severity::debug:
|
|
return ANDROID_LOG_DEBUG;
|
|
case Severity::info:
|
|
case Severity::notice:
|
|
return ANDROID_LOG_INFO;
|
|
case Severity::warning:
|
|
return ANDROID_LOG_WARN;
|
|
case Severity::error:
|
|
return ANDROID_LOG_ERROR;
|
|
case Severity::fatal:
|
|
return ANDROID_LOG_FATAL;
|
|
default:
|
|
return ANDROID_LOG_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
std::string tag = metadata.tag ? metadata.tag.text : (metadata.function ? metadata.function.name : "");
|
|
std::string log_tag;
|
|
if (!ident_.empty() && !tag.empty())
|
|
log_tag = ident_ + "." + tag;
|
|
else if (!ident_.empty())
|
|
log_tag = ident_;
|
|
else if (!tag.empty())
|
|
log_tag = tag;
|
|
else
|
|
log_tag = "log";
|
|
|
|
__android_log_write(get_android_prio(metadata.severity), log_tag.c_str(), message.c_str());
|
|
}
|
|
|
|
protected:
|
|
std::string ident_;
|
|
};
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* @brief
|
|
* Windows: Logging to event logger
|
|
*
|
|
* Not tested due to unavailability of Windows
|
|
*/
|
|
struct SinkEventLog : public Sink
|
|
{
|
|
SinkEventLog(const std::string& ident, const Filter& filter) : Sink(filter)
|
|
{
|
|
#ifdef UNICODE
|
|
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());
|
|
#else
|
|
event_log = RegisterEventSource(NULL, ident.c_str());
|
|
#endif
|
|
}
|
|
|
|
WORD get_type(Severity severity) const
|
|
{
|
|
// https://msdn.microsoft.com/de-de/library/windows/desktop/aa363679(v=vs.85).aspx
|
|
switch (severity)
|
|
{
|
|
case Severity::trace:
|
|
case Severity::debug:
|
|
return EVENTLOG_INFORMATION_TYPE;
|
|
case Severity::info:
|
|
case Severity::notice:
|
|
return EVENTLOG_SUCCESS;
|
|
case Severity::warning:
|
|
return EVENTLOG_WARNING_TYPE;
|
|
case Severity::error:
|
|
case Severity::fatal:
|
|
return EVENTLOG_ERROR_TYPE;
|
|
default:
|
|
return EVENTLOG_INFORMATION_TYPE;
|
|
}
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
#ifdef UNICODE
|
|
std::wstring wide = std::wstring(message.begin(), message.end());
|
|
// We need this temp variable because we cannot take address of rValue
|
|
const auto* c_str = wide.c_str();
|
|
ReportEvent(event_log, get_type(metadata.severity), 0, 0, NULL, 1, 0, &c_str, NULL);
|
|
#else
|
|
const auto* c_str = message.c_str();
|
|
ReportEvent(event_log, get_type(metadata.severity), 0, 0, NULL, 1, 0, &c_str, NULL);
|
|
#endif
|
|
}
|
|
|
|
protected:
|
|
HANDLE event_log;
|
|
};
|
|
#endif
|
|
|
|
/**
|
|
* @brief
|
|
* Log to the system's native sys logger
|
|
*
|
|
* - Android: Android log
|
|
* - macOS: unified log
|
|
* - Windows: event log
|
|
* - Unix: syslog
|
|
*/
|
|
struct SinkNative : public Sink
|
|
{
|
|
SinkNative(const std::string& ident, const Filter& filter) : Sink(filter), log_sink_(nullptr), ident_(ident)
|
|
{
|
|
#ifdef __ANDROID__
|
|
log_sink_ = std::make_shared<SinkAndroid>(ident_, filter);
|
|
#elif HAS_APPLE_UNIFIED_LOG_
|
|
log_sink_ = std::make_shared<SinkUnifiedLogging>(filter);
|
|
#elif _WIN32
|
|
log_sink_ = std::make_shared<SinkEventLog>(ident, filter);
|
|
#elif HAS_SYSLOG_
|
|
log_sink_ = std::make_shared<SinkSyslog>(ident_.c_str(), filter);
|
|
#else
|
|
/// will not throw or something. Use "get_logger()" to check for success
|
|
log_sink_ = nullptr;
|
|
#endif
|
|
}
|
|
|
|
virtual log_sink_ptr get_logger()
|
|
{
|
|
return log_sink_;
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
if (log_sink_ != nullptr)
|
|
log_sink_->log(metadata, message);
|
|
}
|
|
|
|
protected:
|
|
log_sink_ptr log_sink_;
|
|
std::string ident_;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* Forward log messages to a callback function
|
|
*
|
|
* Pass the callback function to the c'tor.
|
|
* This can be any function that matches the signature of "callback_fun"
|
|
* Might also be a lambda function
|
|
*/
|
|
struct SinkCallback : public Sink
|
|
{
|
|
using callback_fun = std::function<void(const Metadata& metadata, const std::string& message)>;
|
|
|
|
SinkCallback(const Filter& filter, callback_fun callback) : Sink(filter), callback_(std::move(callback))
|
|
{
|
|
}
|
|
|
|
void log(const Metadata& metadata, const std::string& message) override
|
|
{
|
|
if (callback_)
|
|
callback_(metadata, message);
|
|
}
|
|
|
|
private:
|
|
callback_fun callback_;
|
|
};
|
|
|
|
/**
|
|
* @brief
|
|
* ostream << operator for "Severity"
|
|
*
|
|
* Severity must be the first thing that is logged into clog, since it will reset the loggers metadata.
|
|
*/
|
|
static std::ostream& operator<<(std::ostream& os, const Severity& log_severity)
|
|
{
|
|
Log* log = dynamic_cast<Log*>(os.rdbuf());
|
|
if (log != nullptr)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(log->mutex_);
|
|
if (log->metadata_.severity != log_severity)
|
|
{
|
|
log->sync();
|
|
log->metadata_.severity = log_severity;
|
|
log->metadata_.timestamp = nullptr;
|
|
log->metadata_.tag = nullptr;
|
|
log->metadata_.function = nullptr;
|
|
log->do_log_ = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
os << to_string(log_severity);
|
|
}
|
|
return os;
|
|
}
|
|
|
|
static std::ostream& operator<<(std::ostream& os, const Timestamp& timestamp)
|
|
{
|
|
Log* log = dynamic_cast<Log*>(os.rdbuf());
|
|
if (log != nullptr)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(log->mutex_);
|
|
log->metadata_.timestamp = timestamp;
|
|
}
|
|
else if (timestamp)
|
|
{
|
|
os << timestamp.to_string();
|
|
}
|
|
return os;
|
|
}
|
|
|
|
static std::ostream& operator<<(std::ostream& os, const Tag& tag)
|
|
{
|
|
Log* log = dynamic_cast<Log*>(os.rdbuf());
|
|
if (log != nullptr)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(log->mutex_);
|
|
log->metadata_.tag = tag;
|
|
}
|
|
else if (tag)
|
|
{
|
|
os << tag.text;
|
|
}
|
|
return os;
|
|
}
|
|
|
|
static std::ostream& operator<<(std::ostream& os, const Function& function)
|
|
{
|
|
Log* log = dynamic_cast<Log*>(os.rdbuf());
|
|
if (log != nullptr)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(log->mutex_);
|
|
log->metadata_.function = function;
|
|
}
|
|
else if (function)
|
|
{
|
|
os << function.name;
|
|
}
|
|
return os;
|
|
}
|
|
|
|
static std::ostream& operator<<(std::ostream& os, const Conditional& conditional)
|
|
{
|
|
Log* log = dynamic_cast<Log*>(os.rdbuf());
|
|
if (log != nullptr)
|
|
{
|
|
std::lock_guard<std::recursive_mutex> lock(log->mutex_);
|
|
log->do_log_ = conditional.is_true();
|
|
}
|
|
return os;
|
|
}
|
|
|
|
static std::ostream& operator<<(std::ostream& os, const TextColor& text_color)
|
|
{
|
|
os << "\033[";
|
|
if ((text_color.foreground == Color::none) && (text_color.background == Color::none))
|
|
os << "0"; // reset colors if no params
|
|
|
|
if (text_color.foreground != Color::none)
|
|
{
|
|
os << 29 + static_cast<int>(text_color.foreground);
|
|
if (text_color.background != Color::none)
|
|
os << ";";
|
|
}
|
|
if (text_color.background != Color::none)
|
|
os << 39 + static_cast<int>(text_color.background);
|
|
os << "m";
|
|
|
|
return os;
|
|
}
|
|
|
|
static std::ostream& operator<<(std::ostream& os, const Color& color)
|
|
{
|
|
os << TextColor(color);
|
|
return os;
|
|
}
|
|
|
|
} // namespace AixLog
|
|
|
|
#ifdef _WIN32
|
|
// We restore the ERROR Windows macro
|
|
#pragma pop_macro("ERROR")
|
|
#pragma pop_macro("DEBUG")
|
|
#endif
|
|
|
|
#endif // AIX_LOG_HPP
|