From 686ac99a109217b69166d75b076a0e69fcb9d139 Mon Sep 17 00:00:00 2001 From: shookapic Date: Fri, 10 Apr 2026 18:13:00 +0900 Subject: [PATCH] feat(windows): add native logger backend --- .github/workflows/build.yml | 80 ++++++++++ CMakeLists.txt | 9 +- README.md | 15 +- scripts/check-license-compliance.sh | 2 +- src/logger.cpp | 220 ++++++++++++---------------- src/logger_platform.hpp | 26 ++++ src/logger_posix.cpp | 65 ++++++++ src/logger_windows.cpp | 88 +++++++++++ tests/test_api_precedence.cpp | 20 ++- tests/test_debug_level.cpp | 20 ++- 10 files changed, 414 insertions(+), 131 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 src/logger_platform.hpp create mode 100644 src/logger_posix.cpp create mode 100644 src/logger_windows.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..b910c94 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: MIT +name: Build + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +jobs: + build-posix: + name: Build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies (Linux) + if: runner.os == 'Linux' + run: | + set -euo pipefail + sudo apt-get update + sudo apt-get install -y --no-install-recommends cmake ninja-build g++ + + - name: Install dependencies (macOS) + if: runner.os == 'macOS' + run: | + set -euo pipefail + brew install cmake ninja + + - name: Configure + run: | + set -euo pipefail + cmake -S . -B build -G Ninja \ + -DCORETRACE_LOGGER_BUILD_TESTS=ON \ + -DCORETRACE_LOGGER_BUILD_EXAMPLES=ON + + - name: Build + run: | + set -euo pipefail + cmake --build build + + - name: Test + run: | + set -euo pipefail + ctest --test-dir build --output-on-failure + + build-windows: + name: Build on Windows + runs-on: windows-2022 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + cmake -S . -B build-win -G "Visual Studio 17 2022" -A x64 ` + -DCORETRACE_LOGGER_BUILD_TESTS=ON ` + -DCORETRACE_LOGGER_BUILD_EXAMPLES=ON + + - name: Build + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + cmake --build build-win --config Release + + - name: Test + shell: pwsh + run: | + $ErrorActionPreference = "Stop" + ctest --test-dir build-win -C Release --output-on-failure diff --git a/CMakeLists.txt b/CMakeLists.txt index 385aab4..5e6d61d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,14 @@ find_package(Threads REQUIRED) ### Library ### -add_library(coretrace_logger STATIC src/logger.cpp) +set(CORETRACE_LOGGER_SOURCES src/logger.cpp) +if(WIN32) + list(APPEND CORETRACE_LOGGER_SOURCES src/logger_windows.cpp) +else() + list(APPEND CORETRACE_LOGGER_SOURCES src/logger_posix.cpp) +endif() + +add_library(coretrace_logger STATIC ${CORETRACE_LOGGER_SOURCES}) target_include_directories(coretrace_logger PUBLIC diff --git a/README.md b/README.md index babeb44..dc21202 100644 --- a/README.md +++ b/README.md @@ -83,8 +83,19 @@ target_link_libraries(my_target PRIVATE coretrace::logger) ## Requirements -- C++20 compiler (Clang 16+, GCC 13+, AppleClang 15+) -- POSIX (Linux, macOS) for `write(2)`, `pthread`, `clock_gettime` +- C++20 compiler (Clang 16+, GCC 13+, AppleClang 15+, Visual Studio/MSVC) +- Linux, macOS, or Windows + +## Windows build + +Native Windows builds work with Visual Studio 2022 and CMake: + +```powershell +cmake -S . -B build-win -G "Visual Studio 17 2022" -A x64 ` + -DCORETRACE_LOGGER_BUILD_TESTS=ON +cmake --build build-win --config Release +ctest --test-dir build-win -C Release --output-on-failure +``` ## API reference diff --git a/scripts/check-license-compliance.sh b/scripts/check-license-compliance.sh index d38d527..cfc3437 100755 --- a/scripts/check-license-compliance.sh +++ b/scripts/check-license-compliance.sh @@ -25,7 +25,7 @@ if [ -f "${WORKFLOW_FILE}" ]; then fi fi -spdx_output="$(git -C "${REPO_ROOT}" grep -n "SPDX-License-Identifier:" -- . 2>/dev/null || true)" +spdx_output="$(git -C "${REPO_ROOT}" grep -n -E "^[[:space:]]*(#|//|/\*)[[:space:]]*SPDX-License-Identifier:" -- . 2>/dev/null || true)" if [ -n "${spdx_output}" ]; then mismatches=() diff --git a/src/logger.cpp b/src/logger.cpp index 807648c..09907bb 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -1,26 +1,25 @@ #include "coretrace/logger.hpp" +#include "logger_platform.hpp" + +#include #include #include -#include -#include -#include -#include - -#if defined(__linux__) -#include -#endif +#include +#include namespace coretrace { -// #################################### -// Global state -// #################################### - namespace { + +// ── Shared limits ───────────────────────── + +constexpr int MAX_MODULES = 32; +constexpr int MODULE_NAME_LEN = 32; + // ── Enable / Disable ───────────────────── -int g_log_enabled = 0; +std::atomic g_log_enabled{0}; // ── Prefix ─────────────────────────────── @@ -29,14 +28,11 @@ size_t g_prefix_len = 6; // ── Level filtering ────────────────────── -int g_min_level = static_cast(Level::Info); -int g_min_level_set_explicitly = 0; +std::atomic g_min_level{static_cast(Level::Info)}; +std::atomic g_min_level_set_explicitly{0}; // ── Module filtering ───────────────────── -constexpr int MAX_MODULES = 32; -constexpr int MODULE_NAME_LEN = 32; - struct ModuleTable { char names[MAX_MODULES][MODULE_NAME_LEN]; int count = 0; @@ -44,38 +40,38 @@ struct ModuleTable { }; ModuleTable g_modules{}; -int g_modules_set_explicitly = 0; +std::atomic g_modules_set_explicitly{0}; // ── Synchronization ────────────────────── // Protects mutable logger state (prefix + modules table). -pthread_mutex_t g_state_mutex = PTHREAD_MUTEX_INITIALIZER; +std::mutex g_state_mutex; // Protects atomicity of one log line output when thread-safe mode is on. -pthread_mutex_t g_output_mutex = PTHREAD_MUTEX_INITIALIZER; -int g_thread_safe = 1; // enabled by default +std::mutex g_output_mutex; +std::atomic g_thread_safe{1}; // enabled by default // ── Sink ───────────────────────────────── -SinkFn g_sink = nullptr; +std::atomic g_sink{nullptr}; // ── Timestamps ─────────────────────────── -int g_timestamps_enabled = 0; +std::atomic g_timestamps_enabled{0}; // ── Source location ────────────────────── -int g_source_location_enabled = 0; +std::atomic g_source_location_enabled{0}; // ── Init ───────────────────────────────── -pthread_once_t g_init_once = PTHREAD_ONCE_INIT; +std::once_flag g_init_once; // ── Small lock guards ──────────────────── struct StateLockGuard { - StateLockGuard() { pthread_mutex_lock(&g_state_mutex); } - ~StateLockGuard() { pthread_mutex_unlock(&g_state_mutex); } + StateLockGuard() { g_state_mutex.lock(); } + ~StateLockGuard() { g_state_mutex.unlock(); } StateLockGuard(const StateLockGuard &) = delete; StateLockGuard &operator=(const StateLockGuard &) = delete; @@ -83,14 +79,14 @@ struct StateLockGuard { struct OutputLockGuard { OutputLockGuard() - : locked(__atomic_load_n(&g_thread_safe, __ATOMIC_ACQUIRE) != 0) { + : locked(g_thread_safe.load(std::memory_order_acquire) != 0) { if (locked) - pthread_mutex_lock(&g_output_mutex); + g_output_mutex.lock(); } ~OutputLockGuard() { if (locked) - pthread_mutex_unlock(&g_output_mutex); + g_output_mutex.unlock(); } OutputLockGuard(const OutputLockGuard &) = delete; @@ -117,21 +113,31 @@ struct PrefixSnapshot { return snapshot; } +// ── Environment ─────────────────────────── + +[[nodiscard]] const char *env_var(const char *name) { +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4996) +#endif + const char *value = std::getenv(name); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + return value; +} + // ── Color detection ────────────────────── [[nodiscard]] bool use_color() { - static int cached = -1; - - if (cached != -1) - return cached != 0; + static const bool enabled = []() { + if (env_var("NO_COLOR") != nullptr) + return false; - if (getenv("NO_COLOR") != nullptr) { - cached = 0; - return false; - } + return platform::stderr_supports_color(); + }(); - cached = isatty(2) ? 1 : 0; - return cached != 0; + return enabled; } // ── String helpers ─────────────────────── @@ -159,6 +165,8 @@ struct PrefixSnapshot { } [[nodiscard]] int parse_level_from_env(const char *value) { + if (!value) + return static_cast(Level::Info); if (cstr_ieq(value, "debug")) return static_cast(Level::Debug); if (cstr_ieq(value, "warn")) @@ -173,55 +181,49 @@ struct PrefixSnapshot { // Writes ISO 8601 timestamp: [2025-01-15T10:45:23.456] // Uses stack buffer, no heap allocation. void write_timestamp_to(char *buf, size_t &idx) { - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - - struct tm tm_buf; - gmtime_r(&ts.tv_sec, &tm_buf); - - int millis = static_cast(ts.tv_nsec / 1000000); + platform::UtcTimestamp ts{}; + if (!platform::utc_timestamp(ts)) + return; // [YYYY-MM-DDThh:mm:ss.mmm] buf[idx++] = '['; // Year - int year = tm_buf.tm_year + 1900; - buf[idx++] = static_cast('0' + (year / 1000)); - buf[idx++] = static_cast('0' + (year / 100) % 10); - buf[idx++] = static_cast('0' + (year / 10) % 10); - buf[idx++] = static_cast('0' + year % 10); + buf[idx++] = static_cast('0' + (ts.year / 1000)); + buf[idx++] = static_cast('0' + (ts.year / 100) % 10); + buf[idx++] = static_cast('0' + (ts.year / 10) % 10); + buf[idx++] = static_cast('0' + ts.year % 10); buf[idx++] = '-'; // Month - int mon = tm_buf.tm_mon + 1; - buf[idx++] = static_cast('0' + mon / 10); - buf[idx++] = static_cast('0' + mon % 10); + buf[idx++] = static_cast('0' + ts.month / 10); + buf[idx++] = static_cast('0' + ts.month % 10); buf[idx++] = '-'; // Day - buf[idx++] = static_cast('0' + tm_buf.tm_mday / 10); - buf[idx++] = static_cast('0' + tm_buf.tm_mday % 10); + buf[idx++] = static_cast('0' + ts.day / 10); + buf[idx++] = static_cast('0' + ts.day % 10); buf[idx++] = 'T'; // Hour - buf[idx++] = static_cast('0' + tm_buf.tm_hour / 10); - buf[idx++] = static_cast('0' + tm_buf.tm_hour % 10); + buf[idx++] = static_cast('0' + ts.hour / 10); + buf[idx++] = static_cast('0' + ts.hour % 10); buf[idx++] = ':'; // Minute - buf[idx++] = static_cast('0' + tm_buf.tm_min / 10); - buf[idx++] = static_cast('0' + tm_buf.tm_min % 10); + buf[idx++] = static_cast('0' + ts.minute / 10); + buf[idx++] = static_cast('0' + ts.minute % 10); buf[idx++] = ':'; // Second - buf[idx++] = static_cast('0' + tm_buf.tm_sec / 10); - buf[idx++] = static_cast('0' + tm_buf.tm_sec % 10); + buf[idx++] = static_cast('0' + ts.second / 10); + buf[idx++] = static_cast('0' + ts.second % 10); buf[idx++] = '.'; // Milliseconds - buf[idx++] = static_cast('0' + millis / 100); - buf[idx++] = static_cast('0' + (millis / 10) % 10); - buf[idx++] = static_cast('0' + millis % 10); + buf[idx++] = static_cast('0' + ts.millisecond / 100); + buf[idx++] = static_cast('0' + (ts.millisecond / 10) % 10); + buf[idx++] = static_cast('0' + ts.millisecond % 10); buf[idx++] = ']'; buf[idx++] = ' '; @@ -235,7 +237,7 @@ void write_timestamp_to(char *buf, size_t &idx) { const char *last = path; for (const char *p = path; *p; ++p) { - if (*p == '/') + if (*p == '/' || *p == '\\') last = p + 1; } return last; @@ -262,16 +264,16 @@ void add_module_locked(std::string_view name) { void init_from_env() { // CT_LOG_LEVEL=debug|info|warn|error // (startup default only, explicit API has priority) - if (__atomic_load_n(&g_min_level_set_explicitly, __ATOMIC_ACQUIRE) == 0) { - const char *env_level = getenv("CT_LOG_LEVEL"); + if (g_min_level_set_explicitly.load(std::memory_order_acquire) == 0) { + const char *env_level = env_var("CT_LOG_LEVEL"); if (env_level) - __atomic_store_n(&g_min_level, parse_level_from_env(env_level), - __ATOMIC_RELEASE); + g_min_level.store(parse_level_from_env(env_level), + std::memory_order_release); } // CT_DEBUG=mod1,mod2,... (default only, explicit API has priority) - if (__atomic_load_n(&g_modules_set_explicitly, __ATOMIC_ACQUIRE) == 0) { - const char *env_debug = getenv("CT_DEBUG"); + if (g_modules_set_explicitly.load(std::memory_order_acquire) == 0) { + const char *env_debug = env_var("CT_DEBUG"); if (env_debug && env_debug[0] != '\0') { StateLockGuard guard; @@ -298,20 +300,18 @@ void init_from_env() { // Init // #################################### -void init_once() { (void)pthread_once(&g_init_once, init_from_env); } +void init_once() { std::call_once(g_init_once, init_from_env); } // #################################### // Enable / Disable // #################################### -void enable_logging() { __atomic_store_n(&g_log_enabled, 1, __ATOMIC_RELEASE); } +void enable_logging() { g_log_enabled.store(1, std::memory_order_release); } -void disable_logging() { - __atomic_store_n(&g_log_enabled, 0, __ATOMIC_RELEASE); -} +void disable_logging() { g_log_enabled.store(0, std::memory_order_release); } [[nodiscard]] bool log_is_enabled() { - return __atomic_load_n(&g_log_enabled, __ATOMIC_ACQUIRE) != 0; + return g_log_enabled.load(std::memory_order_acquire) != 0; } // #################################### @@ -337,13 +337,13 @@ void set_prefix(std::string_view prefix) { // #################################### void set_min_level(Level level) { - __atomic_store_n(&g_min_level_set_explicitly, 1, __ATOMIC_RELEASE); + g_min_level_set_explicitly.store(1, std::memory_order_release); init_once(); - __atomic_store_n(&g_min_level, static_cast(level), __ATOMIC_RELEASE); + g_min_level.store(static_cast(level), std::memory_order_release); } [[nodiscard]] Level min_level() { - return static_cast(__atomic_load_n(&g_min_level, __ATOMIC_ACQUIRE)); + return static_cast(g_min_level.load(std::memory_order_acquire)); } // #################################### @@ -354,7 +354,7 @@ void enable_module(std::string_view name) { if (name.empty() || name.size() >= MODULE_NAME_LEN) return; - __atomic_store_n(&g_modules_set_explicitly, 1, __ATOMIC_RELEASE); + g_modules_set_explicitly.store(1, std::memory_order_release); init_once(); StateLockGuard guard; @@ -365,7 +365,7 @@ void disable_module(std::string_view name) { if (name.empty()) return; - __atomic_store_n(&g_modules_set_explicitly, 1, __ATOMIC_RELEASE); + g_modules_set_explicitly.store(1, std::memory_order_release); init_once(); StateLockGuard guard; @@ -387,7 +387,7 @@ void disable_module(std::string_view name) { } void enable_all_modules() { - __atomic_store_n(&g_modules_set_explicitly, 1, __ATOMIC_RELEASE); + g_modules_set_explicitly.store(1, std::memory_order_release); init_once(); StateLockGuard guard; @@ -415,25 +415,23 @@ void enable_all_modules() { // #################################### void set_thread_safe(bool enabled) { - __atomic_store_n(&g_thread_safe, enabled ? 1 : 0, __ATOMIC_RELEASE); + g_thread_safe.store(enabled ? 1 : 0, std::memory_order_release); } // #################################### // Sink // #################################### -void set_sink(SinkFn fn) { __atomic_store_n(&g_sink, fn, __ATOMIC_RELEASE); } +void set_sink(SinkFn fn) { g_sink.store(fn, std::memory_order_release); } -void reset_sink() { - __atomic_store_n(&g_sink, static_cast(nullptr), __ATOMIC_RELEASE); -} +void reset_sink() { g_sink.store(nullptr, std::memory_order_release); } // #################################### // Timestamps // #################################### void set_timestamps(bool enabled) { - __atomic_store_n(&g_timestamps_enabled, enabled ? 1 : 0, __ATOMIC_RELEASE); + g_timestamps_enabled.store(enabled ? 1 : 0, std::memory_order_release); } // #################################### @@ -441,8 +439,7 @@ void set_timestamps(bool enabled) { // #################################### void set_source_location(bool enabled) { - __atomic_store_n(&g_source_location_enabled, enabled ? 1 : 0, - __ATOMIC_RELEASE); + g_source_location_enabled.store(enabled ? 1 : 0, std::memory_order_release); } // #################################### @@ -579,24 +576,12 @@ void set_source_location(bool enabled) { // #################################### [[nodiscard]] int pid() { - static int cached = 0; - - if (cached == 0) - cached = static_cast(getpid()); - + static const int cached = platform::process_id(); return cached; } [[nodiscard]] unsigned long long thread_id() { -#if defined(__APPLE__) - uint64_t tid = 0; - (void)pthread_threadid_np(nullptr, &tid); - return static_cast(tid); -#elif defined(__linux__) - return static_cast(syscall(SYS_gettid)); -#else - return reinterpret_cast(pthread_self()); -#endif + return platform::current_thread_id(); } // #################################### @@ -608,24 +593,13 @@ void write_raw(const char *data, size_t size) { return; // Custom sink? - SinkFn sink = __atomic_load_n(&g_sink, __ATOMIC_ACQUIRE); + SinkFn sink = g_sink.load(std::memory_order_acquire); if (sink) { sink(data, size); return; } - // Default: stderr (fd=2) with EINTR retry. - while (size > 0) { - ssize_t written = write(2, data, size); - if (written > 0) { - data += static_cast(written); - size -= static_cast(written); - continue; - } - if (written < 0 && errno == EINTR) - continue; - break; - } + platform::write_stderr(data, size); } void write_str(std::string_view value) { @@ -725,7 +699,7 @@ void write_log_line(Level level, std::string_view module, OutputLockGuard output_lock; // Optional timestamp: [2025-01-15T10:45:23.456] - if (__atomic_load_n(&g_timestamps_enabled, __ATOMIC_ACQUIRE)) { + if (g_timestamps_enabled.load(std::memory_order_acquire)) { char ts_buf[32]; size_t ts_idx = 0; write_timestamp_to(ts_buf, ts_idx); @@ -755,7 +729,7 @@ void write_log_line(Level level, std::string_view module, write_str(color(Color::Reset)); // Optional source location: file.cpp:42 - if (__atomic_load_n(&g_source_location_enabled, __ATOMIC_ACQUIRE)) { + if (g_source_location_enabled.load(std::memory_order_acquire)) { write_raw(" ", 1); write_str(color(Color::Dim)); const char *file = basename_of(loc.file_name()); diff --git a/src/logger_platform.hpp b/src/logger_platform.hpp new file mode 100644 index 0000000..3e9e7c4 --- /dev/null +++ b/src/logger_platform.hpp @@ -0,0 +1,26 @@ +#ifndef CORETRACE_LOGGER_PLATFORM_HPP +#define CORETRACE_LOGGER_PLATFORM_HPP + +#include + +namespace coretrace::platform { + +struct UtcTimestamp { + int year = 1970; + int month = 1; + int day = 1; + int hour = 0; + int minute = 0; + int second = 0; + int millisecond = 0; +}; + +[[nodiscard]] bool stderr_supports_color(); +void write_stderr(const char *data, size_t size); +[[nodiscard]] int process_id(); +[[nodiscard]] unsigned long long current_thread_id(); +[[nodiscard]] bool utc_timestamp(UtcTimestamp &out); + +} // namespace coretrace::platform + +#endif // CORETRACE_LOGGER_PLATFORM_HPP diff --git a/src/logger_posix.cpp b/src/logger_posix.cpp new file mode 100644 index 0000000..8955eeb --- /dev/null +++ b/src/logger_posix.cpp @@ -0,0 +1,65 @@ +#include "logger_platform.hpp" + +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +namespace coretrace::platform { + +[[nodiscard]] bool stderr_supports_color() { return isatty(2) != 0; } + +void write_stderr(const char *data, size_t size) { + while (size > 0) { + ssize_t written = write(2, data, size); + if (written > 0) { + data += static_cast(written); + size -= static_cast(written); + continue; + } + if (written < 0 && errno == EINTR) + continue; + break; + } +} + +[[nodiscard]] int process_id() { return static_cast(getpid()); } + +[[nodiscard]] unsigned long long current_thread_id() { +#if defined(__APPLE__) + uint64_t tid = 0; + (void)pthread_threadid_np(nullptr, &tid); + return static_cast(tid); +#elif defined(__linux__) + return static_cast(syscall(SYS_gettid)); +#else + return reinterpret_cast(pthread_self()); +#endif +} + +[[nodiscard]] bool utc_timestamp(UtcTimestamp &out) { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) + return false; + + struct tm tm_buf; + if (gmtime_r(&ts.tv_sec, &tm_buf) == nullptr) + return false; + + out.year = tm_buf.tm_year + 1900; + out.month = tm_buf.tm_mon + 1; + out.day = tm_buf.tm_mday; + out.hour = tm_buf.tm_hour; + out.minute = tm_buf.tm_min; + out.second = tm_buf.tm_sec; + out.millisecond = static_cast(ts.tv_nsec / 1000000); + return true; +} + +} // namespace coretrace::platform diff --git a/src/logger_windows.cpp b/src/logger_windows.cpp new file mode 100644 index 0000000..f44bfac --- /dev/null +++ b/src/logger_windows.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +#include "logger_platform.hpp" + +#include +#include +#include +#include +#include + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include + +namespace coretrace::platform { + +namespace { + +[[nodiscard]] bool enable_virtual_terminal_on_stderr() { + HANDLE handle = GetStdHandle(STD_ERROR_HANDLE); + if (handle == INVALID_HANDLE_VALUE || handle == nullptr) + return false; + + DWORD mode = 0; + if (!GetConsoleMode(handle, &mode)) + return false; + + if ((mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0) + return true; + + return SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; +} + +} // namespace + +[[nodiscard]] bool stderr_supports_color() { + if (_isatty(_fileno(stderr)) == 0) + return false; + + return enable_virtual_terminal_on_stderr(); +} + +void write_stderr(const char *data, size_t size) { + const int fd = _fileno(stderr); + while (size > 0) { + const size_t chunk = + std::min(size, static_cast((std::numeric_limits::max)())); + const int written = _write(fd, data, static_cast(chunk)); + if (written > 0) { + data += static_cast(written); + size -= static_cast(written); + continue; + } + break; + } +} + +[[nodiscard]] int process_id() { return static_cast(GetCurrentProcessId()); } + +[[nodiscard]] unsigned long long current_thread_id() { + return static_cast(GetCurrentThreadId()); +} + +[[nodiscard]] bool utc_timestamp(UtcTimestamp &out) { + using clock = std::chrono::system_clock; + const auto now = clock::now(); + const auto time = clock::to_time_t(now); + const auto millis = + std::chrono::duration_cast( + now.time_since_epoch()) % + 1000; + + std::tm tm_buf{}; + if (gmtime_s(&tm_buf, &time) != 0) + return false; + + out.year = tm_buf.tm_year + 1900; + out.month = tm_buf.tm_mon + 1; + out.day = tm_buf.tm_mday; + out.hour = tm_buf.tm_hour; + out.minute = tm_buf.tm_min; + out.second = tm_buf.tm_sec; + out.millisecond = static_cast(millis.count()); + return true; +} + +} // namespace coretrace::platform diff --git a/tests/test_api_precedence.cpp b/tests/test_api_precedence.cpp index 96b00c3..b80e2f3 100644 --- a/tests/test_api_precedence.cpp +++ b/tests/test_api_precedence.cpp @@ -11,10 +11,26 @@ void capture_sink(const char *data, size_t size) { g_capture.append(data, size); } +void set_env_var(const char *key, const char *value) { +#if defined(_WIN32) + (void)_putenv_s(key, value); +#else + (void)setenv(key, value, 1); +#endif +} + +void unset_env_var(const char *key) { +#if defined(_WIN32) + (void)_putenv_s(key, ""); +#else + (void)unsetenv(key); +#endif +} + } // namespace int main() { - setenv("CT_LOG_LEVEL", "info", 1); + set_env_var("CT_LOG_LEVEL", "info"); coretrace::set_sink(capture_sink); coretrace::enable_logging(); @@ -26,7 +42,7 @@ int main() { coretrace::log(coretrace::Level::Error, "error should pass\n"); coretrace::reset_sink(); - unsetenv("CT_LOG_LEVEL"); + unset_env_var("CT_LOG_LEVEL"); const bool has_warn = g_capture.find("[WARN]") != std::string::npos; const bool has_error = g_capture.find("[ERROR]") != std::string::npos; diff --git a/tests/test_debug_level.cpp b/tests/test_debug_level.cpp index e676203..9884b37 100644 --- a/tests/test_debug_level.cpp +++ b/tests/test_debug_level.cpp @@ -9,12 +9,28 @@ std::string g_capture; void capture_sink(const char *data, size_t size) { g_capture.append(data, size); } +void set_env_var(const char *key, const char *value) { +#if defined(_WIN32) + (void)_putenv_s(key, value); +#else + (void)setenv(key, value, 1); +#endif +} + +void unset_env_var(const char *key) { +#if defined(_WIN32) + (void)_putenv_s(key, ""); +#else + (void)unsetenv(key); +#endif +} + } // namespace int main() { using namespace coretrace; - setenv("CT_LOG_LEVEL", "debug", 1); + set_env_var("CT_LOG_LEVEL", "debug"); set_sink(capture_sink); enable_logging(); @@ -27,7 +43,7 @@ int main() { log(Level::Info, "info still visible\n"); reset_sink(); - unsetenv("CT_LOG_LEVEL"); + unset_env_var("CT_LOG_LEVEL"); const bool has_debug_env = g_capture.find("debug via env") != std::string::npos; const bool has_debug_filtered =