From 9cbe7c046690257d86d2fcdd2a8aba38fa0aebd5 Mon Sep 17 00:00:00 2001 From: nircoe Date: Thu, 1 Jan 2026 19:23:45 +0200 Subject: [PATCH 1/3] [Refactor]: Add logcoe prefix in internal messages --- README.md | 28 ++++----- docs/ARCHITECTURE.md | 96 +++++++++++------------------- docs/CONTRIBUTING.md | 138 +++++-------------------------------------- docs/ROADMAP.md | 10 +--- include/logcoe.hpp | 2 +- src/logcoe.cpp | 25 +++++--- 6 files changed, 83 insertions(+), 216 deletions(-) diff --git a/README.md b/README.md index ab9d602..a295718 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,10 @@ Thread-safe C++ logging library with real-time output and customizable formattin logcoe is a lightweight, thread-safe C++ logging library designed for high-performance applications. It provides flexible output options and real-time logging with minimal overhead: ``` -[2025-06-07_14:30:25] [INFO]: Application started successfully -[2025-06-07_14:30:25] [DEBUG] [NetworkManager]: Connecting to server 192.168.1.100 -[2025-06-07_14:30:26] [WARNING] [Database]: Connection timeout, retrying... -[2025-06-07_14:30:27] [ERROR] [FileSystem]: Failed to open configuration file +[06/07/2025__14:30:25] [INFO]: Application started successfully +[06/07/2025__14:30:25] [DEBUG] [NetworkManager]: Connecting to server 192.168.1.100 +[06/07/2025__14:30:26] [WARNING] [Database]: Connection timeout, retrying... +[06/07/2025__14:30:27] [ERROR] [FileSystem]: Failed to open configuration file ``` Perfect for applications requiring reliable logging across multiple threads with customizable output destinations. @@ -32,7 +32,7 @@ include(FetchContent) FetchContent_Declare( logcoe GIT_REPOSITORY https://github.com/nircoe/logcoe.git - GIT_TAG v0.1.0 + GIT_TAG v0.1.1 ) FetchContent_MakeAvailable(logcoe) @@ -90,14 +90,14 @@ int main() { ## Features -- 🔒 **Thread-Safe** - Concurrent logging from multiple threads -- 📊 **Multiple Log Levels** - DEBUG, INFO, WARNING, ERROR with runtime filtering -- 🖥️ **Dual Output** - Console and file output simultaneously -- ⚡ **High Performance** - Minimal overhead with optional flushing control -- 🎨 **Customizable** - Configurable time formats and output streams -- 🔄 **Dynamic Configuration** - Change settings during runtime -- 📦 **Zero Dependencies** - Header-only public API, pure C++17 -- 🌐 **Cross-Platform** - Windows, Linux, macOS support +- **Thread-Safe** - Concurrent logging from multiple threads +- **Multiple Log Levels** - DEBUG, INFO, WARNING, ERROR with runtime filtering +- **Dual Output** - Console and file output simultaneously +- **High Performance** - Minimal overhead with optional flushing control +- **Customizable** - Configurable time formats and output streams +- **Dynamic Configuration** - Change settings during runtime +- **Zero Dependencies** - Header-only public API, pure C++17 +- **Cross-Platform** - Windows, Linux, macOS support ## API Reference @@ -115,7 +115,7 @@ logcoe::initialize( "application.log" // Filename ); -// Clean shutdown +// shutdown logcoe::shutdown(); ``` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 18fe36e..89c6395 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -7,30 +7,30 @@ logcoe is designed as a lightweight, thread-safe logging library that provides f ## Component Architecture ``` -┌─────────────────────────────────────┐ -│ User Application │ -│ (Client Code) │ -└───────────────┬─────────────────────┘ - │ -┌───────────────▼─────────────────────┐ -│ logcoe API │ -│ (Public Interface Functions) │ -└───────────────┬─────────────────────┘ - │ -┌───────────────▼─────────────────────┐ -│ LoggerImpl │ -│ (Internal Implementation) │ -│ │ -│ ┌─────────────┬─────────────────┐ │ -│ │ Mutex │ Output Streams │ │ -│ │ Protection │ Management │ │ -│ └─────────────┴─────────────────┘ │ -│ │ -│ ┌─────────────┬─────────────────┐ │ -│ │ Log Level │ Timestamp │ │ -│ │ Filtering │ Formatting │ │ -│ └─────────────┴─────────────────┘ │ -└─────────────────────────────────────┘ + ┌─────────────────────────────────────┐ + │ User Application │ + │ (Client Code) │ + └───────────────┬─────────────────────┘ + │ + ┌───────────────▼─────────────────────┐ + │ logcoe API │ + │ (Public Interface Functions) │ + └───────────────┬─────────────────────┘ + │ + ┌───────────────▼─────────────────────┐ + │ LoggerImpl │ + │ (Internal Implementation) │ + │ │ + │ ┌─────────────┬─────────────────┐ │ + │ │ Mutex │ Output Streams │ │ + │ │ Protection │ Management │ │ + │ └─────────────┴─────────────────┘ │ + │ │ + │ ┌─────────────┬─────────────────┐ │ + │ │ Log Level │ Timestamp │ │ + │ │ Filtering │ Formatting │ │ + │ └─────────────┴─────────────────┘ │ + └─────────────────────────────────────┘ ``` ## Core Components @@ -53,9 +53,7 @@ logcoe is designed as a lightweight, thread-safe logging library that provides f ```cpp static std::mutex s_mutex; ``` -- **Purpose**: Ensures thread-safe access to all shared state -- **Scope**: Protects all static members and operations -- **Granularity**: Single global mutex for simplicity and correctness +- Ensures thread-safe access to all static members and operations #### State Management ```cpp @@ -64,9 +62,7 @@ static bool s_useFile; static bool s_useConsole; static std::string s_timeFormat; ``` -- **Purpose**: Maintains current logger configuration -- **Thread Safety**: All access protected by mutex -- **Dynamic Updates**: Can be changed at runtime +- Maintains current logger configuration, Can be changed at runtime #### Output Stream Management ```cpp @@ -76,7 +72,6 @@ static std::ostream* s_consoleStream; ``` - **File Output**: Direct file stream management with automatic opening/closing - **Console Output**: Configurable output stream (default: std::cout) -- **Stream Safety**: All stream operations are mutex-protected ## Data Flow @@ -139,18 +134,8 @@ Release mutex lock ## Thread Safety Implementation -### Mutex Strategy - **Single Global Mutex**: `std::mutex s_mutex` - **Lock Scope**: Every public API call acquires lock for entire duration -- **Benefits**: - - Simple design, no deadlock risk - - Guaranteed consistency across all operations - - Atomic configuration changes - -### Performance Considerations -- **Lock Granularity**: Coarse-grained locking for simplicity -- **Lock Duration**: Minimal time holding locks -- **Flush Control**: Optional flushing to reduce I/O under lock ### Thread Safety Guarantees 1. **Configuration Consistency**: All threads see consistent logger state @@ -172,12 +157,10 @@ std::tm tm_now; ``` - **Windows**: Uses `localtime_s` for thread safety - **Unix/Linux/macOS**: Uses `localtime_r` for thread safety -- **Format**: Standard `strftime` formatting across platforms ### File System Operations - **Path Handling**: Uses standard C++ filesystem operations - **File Permissions**: Relies on OS default permissions -- **Directory Creation**: Not automatic, requires existing directories ### Build System Integration - **CMake**: FetchContent compatible @@ -188,7 +171,7 @@ std::tm tm_now; ### Static Storage - **Lifetime**: All state stored in static variables -- **Initialization**: Lazy initialization through first API call +- **Initialization**: Lazy initialization through `initialize()` - **Cleanup**: Explicit cleanup through `shutdown()` ### Resource Management @@ -198,28 +181,26 @@ std::tm tm_now; ## Log Level Filtering -### Filtering Logic +### Level Hierarchy +``` +DEBUG (0) < INFO (1) < WARNING (2) < ERROR (3) < NONE (4) +``` + ```cpp if (static_cast(level) < static_cast(s_logLevel)) return; ``` + - **Numeric Comparison**: Log levels assigned integer values - **Early Return**: Filtered messages exit immediately -- **Performance**: O(1) filtering with minimal overhead - -### Level Hierarchy -``` -DEBUG (0) < INFO (1) < WARNING (2) < ERROR (3) < NONE (4) -``` ## Message Formatting ### Format Structure ``` -[timestamp] [LEVEL] [source]: message +[timestamp] [LEVEL] [source]: ``` -### Components - **Timestamp**: Configurable format using strftime - **Level**: String representation of LogLevel enum - **Source**: Optional component identifier @@ -229,11 +210,11 @@ DEBUG (0) < INFO (1) < WARNING (2) < ERROR (3) < NONE (4) ### Stream Failures - **File Open Errors**: Logged to console, file output disabled -- **Write Failures**: Silent failure, no cascading errors +- **Write Failures**: Silent failure, no exceptions - **Configuration Errors**: Invalid settings ignored with warnings ### Exception Safety -- **No Throws**: Public API designed to never throw +- **No Exceptions**: Public API designed to never throw exceptions - **Resource Safety**: RAII ensures proper resource cleanup - **State Consistency**: Mutex ensures consistent state even with errors @@ -243,8 +224,3 @@ DEBUG (0) < INFO (1) < WARNING (2) < ERROR (3) < NONE (4) - **Logging**: O(1) for level filtering, O(log_message_length) for formatting - **Configuration**: O(1) for most operations - **Thread Contention**: Minimal with short lock durations - -### Space Complexity -- **Memory Usage**: Fixed overhead independent of message count -- **File Growth**: Linear with number of logged messages -- **Buffer Management**: No internal buffering beyond stream buffers \ No newline at end of file diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index c90e253..b6bd463 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -70,9 +70,8 @@ The CI runs the following checks: ## Making Changes ### Code Style -- Use 4 spaces for indentation (no tabs) - Follow existing naming conventions: - - `snake_case` for functions and variables + - `camelCase` for functions and variables - `PascalCase` for classes and enums - `s_` prefix for static members - Keep lines under 120 characters @@ -81,75 +80,48 @@ The CI runs the following checks: ### Example Code Style ```cpp -// Good -namespace logcoe { - void setLogLevel(LogLevel level) { +namespace logcoe +{ + void setLogLevel(LogLevel level) + { std::lock_guard lock(s_mutex); s_logLevel = level; } } - -// Avoid -namespace logcoe{ -void setLogLevel(LogLevel level){ -std::lock_guard lock(s_mutex); -s_logLevel=level; -} ``` ### Testing Guidelines - Add tests for new features in appropriate test files - Ensure thread safety tests pass for concurrent operations - Test edge cases and error conditions -- Verify cross-platform behavior +- Verify cross-platform behavior through CI - Include performance considerations for new features ### Pull Request Process 1. Fork the repository -2. Create a feature branch (`git checkout -b feature/amazing-feature`) +2. Create a feature branch 3. Make your changes following the code style 4. Add/update tests as needed 5. Run tests locally and ensure they pass 6. Commit with clear, descriptive messages -7. Push to your fork (`git push origin feature/amazing-feature`) +7. Push to your fork 8. Open a Pull Request from your fork to the main repository ### Commit Messages -- Use present tense ("Add feature" not "Added feature") -- Keep first line under 50 characters -- Include detailed description if needed -- Reference issues if applicable (`Fixes #123`) +- Use prefix for PR title `[Subject]: ` +- PR description should describe the major changes in bullet-points +- Sqaushed commit title should be the PR title, and the message should be PR description Example: ``` -Add custom time format validation +[Time Format]: Add custom time format validation - Validate strftime format strings before applying - Return error for invalid formats - Add tests for format validation -- Fixes #45 ``` -## Platform-Specific Considerations - -### Windows -- Test with both MSVC and MinGW compilers -- Verify `localtime_s` usage for thread safety -- Check file path handling with different separators -- Test with different Windows versions if possible - -### Linux -- Test with GCC and Clang -- Verify `localtime_r` usage -- Check behavior with different distributions -- Test with different filesystem types - -### macOS -- Test with Apple Clang -- Verify compatibility across macOS versions -- Check behavior with case-sensitive/insensitive filesystems - ## Adding New Features When adding features: @@ -174,34 +146,6 @@ When adding features: - Add to API reference section - Update architecture docs if needed -### Example: Adding a New Feature - -```cpp -// 1. Add to public API (logcoe.hpp) -void setMaxFileSize(size_t maxBytes); - -// 2. Add to LoggerImpl (logcoe.cpp) -void LoggerImpl::setMaxFileSize(size_t maxBytes) { - std::lock_guard lock(s_mutex); - s_maxFileSize = maxBytes; - // Implementation details... -} - -// 3. Add wrapper function -namespace logcoe { - void setMaxFileSize(size_t maxBytes) { - LoggerImpl::setMaxFileSize(maxBytes); - } -} - -// 4. Add tests -TEST_F(LogcoeTest, MaxFileSizeRotation) { - logcoe::initialize(); - logcoe::setMaxFileSize(1024); - // Test implementation... -} -``` - ## Thread Safety Guidelines When modifying logcoe: @@ -211,62 +155,8 @@ When modifying logcoe: 3. **Avoid Nested Locks**: Current design uses single mutex to prevent deadlocks 4. **Test Concurrency**: Add thread safety tests for new features -### Thread Safety Checklist -- [ ] Function acquires `s_mutex` before accessing static state -- [ ] Lock scope covers all state modifications -- [ ] No function calls while holding lock that could block indefinitely -- [ ] Thread safety test added for new functionality - -## Debugging Tips - -### Common Issues -- **Build Failures**: Check C++17 compiler compatibility -- **Test Failures**: Verify file permissions and disk space -- **Thread Issues**: Use thread sanitizer when available -- **Cross-Platform**: Test path separators and line endings - -### Debugging Tools -```bash -# Build with debug symbols -cmake -DCMAKE_BUILD_TYPE=Debug .. - -# Run with thread sanitizer (GCC/Clang) -cmake -DCMAKE_CXX_FLAGS="-fsanitize=thread" .. - -# Run with address sanitizer -cmake -DCMAKE_CXX_FLAGS="-fsanitize=address" .. -``` - -### Debugging Logging Issues -- Use different log levels to isolate problems -- Check file permissions for file output issues -- Verify stream redirection for console output problems -- Test with single-threaded execution to isolate thread issues - -## Performance Considerations - -When contributing: - -- **Minimize Lock Contention**: Keep critical sections small -- **Avoid Dynamic Allocation**: Use static storage where possible -- **Efficient String Handling**: Consider string view usage for future optimizations -- **I/O Efficiency**: Batch operations when possible - -## Documentation Standards - -- Update README.md for user-facing changes -- Add inline comments for complex algorithms -- Update architecture documentation for internal changes -- Include examples for new API functions -- Maintain consistent documentation style - ## Questions? -Feel free to open an issue for: -- Bug reports with reproduction steps -- Feature requests with use cases -- Questions about the codebase -- Discussion about implementation approaches -- Help with development setup +Feel free to reach out at nircoe@gmail.com -We're here to help make contributing to logcoe as smooth as possible! \ No newline at end of file +I'm here to help make contributing to logcoe as smooth as possible! \ No newline at end of file diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index bb66739..566f429 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -18,25 +18,17 @@ ## Future Plans -- ⏳ Log file rotation based on size or time - ⏳ Asynchronous logging for high-performance scenarios -- ⏳ Structured logging (JSON/XML output formats) - ⏳ Custom log formatters and templates - ⏳ ANSI color support for console output -- ⏳ Network logging (UDP/TCP remote logging) -- ⏳ Syslog integration for Unix systems -- ⏳ Windows Event Log integration -- ⏳ Configuration file support - ⏳ Log filtering by source or pattern - ⏳ Multiple simultaneous log files -- ⏳ Lock-free logging implementation -- ⏳ Performance monitoring and statistics - ⏳ Log compression and archival -- ⏳ Plugin architecture for custom outputs ## Feature Requests Have an idea for logcoe? Please open an issue on GitHub with the "enhancement" label. +And of course, feel free to reach out at nircoe@gmail.com ## Versioning diff --git a/include/logcoe.hpp b/include/logcoe.hpp index 51b77df..6190f9f 100644 --- a/include/logcoe.hpp +++ b/include/logcoe.hpp @@ -13,7 +13,7 @@ namespace logcoe NONE }; - void initialize(LogLevel level = LogLevel::INFO, + void initialize(LogLevel level = LogLevel::DEBUG, const std::string &defaultSource = "", bool enableConsole = true, bool enableFile = false, diff --git a/src/logcoe.cpp b/src/logcoe.cpp index 157b420..b6d6fb7 100644 --- a/src/logcoe.cpp +++ b/src/logcoe.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using logcoe::LogLevel; @@ -64,7 +65,7 @@ namespace std::ostream *LoggerImpl::s_consoleStream = &std::cout; bool LoggerImpl::s_useFile = false; bool LoggerImpl::s_useConsole = true; - std::string LoggerImpl::s_timeFormat = "%Y-%m-%d_%H-%M-%S"; + std::string LoggerImpl::s_timeFormat = "%d/%m/%Y__%H:%M:%S"; std::string LoggerImpl::getCurrentTimestamp() { @@ -144,7 +145,7 @@ namespace { std::lock_guard lock(s_mutex); if(s_initialized) - throw std::runtime_error("logcoe is already initialized, please shutdown before initializing again"); + throw std::runtime_error("[logcoe] logcoe is already initialized, please shutdown before initializing again"); s_initialized = true; s_logLevel = level; @@ -165,15 +166,23 @@ namespace if (s_fileStream.is_open()) s_fileStream.close(); + std::filesystem::path filepath(s_filename); + + if (filepath.has_parent_path() && !std::filesystem::exists(filepath.parent_path())) + std::filesystem::create_directories(filepath.parent_path()); + + if (std::filesystem::exists(filepath) && std::filesystem::is_regular_file(filepath)) + std::filesystem::remove(filepath); + s_fileStream.open(s_filename); if (!s_fileStream.is_open()) { - writeToOutputs("ERROR: Failed to open log file: " + s_filename); + writeToOutputs("[logcoe] ERROR: Failed to open log file: " + s_filename); s_useFile = false; } } - writeToOutputs("Logger initialized with level: " + getLogLevelAsString(s_logLevel)); + writeToOutputs("[logcoe] Initialized, log level: " + getLogLevelAsString(s_logLevel)); } void LoggerImpl::shutdown() @@ -181,7 +190,7 @@ namespace std::lock_guard lock(s_mutex); if(!s_initialized) return; - std::string shutdownMessage = "Logger shutting down"; + std::string shutdownMessage = "[logcoe] shutting down"; writeToOutputs(shutdownMessage); flush(); @@ -233,7 +242,7 @@ namespace s_fileStream.open(s_filename); if (!s_fileStream.is_open()) { - writeToOutputs("ERROR: Failed to open log file: " + s_filename); + writeToOutputs("[logcoe] ERROR: Failed to open log file: " + s_filename); s_useFile = false; } @@ -287,7 +296,7 @@ namespace std::size_t result = std::strftime(buffer, sizeof(buffer), format.c_str(), &tm_now); if (result == 0) { - writeToOutputs("ERROR: Invalid time format provided: \"" + format + "\". Keeping the current format"); + writeToOutputs("[logcoe] ERROR: Invalid time format provided: \"" + format + "\". Keeping the current format"); return; } @@ -296,7 +305,7 @@ namespace catch (const std::exception &e) { std::stringstream message; - message << "ERROR: Exception while validating time format: " << e.what(); + message << "[logcoe] ERROR: Exception while validating time format: " << e.what(); writeToOutputs(message.str()); } } From 95631852d5db60d8b413c80cb36c0b69a7b51829 Mon Sep 17 00:00:00 2001 From: nircoe Date: Thu, 1 Jan 2026 20:31:44 +0200 Subject: [PATCH 2/3] Update testcoe to v0.1.1 to fix errors --- docs/ARCHITECTURE.md | 47 ++++++++++++++++++++++---------------------- tests/CMakeLists.txt | 2 +- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 89c6395..6957e32 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -7,30 +7,29 @@ logcoe is designed as a lightweight, thread-safe logging library that provides f ## Component Architecture ``` - ┌─────────────────────────────────────┐ - │ User Application │ - │ (Client Code) │ - └───────────────┬─────────────────────┘ - │ - ┌───────────────▼─────────────────────┐ - │ logcoe API │ - │ (Public Interface Functions) │ - └───────────────┬─────────────────────┘ - │ - ┌───────────────▼─────────────────────┐ - │ LoggerImpl │ - │ (Internal Implementation) │ - │ │ - │ ┌─────────────┬─────────────────┐ │ - │ │ Mutex │ Output Streams │ │ - │ │ Protection │ Management │ │ - │ └─────────────┴─────────────────┘ │ - │ │ - │ ┌─────────────┬─────────────────┐ │ - │ │ Log Level │ Timestamp │ │ - │ │ Filtering │ Formatting │ │ - │ └─────────────┴─────────────────┘ │ - └─────────────────────────────────────┘ + ┌─────────────────────────────────────┐ + │ User Application │ + │ (Client Code) │ + └──────────────────┬──────────────────┘ + │ + ┌──────────────────▼──────────────────┐ + │ logcoe API │ + │ (Public Interface Functions) │ + └──────────────────┬──────────────────┘ + │ + ┌──────────────────▼──────────────────┐ + │ LoggerImpl │ + │ (Internal Implementation) │ + │ │ + │ ┌─────────────┬─────────────────┐ │ + │ │ Mutex │ Output Streams │ │ + │ │ Protection │ Management │ │ + │ └─────────────┴─────────────────┘ │ + │ ┌─────────────┬─────────────────┐ │ + │ │ Log Level │ Timestamp │ │ + │ │ Filtering │ Formatting │ │ + │ └─────────────┴─────────────────┘ │ + └─────────────────────────────────────┘ ``` ## Core Components diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b96c420..40d24c1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,7 +7,7 @@ else() FetchContent_Declare( testcoe GIT_REPOSITORY https://github.com/nircoe/testcoe.git - GIT_TAG v0.1.0 + GIT_TAG v0.1.1 ) FetchContent_MakeAvailable(testcoe) endif() From 015e052c0ccfe16d689bdbb4260b25920a058ca9 Mon Sep 17 00:00:00 2001 From: nircoe Date: Thu, 1 Jan 2026 20:42:50 +0200 Subject: [PATCH 3/3] Update tests for default LogLevel::Debug --- tests/logcoe_test.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/logcoe_test.cpp b/tests/logcoe_test.cpp index e07af40..ca6f0fa 100644 --- a/tests/logcoe_test.cpp +++ b/tests/logcoe_test.cpp @@ -76,23 +76,23 @@ TEST_F(LogcoeTest, DefaultInitialization) { logcoe::initialize(); - EXPECT_EQ(logcoe::getLogLevel(), logcoe::LogLevel::INFO); + EXPECT_EQ(logcoe::getLogLevel(), logcoe::LogLevel::DEBUG); logcoe::info("Test info message"); } TEST_F(LogcoeTest, CustomInitialization) { - logcoe::initialize(logcoe::LogLevel::DEBUG, "", true, true, testFilename); + logcoe::initialize(logcoe::LogLevel::INFO, "", true, true, testFilename); - EXPECT_EQ(logcoe::getLogLevel(), logcoe::LogLevel::DEBUG); + EXPECT_EQ(logcoe::getLogLevel(), logcoe::LogLevel::INFO); - logcoe::debug("Test debug message"); + logcoe::info("Test debug message"); EXPECT_TRUE(std::filesystem::exists(testFilename)); std::string fileContent = readLogFile(testFilename); - EXPECT_TRUE(matchesLogPattern(fileContent, logcoe::LogLevel::DEBUG, "Test debug message")); + EXPECT_TRUE(matchesLogPattern(fileContent, logcoe::LogLevel::INFO, "Test debug message")); } TEST_F(LogcoeTest, LogLevelFiltering)