A bip-buffer is good for producer/consumer systems, but ideally logs will stay in the buffer until they're ousted because they need to be overwritten. Now they're a regular ring buffer and every entry has an incremental id. Consumers pass in the last id they've seen, and will get the next log in the sequence.
143 lines
3.3 KiB
C++
143 lines
3.3 KiB
C++
#include <new>
|
|
#include <string.h>
|
|
|
|
#include <util/format.h>
|
|
#include <util/no_construct.h>
|
|
|
|
#include "assert.h"
|
|
#include "logger.h"
|
|
#include "objects/system.h"
|
|
#include "objects/thread.h"
|
|
|
|
// The logger is initialized _before_ global constructors are called,
|
|
// so that we can start log output immediately. Keep its constructor
|
|
// from being called here so as to not overwrite the previous initialization.
|
|
static util::no_construct<log::logger> __g_logger_storage;
|
|
log::logger &g_logger = __g_logger_storage.value;
|
|
|
|
|
|
namespace log {
|
|
|
|
namespace {
|
|
|
|
} // anon namespace
|
|
|
|
logger *logger::s_log = nullptr;
|
|
|
|
logger::logger() :
|
|
m_buffer {nullptr, 0},
|
|
m_start {0},
|
|
m_end {0},
|
|
m_count {0}
|
|
{
|
|
memset(&m_levels, 0, sizeof(m_levels));
|
|
s_log = this;
|
|
}
|
|
|
|
logger::logger(util::buffer data) :
|
|
m_buffer {data},
|
|
m_start {0},
|
|
m_end {0},
|
|
m_count {0}
|
|
{
|
|
kassert((data.count & (data.count - 1)) == 0,
|
|
"log buffer size must be a power of two");
|
|
|
|
memset(&m_levels, 0, sizeof(m_levels));
|
|
s_log = this;
|
|
|
|
#define LOG(name, lvl) \
|
|
set_level(logs::name, log::level::lvl);
|
|
#include <j6/tables/log_areas.inc>
|
|
#undef LOG
|
|
}
|
|
|
|
void
|
|
logger::output(level severity, logs area, const char *fmt, va_list args)
|
|
{
|
|
static constexpr size_t buffer_len = 256;
|
|
static constexpr size_t message_len = buffer_len - sizeof(entry);
|
|
|
|
char buffer[buffer_len];
|
|
entry *header = reinterpret_cast<entry *>(buffer);
|
|
|
|
size_t size = sizeof(buffer);
|
|
size += util::vformat({header->message, message_len}, fmt, args);
|
|
|
|
util::scoped_lock lock {m_lock};
|
|
|
|
while (free() < size) {
|
|
// Remove old entries until there's enough space
|
|
const entry *first = util::at<const entry>(m_buffer, start());
|
|
m_start += first->bytes;
|
|
}
|
|
|
|
header->id = ++m_count;
|
|
header->bytes = size;
|
|
header->severity = severity;
|
|
header->area = area;
|
|
|
|
memcpy(util::at<void>(m_buffer, end()), buffer, size);
|
|
m_end += size;
|
|
|
|
m_waiting.clear();
|
|
}
|
|
|
|
size_t
|
|
logger::get_entry(uint64_t seen, void *buffer, size_t size)
|
|
{
|
|
util::scoped_lock lock {m_lock};
|
|
|
|
while (seen == m_count) {
|
|
lock.release();
|
|
m_waiting.wait();
|
|
lock.reacquire();
|
|
}
|
|
|
|
size_t off = m_start;
|
|
entry *ent = util::at<entry>(m_buffer, off);
|
|
while (seen >= ent->id) {
|
|
off += ent->bytes;
|
|
kassert(off < m_end, "Got to the end while looking for new log entry");
|
|
ent = util::at<entry>(m_buffer, off);
|
|
}
|
|
|
|
if (size >= ent->bytes)
|
|
memcpy(buffer, ent, ent->bytes);
|
|
|
|
return ent->bytes;
|
|
}
|
|
|
|
#define LOG_LEVEL_FUNCTION(name) \
|
|
void name (logs area, const char *fmt, ...) { \
|
|
logger *l = logger::s_log; \
|
|
if (!l) return; \
|
|
level limit = l->get_level(area); \
|
|
if (level::name > limit) return; \
|
|
va_list args; \
|
|
va_start(args, fmt); \
|
|
l->output(level::name, area, fmt, args); \
|
|
va_end(args); \
|
|
}
|
|
|
|
LOG_LEVEL_FUNCTION(spam);
|
|
LOG_LEVEL_FUNCTION(verbose);
|
|
LOG_LEVEL_FUNCTION(info);
|
|
LOG_LEVEL_FUNCTION(warn);
|
|
LOG_LEVEL_FUNCTION(error);
|
|
|
|
void fatal(logs area, const char *fmt, ...)
|
|
{
|
|
logger *l = logger::s_log;
|
|
if (!l) return;
|
|
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
l->output(level::fatal, area, fmt, args);
|
|
va_end(args);
|
|
|
|
kassert(false, "log::fatal");
|
|
}
|
|
|
|
} // namespace log
|