#include "utils/log.hpp" #include #include #include #include #include #include #include #include #include namespace Log { namespace { std::mutex g_mutex; std::unordered_map 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(std::tolower(static_cast(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(s.front())) != 0)) { s.remove_prefix(1); } while (!s.empty() && (std::isspace(static_cast(s.back())) != 0)) { s.remove_suffix(1); } return s; } // Itera entrades d'una especificació AEE_LOG separades per `,`, // saltant les buides. template 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 logPrefixed(Level lvl, std::string_view tag, std::string_view prefix, std::string_view msg) { const std::string BODY = std::format("{} {}: {}", prefix, tag, msg); SDL_LogMessage(categoryFor(tag), toSDL(lvl), "%s", BODY.c_str()); } void raw(Level lvl, std::string_view msg) { const std::string TEXT{msg}; SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, toSDL(lvl), "%s", TEXT.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