diff options
author | Carlos Chinchilla <cachinchilla@google.com> | 2021-12-06 15:37:31 -0800 |
---|---|---|
committer | CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com> | 2022-01-10 21:10:33 +0000 |
commit | e4be88b8eafb6354b31cd1aff5fd00fb36adc387 (patch) | |
tree | 5f32415c421bcd3fa790606765d82bc95ef62fa5 /pw_log_rpc | |
parent | cc9f186da4b85cdfeef67bc6fc8cd1c5656b0fe7 (diff) | |
download | pigweed-e4be88b8eafb6354b31cd1aff5fd00fb36adc387.tar.gz |
pw_{log, log_rpc}: Separate Filter Service
- Separate the log and log filter services to make the first not rely on
the second.
- Separated unit tests for both services with minor fixes to existing
unit tests.
- Updated documentation.
Fixes: 570
Change-Id: I08945088f17e9c93623fe44819fa39be5af7a262
Requires: pigweed-internal:18860
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/72502
Reviewed-by: Keir Mierle <keir@google.com>
Pigweed-Auto-Submit: Carlos Chinchilla <cachinchilla@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
Diffstat (limited to 'pw_log_rpc')
-rw-r--r-- | pw_log_rpc/BUILD.bazel | 28 | ||||
-rw-r--r-- | pw_log_rpc/BUILD.gn | 29 | ||||
-rw-r--r-- | pw_log_rpc/docs.rst | 83 | ||||
-rw-r--r-- | pw_log_rpc/log_filter_service.cc | 86 | ||||
-rw-r--r-- | pw_log_rpc/log_filter_service_test.cc | 378 | ||||
-rw-r--r-- | pw_log_rpc/log_service.cc | 75 | ||||
-rw-r--r-- | pw_log_rpc/log_service_test.cc | 401 | ||||
-rw-r--r-- | pw_log_rpc/public/pw_log_rpc/log_filter_service.h | 43 | ||||
-rw-r--r-- | pw_log_rpc/public/pw_log_rpc/log_service.h | 16 | ||||
-rw-r--r-- | pw_log_rpc/rpc_log_drain_test.cc | 4 |
10 files changed, 666 insertions, 477 deletions
diff --git a/pw_log_rpc/BUILD.bazel b/pw_log_rpc/BUILD.bazel index a293c2c10..3db6e856b 100644 --- a/pw_log_rpc/BUILD.bazel +++ b/pw_log_rpc/BUILD.bazel @@ -36,6 +36,19 @@ pw_cc_library( "//pw_log", "//pw_log:log_pwpb", "//pw_log:protos.raw_rpc", + ], +) + +pw_cc_library( + name = "log_filter_service", + srcs = ["log_filter_service.cc"], + hdrs = ["public/pw_log_rpc/log_filter_service.h"], + includes = ["public"], + deps = [ + ":log_filter", + "//pw_log", + "//pw_log:log_pwpb", + "//pw_log:protos.raw_rpc", "//pw_protobuf", "//pw_protobuf:bytes_utils", ], @@ -121,6 +134,21 @@ pw_cc_test( ) pw_cc_test( + name = "log_filter_service_test", + srcs = ["log_filter_service_test.cc"], + deps = [ + ":log_filter", + ":log_filter_service", + "//pw_log:log_pwpb", + "//pw_protobuf", + "//pw_protobuf:bytes_utils", + "//pw_result", + "//pw_rpc/raw:test_method_context", + "//pw_unit_test", + ], +) + +pw_cc_test( name = "log_filter_test", srcs = ["log_filter_test.cc"], deps = [ diff --git a/pw_log_rpc/BUILD.gn b/pw_log_rpc/BUILD.gn index a30d4df12..7582ddc4f 100644 --- a/pw_log_rpc/BUILD.gn +++ b/pw_log_rpc/BUILD.gn @@ -46,11 +46,23 @@ pw_source_set("log_service") { ":log_config", "$dir_pw_log", "$dir_pw_log:protos.pwpb", + ] + public_deps = [ + ":rpc_log_drain", + "$dir_pw_log:protos.raw_rpc", + ] +} + +pw_source_set("log_filter_service") { + public_configs = [ ":public_include_path" ] + public = [ "public/pw_log_rpc/log_filter_service.h" ] + sources = [ "log_filter_service.cc" ] + deps = [ + "$dir_pw_log:protos.pwpb", "$dir_pw_protobuf", ] public_deps = [ ":log_filter", - ":rpc_log_drain", "$dir_pw_log:protos.raw_rpc", "$dir_pw_protobuf:bytes_utils", ] @@ -133,6 +145,20 @@ pw_test("log_service_test") { ] } +pw_test("log_filter_service_test") { + sources = [ "log_filter_service_test.cc" ] + deps = [ + ":log_filter", + ":log_filter_service", + "$dir_pw_log:protos.pwpb", + "$dir_pw_protobuf", + "$dir_pw_protobuf:bytes_utils", + "$dir_pw_result", + "$dir_pw_rpc/raw:test_method_context", + "$dir_pw_status", + ] +} + pw_test("log_filter_test") { sources = [ "log_filter_test.cc" ] deps = [ @@ -162,6 +188,7 @@ pw_doc_group("docs") { pw_test_group("tests") { tests = [ ":log_filter_test", + ":log_filter_service_test", ":log_service_test", ":rpc_log_drain_test", ] diff --git a/pw_log_rpc/docs.rst b/pw_log_rpc/docs.rst index d0dfd9182..951b89ba3 100644 --- a/pw_log_rpc/docs.rst +++ b/pw_log_rpc/docs.rst @@ -9,6 +9,10 @@ reporting -- coming soon! .. warning:: This module is under construction and might change in the future. +----------- +RPC Logging +----------- + How to Use ========== 1. Set up RPC @@ -144,6 +148,35 @@ with different priorities. Calling ``OpenUnrequestedLogStream()`` is a convenient way to set up a log stream that is started without the need to receive an RCP request for logs. +------------- +Log Filtering +------------- +A ``Filter`` anywhere in the path of a ``LogEntry`` proto, for example, in the +``PW_LOG*`` macro implementation, or in an ``RpcLogDrain`` if using RPC logging. +The log filtering service provides read and modify access to the ``Filter``\s +registered in the ``FilterMap``. + +How to Use +========== +1. Set up RPC +------------- +Set up RPC for your target device. See :ref:`module-pw_rpc` for details. + +2. Create ``Filter``\s +---------------------- +Provide each ``Filter`` with its own container for the ``FilterRules`` as big as +the number of rules desired. These rules can be pre-poluated. + +3. Create a ``FilterMap`` and ``FilterService`` +----------------------------------------------- +Set up the ``FilterMap`` with the filters than can be modified with the +``FilterService``. Register the service with the RPC server. + +4. Use RPCs to retrieve and modify filter rules +----------------------------------------------- + +Components Overview +=================== Filter::Rule ------------ Contains a set of values that are compared against a log when set. All @@ -163,24 +196,25 @@ conditions must be met for the rule to be met. Filter ------ -``Filter`` encapsulates a collection of zero or more ``Filter::Rule``\s and has +Encapsulates a collection of zero or more ``Filter::Rule``\s and has an ID used to modify or retrieve its contents. FilterMap --------- Provides a convenient way to retrieve register filters by ID. -Logging example -=============== +---------------------------- +Logging with filters example +---------------------------- The following code shows a sample setup to defer the log handling to the ``RpcLogDrainThread`` to avoid having the log streaming block at the log callsite. main.cc -------- +======= .. code-block:: cpp - #include "foo/foo_log.h" + #include "foo/log.h" #include "pw_log/log.h" #include "pw_thread/detached_thread.h" #include "pw_thread_stl/options.h" @@ -188,7 +222,8 @@ main.cc namespace { void RegisterServices() { - pw::rpc::system_server::Server().RegisterService(foo_log::log_service); + pw::rpc::system_server::Server().RegisterService(foo::log::log_service); + pw::rpc::system_server::Server().RegisterService(foo::log::filter_service); } } // namespace @@ -196,25 +231,28 @@ main.cc PW_LOG_INFO("Deferred logging over RPC example"); pw::rpc::system_server::Init(); RegisterServices(); - pw::thread::DetachedThread(pw::thread::stl::Options(), foo_log::log_thread); + pw::thread::DetachedThread(pw::thread::stl::Options(), foo::log::log_thread); pw::rpc::system_server::Start(); return 0; } -foo_log.cc ----------- +foo/log.cc +========== Example of a log backend implementation, where logs enter the ``MultiSink`` and -log drains are set up. +log drains and filters are set up. .. code-block:: cpp - #include "foo/foo_log.h" + #include "foo/log.h" #include <array> #include <cstdint> #include "pw_chrono/system_clock.h" #include "pw_log/proto_utils.h" + #include "pw_log_rpc/log_filter.h" + #include "pw_log_rpc/log_filter_map.h" + #include "pw_log_rpc/log_filter_service.h" #include "pw_log_rpc/log_service.h" #include "pw_log_rpc/rpc_log_drain.h" #include "pw_log_rpc/rpc_log_drain_map.h" @@ -225,7 +263,7 @@ log drains are set up. #include "pw_sync/mutex.h" #include "pw_tokenizer/tokenize_to_global_handler_with_payload.h" - namespace foo_log { + namespace foo::log { namespace { constexpr size_t kLogBufferSize = 5000; // Tokenized logs are typically 12-24 bytes. @@ -259,6 +297,22 @@ log drains are set up. std::array<std::byte, kMaxLogEntrySize> log_encode_buffer PW_GUARDED_BY(log_encode_lock); + std::array<Filter::Rule, 2> logs_to_host_filter_rules; + std::array<Filter::Rule, 2> logs_to_server_filter_rules{{ + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = pw::log::FilterRule::Level::INFO_LEVEL, + }, + { + .action = Filter::Rule::Action::kDrop, + }, + }}; + std::array<Filter, 2> filters{ + Filter(std::as_bytes(std::span("HOST", 4)), logs_to_host_filter_rules), + Filter(std::as_bytes(std::span("WEB", 3)), logs_to_server_filter_rules), + }; + pw::log_rpc::FilterMap filter_map(filters); + extern "C" void pw_tokenizer_HandleEncodedMessageWithPayload( pw_tokenizer_Payload metadata, const uint8_t message[], size_t size_bytes) { int64_t timestamp = @@ -279,15 +333,16 @@ log drains are set up. pw::log_rpc::RpcLogDrainMap drain_map(drains); pw::log_rpc::RpcLogDrainThread log_thread(GetMultiSink(), drain_map); pw::log_rpc::LogService log_service(drain_map); + pw::log_rpc::FilterService filter_service(filter_map); pw::multisink::MultiSink& GetMultiSink() { static pw::multisink::MultiSink multisink(multisink_buffer); return multisink; } - } // namespace foo_log + } // namespace foo::log Logging in other source files ----------------------------- To defer logging, other source files must simply include ``pw_log/log.h`` and use the :ref:`module-pw_log` APIs, as long as the source set that includes -``foo_log.cc`` is setup as the log backend. +``foo/log.cc`` is setup as the log backend. diff --git a/pw_log_rpc/log_filter_service.cc b/pw_log_rpc/log_filter_service.cc new file mode 100644 index 000000000..b56f3968e --- /dev/null +++ b/pw_log_rpc/log_filter_service.cc @@ -0,0 +1,86 @@ +// Copyright 2020 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. +#include "pw_log_rpc/log_filter_service.h" + +#include "pw_log/log.h" +#include "pw_log/proto/log.pwpb.h" +#include "pw_log_rpc/log_filter.h" +#include "pw_protobuf/decoder.h" + +namespace pw::log_rpc { +StatusWithSize FilterService::SetFilter(ConstByteSpan request, ByteSpan) { + protobuf::Decoder decoder(request); + PW_TRY_WITH_SIZE(decoder.Next()); + if (static_cast<log::SetFilterRequest::Fields>(decoder.FieldNumber()) != + log::SetFilterRequest::Fields::FILTER_ID) { + return StatusWithSize::InvalidArgument(); + } + ConstByteSpan filter_id; + PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_id)); + Result<Filter*> filter = filter_map_.GetFilterFromId(filter_id); + if (!filter.ok()) { + return StatusWithSize::NotFound(); + } + + PW_TRY_WITH_SIZE(decoder.Next()); + ConstByteSpan filter_buffer; + if (static_cast<log::SetFilterRequest::Fields>(decoder.FieldNumber()) != + log::SetFilterRequest::Fields::FILTER) { + return StatusWithSize::InvalidArgument(); + } + PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_buffer)); + PW_TRY_WITH_SIZE(filter.value()->UpdateRulesFromProto(filter_buffer)); + return StatusWithSize(); +} + +StatusWithSize FilterService::GetFilter(ConstByteSpan request, + ByteSpan response) { + protobuf::Decoder decoder(request); + PW_TRY_WITH_SIZE(decoder.Next()); + if (static_cast<log::GetFilterRequest::Fields>(decoder.FieldNumber()) != + log::GetFilterRequest::Fields::FILTER_ID) { + return StatusWithSize::InvalidArgument(); + } + ConstByteSpan filter_id; + PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_id)); + Result<Filter*> filter = filter_map_.GetFilterFromId(filter_id); + if (!filter.ok()) { + return StatusWithSize::NotFound(); + } + + log::Filter::MemoryEncoder encoder(response); + for (auto& rule : (*filter)->rules()) { + log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder(); + rule_encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal) + .IgnoreError(); + rule_encoder.WriteModuleEquals(rule.module_equals).IgnoreError(); + rule_encoder.WriteAnyFlagsSet(rule.any_flags_set).IgnoreError(); + rule_encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action)) + .IgnoreError(); + PW_TRY_WITH_SIZE(rule_encoder.status()); + } + PW_TRY_WITH_SIZE(encoder.status()); + + return StatusWithSize(encoder.size()); +} + +StatusWithSize FilterService::ListFilterIds(ConstByteSpan, ByteSpan response) { + log::FilterIdListResponse::MemoryEncoder encoder(response); + for (auto& filter : filter_map_.filters()) { + PW_TRY_WITH_SIZE(encoder.WriteFilterId(filter.id())); + } + return StatusWithSize(encoder.size()); +} + +} // namespace pw::log_rpc diff --git a/pw_log_rpc/log_filter_service_test.cc b/pw_log_rpc/log_filter_service_test.cc new file mode 100644 index 000000000..232e18e71 --- /dev/null +++ b/pw_log_rpc/log_filter_service_test.cc @@ -0,0 +1,378 @@ +// Copyright 2021 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#include "pw_log_rpc/log_filter_service.h" + +#include <array> +#include <cstdint> +#include <limits> + +#include "gtest/gtest.h" +#include "pw_bytes/endian.h" +#include "pw_log/proto/log.pwpb.h" +#include "pw_log_rpc/log_filter.h" +#include "pw_log_rpc/log_filter_map.h" +#include "pw_protobuf/bytes_utils.h" +#include "pw_protobuf/decoder.h" +#include "pw_result/result.h" +#include "pw_rpc/channel.h" +#include "pw_rpc/raw/test_method_context.h" + +namespace pw::log_rpc { +namespace { + +class FilterServiceTest : public ::testing::Test { + public: + FilterServiceTest() : filter_map_(filters_) {} + + protected: + FilterMap filter_map_; + static constexpr size_t kMaxFilterRules = 3; + std::array<Filter::Rule, kMaxFilterRules> rules1_; + std::array<Filter::Rule, kMaxFilterRules> rules2_; + std::array<Filter::Rule, kMaxFilterRules> rules3_; + static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1_{ + std::byte(65), std::byte(66), std::byte(67), std::byte(0)}; + static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2_{ + std::byte(68), std::byte(69), std::byte(70), std::byte(0)}; + static constexpr std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id3_{ + std::byte(71), std::byte(72), std::byte(73), std::byte(0)}; + static constexpr size_t kMaxFilters = 3; + std::array<Filter, kMaxFilters> filters_ = { + Filter(filter_id1_, rules1_), + Filter(filter_id2_, rules2_), + Filter(filter_id3_, rules3_), + }; +}; + +TEST_F(FilterServiceTest, GetFilterIds) { + PW_RAW_TEST_METHOD_CONTEXT(FilterService, ListFilterIds, 1, 128) + context(filter_map_); + context.call({}); + ASSERT_TRUE(context.done()); + ASSERT_EQ(context.responses().size(), 1u); + protobuf::Decoder decoder(context.responses()[0]); + + for (const auto& filter : filter_map_.filters()) { + ASSERT_EQ(decoder.Next(), OkStatus()); + ASSERT_EQ(decoder.FieldNumber(), 1u); // filter_id + ConstByteSpan filter_id; + ASSERT_EQ(decoder.ReadBytes(&filter_id), OkStatus()); + ASSERT_EQ(filter_id.size(), filter.id().size()); + EXPECT_EQ( + std::memcmp(filter_id.data(), filter.id().data(), filter_id.size()), 0); + } + EXPECT_FALSE(decoder.Next().ok()); + + // No IDs reported when the filter map is empty. + FilterMap empty_filter_map({}); + PW_RAW_TEST_METHOD_CONTEXT(FilterService, ListFilterIds, 1, 128) + no_filter_context(empty_filter_map); + no_filter_context.call({}); + ASSERT_TRUE(no_filter_context.done()); + ASSERT_EQ(no_filter_context.responses().size(), 1u); + protobuf::Decoder no_filter_decoder(no_filter_context.responses()[0]); + uint32_t filter_count = 0; + while (no_filter_decoder.Next().ok()) { + EXPECT_EQ(no_filter_decoder.FieldNumber(), 1u); // filter_id + ++filter_count; + } + EXPECT_EQ(filter_count, 0u); +} + +Status EncodeFilterRule(const Filter::Rule& rule, + log::FilterRule::StreamEncoder& encoder) { + PW_TRY( + encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal)); + PW_TRY(encoder.WriteModuleEquals(rule.module_equals)); + PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set)); + return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action)); +} + +Status EncodeFilter(const Filter& filter, log::Filter::StreamEncoder& encoder) { + for (auto& rule : filter.rules()) { + log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder(); + PW_TRY(EncodeFilterRule(rule, rule_encoder)); + } + return OkStatus(); +} + +Result<ConstByteSpan> EncodeFilterRequest(const Filter& filter, + ByteSpan buffer) { + stream::MemoryWriter writer(buffer); + std::byte encode_buffer[256]; + protobuf::StreamEncoder encoder(writer, encode_buffer); + PW_TRY(encoder.WriteBytes( + static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER_ID), + filter.id())); + { + log::Filter::StreamEncoder filter_encoder = encoder.GetNestedEncoder( + static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER)); + PW_TRY(EncodeFilter(filter, filter_encoder)); + } // Let the StreamEncoder destructor finalize the data. + return ConstByteSpan(writer.data(), writer.bytes_written()); +} + +void VerifyRule(const Filter::Rule& rule, const Filter::Rule& expected_rule) { + EXPECT_EQ(rule.level_greater_than_or_equal, + expected_rule.level_greater_than_or_equal); + EXPECT_EQ(rule.module_equals, expected_rule.module_equals); + EXPECT_EQ(rule.any_flags_set, expected_rule.any_flags_set); + EXPECT_EQ(rule.action, expected_rule.action); +} + +TEST_F(FilterServiceTest, SetFilterRules) { + const std::array<Filter::Rule, 4> new_rules{{ + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL, + .any_flags_set = 0x0f, + .module_equals{std::byte(123)}, + }, + { + .action = Filter::Rule::Action::kInactive, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0xef, + .module_equals{}, + }, + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, + .any_flags_set = 0x1234, + .module_equals{std::byte(99)}, + }, + { + .action = Filter::Rule::Action::kDrop, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0, + .module_equals{std::byte(4)}, + }, + }}; + const Filter new_filter(filters_[0].id(), + const_cast<std::array<Filter::Rule, 4>&>(new_rules)); + + std::byte request_buffer[512]; + const auto request = EncodeFilterRequest(new_filter, request_buffer); + ASSERT_EQ(request.status(), OkStatus()); + + PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1, 128) + context(filter_map_); + context.call(request.value()); + + size_t i = 0; + for (const auto& rule : filters_[0].rules()) { + VerifyRule(rule, new_rules[i++]); + } +} + +TEST_F(FilterServiceTest, SetFilterRulesWhenUsedByDrain) { + const std::array<Filter::Rule, 4> new_filter_rules{{ + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::CRITICAL_LEVEL, + .any_flags_set = 0xfd, + .module_equals{std::byte(543)}, + }, + { + .action = Filter::Rule::Action::kInactive, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0xca, + .module_equals{}, + }, + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, + .any_flags_set = 0xabcd, + .module_equals{std::byte(9000)}, + }, + { + .action = Filter::Rule::Action::kDrop, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0, + .module_equals{std::byte(123)}, + }, + }}; + Filter& filter = filters_[0]; + const Filter new_filter( + filter.id(), const_cast<std::array<Filter::Rule, 4>&>(new_filter_rules)); + + std::byte request_buffer[256]; + const auto request = EncodeFilterRequest(new_filter, request_buffer); + ASSERT_EQ(request.status(), OkStatus()); + + PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1, 128) + context(filter_map_); + context.call(request.value()); + + size_t i = 0; + for (const auto& rule : filter.rules()) { + VerifyRule(rule, new_filter_rules[i++]); + } + + // An empty request should not modify the filter. + PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1, 128) + context_no_filter(filter_map_); + context_no_filter.call({}); + i = 0; + for (const auto& rule : filter.rules()) { + VerifyRule(rule, new_filter_rules[i++]); + } + + // A new request for logs with a new filter updates filter. + const std::array<Filter::Rule, 4> second_filter_rules{{ + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL, + .any_flags_set = 0xab, + .module_equals{}, + }, + { + .action = Filter::Rule::Action::kDrop, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0x11, + .module_equals{std::byte(34)}, + }, + { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0xef, + .module_equals{std::byte(23)}, + }, + { + .action = Filter::Rule::Action::kDrop, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0x0f, + .module_equals{}, + }, + }}; + const Filter second_filter( + filter.id(), + const_cast<std::array<Filter::Rule, 4>&>(second_filter_rules)); + + std::memset(request_buffer, 0, sizeof(request_buffer)); + const auto second_filter_request = + EncodeFilterRequest(second_filter, request_buffer); + ASSERT_EQ(second_filter_request.status(), OkStatus()); + PW_RAW_TEST_METHOD_CONTEXT(FilterService, SetFilter, 1, 128) + context_new_filter(filter_map_); + context_new_filter.call(second_filter_request.value()); + + i = 0; + for (const auto& rule : filter.rules()) { + VerifyRule(rule, second_filter_rules[i++]); + } +} + +void VerifyFilterRule(protobuf::Decoder& decoder, + const Filter::Rule& expected_rule) { + ASSERT_TRUE(decoder.Next().ok()); + ASSERT_EQ(decoder.FieldNumber(), 1u); // level_greater_than_or_equal + log::FilterRule::Level level_greater_than_or_equal; + ASSERT_EQ(decoder.ReadUint32( + reinterpret_cast<uint32_t*>(&level_greater_than_or_equal)), + OkStatus()); + EXPECT_EQ(level_greater_than_or_equal, + expected_rule.level_greater_than_or_equal); + + ASSERT_TRUE(decoder.Next().ok()); + ASSERT_EQ(decoder.FieldNumber(), 2u); // module_equals + ConstByteSpan module_equals; + ASSERT_EQ(decoder.ReadBytes(&module_equals), OkStatus()); + ASSERT_EQ(module_equals.size(), expected_rule.module_equals.size()); + EXPECT_EQ(std::memcmp(module_equals.data(), + expected_rule.module_equals.data(), + module_equals.size()), + 0); + + ASSERT_TRUE(decoder.Next().ok()); + ASSERT_EQ(decoder.FieldNumber(), 3u); // any_flags_set + uint32_t any_flags_set; + ASSERT_EQ(decoder.ReadUint32(&any_flags_set), OkStatus()); + EXPECT_EQ(any_flags_set, expected_rule.any_flags_set); + + ASSERT_TRUE(decoder.Next().ok()); + ASSERT_EQ(decoder.FieldNumber(), 4u); // action + Filter::Rule::Action action; + ASSERT_EQ(decoder.ReadUint32(reinterpret_cast<uint32_t*>(&action)), + OkStatus()); + EXPECT_EQ(action, expected_rule.action); +} + +void VerifyFilterRules(protobuf::Decoder& decoder, + std::span<const Filter::Rule> expected_rules) { + size_t rules_found = 0; + while (decoder.Next().ok()) { + ConstByteSpan rule; + EXPECT_TRUE(decoder.ReadBytes(&rule).ok()); + protobuf::Decoder rule_decoder(rule); + if (rules_found >= expected_rules.size()) { + break; + } + VerifyFilterRule(rule_decoder, expected_rules[rules_found]); + ++rules_found; + } + EXPECT_EQ(rules_found, expected_rules.size()); +} + +TEST_F(FilterServiceTest, GetFilterRules) { + PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1, 128) + context(filter_map_); + + std::byte request_buffer[64]; + log::GetFilterRequest::MemoryEncoder encoder(request_buffer); + encoder.WriteFilterId(filter_id1_); + const auto request = ConstByteSpan(encoder); + context.call(request); + ASSERT_TRUE(context.done()); + ASSERT_EQ(context.responses().size(), 1u); + + // Verify against empty rules. + protobuf::Decoder decoder(context.responses()[0]); + VerifyFilterRules(decoder, rules1_); + + // Partially populate rules. + rules1_[0].action = Filter::Rule::Action::kKeep; + rules1_[0].level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL; + rules1_[0].any_flags_set = 0xab; + const std::array<std::byte, 2> module1{std::byte(123), std::byte(0xab)}; + rules1_[0].module_equals.assign(module1.begin(), module1.end()); + rules1_[1].action = Filter::Rule::Action::kDrop; + rules1_[1].level_greater_than_or_equal = log::FilterRule::Level::ERROR_LEVEL; + rules1_[1].any_flags_set = 0; + + PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1, 128) + context2(filter_map_); + context2.call(request); + ASSERT_EQ(context2.responses().size(), 1u); + protobuf::Decoder decoder2(context2.responses()[0]); + VerifyFilterRules(decoder2, rules1_); + + // Modify the rest of the filter rules. + rules1_[2].action = Filter::Rule::Action::kKeep; + rules1_[2].level_greater_than_or_equal = log::FilterRule::Level::FATAL_LEVEL; + rules1_[2].any_flags_set = 0xcd; + const std::array<std::byte, 2> module2{std::byte(1), std::byte(2)}; + rules1_[2].module_equals.assign(module2.begin(), module2.end()); + rules1_[3].action = Filter::Rule::Action::kInactive; + + PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1, 128) + context3(filter_map_); + context3.call(request); + ASSERT_EQ(context3.responses().size(), 1u); + protobuf::Decoder decoder3(context3.responses()[0]); + VerifyFilterRules(decoder3, rules1_); +} + +} // namespace +} // namespace pw::log_rpc diff --git a/pw_log_rpc/log_service.cc b/pw_log_rpc/log_service.cc index 731b7ce82..c700af49a 100644 --- a/pw_log_rpc/log_service.cc +++ b/pw_log_rpc/log_service.cc @@ -20,8 +20,6 @@ #include "pw_log/log.h" #include "pw_log/proto/log.pwpb.h" -#include "pw_log_rpc/log_filter.h" -#include "pw_protobuf/decoder.h" namespace pw::log_rpc { @@ -38,77 +36,4 @@ void LogService::Listen(ConstByteSpan, rpc::RawServerWriter& writer) { } } -StatusWithSize LogService::SetFilter(ConstByteSpan request, ByteSpan) { - if (filters_ == nullptr) { - return StatusWithSize::NotFound(); - } - - protobuf::Decoder decoder(request); - PW_TRY_WITH_SIZE(decoder.Next()); - if (static_cast<log::SetFilterRequest::Fields>(decoder.FieldNumber()) != - log::SetFilterRequest::Fields::FILTER_ID) { - return StatusWithSize::InvalidArgument(); - } - ConstByteSpan filter_id; - PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_id)); - Result<Filter*> filter = filters_->GetFilterFromId(filter_id); - if (!filter.ok()) { - return StatusWithSize::NotFound(); - } - - PW_TRY_WITH_SIZE(decoder.Next()); - ConstByteSpan filter_buffer; - if (static_cast<log::SetFilterRequest::Fields>(decoder.FieldNumber()) != - log::SetFilterRequest::Fields::FILTER) { - return StatusWithSize::InvalidArgument(); - } - PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_buffer)); - PW_TRY_WITH_SIZE(filter.value()->UpdateRulesFromProto(filter_buffer)); - return StatusWithSize(); -} - -StatusWithSize LogService::GetFilter(ConstByteSpan request, ByteSpan response) { - if (filters_ == nullptr) { - return StatusWithSize::NotFound(); - } - protobuf::Decoder decoder(request); - PW_TRY_WITH_SIZE(decoder.Next()); - if (static_cast<log::GetFilterRequest::Fields>(decoder.FieldNumber()) != - log::GetFilterRequest::Fields::FILTER_ID) { - return StatusWithSize::InvalidArgument(); - } - ConstByteSpan filter_id; - PW_TRY_WITH_SIZE(decoder.ReadBytes(&filter_id)); - Result<Filter*> filter = filters_->GetFilterFromId(filter_id); - if (!filter.ok()) { - return StatusWithSize::NotFound(); - } - - log::Filter::MemoryEncoder encoder(response); - for (auto& rule : (*filter)->rules()) { - log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder(); - rule_encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal) - .IgnoreError(); - rule_encoder.WriteModuleEquals(rule.module_equals).IgnoreError(); - rule_encoder.WriteAnyFlagsSet(rule.any_flags_set).IgnoreError(); - rule_encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action)) - .IgnoreError(); - PW_TRY_WITH_SIZE(rule_encoder.status()); - } - PW_TRY_WITH_SIZE(encoder.status()); - - return StatusWithSize(encoder.size()); -} - -StatusWithSize LogService::ListFilterIds(ConstByteSpan, ByteSpan response) { - if (filters_ == nullptr) { - return StatusWithSize::NotFound(); - } - log::FilterIdListResponse::MemoryEncoder encoder(response); - for (auto& filter : filters_->filters()) { - PW_TRY_WITH_SIZE(encoder.WriteFilterId(filter.id())); - } - return StatusWithSize(encoder.size()); -} - } // namespace pw::log_rpc diff --git a/pw_log_rpc/log_service_test.cc b/pw_log_rpc/log_service_test.cc index 6ebd177bf..0f79f129b 100644 --- a/pw_log_rpc/log_service_test.cc +++ b/pw_log_rpc/log_service_test.cc @@ -70,10 +70,7 @@ constexpr int64_t kSampleTimestamp = 1000; // add to the multisink, and which drain to use. class LogServiceTest : public ::testing::Test { public: - LogServiceTest() - : multisink_(multisink_buffer_), - drain_map_(drains_), - filter_map_(filters_) { + LogServiceTest() : multisink_(multisink_buffer_), drain_map_(drains_) { for (auto& drain : drain_map_.drains()) { multisink_.AttachDrain(drain); } @@ -106,8 +103,7 @@ class LogServiceTest : public ::testing::Test { multisink::MultiSink multisink_; RpcLogDrainMap drain_map_; std::array<std::byte, kMaxLogEntrySize> entry_encode_buffer_; - FilterMap filter_map_; - static constexpr size_t kMaxFilterRules = 4; + static constexpr size_t kMaxFilterRules = 3; std::array<Filter::Rule, kMaxFilterRules> rules1_; std::array<Filter::Rule, kMaxFilterRules> rules2_; std::array<Filter::Rule, kMaxFilterRules> rules3_; @@ -253,7 +249,7 @@ TEST_F(LogServiceTest, AssignWriter) { // Create context directed to drain with ID 1. RpcLogDrain& active_drain = drains_[0]; const uint32_t drain_channel_id = active_drain.channel_id(); - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(drain_channel_id); // Call RPC, which sets the drain's writer. @@ -269,7 +265,7 @@ TEST_F(LogServiceTest, AssignWriter) { // Calling an ongoing log stream must not change the active drain's // writer, and the second writer must not get any responses. - LOG_SERVICE_METHOD_CONTEXT second_call_context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT second_call_context(drain_map_); second_call_context.set_channel_id(drain_channel_id); second_call_context.call(rpc_request_buffer); EXPECT_EQ(active_drain.Flush(), OkStatus()); @@ -278,7 +274,7 @@ TEST_F(LogServiceTest, AssignWriter) { // Setting a new writer on a closed stream is allowed. ASSERT_EQ(active_drain.Close(), OkStatus()); - LOG_SERVICE_METHOD_CONTEXT third_call_context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT third_call_context(drain_map_); third_call_context.set_channel_id(drain_channel_id); third_call_context.call(rpc_request_buffer); EXPECT_EQ(active_drain.Flush(), OkStatus()); @@ -290,7 +286,7 @@ TEST_F(LogServiceTest, AssignWriter) { TEST_F(LogServiceTest, StartAndEndStream) { RpcLogDrain& active_drain = drains_[2]; const uint32_t drain_channel_id = active_drain.channel_id(); - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(drain_channel_id); // Add log entries. @@ -328,7 +324,7 @@ TEST_F(LogServiceTest, StartAndEndStream) { TEST_F(LogServiceTest, HandleDropped) { RpcLogDrain& active_drain = drains_[0]; const uint32_t drain_channel_id = active_drain.channel_id(); - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(drain_channel_id); // Add log entries. @@ -366,7 +362,7 @@ TEST_F(LogServiceTest, HandleDropped) { } TEST_F(LogServiceTest, HandleSmallBuffer) { - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(kSmallBufferDrainId); auto small_buffer_drain = drain_map_.GetDrainFromChannelId(kSmallBufferDrainId); @@ -400,7 +396,7 @@ TEST_F(LogServiceTest, HandleSmallBuffer) { TEST_F(LogServiceTest, FlushDrainWithoutMultisink) { auto& detached_drain = drains_[0]; multisink_.DetachDrain(detached_drain); - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(detached_drain.channel_id()); // Add log entries. @@ -445,7 +441,7 @@ TEST_F(LogServiceTest, LargeLogEntry) { // Start log stream. RpcLogDrain& active_drain = drains_[0]; const uint32_t drain_channel_id = active_drain.channel_id(); - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(drain_channel_id); context.call(rpc_request_buffer); ASSERT_EQ(active_drain.Flush(), OkStatus()); @@ -467,7 +463,7 @@ TEST_F(LogServiceTest, InterruptedLogStreamSendsDropCount) { auto drain = drain_map_.GetDrainFromChannelId(drain_channel_id); ASSERT_TRUE(drain.ok()); - LogService log_service(drain_map_, &filter_map_); + LogService log_service(drain_map_); const size_t output_buffer_size = 128; const size_t max_packets = 10; rpc::RawFakeChannelOutput<10, output_buffer_size, 512> output; @@ -555,7 +551,7 @@ TEST_F(LogServiceTest, InterruptedLogStreamIgnoresErrors) { auto drain = drain_map_.GetDrainFromChannelId(drain_channel_id); ASSERT_TRUE(drain.ok()); - LogService log_service(drain_map_, &filter_map_); + LogService log_service(drain_map_); const size_t output_buffer_size = 128; const size_t max_packets = 20; rpc::RawFakeChannelOutput<max_packets, output_buffer_size, 512> output; @@ -629,229 +625,6 @@ TEST_F(LogServiceTest, InterruptedLogStreamIgnoresErrors) { EXPECT_TRUE(output.done()); } -TEST_F(LogServiceTest, GetFilterIds) { - PW_RAW_TEST_METHOD_CONTEXT(LogService, ListFilterIds, 1, 128) - context(drain_map_, &filter_map_); - context.call({}); - ASSERT_TRUE(context.done()); - ASSERT_EQ(context.responses().size(), 1u); - protobuf::Decoder decoder(context.responses()[0]); - - for (const auto& filter : filter_map_.filters()) { - ASSERT_EQ(decoder.Next(), OkStatus()); - ASSERT_EQ(decoder.FieldNumber(), 1u); // filter_id - ConstByteSpan filter_id; - ASSERT_EQ(decoder.ReadBytes(&filter_id), OkStatus()); - ASSERT_EQ(filter_id.size(), filter.id().size()); - EXPECT_EQ( - std::memcmp(filter_id.data(), filter.id().data(), filter_id.size()), 0); - } - EXPECT_FALSE(decoder.Next().ok()); - - // No IDs reported when none registered in the filter map. - PW_RAW_TEST_METHOD_CONTEXT(LogService, ListFilterIds, 1, 128) - no_filter_context(drain_map_, nullptr); - no_filter_context.call({}); - ASSERT_TRUE(no_filter_context.done()); - ASSERT_EQ(no_filter_context.responses().size(), 1u); - protobuf::Decoder no_filter_decoder(no_filter_context.responses()[0]); - uint32_t filter_count = 0; - while (no_filter_decoder.Next().ok()) { - EXPECT_EQ(no_filter_decoder.FieldNumber(), 1u); // filter_id - ++filter_count; - } - EXPECT_EQ(filter_count, 0u); -} - -Status EncodeFilterRule(const Filter::Rule& rule, - log::FilterRule::StreamEncoder& encoder) { - PW_TRY( - encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal)); - PW_TRY(encoder.WriteModuleEquals(rule.module_equals)); - PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set)); - return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action)); -} - -Status EncodeFilter(const Filter& filter, log::Filter::StreamEncoder& encoder) { - for (auto& rule : filter.rules()) { - log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder(); - PW_TRY(EncodeFilterRule(rule, rule_encoder)); - } - return OkStatus(); -} - -Result<ConstByteSpan> EncodeFilterRequest(const Filter& filter, - ByteSpan buffer) { - stream::MemoryWriter writer(buffer); - std::byte encode_buffer[256]; - protobuf::StreamEncoder encoder(writer, encode_buffer); - PW_TRY(encoder.WriteBytes( - static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER_ID), - filter.id())); - { - log::Filter::StreamEncoder filter_encoder = encoder.GetNestedEncoder( - static_cast<uint32_t>(log::SetFilterRequest::Fields::FILTER)); - PW_TRY(EncodeFilter(filter, filter_encoder)); - } // Let the StreamEncoder destructor finalize the data. - return ConstByteSpan(writer.data(), writer.bytes_written()); -} - -void VerifyRule(const Filter::Rule& rule, const Filter::Rule& expected_rule) { - EXPECT_EQ(rule.level_greater_than_or_equal, - expected_rule.level_greater_than_or_equal); - EXPECT_EQ(rule.module_equals, expected_rule.module_equals); - EXPECT_EQ(rule.any_flags_set, expected_rule.any_flags_set); - EXPECT_EQ(rule.action, expected_rule.action); -} - -TEST_F(LogServiceTest, SetFilterRules) { - const std::array<Filter::Rule, 4> new_rules{{ - { - .action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL, - .any_flags_set = 0x0f, - .module_equals{std::byte(123)}, - }, - { - .action = Filter::Rule::Action::kInactive, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0xef, - .module_equals{}, - }, - { - .action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, - .any_flags_set = 0x1234, - .module_equals{std::byte(99)}, - }, - { - .action = Filter::Rule::Action::kDrop, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0, - .module_equals{std::byte(4)}, - }, - }}; - const Filter new_filter(filters_[0].id(), - const_cast<std::array<Filter::Rule, 4>&>(new_rules)); - - std::byte request_buffer[512]; - const auto request = EncodeFilterRequest(new_filter, request_buffer); - ASSERT_EQ(request.status(), OkStatus()); - - PW_RAW_TEST_METHOD_CONTEXT(LogService, SetFilter, 1, 128) - context(drain_map_, &filter_map_); - context.call(request.value()); - - size_t i = 0; - for (const auto& rule : filters_[0].rules()) { - VerifyRule(rule, new_rules[i++]); - } -} - -TEST_F(LogServiceTest, SetFilterRulesWhenUsedByDrain) { - const std::array<Filter::Rule, 4> new_filter_rules{{ - { - .action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::CRITICAL_LEVEL, - .any_flags_set = 0xfd, - .module_equals{std::byte(543)}, - }, - { - .action = Filter::Rule::Action::kInactive, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0xca, - .module_equals{}, - }, - { - .action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, - .any_flags_set = 0xabcd, - .module_equals{std::byte(9000)}, - }, - { - .action = Filter::Rule::Action::kDrop, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0, - .module_equals{std::byte(123)}, - }, - }}; - Filter& filter = filters_[0]; - const Filter new_filter( - filter.id(), const_cast<std::array<Filter::Rule, 4>&>(new_filter_rules)); - - // Add callback to drain. - RpcLogDrain& drain = drains_[0]; - - std::byte request_buffer[256]; - const auto request = EncodeFilterRequest(new_filter, request_buffer); - ASSERT_EQ(request.status(), OkStatus()); - - PW_RAW_TEST_METHOD_CONTEXT(LogService, SetFilter, 1, 128) - context(drain_map_, &filter_map_); - context.set_channel_id(drain.channel_id()); - context.call(request.value()); - - size_t i = 0; - for (const auto& rule : filter.rules()) { - VerifyRule(rule, new_filter_rules[i++]); - } - - // A request for logs without a filter should not modify the filter. - PW_RAW_TEST_METHOD_CONTEXT(LogService, SetFilter, 1, 128) - context_no_filter(drain_map_, &filter_map_); - context_no_filter.set_channel_id(drain.channel_id()); - context_no_filter.call({}); - i = 0; - for (const auto& rule : filter.rules()) { - VerifyRule(rule, new_filter_rules[i++]); - } - - // A new request for logs with a new filter updates filter. - const std::array<Filter::Rule, 4> second_filter_rules{{ - { - .action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL, - .any_flags_set = 0xab, - .module_equals{}, - }, - { - .action = Filter::Rule::Action::kDrop, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0x11, - .module_equals{std::byte(34)}, - }, - { - .action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0xef, - .module_equals{std::byte(23)}, - }, - { - .action = Filter::Rule::Action::kDrop, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0x0f, - .module_equals{}, - }, - }}; - const Filter second_filter( - filter.id(), - const_cast<std::array<Filter::Rule, 4>&>(second_filter_rules)); - - std::memset(request_buffer, 0, sizeof(request_buffer)); - const auto second_filter_request = - EncodeFilterRequest(second_filter, request_buffer); - ASSERT_EQ(second_filter_request.status(), OkStatus()); - PW_RAW_TEST_METHOD_CONTEXT(LogService, SetFilter, 1, 128) - context_new_filter(drain_map_, &filter_map_); - context_new_filter.set_channel_id(drain.channel_id()); - context_new_filter.call(second_filter_request.value()); - - i = 0; - for (const auto& rule : filter.rules()) { - VerifyRule(rule, second_filter_rules[i++]); - } -} - TEST_F(LogServiceTest, FilterLogs) { // Add a variety of logs. const uint32_t module = 0xcafe; @@ -879,7 +652,7 @@ TEST_F(LogServiceTest, FilterLogs) { AddLogEntry(kMessage, different_module_metadata, kSampleTimestamp).ok()); // Add messages to the stack in the reverse order they are sent. - Vector<TestLogEntry, 6> message_stack; + Vector<TestLogEntry, 3> message_stack; message_stack.push_back( {.metadata = error_metadata, .timestamp = kSampleTimestamp, @@ -893,41 +666,29 @@ TEST_F(LogServiceTest, FilterLogs) { .timestamp = kSampleTimestamp, .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))}); - // Create request with filter. + // Set up filter rules for drain at drains_[1]. + RpcLogDrain& drain = drains_[1]; + for (auto& rule : rules2_) { + rule = {}; + } const auto module_little_endian = bytes::CopyInOrder<uint32_t>(std::endian::little, module); - const std::array<Filter::Rule, 2> rules{{ - {.action = Filter::Rule::Action::kKeep, - .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, - .any_flags_set = flags, - .module_equals{module_little_endian.begin(), - module_little_endian.end()}}, - { - .action = Filter::Rule::Action::kDrop, - .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, - .any_flags_set = 0, - .module_equals{}, - }, - }}; - - RpcLogDrain& drain = drains_[1]; - Filter& filter = filters_[1]; - const Filter new_filter(filter.id(), - const_cast<std::array<Filter::Rule, 2>&>(rules)); - - // Set filter. - std::byte request_buffer[256]; - const auto request = EncodeFilterRequest(new_filter, request_buffer); - ASSERT_EQ(request.status(), OkStatus()); - PW_RAW_TEST_METHOD_CONTEXT(LogService, SetFilter, 1, 128) - set_filter_context(drain_map_, &filter_map_); - set_filter_context.set_channel_id(drain.channel_id()); - set_filter_context.call(request.value()); + rules2_[0] = { + .action = Filter::Rule::Action::kKeep, + .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL, + .any_flags_set = flags, + .module_equals{module_little_endian.begin(), module_little_endian.end()}}; + rules2_[1] = { + .action = Filter::Rule::Action::kDrop, + .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL, + .any_flags_set = 0, + .module_equals{}, + }; // Request logs. - LOG_SERVICE_METHOD_CONTEXT context(drain_map_, &filter_map_); + LOG_SERVICE_METHOD_CONTEXT context(drain_map_); context.set_channel_id(drain.channel_id()); - context.call(request.value()); + context.call({}); ASSERT_EQ(drain.Flush(), OkStatus()); size_t entries_found = 0; @@ -962,105 +723,5 @@ TEST_F(LogServiceTest, ReopenClosedLogStreamWithAcquiredBuffer) { EXPECT_EQ(drain.value()->Flush(), OkStatus()); } -void VerifyFilterRule(protobuf::Decoder& decoder, - const Filter::Rule& expected_rule) { - ASSERT_TRUE(decoder.Next().ok()); - ASSERT_EQ(decoder.FieldNumber(), 1u); // level_greater_than_or_equal - log::FilterRule::Level level_greater_than_or_equal; - ASSERT_EQ(decoder.ReadUint32( - reinterpret_cast<uint32_t*>(&level_greater_than_or_equal)), - OkStatus()); - EXPECT_EQ(level_greater_than_or_equal, - expected_rule.level_greater_than_or_equal); - - ASSERT_TRUE(decoder.Next().ok()); - ASSERT_EQ(decoder.FieldNumber(), 2u); // module_equals - ConstByteSpan module_equals; - ASSERT_EQ(decoder.ReadBytes(&module_equals), OkStatus()); - ASSERT_EQ(module_equals.size(), expected_rule.module_equals.size()); - EXPECT_EQ(std::memcmp(module_equals.data(), - expected_rule.module_equals.data(), - module_equals.size()), - 0); - - ASSERT_TRUE(decoder.Next().ok()); - ASSERT_EQ(decoder.FieldNumber(), 3u); // any_flags_set - uint32_t any_flags_set; - ASSERT_EQ(decoder.ReadUint32(&any_flags_set), OkStatus()); - EXPECT_EQ(any_flags_set, expected_rule.any_flags_set); - - ASSERT_TRUE(decoder.Next().ok()); - ASSERT_EQ(decoder.FieldNumber(), 4u); // action - Filter::Rule::Action action; - ASSERT_EQ(decoder.ReadUint32(reinterpret_cast<uint32_t*>(&action)), - OkStatus()); - EXPECT_EQ(action, expected_rule.action); -} - -void VerifyFilterRules(protobuf::Decoder& decoder, - std::span<const Filter::Rule> expected_rules) { - size_t rules_found = 0; - while (decoder.Next().ok()) { - ConstByteSpan rule; - EXPECT_TRUE(decoder.ReadBytes(&rule).ok()); - protobuf::Decoder rule_decoder(rule); - if (rules_found >= expected_rules.size()) { - break; - } - VerifyFilterRule(rule_decoder, expected_rules[rules_found]); - ++rules_found; - } - EXPECT_EQ(rules_found, expected_rules.size()); -} - -TEST_F(LogServiceTest, GetFilterRules) { - PW_RAW_TEST_METHOD_CONTEXT(LogService, GetFilter, 1, 128) - context(drain_map_, &filter_map_); - - std::byte request_buffer[64]; - log::GetFilterRequest::MemoryEncoder encoder(request_buffer); - encoder.WriteFilterId(filter_id1_); - const auto request = ConstByteSpan(encoder); - context.call(request); - ASSERT_TRUE(context.done()); - ASSERT_EQ(context.responses().size(), 1u); - - // Verify against empty rules. - protobuf::Decoder decoder(context.responses()[0]); - VerifyFilterRules(decoder, rules1_); - - // Partially populate rules. - rules1_[0].action = Filter::Rule::Action::kKeep; - rules1_[0].level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL; - rules1_[0].any_flags_set = 0xab; - const std::array<std::byte, 2> module1{std::byte(123), std::byte(0xab)}; - rules1_[0].module_equals.assign(module1.begin(), module1.end()); - rules1_[1].action = Filter::Rule::Action::kDrop; - rules1_[1].level_greater_than_or_equal = log::FilterRule::Level::ERROR_LEVEL; - rules1_[1].any_flags_set = 0; - - PW_RAW_TEST_METHOD_CONTEXT(LogService, GetFilter, 1, 128) - context2(drain_map_, &filter_map_); - context2.call(request); - ASSERT_EQ(context2.responses().size(), 1u); - protobuf::Decoder decoder2(context2.responses()[0]); - VerifyFilterRules(decoder2, rules1_); - - // Modify the rest of the filter rules. - rules1_[2].action = Filter::Rule::Action::kKeep; - rules1_[2].level_greater_than_or_equal = log::FilterRule::Level::FATAL_LEVEL; - rules1_[2].any_flags_set = 0xcd; - const std::array<std::byte, 2> module2{std::byte(1), std::byte(2)}; - rules1_[2].module_equals.assign(module2.begin(), module2.end()); - rules1_[3].action = Filter::Rule::Action::kInactive; - - PW_RAW_TEST_METHOD_CONTEXT(LogService, GetFilter, 1, 128) - context3(drain_map_, &filter_map_); - context3.call(request); - ASSERT_EQ(context3.responses().size(), 1u); - protobuf::Decoder decoder3(context3.responses()[0]); - VerifyFilterRules(decoder3, rules1_); -} - } // namespace } // namespace pw::log_rpc diff --git a/pw_log_rpc/public/pw_log_rpc/log_filter_service.h b/pw_log_rpc/public/pw_log_rpc/log_filter_service.h new file mode 100644 index 000000000..88b4fe298 --- /dev/null +++ b/pw_log_rpc/public/pw_log_rpc/log_filter_service.h @@ -0,0 +1,43 @@ +// Copyright 2021 The Pigweed Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +#pragma once + +#include "pw_log/proto/log.raw_rpc.pb.h" +#include "pw_log_rpc/log_filter_map.h" +#include "pw_status/status_with_size.h" + +namespace pw::log_rpc { + +// Provides a way to retrieve and modify log filters. +class FilterService final + : public log::pw_rpc::raw::Filters::Service<FilterService> { + public: + FilterService(FilterMap& filter_map) : filter_map_(filter_map) {} + + // Modifies a log filter and its rules. The filter must be registered in the + // provided filter map. + StatusWithSize SetFilter(ConstByteSpan request, ByteSpan); + + // Retrieves a log filter and its rules. The filter must be registered in the + // provided filter map. + StatusWithSize GetFilter(ConstByteSpan request, ByteSpan response); + + StatusWithSize ListFilterIds(ConstByteSpan, ByteSpan response); + + private: + FilterMap& filter_map_; +}; + +} // namespace pw::log_rpc diff --git a/pw_log_rpc/public/pw_log_rpc/log_service.h b/pw_log_rpc/public/pw_log_rpc/log_service.h index 9e0d828c9..d4e15ddda 100644 --- a/pw_log_rpc/public/pw_log_rpc/log_service.h +++ b/pw_log_rpc/public/pw_log_rpc/log_service.h @@ -15,7 +15,6 @@ #pragma once #include "pw_log/proto/log.raw_rpc.pb.h" -#include "pw_log_rpc/log_filter_map.h" #include "pw_log_rpc/rpc_log_drain_map.h" #include "pw_status/status.h" @@ -26,8 +25,7 @@ namespace pw::log_rpc { // and delegated outside the service. class LogService final : public log::pw_rpc::raw::Logs::Service<LogService> { public: - LogService(RpcLogDrainMap& drains, FilterMap* filters = nullptr) - : drains_(drains), filters_(filters) {} + LogService(RpcLogDrainMap& drains) : drains_(drains) {} // Starts listening to logs on the given RPC channel and writer. The call is // ignored if the channel was not pre-registered in the drain map. If there is @@ -36,20 +34,8 @@ class LogService final : public log::pw_rpc::raw::Logs::Service<LogService> { // stream using the previous writer continues. void Listen(ConstByteSpan, rpc::RawServerWriter& writer); - // TODO(pwbug/570): make log filter be its own service. - // Modifies a log filter and its rules. The filter must be registered in the - // provided filter map. - StatusWithSize SetFilter(ConstByteSpan request, ByteSpan); - - // Retrieves a log filter and its rules. The filter must be registered in the - // provided filter map. - StatusWithSize GetFilter(ConstByteSpan request, ByteSpan response); - - StatusWithSize ListFilterIds(ConstByteSpan, ByteSpan response); - private: RpcLogDrainMap& drains_; - FilterMap* filters_; }; } // namespace pw::log_rpc diff --git a/pw_log_rpc/rpc_log_drain_test.cc b/pw_log_rpc/rpc_log_drain_test.cc index 3652950db..7f9ad5f82 100644 --- a/pw_log_rpc/rpc_log_drain_test.cc +++ b/pw_log_rpc/rpc_log_drain_test.cc @@ -106,7 +106,7 @@ TEST(RpcLogDrain, FlushingDrainWithOpenWriter) { nullptr), }; RpcLogDrainMap drain_map(drains); - LogService log_service(drain_map, nullptr); + LogService log_service(drain_map); rpc::RawFakeChannelOutput<3, 128> output; rpc::Channel channel(rpc::Channel::Create<drain_id>(&output)); @@ -145,7 +145,7 @@ TEST(RpcLogDrain, TryReopenOpenedDrain) { nullptr), }; RpcLogDrainMap drain_map(drains); - LogService log_service(drain_map, nullptr); + LogService log_service(drain_map); rpc::RawFakeChannelOutput<1, 128> output; rpc::Channel channel(rpc::Channel::Create<drain_id>(&output)); |