Last active
July 21, 2019 14:22
-
-
Save averne/1fee2de15ed3242f48c847fef93c1f2c to your computer and use it in GitHub Desktop.
Asynchronous logger for the Switch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <cstdio> | |
#include <cstring> | |
#include <cmath> | |
#include <string> | |
#include <switch.h> | |
#include "async_logger.hpp" | |
Result AsyncLogger::start() { | |
if (is_running()) return -1; | |
Result rc = threadCreate(&this->thread, writer_thread, (void *)this, 0x1000, 0x3b, -2); | |
if (R_SUCCEEDED(rc)) { | |
this->running = true; | |
rc = threadStart(&this->thread); | |
if (R_FAILED(rc)) threadClose(&this->thread); | |
} | |
this->running = R_SUCCEEDED(rc); | |
return rc; | |
} | |
void AsyncLogger::stop() { | |
if (!is_running()) return; | |
this->running = false; | |
threadWaitForExit(&this->thread); | |
threadClose(&this->thread); | |
} | |
void AsyncLogger::writer_thread(void *args) { | |
AsyncLogger *s_this = (AsyncLogger *)args; | |
float freq = (float)armGetSystemTickFreq(); | |
static const char * const lvl_strings[] = {"[TRACE]:", "[INFO]: ", "[WARN]: ", "[ERROR]:", "[FATAL]:"}; | |
while (s_this->is_running() || !s_this->messages.empty()) { | |
svcSleepThread(1e+6); | |
for (message_t &message: s_this->messages) { | |
float time_ms = (float)(message.tick - s_this->start_tick) / freq; | |
fprintf(s_this->fp, "[%#.2fs] %s %s", time_ms, lvl_strings[message.lvl], message.string.c_str()); | |
mutexLock(&s_this->dequeue_mutex); | |
s_this->messages.pop_front(); | |
mutexUnlock(&s_this->dequeue_mutex); | |
} | |
} | |
} | |
#ifdef DEBUG | |
void AsyncLogger::data(const void *data, size_t size, unsigned int indent, const char *prefix, log_lvl lvl) { | |
if (lvl < this->lvl || !is_running()) return; | |
size_t prefix_len = (prefix) ? strlen(prefix) : 0; | |
std::string str(prefix_len + ceil(size / 16.0) * (indent + 70) + 1, ' '); | |
char *str_data = (char *)str.data(); | |
if (prefix) strncpy(str_data, prefix, prefix_len); | |
size_t data_idx = prefix_len + indent, ascii_idx = data_idx + 53; | |
for (size_t i = 0; i < size; ++i) { | |
str_data[data_idx + snprintf(&str_data[data_idx], 3, "%02x", ((u8 *)data)[i])] = ' '; | |
str_data[ascii_idx] = (' ' <= ((u8 *)data)[i] && ((u8 *)data)[i] <= '~') ? ((u8 *)data)[i] : '.'; | |
data_idx += 3; | |
++ascii_idx; | |
if ((i + 1) % 16 == 0) { | |
str_data[data_idx + 1] = '|'; | |
str_data[ascii_idx] = '\n'; | |
data_idx += indent + 21; | |
ascii_idx = data_idx + 53; | |
} else if ((i + 1) % 8 == 0) { | |
++data_idx; | |
} else if (i + 1 == size) { | |
str_data[data_idx + (16 - i % 16) * 3 - 1] = '|'; | |
} | |
} | |
*(u16 *)&str_data[str.size() - 2] = '\n'; | |
mutexLock(&this->dequeue_mutex); | |
this->messages.push_back({armGetSystemTick(), lvl, str}); | |
mutexUnlock(&this->dequeue_mutex); | |
} | |
#endif // DEBUG |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <deque> | |
#include <string> | |
#include <cstdio> | |
#include <cstdlib> | |
#include <switch.h> | |
class AsyncLogger { | |
public: | |
enum log_lvl: u8 { | |
lvl_trace, | |
lvl_info, | |
lvl_warn, | |
lvl_error, | |
lvl_fatal, | |
}; | |
AsyncLogger(FILE *fp, log_lvl lvl=lvl_trace): fp(fp), start_tick(armGetSystemTick()), lvl(lvl) | |
{ setvbuf(fp, NULL, _IOLBF, 0x50); } | |
AsyncLogger &operator=(const AsyncLogger &) = delete; | |
AsyncLogger(const AsyncLogger&) = delete; | |
Result start(); | |
void stop(); | |
inline bool is_running() const { return this->running; } | |
inline void set_log_level(log_lvl lvl) { this->lvl = lvl; } | |
inline log_lvl get_log_lvl() const { return this->lvl; } | |
template<typename ...Args> | |
inline void enqueue(log_lvl lvl, const char *fmt, Args &&...args) { | |
#ifdef DEBUG | |
if (lvl < this->lvl || !is_running()) return; | |
std::string fmted(snprintf(nullptr, 0, fmt, std::forward<Args>(args)...) + 1, 0); | |
snprintf((char *)fmted.data(), fmted.size(), fmt, std::forward<Args>(args)...); | |
mutexLock(&this->dequeue_mutex); | |
this->messages.push_back({armGetSystemTick(), lvl, fmted}); | |
mutexUnlock(&this->dequeue_mutex); | |
#endif | |
} | |
template<typename ...Args> | |
inline void enqueue(const std::string &fmt, log_lvl lvl, Args &&...args) { | |
enqueue(lvl, fmt.c_str(), std::forward<Args>(args)...); | |
} | |
#define DECL_LVL_HELPER(lvl) \ | |
template<typename Fmt, typename ...Args> \ | |
inline void lvl(Fmt &&fmt, Args &&...args) { \ | |
enqueue(lvl_##lvl, std::forward<Fmt>(fmt), std::forward<Args>(args)...); \ | |
} | |
DECL_LVL_HELPER(trace) | |
DECL_LVL_HELPER(info) | |
DECL_LVL_HELPER(warn) | |
DECL_LVL_HELPER(error) | |
DECL_LVL_HELPER(fatal) | |
#undef DECL_LVL_HELPER | |
void data(const void *data, size_t size, unsigned int indent=0, const char *prefix=NULL, log_lvl lvl=lvl_trace) | |
#ifdef DEBUG // We rely on the compiler stripping the function to not data abort | |
; | |
#else | |
{ } | |
#endif | |
private: | |
typedef struct { | |
u64 tick; | |
log_lvl lvl; | |
std::string string; | |
} message_t; | |
FILE *fp; | |
u64 start_tick; | |
Thread thread; | |
bool running = false; | |
log_lvl lvl; | |
Mutex dequeue_mutex = {0}; | |
std::deque<message_t> messages; | |
static void writer_thread(void *args); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment