logvisor/include/logvisor/logvisor.hpp

322 lines
9.6 KiB
C++
Raw Normal View History

2018-10-06 20:35:33 -07:00
#pragma once
2015-07-03 15:08:41 -07:00
2017-12-28 23:53:09 -08:00
#include <cstring>
#include <cstdio>
#include <cstdlib>
2015-07-03 15:08:41 -07:00
#include <vector>
#include <atomic>
2015-07-03 15:08:41 -07:00
#include <memory>
#include <mutex>
2015-07-03 15:08:41 -07:00
2019-07-19 21:21:39 -07:00
#include <fmt/format.h>
#if defined(__SWITCH__) && !defined(LOGVISOR_NX_LM)
#define LOGVISOR_NX_LM 0
#endif
2016-03-04 15:01:18 -08:00
extern "C" void logvisorBp();
#define log_typeid(type) std::hash<std::string>()(#type)
2015-11-25 23:32:50 -08:00
2018-12-07 21:17:15 -08:00
namespace logvisor {
2015-07-03 15:08:41 -07:00
[[noreturn]] void logvisorAbort();
2015-07-03 22:20:02 -07:00
#if _WIN32 && UNICODE
2015-07-03 15:08:41 -07:00
#define LOG_UCS2 1
#endif
2015-10-07 16:18:37 -07:00
/* True if ANSI color available */
extern bool XtermColor;
2015-07-03 15:08:41 -07:00
/**
* @brief Severity level for log messages
*/
2018-12-07 21:17:15 -08:00
enum Level {
Info, /**< Non-error informative message */
Warning, /**< Non-error warning message */
Error, /**< Recoverable error message */
Fatal /**< Non-recoverable error message (throws exception) */
2015-07-03 15:08:41 -07:00
};
/**
* @brief Backend interface for receiving app-wide log events
*/
2018-12-07 21:17:15 -08:00
struct ILogger {
private:
uint64_t m_typeHash;
public:
ILogger(uint64_t typeHash) : m_typeHash(typeHash) {}
2019-07-19 21:21:39 -07:00
virtual ~ILogger() = default;
virtual void report(const char* modName, Level severity, fmt::string_view format, fmt::format_args args) = 0;
virtual void report(const char* modName, Level severity, fmt::wstring_view format, fmt::wformat_args args) = 0;
virtual void reportSource(const char* modName, Level severity, const char* file, unsigned linenum,
fmt::string_view format, fmt::format_args args) = 0;
2018-12-07 21:17:15 -08:00
virtual void reportSource(const char* modName, Level severity, const char* file, unsigned linenum,
2019-07-19 21:21:39 -07:00
fmt::wstring_view format, fmt::wformat_args args) = 0;
[[nodiscard]] uint64_t getTypeId() const { return m_typeHash; }
2015-07-03 15:08:41 -07:00
};
2018-01-09 22:14:40 -08:00
/**
* @brief Terminate all child processes
*
* Implicitly called on abort condition.
*/
void KillProcessTree();
2015-07-03 15:08:41 -07:00
/**
* @brief Assign calling thread a descriptive name
* @param name Descriptive thread name
*/
void RegisterThreadName(const char* name);
/**
* @brief Centralized logger vector
*
* All loggers added to this vector will receive reports as they occur
*/
extern std::vector<std::unique_ptr<ILogger>> MainLoggers;
/**
* @brief Centralized error counter
*
* All submodules accumulate this value
*/
extern std::atomic_size_t ErrorCount;
2015-07-03 15:08:41 -07:00
/**
* @brief Centralized frame index
*
* All log events include this as part of their timestamp if non-zero.
* The default value is zero, the app is responsible for updating it
* within its main loop.
*/
2015-07-26 13:55:17 -07:00
extern std::atomic_uint_fast64_t FrameIndex;
2015-07-03 15:08:41 -07:00
/**
* @brief Centralized logging lock
*
* Ensures logging streams aren't written concurrently
*/
2018-12-07 21:17:15 -08:00
struct LogMutex {
bool enabled = true;
std::recursive_mutex mutex;
~LogMutex() { enabled = false; }
std::unique_lock<std::recursive_mutex> lock() {
if (enabled)
return std::unique_lock<std::recursive_mutex>(mutex);
else
return std::unique_lock<std::recursive_mutex>();
}
2017-01-18 12:54:00 -08:00
};
extern LogMutex _LogMutex;
/**
* @brief Take a centralized lock for the logging output stream(s)
* @return RAII mutex lock
*/
2019-07-19 21:21:39 -07:00
inline std::unique_lock<std::recursive_mutex> LockLog() { return _LogMutex.lock(); }
2018-03-23 14:38:33 -07:00
extern uint64_t _LogCounter;
/**
* @brief Get current count of logging events
* @return Log Count
*/
2019-07-19 21:21:39 -07:00
inline uint64_t GetLogCounter() { return _LogCounter; }
2018-03-23 14:38:33 -07:00
2015-07-03 15:08:41 -07:00
/**
* @brief Restore centralized logger vector to default state (silent operation)
*/
2019-07-19 21:21:39 -07:00
inline void UnregisterLoggers() { MainLoggers.clear(); }
2015-07-03 15:08:41 -07:00
/**
* @brief Construct and register a real-time console logger singleton
*
* This will output to stderr on POSIX platforms and spawn a new console window on Windows.
* If there's already a registered console logger, this is a no-op.
*/
void RegisterConsoleLogger();
/**
* @brief Construct and register a file logger
* @param filepath Path to write the file
*
* If there's already a file logger registered to the same file, this is a no-op.
*/
void RegisterFileLogger(const char* filepath);
/**
* @brief Register signal handlers with system for common client exceptions
*/
void RegisterStandardExceptions();
#if SENTRY_ENABLED
/**
* @brief Register Sentry crash reporting & logging.
* @param appName The application name
* @param appVersion The application version
* @param cacheDir Directory for Sentry cache files
*/
void RegisterSentry(const char* appName, const char* appVersion, const char* cacheDir);
#endif
2015-11-04 16:02:40 -08:00
#if _WIN32
/**
* @brief Spawn an application-owned cmd.exe window for displaying console output
*/
void CreateWin32Console();
#endif
2015-07-03 15:08:41 -07:00
#if LOG_UCS2
/**
* @brief Construct and register a file logger (wchar_t version)
* @param filepath Path to write the file
*
* If there's already a file logger registered to the same file, this is a no-op.
*/
void RegisterFileLogger(const wchar_t* filepath);
#endif
/**
* @brief This is constructed per-subsystem in a locally centralized fashion
2015-07-03 15:08:41 -07:00
*/
2018-12-07 21:17:15 -08:00
class Module {
const char* m_modName;
2019-07-19 21:21:39 -07:00
template <typename Char>
void _vreport(Level severity, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
auto lk = LockLog();
++_LogCounter;
if (severity == Fatal)
RegisterConsoleLogger();
for (auto& logger : MainLoggers)
logger->report(m_modName, severity, format, args);
if (severity == Error || severity == Fatal)
logvisorBp();
if (severity == Fatal)
logvisorAbort();
else if (severity == Error)
++ErrorCount;
2018-12-07 21:17:15 -08:00
}
2019-07-19 21:21:39 -07:00
template <typename Char>
void _vreportSource(Level severity, const char* file, unsigned linenum, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
2018-12-07 21:17:15 -08:00
auto lk = LockLog();
++_LogCounter;
if (severity == Fatal)
RegisterConsoleLogger();
2019-07-19 21:21:39 -07:00
for (auto& logger : MainLoggers)
logger->reportSource(m_modName, severity, file, linenum, format, args);
2018-12-07 21:17:15 -08:00
if (severity == Error || severity == Fatal)
logvisorBp();
if (severity == Fatal)
logvisorAbort();
else if (severity == Error)
++ErrorCount;
}
2019-07-19 21:21:39 -07:00
public:
constexpr Module(const char* modName) : m_modName(modName) {}
2019-07-19 21:21:39 -07:00
/**
* @brief Route new log message to centralized ILogger
* @param severity Level of log report severity
* @param format fmt-style format string
2019-07-19 21:21:39 -07:00
*/
template <typename S, typename... Args, typename Char = fmt::char_t<S>>
void report(Level severity, const S& format, Args&&... args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreport(severity, fmt::to_string_view<Char>(format),
fmt::basic_format_args<fmt::buffer_context<Char>>(
fmt::make_args_checked<Args...>(format, std::forward<Args>(args)...)));
2019-07-19 21:21:39 -07:00
}
template <typename Char>
void vreport(Level severity, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreport(severity, format, args);
}
2018-12-07 21:17:15 -08:00
/**
* @brief Route new log message with source info to centralized ILogger
* @param severity Level of log report severity
* @param file Source file name from __FILE__ macro
* @param linenum Source line number from __LINE__ macro
* @param format fmt-style format string
2018-12-07 21:17:15 -08:00
*/
2019-07-19 21:21:39 -07:00
template <typename S, typename... Args, typename Char = fmt::char_t<S>>
void reportSource(Level severity, const char* file, unsigned linenum, const S& format, Args&&... args) {
2018-12-07 21:17:15 -08:00
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreportSource(severity, file, linenum, fmt::to_string_view<Char>(format),
fmt::basic_format_args<fmt::buffer_context<Char>>(
fmt::make_args_checked<Args...>(format, std::forward<Args>(args)...)));
2018-12-07 21:17:15 -08:00
}
2019-07-19 21:21:39 -07:00
template <typename Char>
void vreportSource(Level severity, const char* file, unsigned linenum, fmt::basic_string_view<Char> format,
fmt::basic_format_args<fmt::buffer_context<Char>> args) {
if (MainLoggers.empty() && severity != Level::Fatal)
return;
_vreportSource(severity, file, linenum, format, args);
2018-12-07 21:17:15 -08:00
}
2015-07-03 15:08:41 -07:00
};
#define FMT_CUSTOM_FORMATTER(tp, fmtstr, ...) \
2019-07-19 21:21:39 -07:00
namespace fmt { \
template <> \
struct formatter<tp, char> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
2020-04-11 15:44:21 -07:00
return format_to(ctx.out(), FMT_STRING(fmtstr), __VA_ARGS__); \
2019-07-19 21:21:39 -07:00
} \
}; \
template <> \
struct formatter<tp, wchar_t> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
2020-04-11 15:44:21 -07:00
return format_to(ctx.out(), FMT_STRING(L##fmtstr), __VA_ARGS__); \
2019-07-19 21:21:39 -07:00
} \
}; \
template <> \
struct formatter<tp, char16_t> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
2020-04-11 15:44:21 -07:00
return format_to(ctx.out(), FMT_STRING(u##fmtstr), __VA_ARGS__); \
2019-07-19 21:21:39 -07:00
} \
}; \
template <> \
struct formatter<tp, char32_t> { \
template <typename ParseContext> \
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); } \
template <typename FormatContext> \
auto format(const tp &obj, FormatContext &ctx) { \
2020-04-11 15:44:21 -07:00
return format_to(ctx.out(), FMT_STRING(U##fmtstr), __VA_ARGS__); \
2019-07-19 21:21:39 -07:00
} \
}; \
}
2018-12-07 21:17:15 -08:00
} // namespace logvisor
2020-05-03 23:09:21 -07:00
template <typename S, typename... Args, typename Char = fmt::char_t<S>>
void quicklog(const S& format, Args&&... args) {
logvisor::MainLoggers[0]->report(
"quick", logvisor::Info, fmt::to_string_view<Char>(format),
fmt::basic_format_args<fmt::buffer_context<Char>>(
fmt::make_args_checked<Args...>(format, std::forward<Args>(args)...)));
2020-05-03 23:09:21 -07:00
}