From 747d03472bb96867663ead843916a58baceea450 Mon Sep 17 00:00:00 2001 From: Bharath Tirunagaru Date: Thu, 20 Aug 2026 22:34:35 -0700 Subject: [PATCH] feat(mw/log): add Linux syslog backend for LogMode::kSystem Adds a Linux syslog mw::log backend that fills the LogMode::kSystem slot on Linux (HGY aarch64 and x86 host), mirroring the existing QNX slog backend exactly. slog is target_compatible_with os:qnx and this new backend is target_compatible_with os:linux, so exactly one compiles per build and both can safely reuse the same kSystem slot without any LogMode/dispatch changes. - score/mw/log/detail/syslog/syslog_backend.{h,cpp}: SyslogBackend, a Backend implementation whose FlushSlot formats "appid,ctxid: payload" and forwards to score::os::Syslog::syslog() at a severity mapped from mw::log's LogLevel (kFatal->LOG_CRIT, kError->LOG_ERR, kWarn->LOG_WARNING, kInfo->LOG_INFO, kDebug/kVerbose->LOG_DEBUG); Init() calls openlog(app_id, LOG_PID|LOG_NDELAY, LOG_USER). - score/mw/log/detail/syslog/syslog_recorder_factory.{h,cpp}: CRTP SyslogRecorderFactory wiring a TextRecorder over SyslogBackend, using score::os::Syslog::Default() for the real OS wrapper. - score/mw/log/detail/syslog/{syslog_backend_test, syslog_recorder_factory_test}.cpp: unit tests against @score_baselibs//score/os/mocklib:syslog_mock. - score/mw/log/backend/syslog_registrant.cpp: registers CreateSyslogRecorder against LogMode::kSystem via BackendRegistrant, mirroring slog_registrant.cpp. - score/mw/log/backend/BUILD: new syslog cc_library (target_compatible_with os:linux, alwayslink), plus the detail/syslog BUILD for syslog_backend/syslog_recorder_factory. Depends on the companion score::os::Syslog OS wrapper contributed to the baselibs component (@score_baselibs//score/os:syslog). Signed-off-by: Bharath Tirunagaru --- score/mw/log/backend/BUILD | 16 + score/mw/log/backend/syslog_registrant.cpp | 62 ++++ score/mw/log/detail/syslog/BUILD | 114 +++++++ score/mw/log/detail/syslog/syslog_backend.cpp | 192 ++++++++++++ score/mw/log/detail/syslog/syslog_backend.h | 58 ++++ .../log/detail/syslog/syslog_backend_test.cpp | 289 ++++++++++++++++++ .../detail/syslog/syslog_recorder_factory.cpp | 44 +++ .../detail/syslog/syslog_recorder_factory.h | 44 +++ .../syslog/syslog_recorder_factory_test.cpp | 50 +++ 9 files changed, 869 insertions(+) create mode 100644 score/mw/log/backend/syslog_registrant.cpp create mode 100644 score/mw/log/detail/syslog/BUILD create mode 100644 score/mw/log/detail/syslog/syslog_backend.cpp create mode 100644 score/mw/log/detail/syslog/syslog_backend.h create mode 100644 score/mw/log/detail/syslog/syslog_backend_test.cpp create mode 100644 score/mw/log/detail/syslog/syslog_recorder_factory.cpp create mode 100644 score/mw/log/detail/syslog/syslog_recorder_factory.h create mode 100644 score/mw/log/detail/syslog/syslog_recorder_factory_test.cpp diff --git a/score/mw/log/backend/BUILD b/score/mw/log/backend/BUILD index 846c781f..929d9f65 100644 --- a/score/mw/log/backend/BUILD +++ b/score/mw/log/backend/BUILD @@ -95,6 +95,22 @@ cc_library( alwayslink = True, ) +# Plugin: Linux syslog(3) System Logging +# Automatically included on Linux (HGY aarch64 / x86 host) builds. +cc_library( + name = "syslog", + srcs = ["syslog_registrant.cpp"], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + target_compatible_with = ["@platforms//os:linux"], + visibility = ["//visibility:public"], # platform_only + deps = [ + "//score/mw/log/detail/syslog:syslog_recorder_factory", + "@score_baselibs//score/mw/log:minimal", + ], + alwayslink = True, +) + # Plugin: Custom user-provided Logging backend # Opt-in. Build with --@score_logging//score/mw/log/flags:KCustom_Logging=True # and --@score_logging//score/mw/log/flags:custom_recorder_impl=//your:target. diff --git a/score/mw/log/backend/syslog_registrant.cpp b/score/mw/log/backend/syslog_registrant.cpp new file mode 100644 index 00000000..0643e415 --- /dev/null +++ b/score/mw/log/backend/syslog_registrant.cpp @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/log/backend_table.h" +#include "score/mw/log/detail/syslog/syslog_recorder_factory.h" + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ +namespace +{ + +std::unique_ptr CreateSyslogRecorder(const Configuration& config, + score::cpp::pmr::memory_resource* memory_resource) +{ + SyslogRecorderFactory factory; + return factory.CreateLogRecorder(config, memory_resource); +} + +/* +Deviation from Rule A3-3-2: +- Static and thread-local objects shall be constant-initialized. +Justification: +- BackendRegistrant constructor executes during dynamic initialization to write a function + pointer into gBackendCreators[]. The target array is constant-initialized (zero-init + at load time), so it is valid before this constructor runs. The registrant struct itself + is trivially destructible. This follows the established pattern used by Runtime::Instance(). +Deviation from Rule M0-1-3: +- A project shall not contain unused variables. +Deviation from Rule M0-1-9: +- There shall be no dead code. +Justification: +- The variable IS used via its constructor's side effect during static initialization. + BackendRegistrant's constructor registers the CreateSyslogRecorder function pointer into + gBackendCreators[] at program startup. The variable itself doesn't need to be referenced + elsewhere - its purpose is fulfilled by the constructor's execution. This is an intentional + static registration pattern. +*/ +// coverity[autosar_cpp14_a3_3_2_violation] See above +// coverity[autosar_cpp14_m0_1_3_violation] See above +// coverity[autosar_cpp14_m0_1_9_violation] See above +const BackendRegistrant kSyslogRegistrant{LogMode::kSystem, &CreateSyslogRecorder}; + +} // namespace +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score diff --git a/score/mw/log/detail/syslog/BUILD b/score/mw/log/detail/syslog/BUILD new file mode 100644 index 00000000..24eb8f47 --- /dev/null +++ b/score/mw/log/detail/syslog/BUILD @@ -0,0 +1,114 @@ +# ******************************************************************************* +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") +load("//:score/mw/common_features.bzl", "COMPILER_WARNING_FEATURES") + +cc_library( + name = "syslog_backend", + srcs = [ + "syslog_backend.cpp", + ], + hdrs = [ + "syslog_backend.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + target_compatible_with = ["@platforms//os:linux"], + visibility = [ + "//score/mw/log:__subpackages__", + "@score_baselibs//score/mw/log:__subpackages__", + ], + deps = [ + "@score_baselibs//score/mw/log/detail:backend_interface", + "@score_baselibs//score/mw/log/detail:circular_allocator", + "@score_baselibs//score/mw/log/detail:initialization_reporter", + "@score_baselibs//score/os:syslog", + ], +) + +cc_library( + name = "syslog", + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + target_compatible_with = ["@platforms//os:linux"], + visibility = [ + "//score/mw/log:__subpackages__", + "@score_baselibs//score/mw/log:__subpackages__", + ], + deps = [ + ":syslog_backend", + ":syslog_recorder_factory", + ], +) + +cc_library( + name = "syslog_recorder_factory", + srcs = [ + "syslog_recorder_factory.cpp", + ], + hdrs = [ + "syslog_recorder_factory.h", + ], + features = COMPILER_WARNING_FEATURES, + tags = ["FFI"], + target_compatible_with = ["@platforms//os:linux"], + visibility = [ + "//score/mw/log:__subpackages__", + "@score_baselibs//score/mw/log:__subpackages__", + ], + deps = [ + ":syslog_backend", + "@score_baselibs//score/mw/log/detail:log_recorder_factory", + "@score_baselibs//score/mw/log/detail/text_recorder", + ], +) + +cc_test( + name = "syslog_recorder_factory_test", + srcs = [ + "syslog_recorder_factory_test.cpp", + ], + features = [ + "aborts_upon_exception", + ], + tags = ["unit"], + target_compatible_with = ["@platforms//os:linux"], + deps = [ + ":syslog_recorder_factory", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score_baselibs//score/language/futurecpp:futurecpp_test_support", + "@score_baselibs//score/mw/log/configuration", + ], +) + +cc_test( + name = "syslog_backend_test", + srcs = [ + "syslog_backend_test.cpp", + ], + features = [ + "aborts_upon_exception", + ], + tags = ["unit"], + target_compatible_with = ["@platforms//os:linux"], + deps = [ + ":syslog", + "@googletest//:gtest", + "@googletest//:gtest_main", + "@score_baselibs//score/language/futurecpp:futurecpp_test_support", + "@score_baselibs//score/mw/log/configuration", + "@score_baselibs//score/os/mocklib:syslog_mock", + ], +) diff --git a/score/mw/log/detail/syslog/syslog_backend.cpp b/score/mw/log/detail/syslog/syslog_backend.cpp new file mode 100644 index 00000000..2367bafd --- /dev/null +++ b/score/mw/log/detail/syslog/syslog_backend.cpp @@ -0,0 +1,192 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/log/detail/syslog/syslog_backend.h" + +#include "score/mw/log/detail/error.h" +#include "score/mw/log/detail/initialization_reporter.h" + +#include +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ + +namespace +{ + +std::size_t CheckTheMaxCapacity(const std::size_t capacity) noexcept +{ + const auto is_within_max_capacity = (capacity <= std::numeric_limits::max()); + if (is_within_max_capacity) + { + return capacity; + } + else + { + return static_cast(std::numeric_limits::max()); + } +} + +// syslog(3) priorities from . kInvalid marks a level that must not be emitted +// (e.g. kOff / out-of-range), mirroring slog's kInvalid handling but skipping the emit. +enum class SyslogPriority : std::int32_t +{ + kCrit = LOG_CRIT, + kErr = LOG_ERR, + kWarning = LOG_WARNING, + kInfo = LOG_INFO, + kDebug = LOG_DEBUG, + kInvalid = -1 +}; + +constexpr SyslogPriority ConvertMwLogLevelToSyslogPriority(const LogLevel level) +{ + SyslogPriority priority = SyslogPriority::kInvalid; + switch (level) + { + case LogLevel::kVerbose: + priority = SyslogPriority::kDebug; + break; + case LogLevel::kDebug: + priority = SyslogPriority::kDebug; + break; + case LogLevel::kInfo: + priority = SyslogPriority::kInfo; + break; + case LogLevel::kWarn: + priority = SyslogPriority::kWarning; + break; + case LogLevel::kError: + priority = SyslogPriority::kErr; + break; + case LogLevel::kFatal: + priority = SyslogPriority::kCrit; + break; + case LogLevel::kOff: + default: + priority = SyslogPriority::kInvalid; + break; + } + return priority; +} + +constexpr SyslogPriority ToSyslogPriority(const LogLevel log_level) noexcept +{ + if (log_level <= GetMaxLogLevelValue()) + { + return ConvertMwLogLevelToSyslogPriority(log_level); + } + else + { + return SyslogPriority::kInvalid; + } +} + +} // namespace + +SyslogBackend::SyslogBackend(const std::size_t number_of_slots, + const LogRecord& initial_slot_value, + const std::string_view app_id, + score::cpp::pmr::unique_ptr syslog_instance) noexcept + : Backend::Backend(), + app_id_{app_id.data(), app_id.size()}, + buffer_{CheckTheMaxCapacity(number_of_slots), initial_slot_value}, + syslog_instance_{std::move(syslog_instance)} +{ + Init(); +} + +score::cpp::optional SyslogBackend::ReserveSlot() noexcept +{ + const auto& slot = buffer_.AcquireSlotToWrite(); + if (slot.has_value()) + { + if (slot.value() < std::numeric_limits::max()) // LCOV_EXCL_BR_LINE: As it always true case,we can't + // control slot.value() it is received from AcquireSlotToWrite() function + // which wraps around and resulting in a value within the valid range. + { + // CircularAllocator has capacity limited by CheckTheMaxCapacity thus the cast is valid: + // We intentionally static cast to SlotIndex(uint8_t) to limit memory allocations + // to the required levels during startup, since there is no need to support slots greater + // than uint8 as per the current system needs. + // coverity[autosar_cpp14_a4_7_1_violation] + return SlotHandle{static_cast(slot.value())}; + } + } + return {}; +} + +LogRecord& SyslogBackend::GetLogRecord(const SlotHandle& slot) noexcept +{ + // static cast from std::uint8_t to std::size_t + return buffer_.GetUnderlyingBufferFor(static_cast(slot.GetSlotOfSelectedRecorder())); +} + +void SyslogBackend::FlushSlot(const SlotHandle& slot) noexcept +{ + // static cast from std::uint8_t to std::size_t + auto& log_entry = + buffer_.GetUnderlyingBufferFor(static_cast(slot.GetSlotOfSelectedRecorder())).GetLogEntry(); + + constexpr std::size_t kMaxIdLength{4U}; + + // Cast appid length to int32 without overflow. + const std::int32_t app_id_length = static_cast(std::min(kMaxIdLength, app_id_.size())); + + // Cast context length to int32 without overflow. + const std::int32_t ctx_id_length = + static_cast(std::min(kMaxIdLength, log_entry.ctx_id.GetStringView().size())); + + // Cast payload size to int32_t without overflow. + const std::int32_t payload_length = static_cast( + std::min(static_cast(std::numeric_limits::max()), log_entry.payload.size())); + + const auto priority = ToSyslogPriority(log_entry.log_level); + if (priority != SyslogPriority::kInvalid) + { + // Log message with appid and ctxid. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) no available alternative for syslog + syslog_instance_->syslog(static_cast(priority), + "%.*s,%.*s: %.*s", + app_id_length, + app_id_.c_str(), + ctx_id_length, + // above variable `ctx_id_length` contains corresponding length information + // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage) justified above + log_entry.ctx_id.GetStringView().data(), + payload_length, + log_entry.payload.data()); + } + + buffer_.ReleaseSlot(static_cast(slot.GetSlotOfSelectedRecorder())); +} + +void SyslogBackend::Init() noexcept +{ + // glibc openlog(3) stores the `ident` pointer (it does not copy the string); app_id_ is a + // member and outlives every syslog() call, so passing its c_str() is safe. + syslog_instance_->openlog(app_id_.c_str(), LOG_PID | LOG_NDELAY, LOG_USER); +} + +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score diff --git a/score/mw/log/detail/syslog/syslog_backend.h b/score/mw/log/detail/syslog/syslog_backend.h new file mode 100644 index 00000000..644b2475 --- /dev/null +++ b/score/mw/log/detail/syslog/syslog_backend.h @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_LOG_DETAIL_SYSLOG_SYSLOG_BACKEND_H +#define SCORE_MW_LOG_DETAIL_SYSLOG_SYSLOG_BACKEND_H + +#include "score/os/syslog.h" +#include "score/mw/log/detail/backend.h" +#include "score/mw/log/detail/circular_allocator.h" + +#include +#include +#include + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ + +class SyslogBackend final : public Backend +{ + public: + explicit SyslogBackend(const std::size_t number_of_slots, + const LogRecord& initial_slot_value, + const std::string_view app_id, + score::cpp::pmr::unique_ptr syslog_instance) noexcept; + + score::cpp::optional ReserveSlot() noexcept override; + void FlushSlot(const SlotHandle& slot) noexcept override; + LogRecord& GetLogRecord(const SlotHandle& slot) noexcept override; + + private: + void Init() noexcept; + + std::string app_id_; + CircularAllocator buffer_; + score::cpp::pmr::unique_ptr syslog_instance_; +}; + +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score + +#endif // SCORE_MW_LOG_DETAIL_SYSLOG_SYSLOG_BACKEND_H diff --git a/score/mw/log/detail/syslog/syslog_backend_test.cpp b/score/mw/log/detail/syslog/syslog_backend_test.cpp new file mode 100644 index 00000000..827d7cb7 --- /dev/null +++ b/score/mw/log/detail/syslog/syslog_backend_test.cpp @@ -0,0 +1,289 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "gtest/gtest.h" + +#include "score/os/mocklib/mock_syslog.h" +#include "score/mw/log/configuration/configuration.h" +#include "score/mw/log/detail/syslog/syslog_backend.h" + +#include "score/assert_support.hpp" + +#include + +#include + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ +namespace +{ + +using ::testing::_; +using ::testing::StrEq; + +const std::string kDefaultApp{"a1"}; +const std::string kDefaultContext{"c1"}; +const std::string kDefaultMessage{"default message"}; + +struct SyslogBackendFixture : ::testing::Test +{ + void SetUp() override + { + syslog_mock_ = score::cpp::pmr::make_unique(score::cpp::pmr::get_default_resource()); + syslog_mock_raw_ptr_ = syslog_mock_.get(); + } + + protected: + void SimulateLogging(LogLevel log_level, + const std::string& app_id = kDefaultApp, + const std::string& ctx_id = kDefaultContext, + const std::string& message = kDefaultMessage) + { + SyslogBackend backend(config_.GetNumberOfSlots(), log_record_, app_id, std::move(syslog_mock_)); + + auto slot = backend.ReserveSlot(); + EXPECT_TRUE(slot.has_value()); + + auto& payload = backend.GetLogRecord(slot.value()); + auto& log_entry = payload.GetLogEntry(); + log_entry.ctx_id = LoggingIdentifier(std::string_view(ctx_id)); + log_entry.log_level = log_level; + log_entry.payload = ByteVector(message.begin(), message.end()); + + backend.FlushSlot(slot.value()); + } + + LogRecord log_record_{}; + Configuration config_{}; + score::cpp::pmr::unique_ptr syslog_mock_{}; + score::os::MockSyslog* syslog_mock_raw_ptr_; +}; + +TEST_F(SyslogBackendFixture, SyslogOpenlog) +{ + RecordProperty("Description", "Verifies the backend opens the syslog connection on construction."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)); + + SyslogBackend unit(config_.GetNumberOfSlots(), log_record_, config_.GetAppId(), std::move(syslog_mock_)); +} + +TEST_F(SyslogBackendFixture, SyslogOpenlogWithCapacityBiggerThanTheMaximum) +{ + RecordProperty("Description", "Verifies backend construction with slots' capacity bigger than the maximum."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + auto capacity = std::numeric_limits::max() + 1; + config_.SetNumberOfSlots(capacity); + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)); + + SyslogBackend unit(config_.GetNumberOfSlots(), log_record_, config_.GetAppId(), std::move(syslog_mock_)); +} + +TEST_F(SyslogBackendFixture, ReserveSlotShouldAcquireSlot) +{ + RecordProperty("Description", "Verifies the ability of reserving slot."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)); + + SyslogBackend unit(config_.GetNumberOfSlots(), log_record_, config_.GetAppId(), std::move(syslog_mock_)); + + auto slot = unit.ReserveSlot(); + EXPECT_TRUE(slot.has_value()); +} + +TEST_F(SyslogBackendFixture, LevelOffProducesNoEmit) +{ + RecordProperty("Description", "A kOff level shall not be emitted to syslog."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(_, _)).Times(0); + + SimulateLogging(LogLevel::kOff); +} + +TEST_F(SyslogBackendFixture, FatalLog) +{ + RecordProperty("Description", "Verifies the ability of logging fatal message."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_CRIT, _)).Times(1); + + SimulateLogging(LogLevel::kFatal); +} + +TEST_F(SyslogBackendFixture, ErrorLog) +{ + RecordProperty("Description", "Verifies the ability of logging error message."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_ERR, _)).Times(1); + + SimulateLogging(LogLevel::kError); +} + +TEST_F(SyslogBackendFixture, WarningLog) +{ + RecordProperty("Description", "Verifies the ability of logging warning message."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_WARNING, _)).Times(1); + + SimulateLogging(LogLevel::kWarn); +} + +TEST_F(SyslogBackendFixture, InfoLog) +{ + RecordProperty("Description", "Verifies the ability of logging info message."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_INFO, _)).Times(1); + + SimulateLogging(LogLevel::kInfo); +} + +TEST_F(SyslogBackendFixture, DebugLog) +{ + RecordProperty("Description", "Verifies the ability of logging debug message."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_DEBUG, _)).Times(1); + + SimulateLogging(LogLevel::kDebug); +} + +TEST_F(SyslogBackendFixture, VerboseLog) +{ + RecordProperty("Description", "Verifies verbose maps to LOG_DEBUG (syslog has no separate verbose level)."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_DEBUG, _)).Times(1); + + SimulateLogging(LogLevel::kVerbose); +} + +TEST_F(SyslogBackendFixture, MessageShouldContainAppCtxPayload) +{ + RecordProperty("Description", "Verifies log message contains application id, context id and payload."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_DEBUG, StrEq("MyAp,MyCt: Hello World"))).Times(1); + + SimulateLogging(LogLevel::kVerbose, "MyAp", "MyCt", "Hello World"); +} + +TEST_F(SyslogBackendFixture, BackendShouldHandleEmptyPayload) +{ + RecordProperty("Description", "Verifies the ability of the backend of handling empty payload."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_DEBUG, StrEq(",: "))).Times(1); + + SimulateLogging(LogLevel::kVerbose, "", "", ""); +} + +TEST_F(SyslogBackendFixture, LongIdentifiersShouldBeCropped) +{ + RecordProperty("Description", + "Verifies that the application or context IDs should be cropped if it exceeds 4 characters length."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(LOG_DEBUG, StrEq("1234,4567: "))).Times(1); + + SimulateLogging(LogLevel::kVerbose, "12345", "45678", ""); +} + +TEST_F(SyslogBackendFixture, NoSlotAvailableShouldReturnEmptyHandle) +{ + RecordProperty("Description", "Verifies returning empty handler in case of no available slots."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + SyslogBackend backend(config_.GetNumberOfSlots(), log_record_, config_.GetAppId(), std::move(syslog_mock_)); + + for (std::size_t i = 0; i < config_.GetNumberOfSlots(); ++i) + { + EXPECT_TRUE(backend.ReserveSlot().has_value()); + } + + EXPECT_FALSE(backend.ReserveSlot().has_value()); +} + +TEST_F(SyslogBackendFixture, TooMuchSlotsRequestedShallBeTruncated) +{ + RecordProperty("Description", "Verifies requesting too much slots shall be truncated."); + RecordProperty("TestingTechnique", "Requirements-based test"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + const auto kMaxSlotCount = std::numeric_limits::max(); + const std::size_t kSlotNumberOverflow = static_cast(kMaxSlotCount) + 2UL; + + SyslogBackend backend(kSlotNumberOverflow, log_record_, config_.GetAppId(), std::move(syslog_mock_)); + + for (std::size_t i = 0; i < kMaxSlotCount; ++i) + { + EXPECT_TRUE(backend.ReserveSlot().has_value()); + } + + EXPECT_FALSE(backend.ReserveSlot().has_value()); +} + +TEST_F(SyslogBackendFixture, ToSyslogPriorityInvalidLevel) +{ + RecordProperty("Description", "Tests ToSyslogPriority with an invalid log level, which must not be emitted."); + RecordProperty("TestingTechnique", "Boundary value analysis"); + RecordProperty("DerivationTechnique", "Analysis of requirements"); + + EXPECT_CALL(*syslog_mock_raw_ptr_, openlog(_, _, _)).Times(1); + EXPECT_CALL(*syslog_mock_raw_ptr_, MockedSyslog(_, _)).Times(0); + + // Pass a log level greater than GetMaxLogLevelValue() to trigger the `else` branch (kInvalid, no emit). + LogLevel invalid_log_level = static_cast(static_cast(LogLevel::kVerbose) + 1); + SimulateLogging(invalid_log_level); +} + +} // namespace +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score diff --git a/score/mw/log/detail/syslog/syslog_recorder_factory.cpp b/score/mw/log/detail/syslog/syslog_recorder_factory.cpp new file mode 100644 index 00000000..1f06ffb1 --- /dev/null +++ b/score/mw/log/detail/syslog/syslog_recorder_factory.cpp @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#include "score/mw/log/detail/syslog/syslog_recorder_factory.h" + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ +std::unique_ptr SyslogRecorderFactory::CreateConcreteLogRecorder( + const Configuration& config, + score::cpp::pmr::memory_resource* memory_resource) +{ + auto backend = CreateSystemBackend(config, memory_resource); // LCOV_EXCL_LINE : no branches to test + constexpr bool kCheckLogLevelForConsole = false; + return std::make_unique(config, std::move(backend), kCheckLogLevelForConsole); +} + +std::unique_ptr SyslogRecorderFactory::CreateSystemBackend(const Configuration& config, + score::cpp::pmr::memory_resource* memory_resource) +{ + return std::make_unique(config.GetNumberOfSlots(), + LogRecord{config.GetSlotSizeInBytes()}, + config.GetAppId(), + score::os::Syslog::Default(memory_resource)); +} + +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score diff --git a/score/mw/log/detail/syslog/syslog_recorder_factory.h b/score/mw/log/detail/syslog/syslog_recorder_factory.h new file mode 100644 index 00000000..4a68ecac --- /dev/null +++ b/score/mw/log/detail/syslog/syslog_recorder_factory.h @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ +#ifndef SCORE_MW_LOG_DETAIL_SYSLOG_SYSLOG_RECORDER_FACTORY_H +#define SCORE_MW_LOG_DETAIL_SYSLOG_SYSLOG_RECORDER_FACTORY_H + +#include "score/mw/log/detail/log_recorder_factory.hpp" +#include "score/mw/log/detail/syslog/syslog_backend.h" +#include "score/mw/log/detail/text_recorder/text_recorder.h" + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ +class SyslogRecorderFactory : public LogRecorderFactory +{ + public: + std::unique_ptr CreateConcreteLogRecorder(const Configuration& config, + score::cpp::pmr::memory_resource* memory_resource); + + private: + std::unique_ptr CreateSystemBackend(const Configuration& config, + score::cpp::pmr::memory_resource* memory_resource); +}; + +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score + +#endif // SCORE_MW_LOG_DETAIL_SYSLOG_SYSLOG_RECORDER_FACTORY_H diff --git a/score/mw/log/detail/syslog/syslog_recorder_factory_test.cpp b/score/mw/log/detail/syslog/syslog_recorder_factory_test.cpp new file mode 100644 index 00000000..0030ed3e --- /dev/null +++ b/score/mw/log/detail/syslog/syslog_recorder_factory_test.cpp @@ -0,0 +1,50 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +#include "gtest/gtest.h" + +#include "score/mw/log/detail/syslog/syslog_recorder_factory.h" + +namespace score +{ +namespace mw +{ +namespace log +{ +namespace detail +{ + +template +bool IsRecorderOfType(const std::unique_ptr& recorder) noexcept +{ + static_assert(std::is_base_of::value, + "Concrete recorder shall be derived from Recorder base class"); + + return dynamic_cast(recorder.get()) != nullptr; +} + +TEST(SyslogRecorderFactoryTest, CreateRecorder) +{ + Configuration config; + score::cpp::pmr::memory_resource* memory_resource = score::cpp::pmr::get_default_resource(); + + auto recorder = SyslogRecorderFactory{}.CreateConcreteLogRecorder(config, memory_resource); + + // Syslog uses TextRecorder + EXPECT_TRUE(IsRecorderOfType(recorder)); +} + +} // namespace detail +} // namespace log +} // namespace mw +} // namespace score