Program Listing for File PDJE_LOG_SETTER.cpp
↰ Return to documentation for file (include\global\PDJE_LOG_SETTER.cpp)
#include "PDJE_LOG_SETTER.hpp"
#include <spdlog/sinks/basic_file_sink.h>
#include <cstdio>
#include <filesystem>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
namespace {
enum class PDJE_LogBackendKindInternal {
kInternalSpdlog = 0,
kHostCallback = 1,
};
struct PDJE_LoggingState {
std::mutex mutex;
bool initialized = false;
PDJE_LogBackendKindInternal backend_kind =
PDJE_LogBackendKindInternal::kInternalSpdlog;
PDJE_LogHostSinkV1 host_sink = {};
int min_level = PDJE_LOG_LEVEL_INFO_V1;
bool autoinit_enabled = true;
bool internal_spdlog_owner = false;
std::shared_ptr<spdlog::logger> logger;
std::string file_path = "logs/pdjeLog.txt";
};
constexpr const char *PDJE_LOGGER_NAME = "global_logger";
PDJE_LoggingState &
GetLoggingState()
{
static PDJE_LoggingState state;
return state;
}
int
DefaultMinLevel()
{
#ifndef NDEBUG
return PDJE_LOG_LEVEL_DEBUG_V1;
#else
return PDJE_LOG_LEVEL_ERROR_V1;
#endif
}
bool
StrictExplicitInitEnabled()
{
#ifdef PDJE_LOG_STRICT_EXPLICIT_INIT
return true;
#else
return false;
#endif
}
int
NormalizeLevel(const int level)
{
if (level < PDJE_LOG_LEVEL_TRACE_V1) {
return PDJE_LOG_LEVEL_TRACE_V1;
}
if (level > PDJE_LOG_LEVEL_OFF_V1) {
return PDJE_LOG_LEVEL_OFF_V1;
}
return level;
}
bool
ShouldEmit(const int level, const int min_level)
{
return NormalizeLevel(level) >= NormalizeLevel(min_level) &&
NormalizeLevel(level) < PDJE_LOG_LEVEL_OFF_V1;
}
spdlog::level::level_enum
ToSpdlogLevel(const int level)
{
switch (NormalizeLevel(level)) {
case PDJE_LOG_LEVEL_TRACE_V1:
return spdlog::level::trace;
case PDJE_LOG_LEVEL_DEBUG_V1:
return spdlog::level::debug;
case PDJE_LOG_LEVEL_INFO_V1:
return spdlog::level::info;
case PDJE_LOG_LEVEL_WARN_V1:
return spdlog::level::warn;
case PDJE_LOG_LEVEL_ERROR_V1:
return spdlog::level::err;
case PDJE_LOG_LEVEL_CRITICAL_V1:
return spdlog::level::critical;
case PDJE_LOG_LEVEL_OFF_V1:
default:
return spdlog::level::off;
}
}
bool
ConfigRequestsHostCallback(const PDJE_LogConfigV1 *cfg)
{
if (cfg == nullptr) {
return false;
}
return cfg->backend == PDJE_LOG_BACKEND_HOST_CALLBACK_V1;
}
bool
ConfigIsValid(const PDJE_LogConfigV1 *cfg)
{
if (cfg == nullptr) {
return true;
}
if (cfg->struct_size != 0 && cfg->struct_size < sizeof(PDJE_LogConfigV1)) {
return false;
}
if (cfg->backend != PDJE_LOG_BACKEND_INTERNAL_SPDLOG_V1 &&
cfg->backend != PDJE_LOG_BACKEND_HOST_CALLBACK_V1) {
return false;
}
if (ConfigRequestsHostCallback(cfg)) {
if (cfg->host_sink.write == nullptr) {
return false;
}
if (cfg->host_sink.struct_size != 0 &&
cfg->host_sink.struct_size < sizeof(PDJE_LogHostSinkV1)) {
return false;
}
}
return true;
}
PDJE_LogInitResultV1
InitInternalSpdlogLocked(PDJE_LoggingState &state,
const PDJE_LogConfigV1 *cfg) noexcept
{
try {
std::string log_path = "logs/pdjeLog.txt";
if (cfg != nullptr && cfg->file_path != nullptr && cfg->file_path[0] != '\0') {
log_path = cfg->file_path;
}
std::filesystem::path path(log_path);
const auto parent = path.parent_path();
if (!parent.empty()) {
std::filesystem::create_directories(parent);
}
spdlog::drop(PDJE_LOGGER_NAME);
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(
path.string(), false);
std::vector<spdlog::sink_ptr> sinks{ file_sink };
auto logger = std::make_shared<spdlog::logger>(
PDJE_LOGGER_NAME, sinks.begin(), sinks.end());
logger->set_level(ToSpdlogLevel(state.min_level));
logger->flush_on(spdlog::level::err);
logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] %v");
// Compatibility: keep the default logger path available for legacy code
// that still calls spdlog directly.
spdlog::set_default_logger(logger);
state.logger = std::move(logger);
state.file_path = path.string();
state.backend_kind = PDJE_LogBackendKindInternal::kInternalSpdlog;
state.internal_spdlog_owner = true;
state.initialized = true;
return PDJE_LOG_INIT_OK_V1;
} catch (...) {
return PDJE_LOG_INIT_FAILED_V1;
}
}
PDJE_LogInitResultV1
InitHostCallbackLocked(PDJE_LoggingState &state,
const PDJE_LogConfigV1 *cfg) noexcept
{
if (cfg == nullptr || cfg->host_sink.write == nullptr) {
return PDJE_LOG_INIT_INVALID_ARGUMENT_V1;
}
state.host_sink = cfg->host_sink;
state.logger.reset();
state.backend_kind = PDJE_LogBackendKindInternal::kHostCallback;
state.internal_spdlog_owner = false;
state.initialized = true;
return PDJE_LOG_INIT_OK_V1;
}
PDJE_LogInitResultV1
InitLocked(PDJE_LoggingState &state, const PDJE_LogConfigV1 *cfg) noexcept
{
state.min_level = DefaultMinLevel();
if (cfg != nullptr) {
state.min_level = NormalizeLevel(cfg->min_level);
}
if (cfg != nullptr &&
(cfg->flags & PDJE_LOG_CFG_FLAG_DISABLE_AUTOINIT_V1) != 0u) {
state.autoinit_enabled = false;
} else {
state.autoinit_enabled = !StrictExplicitInitEnabled();
}
if (ConfigRequestsHostCallback(cfg)) {
return InitHostCallbackLocked(state, cfg);
}
return InitInternalSpdlogLocked(state, cfg);
}
void
ResetStateLocked(PDJE_LoggingState &state)
{
state.initialized = false;
state.backend_kind = PDJE_LogBackendKindInternal::kInternalSpdlog;
state.host_sink = {};
state.min_level = DefaultMinLevel();
state.autoinit_enabled = !StrictExplicitInitEnabled();
state.internal_spdlog_owner = false;
state.logger.reset();
state.file_path = "logs/pdjeLog.txt";
}
struct PDJE_LogDispatchSnapshot {
bool initialized = false;
PDJE_LogBackendKindInternal backend_kind =
PDJE_LogBackendKindInternal::kInternalSpdlog;
int min_level = PDJE_LOG_LEVEL_INFO_V1;
bool autoinit = true;
PDJE_LogHostSinkV1 host_sink = {};
std::shared_ptr<spdlog::logger> logger;
};
PDJE_LogDispatchSnapshot
CaptureSnapshot()
{
auto &state = GetLoggingState();
std::lock_guard<std::mutex> guard(state.mutex);
PDJE_LogDispatchSnapshot snapshot;
snapshot.initialized = state.initialized;
snapshot.backend_kind = state.backend_kind;
if (state.initialized) {
snapshot.min_level = state.min_level;
snapshot.autoinit = state.autoinit_enabled;
} else {
snapshot.min_level = DefaultMinLevel();
snapshot.autoinit = !StrictExplicitInitEnabled();
}
snapshot.host_sink = state.host_sink;
snapshot.logger = state.logger;
return snapshot;
}
void
EmitStrictInitWarningOnce() noexcept
{
static std::mutex warn_mutex;
static bool warned = false;
std::lock_guard<std::mutex> guard(warn_mutex);
if (warned) {
return;
}
warned = true;
std::fprintf(stderr,
"[PDJE] logging dropped: runtime not initialized and strict "
"explicit init is enabled\n");
}
void
DispatchLog(const PDJE_LogDispatchSnapshot &snapshot,
const int level,
const char *message,
const size_t message_len) noexcept
{
if (!snapshot.initialized) {
return;
}
if (!ShouldEmit(level, snapshot.min_level)) {
return;
}
const char *safe_message = message != nullptr ? message : "";
const size_t safe_len =
(safe_message == message) ? message_len : static_cast<size_t>(0);
try {
if (snapshot.backend_kind == PDJE_LogBackendKindInternal::kHostCallback) {
if (snapshot.host_sink.write != nullptr) {
snapshot.host_sink.write(
NormalizeLevel(level),
safe_message,
safe_len,
snapshot.host_sink.user_data);
}
return;
}
if (snapshot.logger != nullptr) {
snapshot.logger->log(
ToSpdlogLevel(level), spdlog::string_view_t(safe_message, safe_len));
}
} catch (...) {
// Never throw across log dispatch boundaries.
}
}
} // namespace
extern "C" PDJE_API int PDJE_CALL
pdje_logging_init_v1(const PDJE_LogConfigV1 *cfg)
{
#ifdef LOG_OFF
(void)cfg;
return PDJE_LOG_INIT_OK_V1;
#else
try {
if (!ConfigIsValid(cfg)) {
return PDJE_LOG_INIT_INVALID_ARGUMENT_V1;
}
auto &state = GetLoggingState();
std::lock_guard<std::mutex> guard(state.mutex);
if (state.initialized) {
return PDJE_LOG_INIT_ALREADY_INITIALIZED_V1;
}
return InitLocked(state, cfg);
} catch (...) {
return PDJE_LOG_INIT_FAILED_V1;
}
#endif
}
extern "C" PDJE_API int PDJE_CALL
pdje_logging_shutdown_v1(void)
{
#ifdef LOG_OFF
return PDJE_LOG_SHUTDOWN_OK_V1;
#else
try {
bool had_internal_owner = false;
{
auto &state = GetLoggingState();
std::lock_guard<std::mutex> guard(state.mutex);
if (!state.initialized) {
return PDJE_LOG_SHUTDOWN_NOT_INITIALIZED_V1;
}
had_internal_owner = state.internal_spdlog_owner;
ResetStateLocked(state);
}
if (had_internal_owner) {
try {
spdlog::drop(PDJE_LOGGER_NAME);
} catch (...) {
}
}
return PDJE_LOG_SHUTDOWN_OK_V1;
} catch (...) {
return PDJE_LOG_SHUTDOWN_FAILED_V1;
}
#endif
}
extern "C" PDJE_API int PDJE_CALL
pdje_logging_is_initialized_v1(void)
{
#ifdef LOG_OFF
return 0;
#else
auto &state = GetLoggingState();
std::lock_guard<std::mutex> guard(state.mutex);
return state.initialized ? 1 : 0;
#endif
}
extern "C" PDJE_API void PDJE_CALL
pdje_log_write_v1(int level, const char *message, size_t message_len)
{
#ifdef LOG_OFF
(void)level;
(void)message;
(void)message_len;
return;
#else
auto snapshot = CaptureSnapshot();
if (!snapshot.initialized) {
if (snapshot.autoinit) {
(void)pdje_logging_init_v1(nullptr);
snapshot = CaptureSnapshot();
} else {
EmitStrictInitWarningOnce();
return;
}
}
DispatchLog(snapshot, level, message, message_len);
#endif
}
void
startlog()
{
(void)pdje_logging_init_v1(nullptr);
}
void
shutdownlog()
{
(void)pdje_logging_shutdown_v1();
}