Logger lleuger a source/utils/log.{hpp,cpp} amb API basada en std::format
i filtre runtime via la variable d'entorn AEE_LOG (sintaxi
*:info,Resource::Helper:warn,scene:debug). Substitueix gradualment els
std::cout/cerr dispersos.
- Categories CUSTOM registrades sota demanda; per defecte INFO.
- Categories SDL natives no es toquen tret que l'usuari posi *:level.
- Migra resource_helper.cpp i els marcadors d'inici/fi (main.cpp) com a
testimoni; la resta de subsistemes es migra en commits següents.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
151 lines
6.4 KiB
C++
151 lines
6.4 KiB
C++
#include "utils/log.hpp"
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cstdlib>
|
|
#include <format>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
|
|
namespace Log {
|
|
|
|
namespace {
|
|
|
|
std::mutex g_mutex;
|
|
std::unordered_map<std::string, int> g_categories;
|
|
int g_next_category = SDL_LOG_CATEGORY_CUSTOM;
|
|
// Prioritat per defecte aplicada a tota CUSTOM nova. Es modifica només
|
|
// si l'usuari ha posat `*:level` (o un nivell pelat) a AEE_LOG.
|
|
SDL_LogPriority g_default_priority = SDL_LOG_PRIORITY_INFO;
|
|
|
|
auto categoryFor(std::string_view tag) -> int {
|
|
std::lock_guard lk{g_mutex};
|
|
const std::string KEY{tag};
|
|
auto it = g_categories.find(KEY);
|
|
if (it != g_categories.end()) { return it->second; }
|
|
const int CAT = g_next_category++;
|
|
g_categories.emplace(KEY, CAT);
|
|
// Cada categoria CUSTOM neix amb la prioritat default de l'app
|
|
// (sense aquesta línia, SDL retorna ERROR per a qualsevol CUSTOM
|
|
// no inicialitzada, silenciant els nostres `info`).
|
|
SDL_SetLogPriority(CAT, g_default_priority);
|
|
return CAT;
|
|
}
|
|
|
|
auto toSDL(Level lvl) -> SDL_LogPriority {
|
|
switch (lvl) {
|
|
case Level::TRACE:
|
|
return SDL_LOG_PRIORITY_TRACE;
|
|
case Level::DEBUG:
|
|
return SDL_LOG_PRIORITY_DEBUG;
|
|
case Level::INFO:
|
|
return SDL_LOG_PRIORITY_INFO;
|
|
case Level::WARN:
|
|
return SDL_LOG_PRIORITY_WARN;
|
|
case Level::ERROR:
|
|
return SDL_LOG_PRIORITY_ERROR;
|
|
case Level::CRITICAL:
|
|
return SDL_LOG_PRIORITY_CRITICAL;
|
|
}
|
|
return SDL_LOG_PRIORITY_INFO;
|
|
}
|
|
|
|
// Parser tolerant de noms de prioritat. Sinònims acceptats per costum:
|
|
// verbose↔debug, warning↔warn, err↔error, fatal/crit↔critical.
|
|
auto parsePriority(std::string_view s) -> SDL_LogPriority {
|
|
std::string lower(s.size(), '\0');
|
|
std::ranges::transform(s, lower.begin(), [](char c) {
|
|
return static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
});
|
|
if (lower == "trace") { return SDL_LOG_PRIORITY_TRACE; }
|
|
if (lower == "debug" || lower == "verbose") { return SDL_LOG_PRIORITY_DEBUG; }
|
|
if (lower == "info") { return SDL_LOG_PRIORITY_INFO; }
|
|
if (lower == "warn" || lower == "warning") { return SDL_LOG_PRIORITY_WARN; }
|
|
if (lower == "error" || lower == "err") { return SDL_LOG_PRIORITY_ERROR; }
|
|
if (lower == "critical" || lower == "crit" || lower == "fatal") {
|
|
return SDL_LOG_PRIORITY_CRITICAL;
|
|
}
|
|
return SDL_LOG_PRIORITY_INFO;
|
|
}
|
|
|
|
auto trim(std::string_view s) -> std::string_view {
|
|
while (!s.empty() && (std::isspace(static_cast<unsigned char>(s.front())) != 0)) {
|
|
s.remove_prefix(1);
|
|
}
|
|
while (!s.empty() && (std::isspace(static_cast<unsigned char>(s.back())) != 0)) {
|
|
s.remove_suffix(1);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
// Itera entrades d'una especificació AEE_LOG separades per `,`,
|
|
// saltant les buides.
|
|
template <typename Fn>
|
|
void forEachEntry(std::string_view spec, Fn fn) {
|
|
std::size_t i = 0;
|
|
while (i <= spec.size()) {
|
|
const std::size_t COMMA = spec.find(',', i);
|
|
const std::size_t END = (COMMA == std::string_view::npos) ? spec.size() : COMMA;
|
|
const std::string_view ENTRY = trim(spec.substr(i, END - i));
|
|
i = (COMMA == std::string_view::npos) ? spec.size() + 1 : COMMA + 1;
|
|
if (!ENTRY.empty()) { fn(ENTRY); }
|
|
}
|
|
}
|
|
|
|
// Una entrada AEE_LOG global aplica a totes les categories (SDL i
|
|
// CUSTOM). Forma curta `level` ↔ tag explícit `*:level`.
|
|
void applyGlobalEntry(std::string_view entry) {
|
|
const std::size_t COLON = entry.rfind(':');
|
|
const bool IS_BARE = (COLON == std::string_view::npos);
|
|
const std::string_view TAG =
|
|
IS_BARE ? std::string_view{} : trim(entry.substr(0, COLON));
|
|
if (!IS_BARE && TAG != "*" && !TAG.empty()) { return; }
|
|
const SDL_LogPriority PRIO =
|
|
parsePriority(IS_BARE ? entry : trim(entry.substr(COLON + 1)));
|
|
SDL_SetLogPriorities(PRIO);
|
|
g_default_priority = PRIO;
|
|
}
|
|
|
|
// Override per tag concret (sobreescriu el global per a aquesta
|
|
// categoria). Si la categoria és nova es registra al vol.
|
|
void applyTagEntry(std::string_view entry) {
|
|
const std::size_t COLON = entry.rfind(':');
|
|
if (COLON == std::string_view::npos) { return; }
|
|
const std::string_view TAG = trim(entry.substr(0, COLON));
|
|
if (TAG == "*" || TAG.empty()) { return; }
|
|
const SDL_LogPriority PRIO = parsePriority(trim(entry.substr(COLON + 1)));
|
|
SDL_SetLogPriority(categoryFor(TAG), PRIO);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void log(Level lvl, std::string_view tag, std::string_view msg) {
|
|
// `"%s"` + cos com a argument: si `msg` conté `%` (rar però possible
|
|
// en paths o rutes amb urlencoded), no s'interpreta com a format spec.
|
|
const std::string BODY = std::format("{}: {}", tag, msg);
|
|
SDL_LogMessage(categoryFor(tag), toSDL(lvl), "%s", BODY.c_str());
|
|
}
|
|
|
|
void init() {
|
|
// No toquem cap categoria SDL nativa per defecte: així preservem el
|
|
// silenci de SDL_LOG_CATEGORY_GPU, RENDER, etc. (default = ERROR), i
|
|
// només el que arriba via Log::* es veu, mantenint l'output històric.
|
|
g_default_priority = SDL_LOG_PRIORITY_INFO;
|
|
|
|
const char* env = std::getenv("AEE_LOG");
|
|
if (env == nullptr || *env == '\0') { return; }
|
|
|
|
// Tags poden contenir `::` (p. ex. `Resource::Helper`); busquem el
|
|
// separador amb rfind. Dues passades per fer l'override per-tag
|
|
// insensible a l'ordre (`Tag:debug,*:warn` deixa Tag a debug).
|
|
const std::string_view SPEC{env};
|
|
forEachEntry(SPEC, applyGlobalEntry);
|
|
forEachEntry(SPEC, applyTagEntry);
|
|
}
|
|
|
|
} // namespace Log
|