Update .gitignore, CMakeLists.txt, CMakePresets.json, and 10 more files

This commit is contained in:
random committed 2026-09-15 08:32:55 +02:00
1 parent 88ff181bfd
commit 1f58c33bb7
13 files changed
+3380

No files matched your search

+4
View File
@@ -0,0 +1,4 @@
/build*
/cpm_modules
.DS_Store
.cache*
+90
View File
@@ -0,0 +1,90 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
# Standalone configuration of the spdlog logger from a JSON file.
project(
SPDLOGConfig
VERSION 1.0.0
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
if(PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)
message(
FATAL_ERROR
"In-source builds not allowed. Please make a new directory (called a build directory) and run CMake from there."
)
endif()
# ---- Dependencies via CPM ----
set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/../.cache-CPM-packages")
include(cmake/CPM.cmake)
CPMAddPackage("gh:TheLartians/PackageProject.cmake@1.13.0")
# ---- spdlog (shared, std::format backend like the main project) ----
CPMAddPackage(
NAME spdlog
GITHUB_REPOSITORY gabime/spdlog
GIT_TAG "v1.17.0"
OPTIONS
"SPDLOG_BUILD_EXAMPLE OFF"
"SPDLOG_BUILD_SHARED ON"
"SPDLOG_USE_STD_FORMAT ON"
"SPDLOG_INSTALL ON"
)
# ---- reflect-cpp (JSON deserialization of the config file) ----
CPMAddPackage(
NAME reflectcpp
GITHUB_REPOSITORY getml/reflect-cpp
GIT_TAG "v0.24.0"
OPTIONS
"REFLECTCPP_BUILD_SHARED ON"
"CMAKE_POSITION_INDEPENDENT_CODE ON"
"REFLECTCPP_INSTALL ON"
)
add_library(${PROJECT_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
target_compile_features(${PROJECT_NAME} INTERFACE cxx_std_20)
target_link_libraries(${PROJECT_NAME} INTERFACE spdlog::spdlog reflectcpp::reflectcpp)
target_include_directories(
${PROJECT_NAME}
INTERFACE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}-${PROJECT_VERSION}>
)
# ---- Tests (only when top-level and not disabled by consumers) ----
option(SPDLOGCONFIG_BUILD_TESTS "Build SPDLOGConfig's own test suite" ON)
if(PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR AND SPDLOGCONFIG_BUILD_TESTS)
enable_testing()
add_subdirectory(test)
endif()
# ---- Installable / CPM-compatible package ----
install(TARGETS ${PROJECT_NAME}
INCLUDES DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION}
)
packageProject(
NAME ${PROJECT_NAME}
VERSION ${PROJECT_VERSION}
NAMESPACE ${PROJECT_NAME}
BINARY_DIR ${PROJECT_BINARY_DIR}
INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include
INCLUDE_DESTINATION include/${PROJECT_NAME}-${PROJECT_VERSION}
VERSION_HEADER ""
COMPATIBILITY SameMajorVersion
DEPENDENCIES "spdlog 1.17.0;reflectcpp 0.24.0"
)
+85
View File
@@ -0,0 +1,85 @@
{
"version": 3,
"cmakeMinimumRequired": {
"major": 3,
"minor": 21,
"patch": 0
},
"configurePresets": [
{
"name": "linux-clang-21-debug",
"displayName": "Linux Clang 21 Debug",
"description": "Debug build on Linux using Clang 21",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/../build/spdlogConfig-linux-clang-21-debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/../cmake-toolchain-files/clang_21_x86_64-pc-linux-gnu.cmake"
}
},
{
"name": "linux-clang-21-release",
"displayName": "Linux Clang 21 Release",
"description": "Release build on Linux using Clang 21",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/../build/spdlogConfig-linux-clang-21-release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/../cmake-toolchain-files/clang_21_x86_64-pc-linux-gnu.cmake"
}
},
{
"name": "linux-clang-21-debug-asan",
"displayName": "Linux Clang 21 Debug (ASan)",
"description": "Debug build with AddressSanitizer on Linux using Clang 21",
"generator": "Unix Makefiles",
"binaryDir": "${sourceDir}/../build/spdlogConfig-linux-clang-21-debug-asan",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/../cmake-toolchain-files/clang_21_x86_64-pc-linux-gnu.cmake",
"USE_SANITIZER": "Address"
}
}
],
"buildPresets": [
{
"name": "linux-clang-21-debug",
"displayName": "Build Linux Clang 21 Debug",
"configurePreset": "linux-clang-21-debug",
"jobs": 8
},
{
"name": "linux-clang-21-release",
"displayName": "Build Linux Clang 21 Release",
"configurePreset": "linux-clang-21-release",
"jobs": 8
},
{
"name": "linux-clang-21-debug-asan",
"displayName": "Build Linux Clang 21 Debug (ASan)",
"configurePreset": "linux-clang-21-debug-asan",
"jobs": 8
}
],
"testPresets": [
{
"name": "linux-clang-21-debug",
"displayName": "Run Linux Clang 21 Debug tests",
"configurePreset": "linux-clang-21-debug",
"output": {
"outputOnFailure": true
}
},
{
"name": "linux-clang-21-release",
"displayName": "Run Linux Clang 21 Release tests",
"configurePreset": "linux-clang-21-release",
"output": {
"outputOnFailure": true
}
}
]
}
+24
View File
@@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org>
+91
View File
@@ -0,0 +1,91 @@
# SPDLOGConfig
Eigenständiges, header-only CMake-Projekt zur Konfiguration von [spdlog](https://github.com/gabime/spdlog)
aus einer JSON-Datei. Es ist die aus [RelixTransferAI](../README.md) extrahierte Logging-Konfiguration
(Namespace `spdlogconfig`) und wird dort sowie in anderen Projekten
über [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake) eingebunden.
## Features
- Komplette Logger-Definition über eine JSON-Datei: globale und logger-spezifische Patterns,
Level, Sinks, async Logger (globaler und lokaler Thread-Pool, Overflow-Policies).
- Sinks: `console_st/mt`, `color_console_st/mt`, `basic_file_st/mt`, `daily_file_st/mt`,
`rotating_file_st/mt`, `null_sink_st/mt`, `syslog_st/mt` (POSIX), `msvc_sink_st/mt` (Windows),
optional `kafka_*`/`mongo_*` falls `librdkafka`/`mongocxx` im Include-Pfad liegen.
- Reload-Unterstützung: Level zur Laufzeit ändern, ohne Neustart (`setupFromReload()`).
- `LevelManager` als Singleton zur Laufzeit-Verwaltung einzelner Logger-Level.
- Installierbare CMake-Package (`find_package(SPDLOGConfig)`), CPM- und FetchContent-tauglich.
## Verwendung
### Einbindung über CPM
```cmake
include(cmake/CPM.cmake)
CPMAddPackage(
NAME SPDLOGConfig
GITHUB_REPOSITORY <user>/SPDLOGConfig
GIT_TAG v1.0.0
)
target_link_libraries(myTarget PRIVATE SPDLOGConfig::SPDLOGConfig)
```
Lokal entwickeltes Checkout statt GitHub-Release (Source-Override vor dem `CPMAddPackage`-Aufruf):
```cmake
set(CPM_SPDLOGConfig_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/../spdlogConfig")
```
### Verwendung im Code
```cpp
#include <spdlogconfig/SPDLOGConfig.h>
int main() {
spdlogconfig::setupFrom("config/logging.json");
// spdlogconfig::setupFromReload(); // später: Konfiguration neu einlesen
auto logger = spdlog::get("root");
logger->info("hello from SPDLOGConfig");
// Laufzeit-Level-Verwaltung:
spdlogconfig::LevelManager::instance().setLoggerLevel("root", "debug");
}
```
Fehler beim Öffnen/Parsen der Konfiguration werden von `setupFrom()` aktuell
**intern behandelt** (Ausgabe auf stdout/stderr, keine Propagation) — das ist
Legacy-Verhalten aus der ursprünglichen RelixTransfer-Headerdatei. Der Typ
`spdlogconfig::spdlogconfig_error` existiert trotzdem und wird intern für
teilweise Fehlerkontexte verwendet; auf Propagation sollte sich in neuen
Programmen nicht verlassen werden.
## Konfigurationsformat
Ein vollständiges Beispiel liegt unter [`test/full.json`](test/full.json). Das Schema
(`spdlog_conf_t`) wird mit [reflect-cpp](https://github.com/getml/reflect-cpp) deserialisiert;
unbekannte Felder, fehlende optionale Felder und Default-Werte werden vom Framework behandelt.
## Abhängigkeiten
| Paket | Version | Zweck |
|----------------|---------|------------------------------|
| spdlog | 1.17.0 | Logging (std::format-Modus) |
| reflect-cpp | 0.24.0 | JSON-Deserialisierung |
| doctest | 2.5.2 | Tests (nur test/) |
## Bauen und Testen
Voraussetzung: CMake ≥ 3.21, Clang 20/21 (oder MSVC/MSYS2-Clang), Ninja oder Make.
```sh
cmake --preset linux-clang-21-release
cmake --build --preset linux-clang-21-release
ctest --preset linux-clang-21-release
```
## Lizenz
Public Domain (Unlicense) — siehe [LICENSE](LICENSE).
+1363
View File
File diff suppressed because it is too large. Load diff
+20
View File
@@ -0,0 +1,20 @@
# tools.cmake — on-demand tool activation via `-DUSE_<TOOL>=<VALUE>`.
# only activate tools for top level project
if(NOT PROJECT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
return()
endif()
include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake)
# enables sanitizers support using the `USE_SANITIZER` flag; available values are: Address,
# Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'
if(USE_SANITIZER)
CPMAddPackage("gh:StableCoder/cmake-scripts#25.08")
include(${cmake-scripts_SOURCE_DIR}/sanitizers.cmake)
endif()
# enables CCACHE support through the USE_CCACHE flag; possible values are: YES, NO or equivalent
if(USE_CCACHE)
CPMAddPackage("gh:TheLartians/Ccache.cmake@1.2.4")
endif()
File diff suppressed because it is too large. Load diff
+56
View File
@@ -0,0 +1,56 @@
#pragma once
#include <exception>
#include <string>
namespace spdlogconfig {
// declaration section
/**
* Setup error with a textual description.
*
* This exception type is used to report configuration and initialization
* failures within the spdlog configuration module.
*/
class spdlogconfig_error : public std::exception {
public:
/**
* Construct an error from a C-string message.
*
* @param msg Error message to store.
* @throw std::bad_alloc If memory allocation for the message fails.
*/
spdlogconfig_error(const char *const msg);
/**
* Construct an error from a std::string message.
*
* @param msg Error message to store (moved into this object).
* @throw std::bad_alloc If memory allocation for the message fails.
*/
spdlogconfig_error(std::string msg);
/**
* Retrieve the stored error message.
*
* @return Null-terminated error message string.
*/
auto what() const noexcept -> const char * override;
private:
/** Stored error message text. */
std::string msg;
};
// implementation section
inline spdlogconfig_error::spdlogconfig_error(const char *const msg)
: msg(msg) {}
inline spdlogconfig_error::spdlogconfig_error(std::string msg)
: msg(std::move(msg)) {}
inline auto spdlogconfig_error::what() const noexcept -> const char * {
return msg.c_str();
}
} // namespace spdlogconfig
+30
View File
@@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(SPDLOGConfigTests LANGUAGES CXX)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
include(CTest)
# ---- Dependencies ----
CPMAddPackage("gh:doctest/doctest@2.5.2")
file(GLOB sources CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp)
add_executable(${PROJECT_NAME} ${sources})
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD 20)
target_link_libraries(${PROJECT_NAME} PRIVATE doctest::doctest SPDLOGConfig::SPDLOGConfig)
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wpedantic -Wextra)
elseif(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE /W4)
target_compile_definitions(${PROJECT_NAME} PRIVATE DOCTEST_CONFIG_USE_STD_HEADERS)
endif()
# ---- Register doctest test cases with CTest ----
include(${doctest_SOURCE_DIR}/scripts/cmake/doctest.cmake)
doctest_discover_tests(${PROJECT_NAME})
+159
View File
@@ -0,0 +1,159 @@
{
"global_pattern": "[%Y-%m-%dT%T%z] [%L] <%n>: %v",
"logger": [
{
"level": "trace",
"name": "root",
"sinks": [
"console_st",
"console_mt",
"color_console_st",
"color_console_mt",
"daily_out",
"daily_err",
"file_out",
"file_err",
"rotate_out",
"rotate_err",
"null_sink_st",
"null_sink_mt",
"syslog_st",
"syslog_mt",
"msvc_sink_st",
"msvc_sink_mt"
]
},
{
"level": "trace",
"name": "console",
"pattern": "succient",
"sinks": [
"console_st",
"console_mt"
]
},
{
"level": "trace",
"name": "global_async",
"pattern": "succient",
"sinks": [
"console_mt"
],
"type": "async"
},
{
"level": "trace",
"name": "local_async",
"overflow_policy": "overrun_oldest",
"pattern": "succient",
"sinks": [
"console_mt"
],
"thread_pool": "tp",
"type": "async"
}
],
"pattern": [
{
"name": "succient",
"value": "%c-%L: %v"
}
],
"sink": [
{
"name": "console_st",
"type": "stdout_sink_st"
},
{
"name": "console_mt",
"type": "stdout_sink_mt"
},
{
"name": "color_console_st",
"type": "stdout_color_sink_st"
},
{
"name": "color_console_mt",
"type": "stdout_color_sink_mt"
},
{
"create_parent_dir": true,
"filename": "log/spdlog_setup.log",
"level": "info",
"name": "file_out",
"type": "basic_file_sink_st"
},
{
"filename": "log/spdlog_setup_err.log",
"level": "err",
"name": "file_err",
"truncate": true,
"type": "basic_file_sink_mt"
},
{
"base_filename": "log/rotate_spdlog_setup.log",
"level": "info",
"max_files": 10,
"max_size": "1M",
"name": "rotate_out",
"type": "rotating_file_sink_st"
},
{
"base_filename": "log/rotate_spdlog_setup_err.log",
"level": "err",
"max_files": 10,
"max_size": "1M",
"name": "rotate_err",
"type": "rotating_file_sink_mt"
},
{
"base_filename": "log/daily_spdlog_setup.log",
"level": "info",
"name": "daily_out",
"rotation_time": "17:30",
"type": "daily_file_sink_st"
},
{
"base_filename": "log/daily_spdlog_setup_err.log",
"level": "err",
"name": "daily_err",
"rotation_time": "17:30",
"type": "daily_file_sink_mt"
},
{
"name": "null_sink_st",
"type": "null_sink_st"
},
{
"name": "null_sink_mt",
"type": "null_sink_mt"
},
{
"name": "syslog_st",
"type": "syslog_sink_st"
},
{
"name": "syslog_mt",
"type": "syslog_sink_mt"
},
{
"name": "msvc_sink_st",
"type": "msvc_sink_st"
},
{
"name": "msvc_sink_mt",
"type": "msvc_sink_mt"
}
],
"thread_pools": [
{
"name": "tp",
"num_threads": 2,
"queue_size": 4096
}
],
"global_thread_pool": {
"num_threads": 1,
"queue_size": 8192
}
}
+3
View File
@@ -0,0 +1,3 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>
+344
View File
@@ -0,0 +1,344 @@
#include "spdlogconfig/SPDLOGConfig.h"
#include "spdlog/spdlog.h"
#include <doctest/doctest.h>
#include <filesystem>
#include <memory>
// -----------------------------------------------------------------------------
// Test helpers
// -----------------------------------------------------------------------------
namespace {
/**
* Build the path to `full.json` relative to this source file.
*
* Layout assumption:
* - This file lives in: .../test/source/spdlogconfig.cpp
* - `full.json` lives in: .../test/full.json
*
* @return Absolute/relative path string to `full.json`.
*/
inline std::string full_json_path() {
namespace fs = std::filesystem;
static const fs::path base =
fs::path(__FILE__).parent_path().parent_path(); // .../test
return (base / "full.json").string();
}
/**
* Common setup for tests that require a fully initialized spdlog configuration.
*
* Drops all existing loggers first to keep test cases independent.
*/
inline void setup_spdlog_from_full_json() {
spdlog::drop_all();
spdlogconfig::setupFrom(full_json_path());
}
} // namespace
// -----------------------------------------------------------------------------
// Tests for full.json
// -----------------------------------------------------------------------------
TEST_SUITE("Tests for full.json") {
TEST_CASE("full Setup") {
// Ensure this test is independent from others.
spdlog::drop_all();
SUBCASE("No loggers should be created yet.") {
CHECK(spdlog::get("root") == nullptr);
CHECK(spdlog::get("console") == nullptr);
CHECK(spdlog::get("global_async") == nullptr);
CHECK(spdlog::get("local_async") == nullptr);
}
// Now load configuration.
spdlogconfig::setupFrom(full_json_path());
SUBCASE("All loggers should be created.") {
auto root = spdlog::get("root");
auto console = spdlog::get("console");
auto global_async = spdlog::get("global_async");
auto local_async = spdlog::get("local_async");
CHECK(root != nullptr);
CHECK(console != nullptr);
CHECK(global_async != nullptr);
CHECK(local_async != nullptr);
}
}
TEST_CASE("full Root logger") {
setup_spdlog_from_full_json();
auto logger = spdlog::get("root");
REQUIRE(logger != nullptr); // avoid segfault
SUBCASE("Root logger should have expected sinks") {
// JSON defines 16 sinks, but 2 are platform-specific (syslog or msvc)
// and may not be created -> effectively 14 sinks.
CHECK(logger->sinks().size() == 14);
}
SUBCASE("Root logger's level should be trace.") {
CHECK(logger->level() == spdlog::level::level_enum::trace);
}
}
TEST_CASE("Console logger") {
setup_spdlog_from_full_json();
auto logger_console = spdlog::get("console");
REQUIRE(logger_console != nullptr);
SUBCASE("Console logger should have two sinks.") {
CHECK(logger_console->sinks().size() == 2);
}
}
TEST_CASE("Global async logger") {
setup_spdlog_from_full_json();
auto logger_global_async = spdlog::get("global_async");
REQUIRE(logger_global_async != nullptr);
SUBCASE("Global async logger should have only one sink.") {
CHECK(logger_global_async->sinks().size() == 1);
}
}
TEST_CASE("Local async logger") {
setup_spdlog_from_full_json();
auto logger_local_async = spdlog::get("local_async");
REQUIRE(logger_local_async != nullptr);
SUBCASE("Local async logger should have only one sink.") {
CHECK(logger_local_async->sinks().size() == 1);
}
}
// ---------------------------------------------------------------------------
// Additional tests: root logger sinks / file sinks / platform sinks
// ---------------------------------------------------------------------------
TEST_CASE("Root logger sink configuration") {
setup_spdlog_from_full_json();
auto logger = spdlog::get("root");
REQUIRE(logger != nullptr);
auto &sinks = logger->sinks();
SUBCASE("basic_file_sinks have correct filenames and levels") {
std::shared_ptr<spdlog::sinks::basic_file_sink_st> file_out_sink;
std::shared_ptr<spdlog::sinks::basic_file_sink_mt> file_err_sink;
for (auto &s : sinks) {
auto as_st =
std::dynamic_pointer_cast<spdlog::sinks::basic_file_sink_st>(s);
if (as_st &&
as_st->filename() == SPDLOG_FILENAME_T("log/spdlog_setup.log")) {
file_out_sink = as_st;
}
auto as_mt =
std::dynamic_pointer_cast<spdlog::sinks::basic_file_sink_mt>(s);
if (as_mt && as_mt->filename() ==
SPDLOG_FILENAME_T("log/spdlog_setup_err.log")) {
file_err_sink = as_mt;
}
}
CHECK(file_out_sink != nullptr);
CHECK(file_err_sink != nullptr);
if (file_out_sink) {
CHECK(file_out_sink->level() == spdlog::level::info);
}
if (file_err_sink) {
CHECK(file_err_sink->level() == spdlog::level::err);
}
// create_parent_dir=true -> "log" should exist
CHECK(std::filesystem::exists("log"));
CHECK(std::filesystem::is_directory("log"));
}
SUBCASE("rotating_file_sinks have correct configuration") {
std::shared_ptr<spdlog::sinks::rotating_file_sink_st> rotate_out_sink;
std::shared_ptr<spdlog::sinks::rotating_file_sink_mt> rotate_err_sink;
for (auto &s : sinks) {
auto as_st =
std::dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_st>(s);
if (as_st && as_st->filename() ==
SPDLOG_FILENAME_T("log/rotate_spdlog_setup.log")) {
rotate_out_sink = as_st;
}
auto as_mt =
std::dynamic_pointer_cast<spdlog::sinks::rotating_file_sink_mt>(s);
if (as_mt && as_mt->filename() ==
SPDLOG_FILENAME_T("log/rotate_spdlog_setup_err.log")) {
rotate_err_sink = as_mt;
}
}
CHECK(rotate_out_sink != nullptr);
CHECK(rotate_err_sink != nullptr);
const auto expected_size =
spdlogconfig::utils::GetMaxSizeInt("1M"); // from JSON
const std::size_t expected_files = 10;
if (rotate_out_sink) {
CHECK(rotate_out_sink->get_max_size() == expected_size);
CHECK(rotate_out_sink->get_max_files() == expected_files);
CHECK(rotate_out_sink->level() == spdlog::level::info);
}
if (rotate_err_sink) {
CHECK(rotate_err_sink->get_max_size() == expected_size);
CHECK(rotate_err_sink->get_max_files() == expected_files);
CHECK(rotate_err_sink->level() == spdlog::level::err);
}
}
SUBCASE("Root logger has daily-file sinks") {
bool has_daily_st = false;
bool has_daily_mt = false;
for (auto &s : sinks) {
if (std::dynamic_pointer_cast<spdlog::sinks::daily_file_sink_st>(s)) {
has_daily_st = true;
}
if (std::dynamic_pointer_cast<spdlog::sinks::daily_file_sink_mt>(s)) {
has_daily_mt = true;
}
}
CHECK(has_daily_st);
CHECK(has_daily_mt);
}
SUBCASE("Null sinks exist") {
bool has_null_st = false;
bool has_null_mt = false;
for (auto &s : sinks) {
if (std::dynamic_pointer_cast<spdlog::sinks::null_sink_st>(s)) {
has_null_st = true;
}
if (std::dynamic_pointer_cast<spdlog::sinks::null_sink_mt>(s)) {
has_null_mt = true;
}
}
CHECK(has_null_st);
CHECK(has_null_mt);
}
SUBCASE("Platform-specific sinks exist") {
#ifndef _WIN32
bool has_syslog_st = false;
bool has_syslog_mt = false;
for (auto &s : sinks) {
if (dynamic_cast<spdlog::sinks::syslog_sink_st *>(s.get())) {
has_syslog_st = true;
}
if (dynamic_cast<spdlog::sinks::syslog_sink_mt *>(s.get())) {
has_syslog_mt = true;
}
}
CHECK(has_syslog_st);
CHECK(has_syslog_mt);
#else
bool has_msvc_mt = false;
for (auto &s : sinks) {
if (dynamic_cast<spdlog::sinks::msvc_sink_mt *>(s.get())) {
has_msvc_mt = true;
}
}
CHECK(has_msvc_mt);
#endif
}
SUBCASE("Console sinks inherit the logger level trace") {
for (auto &s : sinks) {
if (std::dynamic_pointer_cast<spdlog::sinks::stdout_sink_st>(s) ||
std::dynamic_pointer_cast<spdlog::sinks::stdout_sink_mt>(s) ||
std::dynamic_pointer_cast<spdlog::sinks::stdout_color_sink_st>(s) ||
std::dynamic_pointer_cast<spdlog::sinks::stdout_color_sink_mt>(s)) {
CHECK(s->level() == spdlog::level::trace);
}
}
}
}
// ---------------------------------------------------------------------------
// Additional tests: async logger & thread pool
// ---------------------------------------------------------------------------
TEST_CASE("Async logger configuration and thread pool") {
setup_spdlog_from_full_json();
SUBCASE("Global thread pool is initialized") {
auto tp = spdlog::thread_pool();
CHECK(tp != nullptr);
}
SUBCASE("global_async and local_async are async_logger") {
auto global_async = spdlog::get("global_async");
auto local_async = spdlog::get("local_async");
REQUIRE(global_async != nullptr);
REQUIRE(local_async != nullptr);
auto as_global_async =
std::dynamic_pointer_cast<spdlog::async_logger>(global_async);
auto as_local_async =
std::dynamic_pointer_cast<spdlog::async_logger>(local_async);
CHECK(as_global_async != nullptr);
CHECK(as_local_async != nullptr);
}
}
}
// -----------------------------------------------------------------------------
// Additional tests: utility functions
// -----------------------------------------------------------------------------
TEST_SUITE("spdlogconfig::utils") {
using spdlogconfig::utils::GetMaxSizeInt;
TEST_CASE("GetMaxSizeInt without suffix") {
CHECK(GetMaxSizeInt("0") == static_cast<std::size_t>(0));
CHECK(GetMaxSizeInt("42") == static_cast<std::size_t>(42));
CHECK(GetMaxSizeInt("10485760") == static_cast<std::size_t>(10485760));
}
TEST_CASE("GetMaxSizeInt with K/M/G/T suffix") {
CHECK(GetMaxSizeInt("1K") == static_cast<std::size_t>(1024));
CHECK(GetMaxSizeInt("2M") == static_cast<std::size_t>(2 * 1024 * 1024));
CHECK(GetMaxSizeInt("3G") ==
static_cast<std::size_t>(3ull * 1024 * 1024 * 1024));
CHECK(GetMaxSizeInt("1T") ==
static_cast<std::size_t>(1ull * 1024 * 1024 * 1024 * 1024));
}
TEST_CASE("GetMaxSizeInt ignores whitespace around the number") {
CHECK(GetMaxSizeInt(" 1K") == static_cast<std::size_t>(1024));
CHECK(GetMaxSizeInt("2M ") == static_cast<std::size_t>(2 * 1024 * 1024));
}
}