From 380cbeb2f2e84917200c9d9949303e077d64ed85 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Thu, 30 Jul 2026 13:19:24 +0200 Subject: [PATCH 1/3] Add overload to prevent use of fmt for logs without args Signed-off-by: Konrad Breitsprecher --- SilKit/IntegrationTests/ITest_Logging.cpp | 152 ++++++++++++++++++ .../source/services/logging/LoggerMessage.hpp | 10 ++ 2 files changed, 162 insertions(+) create mode 100644 SilKit/IntegrationTests/ITest_Logging.cpp diff --git a/SilKit/IntegrationTests/ITest_Logging.cpp b/SilKit/IntegrationTests/ITest_Logging.cpp new file mode 100644 index 000000000..c9f2630e5 --- /dev/null +++ b/SilKit/IntegrationTests/ITest_Logging.cpp @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2026 Vector Informatik GmbH +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include +#include +#include + +#include "silkit/SilKit.hpp" +#include "silkit/services/flexray/string_utils.hpp" +#include "silkit/services/logging/ILogger.hpp" +#include "silkit/vendor/CreateSilKitRegistry.hpp" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + + +namespace { + +namespace fs = std::filesystem; + +using namespace std::chrono_literals; +using namespace SilKit::Services::Logging; + +const std::string participantName{"LoggingParticipant"}; +const std::string simpleLogName{"ITest_Logging_Simple"}; +const std::string jsonLogName{"ITest_Logging_Json"}; + +// The demos render bus events into their log messages, and the SIL Kit stream operators use braces: +// "Received Flexray::FlexraySymbolTransmitEvent{pattern=Wus, channel=A @ 12.796ms}". Such a message must +// reach the sinks verbatim. If it is handed on as a fmt format string instead, '{pattern=' is parsed as a +// replacement field and fmt::format throws - which used to surface as a SilKitError inside the user's +// event handler. +auto MakeBracedMessage() -> std::string +{ + SilKit::Services::Flexray::FlexraySymbolTransmitEvent symbol{}; + symbol.timestamp = 12796us; + symbol.channel = SilKit::Services::Flexray::FlexrayChannel::A; + symbol.pattern = SilKit::Services::Flexray::FlexraySymbolPattern::Wus; + + std::stringstream ss; + ss << "Received " << symbol; + return ss.str(); +} + +auto MakeParticipantConfiguration() -> std::string +{ + std::stringstream config; + config << R"({"Logging":{"FlushLevel":"Trace","Sinks":[)" + << R"({"Type":"File","Format":"Simple","Level":"Trace","LogName":")" << simpleLogName << R"("},)" + << R"({"Type":"File","Format":"Json","Level":"Trace","LogName":")" << jsonLogName << R"("}]}})"; + return config.str(); +} + +class ITest_Logging : public testing::Test +{ +protected: + void SetUp() override + { + RemoveLogFiles(); + } + + void TearDown() override + { + RemoveLogFiles(); + } + + // The sinks append a participant name and a timestamp to the configured log name, so the files can only + // be identified by their prefix. + static auto FindLogFiles(const std::string& logName) -> std::vector + { + const auto prefix = logName + "_"; + + std::vector logFiles; + for (const auto& entry : fs::directory_iterator{fs::current_path()}) + { + if (entry.is_regular_file() && entry.path().filename().string().compare(0, prefix.size(), prefix) == 0) + { + logFiles.push_back(entry.path()); + } + } + return logFiles; + } + + static void RemoveLogFiles() + { + for (const auto& logName : {simpleLogName, jsonLogName}) + { + for (const auto& logFile : FindLogFiles(logName)) + { + std::error_code ec; + fs::remove(logFile, ec); + } + } + } + + static auto ReadLogFile(const std::string& logName) -> std::string + { + const auto logFiles = FindLogFiles(logName); + EXPECT_EQ(logFiles.size(), 1u) << "Expected exactly one log file for '" << logName << "'"; + if (logFiles.size() != 1u) + { + return {}; + } + + std::ifstream stream{logFiles.front()}; + EXPECT_TRUE(stream.good()) << "Cannot open " << logFiles.front().string(); + + std::stringstream contents; + contents << stream.rdbuf(); + return contents.str(); + } +}; + +TEST_F(ITest_Logging, log_message_with_braces_is_not_parsed_as_format_string) +{ + const auto bracedMessage = MakeBracedMessage(); + ASSERT_THAT(bracedMessage, testing::HasSubstr("{pattern=Wus, channel=A @ 12.796ms}")); + + // An unbalanced brace is the degenerate case: fmt cannot even recover by treating the field as named. + const std::string unbalancedMessage{"A lone opening brace { and a lone closing brace }"}; + + { + auto registryConfig = SilKit::Config::ParticipantConfigurationFromString(""); + auto registry = SilKit::Vendor::Vector::CreateSilKitRegistry(registryConfig); + const auto registryUri = registry->StartListening("silkit://127.0.0.1:0"); + + auto participantConfig = SilKit::Config::ParticipantConfigurationFromString(MakeParticipantConfiguration()); + auto participant = SilKit::CreateParticipant(participantConfig, participantName, registryUri); + + auto* logger = participant->GetLogger(); + ASSERT_NE(logger, nullptr); + + EXPECT_NO_THROW(logger->Info(bracedMessage)); + EXPECT_NO_THROW(logger->Log(Level::Warn, bracedMessage)); + EXPECT_NO_THROW(logger->Error(unbalancedMessage)); + } + // The participant is gone, so the file sinks are flushed and closed. + + const auto simpleLog = ReadLogFile(simpleLogName); + EXPECT_THAT(simpleLog, testing::HasSubstr(bracedMessage)); + EXPECT_THAT(simpleLog, testing::HasSubstr(unbalancedMessage)); + + const auto jsonLog = ReadLogFile(jsonLogName); + EXPECT_THAT(jsonLog, testing::HasSubstr(bracedMessage)); + EXPECT_THAT(jsonLog, testing::HasSubstr(unbalancedMessage)); +} + +} // namespace diff --git a/SilKit/source/services/logging/LoggerMessage.hpp b/SilKit/source/services/logging/LoggerMessage.hpp index c3b62b72b..b9e0593a9 100644 --- a/SilKit/source/services/logging/LoggerMessage.hpp +++ b/SilKit/source/services/logging/LoggerMessage.hpp @@ -73,6 +73,16 @@ class LoggerMessage return *this; } + LoggerMessage& SetMessage(std::string msg) + { + if (_logger->GetLogLevel() <= _level) + { + _msg = std::move(msg); + } + + return *this; + } + auto SetTopic(Topic topic) -> LoggerMessage& { _topic = topic; From c7fc2af6f5589991321da1121300475b6e6affe4 Mon Sep 17 00:00:00 2001 From: Konrad Breitsprecher Date: Fri, 31 Jul 2026 08:46:28 +0200 Subject: [PATCH 2/3] Use YAML config; Update changelog Signed-off-by: Konrad Breitsprecher --- SilKit/IntegrationTests/CMakeLists.txt | 4 ++++ SilKit/IntegrationTests/ITest_Logging.cpp | 21 +++++++++++++++------ docs/changelog/versions/latest.md | 3 ++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/SilKit/IntegrationTests/CMakeLists.txt b/SilKit/IntegrationTests/CMakeLists.txt index 062939788..f53267d65 100644 --- a/SilKit/IntegrationTests/CMakeLists.txt +++ b/SilKit/IntegrationTests/CMakeLists.txt @@ -66,6 +66,10 @@ add_silkit_test_to_executable(SilKitIntegrationTests SOURCES ITest_LabelsMatching.cpp ) +add_silkit_test_to_executable(SilKitIntegrationTests + SOURCES ITest_Logging.cpp +) + add_silkit_test_to_executable(SilKitInternalIntegrationTests SOURCES ITest_Internals_TargetedMessaging.cpp ) diff --git a/SilKit/IntegrationTests/ITest_Logging.cpp b/SilKit/IntegrationTests/ITest_Logging.cpp index c9f2630e5..b3f9cd909 100644 --- a/SilKit/IntegrationTests/ITest_Logging.cpp +++ b/SilKit/IntegrationTests/ITest_Logging.cpp @@ -46,13 +46,22 @@ auto MakeBracedMessage() -> std::string return ss.str(); } -auto MakeParticipantConfiguration() -> std::string +auto MakeParticipantConfiguration() -> const std::string { - std::stringstream config; - config << R"({"Logging":{"FlushLevel":"Trace","Sinks":[)" - << R"({"Type":"File","Format":"Simple","Level":"Trace","LogName":")" << simpleLogName << R"("},)" - << R"({"Type":"File","Format":"Json","Level":"Trace","LogName":")" << jsonLogName << R"("}]}})"; - return config.str(); + std::string config = R"( +Logging: + FlushLevel: Trace + Sinks: + - Type: File + Level: Trace + Format: Simple + LogName: ITest_Logging_Simple + - Type: File + Level: Trace + Format: Json + LogName: ITest_Logging_Json + )"; + return config; } class ITest_Logging : public testing::Test diff --git a/docs/changelog/versions/latest.md b/docs/changelog/versions/latest.md index c344f4f3e..db68c2c85 100644 --- a/docs/changelog/versions/latest.md +++ b/docs/changelog/versions/latest.md @@ -1,4 +1,4 @@ -# [5.0.7] - 2026-07-29 +# [5.0.7] - 2026-07-31 ## Added @@ -15,6 +15,7 @@ simulation step sizes, aligning each simulation step to the minimal step among all synchronized participants. Tri-state: `true` requests it for the whole simulation, `false` opts out, and leaving it unset follows the network. Off by default. +- `logging`: Fixes a bug where some log messages (e.g., user-level log messages) were passed to fmt and caused exceptions when placeholder characters were present. These log messages are no longer passed to fmt. ## Changed From e1e8b38969c4a0d67b13b71a7d8d9e4e0f50cd5c Mon Sep 17 00:00:00 2001 From: KonradBreitsprecherBkd <117755498+KonradBreitsprecherBkd@users.noreply.github.com> Date: Fri, 31 Jul 2026 08:48:04 +0200 Subject: [PATCH 3/3] Remove blank line Signed-off-by: KonradBreitsprecherBkd <117755498+KonradBreitsprecherBkd@users.noreply.github.com> --- SilKit/source/services/logging/LoggerMessage.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/SilKit/source/services/logging/LoggerMessage.hpp b/SilKit/source/services/logging/LoggerMessage.hpp index b9e0593a9..9236a488d 100644 --- a/SilKit/source/services/logging/LoggerMessage.hpp +++ b/SilKit/source/services/logging/LoggerMessage.hpp @@ -79,7 +79,6 @@ class LoggerMessage { _msg = std::move(msg); } - return *this; }