feat(log): introdueix Log:: wrapper sobre SDL_Log
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>
This commit is contained in:
1 parent
c910a16834
commit
1243b457be
5 files changed
+226
-12
No files matched your search
@@ -104,6 +104,7 @@ set(APP_SOURCES
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/core/resources/palette_loader.cpp
|
||||
source/utils/easing.cpp
|
||||
source/utils/log.cpp
|
||||
source/utils/utils.cpp
|
||||
source/game/debug_config.cpp
|
||||
source/game/options.cpp
|
||||
@@ -365,6 +366,7 @@ if(BUILD_TESTS)
|
||||
source/core/resources/resource_helper.cpp
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/game/map/map.cpp
|
||||
source/utils/log.cpp
|
||||
${ENTITY_SOURCES_BASE}
|
||||
)
|
||||
target_include_directories(test_entities_smoke PRIVATE
|
||||
@@ -394,6 +396,7 @@ if(BUILD_TESTS)
|
||||
source/core/resources/resource_helper.cpp
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/game/map/map.cpp
|
||||
source/utils/log.cpp
|
||||
${ENTITY_SOURCES_BASE}
|
||||
source/game/entities/concrete/whip_attack.cpp
|
||||
)
|
||||
@@ -418,6 +421,7 @@ if(BUILD_TESTS)
|
||||
source/core/input/key_config.cpp
|
||||
source/core/resources/resource_helper.cpp
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/utils/log.cpp
|
||||
)
|
||||
target_include_directories(test_scene_flow PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/source"
|
||||
@@ -457,6 +461,7 @@ if(BUILD_TESTS)
|
||||
source/core/resources/resource_helper.cpp
|
||||
source/core/resources/resource_pack.cpp
|
||||
source/utils/easing.cpp
|
||||
source/utils/log.cpp
|
||||
)
|
||||
target_include_directories(test_game_fsm PRIVATE
|
||||
"${CMAKE_SOURCE_DIR}/source"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#include "core/resources/resource_helper.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "core/resources/resource_pack.hpp"
|
||||
#include "utils/defaults.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
namespace Resource::Helper {
|
||||
|
||||
@@ -27,14 +27,14 @@ namespace Resource::Helper {
|
||||
// Silenciem si veníem de prefer_disk: aleshores caurem
|
||||
// al pack i no és cap error real que el fitxer no estigui
|
||||
// al disc.
|
||||
if (verbose) { std::cerr << "Resource::Helper: cannot open " << FULL << '\n'; }
|
||||
if (verbose) { Log::error("Resource::Helper", "cannot open {}", FULL); }
|
||||
return {};
|
||||
}
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
std::vector<std::uint8_t> data(static_cast<std::size_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
|
||||
std::cerr << "Resource::Helper: failed to read " << FULL << '\n';
|
||||
Log::error("Resource::Helper", "failed to read {}", FULL);
|
||||
return {};
|
||||
}
|
||||
return data;
|
||||
@@ -52,14 +52,11 @@ namespace Resource::Helper {
|
||||
g_pack_loaded = g_pack.loadPack(pack_file);
|
||||
|
||||
if (g_pack_loaded) {
|
||||
std::cout << "Resource::Helper: pack loaded (" << g_pack.getResourceCount()
|
||||
<< " entries) from " << pack_file << '\n';
|
||||
Log::info("Resource::Helper", "pack loaded ({} entries) from {}", g_pack.getResourceCount(), pack_file);
|
||||
} else if (enable_fallback) {
|
||||
std::cout << "Resource::Helper: no pack at " << pack_file
|
||||
<< " — using filesystem fallback\n";
|
||||
Log::info("Resource::Helper", "no pack at {} — using filesystem fallback", pack_file);
|
||||
} else {
|
||||
std::cerr << "Resource::Helper: FATAL — no pack at " << pack_file
|
||||
<< " and fallback disabled\n";
|
||||
Log::critical("Resource::Helper", "FATAL — no pack at {} and fallback disabled", pack_file);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
+5
-3
@@ -3,18 +3,20 @@
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_main.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "core/system/director.hpp"
|
||||
#include "core/system/event_buffer.hpp"
|
||||
#include "utils/log.hpp"
|
||||
|
||||
auto SDL_AppInit(void** appstate, int argc, char* argv[]) -> SDL_AppResult {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
// Logger primer de tot perquè l'arrencada del Director ja emet logs.
|
||||
Log::init();
|
||||
// Marcador d'inici: ha de ser la PRIMERA línia de log del programa.
|
||||
std::cout << "Game start\n";
|
||||
Log::info("App", "Game start");
|
||||
|
||||
// Construïm el Director amb make_unique i l'entreguem en cru a SDL.
|
||||
// El SDL_AppQuit reclamarà la propietat amb un altre unique_ptr (RAII).
|
||||
@@ -43,5 +45,5 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
||||
std::unique_ptr<Director> director(static_cast<Director*>(appstate));
|
||||
}
|
||||
// Marcador de fi: ha de ser l'ÚLTIMA línia de log del programa.
|
||||
std::cout << "Bye!\n";
|
||||
Log::info("App", "Bye!");
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
#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
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <format>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
// Logger lleuger embolicant `SDL_Log*`. Una sola entrada per nivell, format
|
||||
// type-safe via `std::format`. Filtre runtime amb la variable d'entorn
|
||||
// AEE_LOG (sintaxi `*:info,Resource::Helper:warn,scene:debug`). Ús típic:
|
||||
//
|
||||
// Log::info("Resource::Helper", "pack loaded ({} entries)", n);
|
||||
// Log::warn("Options", "preset \"{}\" no trobat; aplicant defaults.", name);
|
||||
// Log::error("Director", "no s'ha pogut carregar config.yaml a {}", path);
|
||||
//
|
||||
namespace Log {
|
||||
|
||||
enum class Level : std::uint8_t {
|
||||
TRACE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
CRITICAL,
|
||||
};
|
||||
|
||||
// Llig AEE_LOG i configura les prioritats SDL. Crida una sola vegada,
|
||||
// tan aviat com sigui possible (abans del primer `Log::*`).
|
||||
void init();
|
||||
|
||||
// Backend usat per les funcions per nivell. Composa "tag: msg" i ho
|
||||
// entrega a SDL_LogMessage amb la categoria registrada per al tag.
|
||||
void log(Level lvl, std::string_view tag, std::string_view msg);
|
||||
|
||||
template <typename... Args>
|
||||
void trace(std::string_view tag, std::format_string<Args...> fmt, Args&&... args) {
|
||||
log(Level::TRACE, tag, std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
template <typename... Args>
|
||||
void debug(std::string_view tag, std::format_string<Args...> fmt, Args&&... args) {
|
||||
log(Level::DEBUG, tag, std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
template <typename... Args>
|
||||
void info(std::string_view tag, std::format_string<Args...> fmt, Args&&... args) {
|
||||
log(Level::INFO, tag, std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
template <typename... Args>
|
||||
void warn(std::string_view tag, std::format_string<Args...> fmt, Args&&... args) {
|
||||
log(Level::WARN, tag, std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
template <typename... Args>
|
||||
void error(std::string_view tag, std::format_string<Args...> fmt, Args&&... args) {
|
||||
log(Level::ERROR, tag, std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
template <typename... Args>
|
||||
void critical(std::string_view tag, std::format_string<Args...> fmt, Args&&... args) {
|
||||
log(Level::CRITICAL, tag, std::format(fmt, std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
Reference in new issue
Block a user