Skip to content

Instantly share code, notes, and snippets.

@averne
Last active July 21, 2019 14:22
Show Gist options
  • Save averne/1fee2de15ed3242f48c847fef93c1f2c to your computer and use it in GitHub Desktop.
Save averne/1fee2de15ed3242f48c847fef93c1f2c to your computer and use it in GitHub Desktop.
Asynchronous logger for the Switch
#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
#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