mirror of
https://github.com/AxioDL/logvisor.git
synced 2025-05-16 20:31:24 +00:00
initial round of logger integration
This commit is contained in:
parent
0a742ac974
commit
a3e1cea106
@ -4,6 +4,8 @@ TARGET = LogVisor
|
||||
CONFIG -= Qt
|
||||
QT =
|
||||
|
||||
INCLUDEPATH += include
|
||||
|
||||
HEADERS += \
|
||||
include/LogVisor/LogVisor.hpp
|
||||
|
||||
|
@ -0,0 +1,124 @@
|
||||
#ifndef __LOG_VISOR_HPP__
|
||||
#define __LOG_VISOR_HPP__
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace LogVisor
|
||||
{
|
||||
|
||||
#if WIN32 && UNICODE
|
||||
#define LOG_UCS2 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Severity level for log messages
|
||||
*/
|
||||
enum Level
|
||||
{
|
||||
INFO, /**< Non-error informative message */
|
||||
WARNING, /**< Non-error warning message */
|
||||
ERROR, /**< Recoverable error message */
|
||||
FATAL_ERROR /**< Non-recoverable error message (calls abort) */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Backend interface for receiving app-wide log events
|
||||
*/
|
||||
struct ILogger
|
||||
{
|
||||
virtual ~ILogger() {}
|
||||
virtual void report(const char* modName, Level severity,
|
||||
const char* format, va_list ap)=0;
|
||||
virtual void report(const char* modName, Level severity,
|
||||
const wchar_t* format, va_list ap)=0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 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.
|
||||
*/
|
||||
extern uint64_t FrameIndex;
|
||||
|
||||
/**
|
||||
* @brief Restore centralized logger vector to default state (silent operation)
|
||||
*/
|
||||
static inline void UnregisterLoggers() {MainLoggers.clear();}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
#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 fashon
|
||||
*/
|
||||
class LogModule
|
||||
{
|
||||
const char* m_modName;
|
||||
public:
|
||||
LogModule(const char* modName) : m_modName(modName) {}
|
||||
|
||||
/**
|
||||
* @brief Route new log message to centralized ILogger
|
||||
* @param severity Level of log report severity
|
||||
* @param format Standard printf-style format string
|
||||
*/
|
||||
template <typename CharType>
|
||||
inline void report(Level severity, const CharType* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
for (auto& logger : MainLoggers)
|
||||
logger->report(m_modName, severity, format, ap);
|
||||
va_end(ap);
|
||||
if (severity == FATAL_ERROR)
|
||||
abort();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // __LOG_VISOR_HPP__
|
329
lib/LogVisor.cpp
329
lib/LogVisor.cpp
@ -0,0 +1,329 @@
|
||||
#if _WIN32
|
||||
#define _WIN32_LEAN_AND_MEAN 1
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <stdio.h>
|
||||
#include "LogVisor/LogVisor.hpp"
|
||||
|
||||
/* ANSI sequences */
|
||||
#define RED "\x1b[39m"
|
||||
#define YELLOW "\x1b[41m"
|
||||
#define GREEN "\x1b[40m"
|
||||
#define CYAN "\x1b[44m"
|
||||
#define BOLD "\x1b[1m"
|
||||
#define NORMAL "\x1b[0m"
|
||||
|
||||
namespace LogVisor
|
||||
{
|
||||
|
||||
static std::unordered_map<std::thread::id, const char*> ThreadMap;
|
||||
void RegisterThreadName(const char* name)
|
||||
{
|
||||
ThreadMap[std::this_thread::get_id()] = name;
|
||||
#if __APPLE__
|
||||
pthread_setname_np(name);
|
||||
#elif __linux__
|
||||
pthread_setname_np(pthread_self(), name);
|
||||
#elif _WIN32
|
||||
struct
|
||||
{
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} info = {0x1000, name, -1, 0};
|
||||
__try
|
||||
{
|
||||
RaiseException(0x406D1388, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except(EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<ILogger>> MainLoggers;
|
||||
static std::chrono::steady_clock MonoClock;
|
||||
static std::chrono::steady_clock::time_point GlobalStart = MonoClock.now();
|
||||
static inline std::chrono::steady_clock::duration CurrentUptime()
|
||||
{return MonoClock.now() - GlobalStart;}
|
||||
uint64_t FrameIndex = 0;
|
||||
|
||||
#if _WIN32
|
||||
static HANDLE Term = 0;
|
||||
#else
|
||||
static const char* Term = nullptr;
|
||||
#endif
|
||||
static bool XtermColor = false;
|
||||
struct ConsoleLogger : public ILogger
|
||||
{
|
||||
std::mutex m;
|
||||
ConsoleLogger()
|
||||
{
|
||||
#if _WIN32
|
||||
if (!Term)
|
||||
Term = GetStdHandle(STD_ERROR_HANDLE);
|
||||
#else
|
||||
if (!Term)
|
||||
{
|
||||
Term = getenv("TERM");
|
||||
if (!strncmp(Term, "xterm", 5))
|
||||
XtermColor = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void _reportHead(const char* modName, Level severity)
|
||||
{
|
||||
std::chrono::steady_clock::duration tm = CurrentUptime();
|
||||
double tmd = std::chrono::steady_clock::duration::period::num * tm.count() /
|
||||
(double)std::chrono::steady_clock::duration::period::den;
|
||||
std::thread::id thrId = std::this_thread::get_id();
|
||||
const char* thrName = nullptr;
|
||||
if (ThreadMap.find(thrId) != ThreadMap.end())
|
||||
thrName = ThreadMap[thrId];
|
||||
|
||||
#if _WIN32
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY);
|
||||
fprintf(stderr, "[");
|
||||
switch (severity)
|
||||
{
|
||||
case INFO:
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_BLUE);
|
||||
fprintf(stderr, "INFO");
|
||||
break;
|
||||
case WARNING:
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN);
|
||||
fprintf(stderr, "WARNING");
|
||||
break;
|
||||
case ERROR:
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED);
|
||||
fprintf(stderr, "ERROR");
|
||||
break;
|
||||
case FATAL_ERROR:
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_RED);
|
||||
fprintf(stderr, "FATAL ERROR");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY);
|
||||
fprintf(stderr, " %s", modName);
|
||||
if (thrName)
|
||||
fprintf(stderr, " (%s)", thrName);
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
|
||||
fprintf(stderr, " %5.4f", tmd);
|
||||
SetConsoleTextAttribute(Term, FOREGROUND_INTENSITY);
|
||||
fprintf(stderr, "] ");
|
||||
SetConsoleTextAttribute(Term, 0);
|
||||
#else
|
||||
if (XtermColor)
|
||||
{
|
||||
fprintf(stderr, BOLD "[");
|
||||
switch (severity)
|
||||
{
|
||||
case INFO:
|
||||
fprintf(stderr, BOLD CYAN "INFO");
|
||||
break;
|
||||
case WARNING:
|
||||
fprintf(stderr, BOLD YELLOW "WARNING");
|
||||
break;
|
||||
case ERROR:
|
||||
fprintf(stderr, BOLD RED "ERROR");
|
||||
break;
|
||||
case FATAL_ERROR:
|
||||
fprintf(stderr, BOLD RED "FATAL ERROR");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
fprintf(stderr, NORMAL BOLD " %s", modName);
|
||||
if (thrName)
|
||||
fprintf(stderr, " (%s)", thrName);
|
||||
fprintf(stderr, GREEN " %5.4f" NORMAL BOLD, tmd);
|
||||
fprintf(stderr, "] " NORMAL);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "[");
|
||||
switch (severity)
|
||||
{
|
||||
case INFO:
|
||||
fprintf(stderr, "INFO");
|
||||
break;
|
||||
case WARNING:
|
||||
fprintf(stderr, "WARNING");
|
||||
break;
|
||||
case ERROR:
|
||||
fprintf(stderr, "ERROR");
|
||||
break;
|
||||
case FATAL_ERROR:
|
||||
fprintf(stderr, "FATAL ERROR");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
fprintf(stderr, " %s", modName);
|
||||
if (thrName)
|
||||
fprintf(stderr, " (%s)", thrName);
|
||||
fprintf(stderr, " %5.4f", tmd);
|
||||
fprintf(stderr, "] ");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void report(const char* modName, Level severity,
|
||||
const char* format, va_list ap)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
_reportHead(modName, severity);
|
||||
vfprintf(stderr, format, ap);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
void report(const char* modName, Level severity,
|
||||
const wchar_t* format, va_list ap)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
_reportHead(modName, severity);
|
||||
vfwprintf(stderr, format, ap);
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
};
|
||||
|
||||
void RegisterConsoleLogger()
|
||||
{
|
||||
/* Determine if console logger already added */
|
||||
for (auto& logger : MainLoggers)
|
||||
{
|
||||
if (typeid(logger.get()) == typeid(ConsoleLogger))
|
||||
return;
|
||||
}
|
||||
|
||||
/* Otherwise construct new console logger */
|
||||
MainLoggers.emplace_back(new ConsoleLogger);
|
||||
}
|
||||
|
||||
struct FileLogger : public ILogger
|
||||
{
|
||||
FILE* fp;
|
||||
std::mutex m;
|
||||
virtual void openFile()=0;
|
||||
virtual void closeFile() {fclose(fp);}
|
||||
|
||||
void _reportHead(const char* modName, Level severity)
|
||||
{
|
||||
std::chrono::steady_clock::duration tm = CurrentUptime();
|
||||
double tmd = std::chrono::steady_clock::duration::period::num * tm.count() /
|
||||
(double)std::chrono::steady_clock::duration::period::den;
|
||||
std::thread::id thrId = std::this_thread::get_id();
|
||||
const char* thrName = nullptr;
|
||||
if (ThreadMap.find(thrId) != ThreadMap.end())
|
||||
thrName = ThreadMap[thrId];
|
||||
|
||||
fprintf(fp, "[");
|
||||
switch (severity)
|
||||
{
|
||||
case INFO:
|
||||
fprintf(fp, "INFO");
|
||||
break;
|
||||
case WARNING:
|
||||
fprintf(fp, "WARNING");
|
||||
break;
|
||||
case ERROR:
|
||||
fprintf(fp, "ERROR");
|
||||
break;
|
||||
case FATAL_ERROR:
|
||||
fprintf(fp, "FATAL ERROR");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
fprintf(fp, " %s", modName);
|
||||
if (thrName)
|
||||
fprintf(fp, " (%s)", thrName);
|
||||
fprintf(fp, " %5.4f", tmd);
|
||||
fprintf(fp, "] ");
|
||||
}
|
||||
|
||||
void report(const char* modName, Level severity,
|
||||
const char* format, va_list ap)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
openFile();
|
||||
_reportHead(modName, severity);
|
||||
vfprintf(fp, format, ap);
|
||||
fprintf(fp, "\n");
|
||||
closeFile();
|
||||
}
|
||||
|
||||
void report(const char* modName, Level severity,
|
||||
const wchar_t* format, va_list ap)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m);
|
||||
openFile();
|
||||
_reportHead(modName, severity);
|
||||
vfwprintf(fp, format, ap);
|
||||
fprintf(fp, "\n");
|
||||
closeFile();
|
||||
}
|
||||
};
|
||||
|
||||
struct FileLogger8 : public FileLogger
|
||||
{
|
||||
const char* m_filepath;
|
||||
FileLogger8(const char* filepath) : m_filepath(filepath) {}
|
||||
void openFile() {fp = fopen(m_filepath, "a");}
|
||||
};
|
||||
|
||||
void RegisterFileLogger(const char* filepath)
|
||||
{
|
||||
/* Determine if file logger already added */
|
||||
for (auto& logger : MainLoggers)
|
||||
{
|
||||
FileLogger8* filelogger = dynamic_cast<FileLogger8*>(logger.get());
|
||||
if (filelogger)
|
||||
{
|
||||
if (!strcmp(filepath, filelogger->m_filepath))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise construct new file logger */
|
||||
MainLoggers.emplace_back(new FileLogger8(filepath));
|
||||
}
|
||||
|
||||
#if LOG_UCS2
|
||||
|
||||
struct FileLogger16 : public FileLogger
|
||||
{
|
||||
const wchar_t* m_filepath;
|
||||
FileLogger16(const wchar_t* filepath) : m_filepath(filepath) {}
|
||||
void openFile() {fp = _wfopen(m_filepath, L"a");}
|
||||
};
|
||||
|
||||
void RegisterFileLogger(const wchar_t* filepath)
|
||||
{
|
||||
/* Determine if file logger already added */
|
||||
for (auto& logger : MainLoggers)
|
||||
{
|
||||
FileLogger16* filelogger = dynamic_cast<FileLogger16*>(logger.get());
|
||||
if (filelogger)
|
||||
{
|
||||
if (!wcscmp(filepath, filelogger->m_filepath))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise construct new file logger */
|
||||
MainLoggers.emplace_back(new FileLogger16(filepath));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user