aboutsummaryrefslogtreecommitdiff
path: root/pw_log_rpc
diff options
context:
space:
mode:
authorCarlos Chinchilla <cachinchilla@google.com>2021-12-06 15:37:31 -0800
committerCQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>2022-01-10 21:10:33 +0000
commite4be88b8eafb6354b31cd1aff5fd00fb36adc387 (patch)
tree5f32415c421bcd3fa790606765d82bc95ef62fa5 /pw_log_rpc
parentcc9f186da4b85cdfeef67bc6fc8cd1c5656b0fe7 (diff)
downloadpigweed-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.bazel28
-rw-r--r--pw_log_rpc/BUILD.gn29
-rw-r--r--pw_log_rpc/docs.rst83
-rw-r--r--pw_log_rpc/log_filter_service.cc86
-rw-r--r--pw_log_rpc/log_filter_service_test.cc378
-rw-r--r--pw_log_rpc/log_service.cc75
-rw-r--r--pw_log_rpc/log_service_test.cc401
-rw-r--r--pw_log_rpc/public/pw_log_rpc/log_filter_service.h43
-rw-r--r--pw_log_rpc/public/pw_log_rpc/log_service.h16
-rw-r--r--pw_log_rpc/rpc_log_drain_test.cc4
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));