pw_log_rpc: Add log filter thread name support

- Add supporting infastructure for thread label.
- Add thread label to existing tests for log_service and log_filter.
- Create tests showing thread labels impact.

Change-Id: Id56e9723f82ba44149fcccdc496b8eacd3c5ed8e
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/96622
Reviewed-by: Armando Montanez <amontanez@google.com>
Commit-Queue: Carlos Chinchilla <cachinchilla@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Reviewed-by: Carlos Chinchilla <cachinchilla@google.com>
diff --git a/pw_log/log.proto b/pw_log/log.proto
index 611724a..14c3155 100644
--- a/pw_log/log.proto
+++ b/pw_log/log.proto
@@ -199,6 +199,9 @@
     DROP = 2;      // Drop the log entry if all conditions are met
   };
   Action action = 4;
+
+  // Condition 4: (thread_equals.size() == 0 || log.thread == thread_equals).
+  optional bytes thread_equals = 5 [(tokenizer.format) = TOKENIZATION_OPTIONAL];
 }
 
 // A filter is a series of rules. First matching rule wins.
diff --git a/pw_log_rpc/docs.rst b/pw_log_rpc/docs.rst
index bc0d78c..7586891 100644
--- a/pw_log_rpc/docs.rst
+++ b/pw_log_rpc/docs.rst
@@ -296,8 +296,8 @@
 - ``module_equals``: the condition is met if this byte array is empty, or the
   log module equals the contents of this byte array.
 
-- ``thread``: the condition is met if this byte array is empty or the log
-  thread equals the contents of this byte array.
+- ``thread_equals``: the condition is met if this byte array is empty or the
+  log thread equals the contents of this byte array.
 
 Filter
 ------
diff --git a/pw_log_rpc/log_filter.cc b/pw_log_rpc/log_filter.cc
index 5385be4..044640c 100644
--- a/pw_log_rpc/log_filter.cc
+++ b/pw_log_rpc/log_filter.cc
@@ -25,7 +25,8 @@
 bool IsRuleMet(const Filter::Rule& rule,
                uint32_t level,
                ConstByteSpan module,
-               uint32_t flags) {
+               uint32_t flags,
+               ConstByteSpan thread) {
   if (level < static_cast<uint32_t>(rule.level_greater_than_or_equal)) {
     return false;
   }
@@ -38,6 +39,12 @@
                                                  rule.module_equals.end())) {
     return false;
   }
+  if (!rule.thread_equals.empty() && !std::equal(thread.begin(),
+                                                 thread.end(),
+                                                 rule.thread_equals.begin(),
+                                                 rule.thread_equals.end())) {
+    return false;
+  }
   return true;
 }
 
@@ -82,6 +89,14 @@
           PW_TRY(rule_decoder.ReadUint32(
               reinterpret_cast<uint32_t*>(&rules_[i].action)));
           break;
+        case log::FilterRule::Fields::THREAD_EQUALS: {
+          ConstByteSpan thread;
+          PW_TRY(rule_decoder.ReadBytes(&thread));
+          if (thread.size() > rules_[i].thread_equals.max_size()) {
+            return Status::InvalidArgument();
+          }
+          rules_[i].thread_equals.assign(thread.begin(), thread.end());
+        } break;
       }
     }
   }
@@ -95,6 +110,7 @@
 
   uint32_t log_level = 0;
   ConstByteSpan log_module;
+  ConstByteSpan log_thread;
   uint32_t log_flags = 0;
   protobuf::Decoder decoder(entry);
   while (decoder.Next().ok()) {
@@ -110,6 +126,9 @@
       case log::LogEntry::Fields::FLAGS:
         decoder.ReadUint32(&log_flags).IgnoreError();
         break;
+      case log::LogEntry::Fields::THREAD:
+        decoder.ReadBytes(&log_thread).IgnoreError();
+        break;
       default:
         break;
     }
@@ -120,7 +139,7 @@
     if (rule.action == Filter::Rule::Action::kInactive) {
       continue;
     }
-    if (IsRuleMet(rule, log_level, log_module, log_flags)) {
+    if (IsRuleMet(rule, log_level, log_module, log_flags, log_thread)) {
       return rule.action == Filter::Rule::Action::kDrop;
     }
   }
diff --git a/pw_log_rpc/log_filter_service.cc b/pw_log_rpc/log_filter_service.cc
index 65e3974..2d8b63c 100644
--- a/pw_log_rpc/log_filter_service.cc
+++ b/pw_log_rpc/log_filter_service.cc
@@ -69,6 +69,7 @@
     rule_encoder.WriteAnyFlagsSet(rule.any_flags_set).IgnoreError();
     rule_encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action))
         .IgnoreError();
+    rule_encoder.WriteThreadEquals(rule.thread_equals).IgnoreError();
     PW_TRY_WITH_SIZE(rule_encoder.status());
   }
   PW_TRY_WITH_SIZE(encoder.status());
diff --git a/pw_log_rpc/log_filter_service_test.cc b/pw_log_rpc/log_filter_service_test.cc
index d0d9afc..a608caf 100644
--- a/pw_log_rpc/log_filter_service_test.cc
+++ b/pw_log_rpc/log_filter_service_test.cc
@@ -99,6 +99,7 @@
       encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal));
   PW_TRY(encoder.WriteModuleEquals(rule.module_equals));
   PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set));
+  PW_TRY(encoder.WriteThreadEquals(rule.thread_equals));
   return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action));
 }
 
@@ -131,6 +132,7 @@
             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.thread_equals, expected_rule.thread_equals);
   EXPECT_EQ(rule.action, expected_rule.action);
 }
 
@@ -141,24 +143,36 @@
           .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
           .any_flags_set = 0x0f,
           .module_equals{std::byte(123)},
+          .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
       },
       {
           .action = Filter::Rule::Action::kInactive,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xef,
           .module_equals{},
+          .thread_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)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
       {
           .action = Filter::Rule::Action::kDrop,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0,
           .module_equals{std::byte(4)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
   }};
   const Filter new_filter(filters_[0].id(),
@@ -186,24 +200,40 @@
           .level_greater_than_or_equal = log::FilterRule::Level::CRITICAL_LEVEL,
           .any_flags_set = 0xfd,
           .module_equals{std::byte(543)},
+          .thread_equals{std::byte('M'),
+                         std::byte('0'),
+                         std::byte('L'),
+                         std::byte('O'),
+                         std::byte('G')},
       },
       {
           .action = Filter::Rule::Action::kInactive,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xca,
           .module_equals{},
+          .thread_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)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
       {
           .action = Filter::Rule::Action::kDrop,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0,
           .module_equals{std::byte(123)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
   }};
   Filter& filter = filters_[0];
@@ -242,24 +272,28 @@
           .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
           .any_flags_set = 0xab,
           .module_equals{},
+          .thread_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)},
+          .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
       },
       {
           .action = Filter::Rule::Action::kKeep,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xef,
           .module_equals{std::byte(23)},
+          .thread_equals{std::byte('R'), std::byte('P'), std::byte('C')},
       },
       {
           .action = Filter::Rule::Action::kDrop,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0x0f,
           .module_equals{},
+          .thread_equals{std::byte('R'), std::byte('P'), std::byte('C')},
       },
   }};
   const Filter second_filter(
@@ -284,7 +318,9 @@
 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
+  ASSERT_EQ(decoder.FieldNumber(),
+            static_cast<uint32_t>(
+                log::FilterRule::Fields::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)),
@@ -293,7 +329,8 @@
             expected_rule.level_greater_than_or_equal);
 
   ASSERT_TRUE(decoder.Next().ok());
-  ASSERT_EQ(decoder.FieldNumber(), 2u);  // module_equals
+  ASSERT_EQ(decoder.FieldNumber(),
+            static_cast<uint32_t>(log::FilterRule::Fields::MODULE_EQUALS));
   ConstByteSpan module_equals;
   ASSERT_EQ(decoder.ReadBytes(&module_equals), OkStatus());
   ASSERT_EQ(module_equals.size(), expected_rule.module_equals.size());
@@ -303,17 +340,30 @@
             0);
 
   ASSERT_TRUE(decoder.Next().ok());
-  ASSERT_EQ(decoder.FieldNumber(), 3u);  // any_flags_set
+  ASSERT_EQ(decoder.FieldNumber(),
+            static_cast<uint32_t>(log::FilterRule::Fields::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
+  ASSERT_EQ(decoder.FieldNumber(),
+            static_cast<uint32_t>(log::FilterRule::Fields::ACTION));
   Filter::Rule::Action action;
   ASSERT_EQ(decoder.ReadUint32(reinterpret_cast<uint32_t*>(&action)),
             OkStatus());
   EXPECT_EQ(action, expected_rule.action);
+
+  ASSERT_TRUE(decoder.Next().ok());
+  ASSERT_EQ(decoder.FieldNumber(),
+            static_cast<uint32_t>(log::FilterRule::Fields::THREAD_EQUALS));
+  ConstByteSpan thread;
+  ASSERT_EQ(decoder.ReadBytes(&thread), OkStatus());
+  ASSERT_EQ(thread.size(), expected_rule.thread_equals.size());
+  EXPECT_EQ(
+      std::memcmp(
+          thread.data(), expected_rule.thread_equals.data(), thread.size()),
+      0);
 }
 
 void VerifyFilterRules(protobuf::Decoder& decoder,
@@ -355,6 +405,9 @@
   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());
+  const std::array<std::byte, 4> thread1{
+      std::byte('H'), std::byte('O'), std::byte('S'), std::byte('T')};
+  rules1_[0].thread_equals.assign(thread1.begin(), thread1.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;
@@ -373,6 +426,9 @@
   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());
+  const std::array<std::byte, 3> thread2{
+      std::byte('A'), std::byte('P'), std::byte('P')};
+  rules1_[2].thread_equals.assign(thread2.begin(), thread2.end());
   rules1_[3].action = Filter::Rule::Action::kInactive;
 
   PW_RAW_TEST_METHOD_CONTEXT(FilterService, GetFilter, 1)
diff --git a/pw_log_rpc/log_filter_test.cc b/pw_log_rpc/log_filter_test.cc
index be1fcdd..8f85fef 100644
--- a/pw_log_rpc/log_filter_test.cc
+++ b/pw_log_rpc/log_filter_test.cc
@@ -35,6 +35,8 @@
 
 constexpr uint32_t kSampleModule = 0x1234;
 constexpr uint32_t kSampleFlags = 0x3;
+const std::array<std::byte, cfg::kMaxThreadNameBytes - 7> kSampleThread = {
+    std::byte('R'), std::byte('P'), std::byte('C')};
 constexpr char kSampleMessage[] = "message";
 constexpr auto kSampleModuleLittleEndian =
     bytes::CopyInOrder<uint32_t>(std::endian::little, kSampleModule);
@@ -42,12 +44,13 @@
 // Creates and encodes a log entry in the provided buffer.
 template <uintptr_t log_level, uintptr_t module, uintptr_t flags>
 Result<ConstByteSpan> EncodeLogEntry(std::string_view message,
-                                     ByteSpan buffer) {
+                                     ByteSpan buffer,
+                                     ConstByteSpan thread) {
   auto metadata = log_tokenized::Metadata::Set<log_level, module, flags, 0>();
   return log::EncodeTokenizedLog(metadata,
                                  std::as_bytes(std::span(message)),
                                  /*ticks_since_epoch=*/0,
-                                 /*thread_name=*/{},
+                                 thread,
                                  buffer);
 }
 
@@ -57,6 +60,7 @@
       encoder.WriteLevelGreaterThanOrEqual(rule.level_greater_than_or_equal));
   PW_TRY(encoder.WriteModuleEquals(rule.module_equals));
   PW_TRY(encoder.WriteAnyFlagsSet(rule.any_flags_set));
+  PW_TRY(encoder.WriteThreadEquals(rule.thread_equals));
   return encoder.WriteAction(static_cast<log::FilterRule::Action>(rule.action));
 }
 
@@ -74,6 +78,7 @@
             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.thread_equals, expected_rule.thread_equals);
   EXPECT_EQ(rule.action, expected_rule.action);
 }
 
@@ -128,24 +133,32 @@
           .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
           .any_flags_set = 0x0f,
           .module_equals{std::byte(123)},
+          .thread_equals{},
       },
       {
           .action = Filter::Rule::Action::kInactive,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xef,
           .module_equals{},
+          .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
       },
       {
           .action = Filter::Rule::Action::kKeep,
           .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
           .any_flags_set = 0x1234,
           .module_equals{std::byte(99)},
+          .thread_equals{},
       },
       {
           .action = Filter::Rule::Action::kDrop,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0,
           .module_equals{std::byte(4)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
   }};
 
@@ -173,6 +186,7 @@
       .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
       .any_flags_set = 0,
       .module_equals{},
+      .thread_equals{},
   };
   for (const auto& rule : filter.rules()) {
     VerifyRule(rule, empty_rule);
@@ -186,12 +200,18 @@
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xef,
           .module_equals{},
+          .thread_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)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
   }};
   const Filter filter_few_rules(
@@ -216,36 +236,50 @@
           .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
           .any_flags_set = 0x0f,
           .module_equals{std::byte(123)},
+          .thread_equals{std::byte('P'),
+                         std::byte('O'),
+                         std::byte('W'),
+                         std::byte('E'),
+                         std::byte('R')},
       },
       {
           .action = Filter::Rule::Action::kInactive,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xef,
           .module_equals{},
+          .thread_equals{},
       },
       {
           .action = Filter::Rule::Action::kInactive,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0xef,
           .module_equals{},
+          .thread_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)},
+          .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
       },
       {
           .action = Filter::Rule::Action::kDrop,
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0,
           .module_equals{std::byte(4)},
+          .thread_equals{std::byte('L'), std::byte('O'), std::byte('G')},
       },
       {
           .action = Filter::Rule::Action::kKeep,
           .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
           .any_flags_set = 0x1234,
-          .module_equals{std::byte(99)},
+          .module_equals{std::byte('M'),
+                         std::byte('0'),
+                         std::byte('L'),
+                         std::byte('O'),
+                         std::byte('G')},
+          .thread_equals{},
       },
   }};
   const Filter filter_extra_rules(
@@ -273,6 +307,7 @@
           .any_flags_set = kSampleFlags,
           .module_equals{kSampleModuleLittleEndian.begin(),
                          kSampleModuleLittleEndian.end()},
+          .thread_equals{kSampleThread.begin(), kSampleThread.end()},
       },
       // This rule catches all logs.
       {
@@ -280,6 +315,7 @@
           .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
           .any_flags_set = 0,
           .module_equals = {},
+          .thread_equals{},
       },
   }};
   const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
@@ -290,34 +326,34 @@
   std::array<std::byte, 50> buffer;
   const Result<ConstByteSpan> log_entry_info =
       EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_info.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_debug =
       EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_debug.status(), OkStatus());
   EXPECT_TRUE(filter.ShouldDropLog(log_entry_debug.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_warn =
       EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_warn.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_error =
       EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_error.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_info_different =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
+      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_info_different.status(), OkStatus());
   EXPECT_TRUE(filter.ShouldDropLog(log_entry_info_different.value()));
   // Because the last rule catches all logs, the filter default action is not
@@ -329,15 +365,21 @@
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_same_flags =
-      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
+      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
   EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_flags.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_same_module =
-      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
+      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_same_module.status(), OkStatus());
   EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_module.value()));
+
+  buffer.fill(std::byte(0));
+  const Result<ConstByteSpan> log_entry_same_thread =
+      EncodeLogEntry<0, 0, 0>(kSampleMessage, buffer, kSampleThread);
+  ASSERT_EQ(log_entry_same_thread.status(), OkStatus());
+  EXPECT_TRUE(filter.ShouldDropLog(log_entry_same_thread.value()));
 }
 
 TEST(FilterTest, FilterLogsKeepLogsWhenNoRuleMatches) {
@@ -349,6 +391,7 @@
           .any_flags_set = kSampleFlags,
           .module_equals = {kSampleModuleLittleEndian.begin(),
                             kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
       },
   }};
 
@@ -362,48 +405,54 @@
   std::array<std::byte, 50> buffer;
   const Result<ConstByteSpan> log_entry_info =
       EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_info.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_debug =
       EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_debug.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_warn =
       EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_warn.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_error =
       EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_error.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_info_different =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
+      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_info_different.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_same_flags =
-      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
+      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_same_module =
-      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
+      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_same_module.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value()));
+
+  buffer.fill(std::byte(0));
+  const Result<ConstByteSpan> log_entry_same_thread =
+      EncodeLogEntry<0, 0, 0>(kSampleMessage, buffer, kSampleThread);
+  ASSERT_EQ(log_entry_same_thread.status(), OkStatus());
+  EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_thread.value()));
 }
 
 TEST(FilterTest, FilterLogsKeepLogsWhenRulesEmpty) {
@@ -416,48 +465,54 @@
   std::array<std::byte, 50> buffer;
   const Result<ConstByteSpan> log_entry_info =
       EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_info.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_debug =
       EncodeLogEntry<PW_LOG_LEVEL_DEBUG, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_debug.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_debug.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_warn =
       EncodeLogEntry<PW_LOG_LEVEL_WARN, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_warn.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_warn.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_error =
       EncodeLogEntry<PW_LOG_LEVEL_ERROR, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_error.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_error.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_info_different =
-      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer);
+      EncodeLogEntry<PW_LOG_LEVEL_INFO, 0, 0>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_info_different.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_info_different.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_same_flags =
-      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer);
+      EncodeLogEntry<0, 0, kSampleFlags>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_same_flags.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_flags.value()));
 
   buffer.fill(std::byte(0));
   const Result<ConstByteSpan> log_entry_same_module =
-      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer);
+      EncodeLogEntry<0, kSampleModule, 0>(kSampleMessage, buffer, {});
   ASSERT_EQ(log_entry_same_module.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_module.value()));
+
+  buffer.fill(std::byte(0));
+  const Result<ConstByteSpan> log_entry_same_thread =
+      EncodeLogEntry<0, 0, 0>(kSampleMessage, buffer, kSampleThread);
+  ASSERT_EQ(log_entry_same_thread.status(), OkStatus());
+  EXPECT_FALSE(filter.ShouldDropLog(log_entry_same_thread.value()));
 }
 
 TEST(FilterTest, FilterLogsFirstRuleWins) {
@@ -468,6 +523,7 @@
           .any_flags_set = kSampleFlags,
           .module_equals = {kSampleModuleLittleEndian.begin(),
                             kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
       },
       {
           .action = Filter::Rule::Action::kDrop,
@@ -475,6 +531,7 @@
           .any_flags_set = kSampleFlags,
           .module_equals = {kSampleModuleLittleEndian.begin(),
                             kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
       },
   }};
   const std::array<Filter::Rule, 2> rules_reversed{{
@@ -484,6 +541,7 @@
           .any_flags_set = kSampleFlags,
           .module_equals = {kSampleModuleLittleEndian.begin(),
                             kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
       },
       {
           .action = Filter::Rule::Action::kKeep,
@@ -491,6 +549,7 @@
           .any_flags_set = kSampleFlags,
           .module_equals = {kSampleModuleLittleEndian.begin(),
                             kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
       },
   }};
   const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{
@@ -505,11 +564,157 @@
   std::array<std::byte, 50> buffer;
   const Result<ConstByteSpan> log_entry_info =
       EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
-          kSampleMessage, buffer);
+          kSampleMessage, buffer, kSampleThread);
   ASSERT_EQ(log_entry_info.status(), OkStatus());
   EXPECT_FALSE(filter.ShouldDropLog(log_entry_info.value()));
   EXPECT_TRUE(filter_reverse_rules.ShouldDropLog(log_entry_info.value()));
 }
 
+TEST(FilterTest, DropFilterRuleDueToThreadName) {
+  const std::array<std::byte, cfg::kMaxThreadNameBytes - 7> kDropThread = {
+      std::byte('L'), std::byte('O'), std::byte('G')};
+  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 = kSampleFlags,
+          .module_equals = {kSampleModuleLittleEndian.begin(),
+                            kSampleModuleLittleEndian.end()},
+          .thread_equals = {kDropThread.begin(), kDropThread.end()},
+      },
+      {
+          .action = Filter::Rule::Action::kDrop,
+          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
+          .any_flags_set = kSampleFlags,
+          .module_equals = {kSampleModuleLittleEndian.begin(),
+                            kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
+      },
+  }};
+
+  const std::array<Filter::Rule, 2> drop_rule{{
+      {
+          .action = Filter::Rule::Action::kDrop,
+          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
+          .any_flags_set = kSampleFlags,
+          .module_equals = {kSampleModuleLittleEndian.begin(),
+                            kSampleModuleLittleEndian.end()},
+          .thread_equals = {kDropThread.begin(), kDropThread.end()},
+      },
+  }};
+
+  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id1{
+      std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)};
+  // A filter's thread_equals name that does and does not match the log's thread
+  // name.
+  const Filter filter(filter_id1,
+                      const_cast<std::array<Filter::Rule, 2>&>(rules));
+  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id2{
+      std::byte(0), std::byte(0), std::byte(0), std::byte(2)};
+  // A filter's thread_equals name that does not match the log's thread name.
+  const Filter filter_with_unregistered_filter_rule(
+      filter_id2, const_cast<std::array<Filter::Rule, 2>&>(drop_rule));
+  std::array<std::byte, 50> buffer;
+  const Result<ConstByteSpan> log_entry_thread =
+      EncodeLogEntry<PW_LOG_LEVEL_INFO, kSampleModule, kSampleFlags>(
+          kSampleMessage, buffer, kSampleThread);
+  ASSERT_EQ(log_entry_thread.status(), OkStatus());
+  // Set filter rules to kDrop to showcase the output difference.
+  // Drop_rule not being dropped, while rules is dropped successfully.
+  EXPECT_TRUE(filter.ShouldDropLog(log_entry_thread.value()));
+  EXPECT_FALSE(filter_with_unregistered_filter_rule.ShouldDropLog(
+      log_entry_thread.value()));
+}
+
+TEST(FilterTest, UpdateFilterWithLargeThreadNamePasses) {
+  // Threads are limited to a size of kMaxThreadNameBytes.
+  // However, the excess bytes will not be in the updated rules.
+  const std::array<std::byte, cfg::kMaxThreadNameBytes + 1>
+      kThreadNameLongerThanAllowed = {
+          std::byte('L'),
+          std::byte('O'),
+          std::byte('C'),
+          std::byte('A'),
+          std::byte('L'),
+          std::byte('E'),
+          std::byte('G'),
+          std::byte('R'),
+          std::byte('E'),
+          std::byte('S'),
+          std::byte('S'),
+      };
+
+  const std::array<Filter::Rule, 2> rule{{
+      {
+          .action = Filter::Rule::Action::kKeep,
+          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
+          .any_flags_set = kSampleFlags,
+          .module_equals = {kSampleModuleLittleEndian.begin(),
+                            kSampleModuleLittleEndian.end()},
+          .thread_equals = {kThreadNameLongerThanAllowed.begin(),
+                            kThreadNameLongerThanAllowed.end()},
+      },
+      {
+          .action = Filter::Rule::Action::kKeep,
+          .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
+          .any_flags_set = kSampleFlags,
+          .module_equals = {kSampleModuleLittleEndian.begin(),
+                            kSampleModuleLittleEndian.end()},
+          .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
+      },
+  }};
+
+  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
+      std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)};
+  Filter filter(filter_id, const_cast<std::array<Filter::Rule, 2>&>(rule));
+  std::byte buffer[256];
+  auto encode_result = EncodeFilter(filter, buffer);
+  ASSERT_EQ(encode_result.status(), OkStatus());
+  EXPECT_EQ(filter.UpdateRulesFromProto(encode_result.value()), OkStatus());
+  size_t i = 0;
+  for (const auto& rules : filter.rules()) {
+    VerifyRule(rules, rule[i++]);
+  }
+}
+
+TEST(FilterTest, UpdateFilterWithLargeThreadNameFails) {
+  const std::array<Filter::Rule, 1> rule_with_more_than_ten_bytes{{{
+      .action = Filter::Rule::Action::kKeep,
+      .level_greater_than_or_equal = log::FilterRule::Level::INFO_LEVEL,
+      .any_flags_set = kSampleFlags,
+      .module_equals = {kSampleModuleLittleEndian.begin(),
+                        kSampleModuleLittleEndian.end()},
+      .thread_equals = {kSampleThread.begin(), kSampleThread.end()},
+  }}};
+  const std::array<std::byte, cfg::kMaxFilterIdBytes> filter_id{
+      std::byte(0xba), std::byte(0x1d), std::byte(0xba), std::byte(0xb1)};
+  Filter filter(
+      filter_id,
+      const_cast<std::array<Filter::Rule, 1>&>(rule_with_more_than_ten_bytes));
+  std::byte buffer[256];
+  log::Filter::MemoryEncoder encoder(buffer);
+  {
+    std::array<const std::byte, cfg::kMaxThreadNameBytes + 1>
+        kThreadNameLongerThanAllowed = {
+            std::byte('L'),
+            std::byte('O'),
+            std::byte('C'),
+            std::byte('A'),
+            std::byte('L'),
+            std::byte('E'),
+            std::byte('G'),
+            std::byte('R'),
+            std::byte('E'),
+            std::byte('S'),
+            std::byte('S'),
+        };
+    // Stream encoder writes to the buffer when it goes out of scope.
+    log::FilterRule::StreamEncoder rule_encoder = encoder.GetRuleEncoder();
+    ASSERT_EQ(rule_encoder.WriteThreadEquals(kThreadNameLongerThanAllowed),
+              OkStatus());
+  }
+  EXPECT_EQ(filter.UpdateRulesFromProto(ConstByteSpan(encoder)),
+            Status::InvalidArgument());
+}
 }  // namespace
 }  // namespace pw::log_rpc
diff --git a/pw_log_rpc/log_service_test.cc b/pw_log_rpc/log_service_test.cc
index 1072ec5..2527228 100644
--- a/pw_log_rpc/log_service_test.cc
+++ b/pw_log_rpc/log_service_test.cc
@@ -60,6 +60,11 @@
 static_assert(sizeof(kLongMessage) + RpcLogDrain::kMinEntrySizeWithoutPayload >
               RpcLogDrain::kMinEntryBufferSize);
 std::array<std::byte, 1> rpc_request_buffer;
+const std::array<std::byte, 5> kSampleThread = {std::byte('M'),
+                                                std::byte('0'),
+                                                std::byte('L'),
+                                                std::byte('O'),
+                                                std::byte('G')};
 constexpr auto kSampleMetadata =
     log_tokenized::Metadata::Set<PW_LOG_LEVEL_INFO, 123, 0x03, __LINE__>();
 constexpr auto kDropMessageMetadata =
@@ -82,20 +87,22 @@
   void AddLogEntries(size_t log_count,
                      std::string_view message,
                      log_tokenized::Metadata metadata,
-                     int64_t timestamp) {
+                     int64_t timestamp,
+                     ConstByteSpan thread) {
     for (size_t i = 0; i < log_count; ++i) {
-      ASSERT_TRUE(AddLogEntry(message, metadata, timestamp).ok());
+      ASSERT_TRUE(AddLogEntry(message, metadata, timestamp, thread).ok());
     }
   }
 
   StatusWithSize AddLogEntry(std::string_view message,
                              log_tokenized::Metadata metadata,
-                             int64_t timestamp) {
+                             int64_t timestamp,
+                             ConstByteSpan thread) {
     Result<ConstByteSpan> encoded_log_result =
         log::EncodeTokenizedLog(metadata,
                                 std::as_bytes(std::span(message)),
                                 timestamp,
-                                /*thread_name=*/{},
+                                thread,
                                 entry_encode_buffer_);
     PW_TRY_WITH_SIZE(encoded_log_result.status());
     multisink_.HandleEntry(encoded_log_result.value());
@@ -203,7 +210,7 @@
 
   // Add log entries.
   const size_t total_entries = 10;
-  AddLogEntries(total_entries, kMessage, kSampleMetadata, kSampleTimestamp);
+  AddLogEntries(total_entries, kMessage, kSampleMetadata, kSampleTimestamp, {});
 
   // Request logs.
   context.call(rpc_request_buffer);
@@ -221,10 +228,11 @@
   // Verify data in responses.
   Vector<TestLogEntry, total_entries> expected_messages;
   for (size_t i = 0; i < total_entries; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
+    expected_messages.push_back(
+        {.metadata = kSampleMetadata,
+         .timestamp = kSampleTimestamp,
+         .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+         .thread = {}});
   }
   size_t entries_found = 0;
   uint32_t drop_count_found = 0;
@@ -252,13 +260,17 @@
   const uint32_t total_drop_count = 2;
 
   // Force a drop entry in between entries.
-  AddLogEntries(
-      entries_before_drop, kMessage, kSampleMetadata, kSampleTimestamp);
+  AddLogEntries(entries_before_drop,
+                kMessage,
+                kSampleMetadata,
+                kSampleTimestamp,
+                kSampleThread);
   multisink_.HandleDropped(total_drop_count);
   AddLogEntries(total_entries - entries_before_drop,
                 kMessage,
                 kSampleMetadata,
-                kSampleTimestamp);
+                kSampleTimestamp,
+                kSampleThread);
 
   // Request logs.
   context.call(rpc_request_buffer);
@@ -271,21 +283,24 @@
   Vector<TestLogEntry, total_entries + 1> expected_messages;
   size_t i = 0;
   for (; i < entries_before_drop; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
+    expected_messages.push_back(
+        {.metadata = kSampleMetadata,
+         .timestamp = kSampleTimestamp,
+         .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+         .thread = kSampleThread});
   }
   expected_messages.push_back(
       {.metadata = kDropMessageMetadata,
        .dropped = total_drop_count,
        .tokenized_data = std::as_bytes(
-           std::span(std::string_view(RpcLogDrain::kIngressErrorMessage)))});
+           std::span(std::string_view(RpcLogDrain::kIngressErrorMessage))),
+       .thread = {}});
   for (; i < total_entries; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
+    expected_messages.push_back(
+        {.metadata = kSampleMetadata,
+         .timestamp = kSampleTimestamp,
+         .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+         .thread = kSampleThread});
   }
 
   // Verify data in responses.
@@ -320,14 +335,16 @@
   for (size_t i = 1; i < total_entries; ++i) {
     ASSERT_EQ(
         OkStatus(),
-        AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp).status());
+        AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp, kSampleThread)
+            .status());
     multisink_.HandleDropped(1);
   }
   // Add message that won't be filtered out.
   constexpr auto metadata =
       log_tokenized::Metadata::Set<PW_LOG_LEVEL_DEBUG, 0, 0, __LINE__>();
   ASSERT_EQ(OkStatus(),
-            AddLogEntry(kMessage, metadata, kSampleTimestamp).status());
+            AddLogEntry(kMessage, metadata, kSampleTimestamp, kSampleThread)
+                .status());
 
   // Request logs.
   context.call(rpc_request_buffer);
@@ -342,11 +359,13 @@
       {.metadata = kDropMessageMetadata,
        .dropped = total_drop_count,
        .tokenized_data = std::as_bytes(
-           std::span(std::string_view(RpcLogDrain::kIngressErrorMessage)))});
+           std::span(std::string_view(RpcLogDrain::kIngressErrorMessage))),
+       .thread = {}});
   expected_messages.push_back(
       {.metadata = metadata,
        .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))});
+       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+       .thread = kSampleThread});
 
   // Verify data in responses.
   size_t entries_found = 0;
@@ -374,10 +393,15 @@
   // one, since drop count messages are only sent when a log entry can be sent.
   const size_t total_entries = 5;
   const uint32_t total_drop_count = total_entries - 1;
-  AddLogEntries(
-      total_drop_count, kLongMessage, kSampleMetadata, kSampleTimestamp);
-  EXPECT_EQ(OkStatus(),
-            AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp).status());
+  AddLogEntries(total_drop_count,
+                kLongMessage,
+                kSampleMetadata,
+                kSampleTimestamp,
+                kSampleThread);
+  EXPECT_EQ(
+      OkStatus(),
+      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp, kSampleThread)
+          .status());
 
   // Request logs.
   context.call(rpc_request_buffer);
@@ -391,11 +415,13 @@
       {.metadata = kDropMessageMetadata,
        .dropped = total_drop_count,
        .tokenized_data = std::as_bytes(std::span(
-           std::string_view(RpcLogDrain::kSmallStackBufferErrorMessage)))});
+           std::string_view(RpcLogDrain::kSmallStackBufferErrorMessage))),
+       .thread = {}});
   expected_messages.push_back(
       {.metadata = kSampleMetadata,
        .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))});
+       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+       .thread = kSampleThread});
 
   // Expect one drop message with the total drop count, and the only message
   // that fits the buffer.
@@ -421,7 +447,11 @@
 
   // Add log entries.
   const size_t total_entries = 5;
-  AddLogEntries(total_entries, kMessage, kSampleMetadata, kSampleTimestamp);
+  AddLogEntries(total_entries,
+                kMessage,
+                kSampleMetadata,
+                kSampleTimestamp,
+                kSampleThread);
   // Request logs.
   context.call(rpc_request_buffer);
   EXPECT_EQ(detached_drain.Close(), OkStatus());
@@ -438,6 +468,7 @@
                                        (1 << PW_LOG_TOKENIZED_LINE_BITS) - 1>(),
       .timestamp = std::numeric_limits<int64_t>::max(),
       .tokenized_data = std::as_bytes(std::span(kMessage)),
+      .thread = kSampleThread,
   };
 
   // Add entry to multisink.
@@ -455,6 +486,7 @@
   ASSERT_EQ(
       encoder.WriteModule(std::as_bytes(std::span(&little_endian_module, 1))),
       OkStatus());
+  ASSERT_EQ(encoder.WriteThread(expected_entry.thread), OkStatus());
   ASSERT_EQ(encoder.status(), OkStatus());
   multisink_.HandleEntry(encoder);
 
@@ -493,7 +525,7 @@
 
   // Add as many entries needed to have multiple packets send.
   StatusWithSize status =
-      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp);
+      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp, kSampleThread);
   ASSERT_TRUE(status.ok());
 
   const uint32_t max_messages_per_response =
@@ -504,7 +536,11 @@
   const size_t max_entries = 50;
   // Check we can test all these entries.
   ASSERT_GE(max_entries, total_entries);
-  AddLogEntries(total_entries - 1, kMessage, kSampleMetadata, kSampleTimestamp);
+  AddLogEntries(total_entries - 1,
+                kMessage,
+                kSampleMetadata,
+                kSampleTimestamp,
+                kSampleThread);
 
   // Interrupt log stream with an error.
   const uint32_t successful_packets_sent = packets_sent / 2;
@@ -524,10 +560,11 @@
   // Verify data in responses.
   Vector<TestLogEntry, max_entries> expected_messages;
   for (size_t i = 0; i < total_entries; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
+    expected_messages.push_back(
+        {.metadata = kSampleMetadata,
+         .timestamp = kSampleTimestamp,
+         .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+         .thread = kSampleThread});
   }
   size_t entries_found = 0;
   uint32_t drop_count_found = 0;
@@ -568,8 +605,8 @@
     expected_messages_after_reset.push_back(
         {.metadata = kSampleMetadata,
          .timestamp = kSampleTimestamp,
-         .tokenized_data =
-             std::as_bytes(std::span(std::string_view(kMessage)))});
+         .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+         .thread = kSampleThread});
   }
 
   size_t entries_found_after_reset = 0;
@@ -600,7 +637,7 @@
 
   // Add as many entries needed to have multiple packets send.
   StatusWithSize status =
-      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp);
+      AddLogEntry(kMessage, kSampleMetadata, kSampleTimestamp, kSampleThread);
   ASSERT_TRUE(status.ok());
 
   const uint32_t max_messages_per_response =
@@ -611,7 +648,11 @@
   const size_t max_entries = 50;
   // Check we can test all these entries.
   ASSERT_GT(max_entries, total_entries);
-  AddLogEntries(total_entries - 1, kMessage, kSampleMetadata, kSampleTimestamp);
+  AddLogEntries(total_entries - 1,
+                kMessage,
+                kSampleMetadata,
+                kSampleTimestamp,
+                kSampleThread);
 
   // Interrupt log stream with an error.
   const uint32_t error_on_packet_count = packets_sent / 2;
@@ -640,10 +681,11 @@
   const uint32_t total_drop_count = total_entries - entries_found;
   Vector<TestLogEntry, max_entries> expected_messages;
   for (size_t i = 0; i < entries_found; ++i) {
-    expected_messages.push_back({.metadata = kSampleMetadata,
-                                 .timestamp = kSampleTimestamp,
-                                 .tokenized_data = std::as_bytes(
-                                     std::span(std::string_view(kMessage)))});
+    expected_messages.push_back(
+        {.metadata = kSampleMetadata,
+         .timestamp = kSampleTimestamp,
+         .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+         .thread = kSampleThread});
   }
 
   entries_found = 0;
@@ -687,37 +729,68 @@
   const uint32_t module = 0xcafe;
   const uint32_t flags = 0x02;
   const uint32_t line_number = 100;
+  const std::array<std::byte, 3> kNewThread = {
+      std::byte('A'), std::byte('P'), std::byte('P')};
+  const std::array<std::byte, 3> kInvalidThread = {
+      std::byte('C'), std::byte('D'), std::byte('C')};
   const auto debug_metadata = log_tokenized::Metadata::
       Set<PW_LOG_LEVEL_DEBUG, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, debug_metadata, kSampleTimestamp).ok());
+  ASSERT_TRUE(
+      AddLogEntry(kMessage, debug_metadata, kSampleTimestamp, kSampleThread)
+          .ok());
   const auto info_metadata = log_tokenized::Metadata::
       Set<PW_LOG_LEVEL_INFO, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, info_metadata, kSampleTimestamp).ok());
+  ASSERT_TRUE(
+      AddLogEntry(kMessage, info_metadata, kSampleTimestamp, kSampleThread)
+          .ok());
   const auto warn_metadata = log_tokenized::Metadata::
       Set<PW_LOG_LEVEL_WARN, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, warn_metadata, kSampleTimestamp).ok());
+  ASSERT_TRUE(
+      AddLogEntry(kMessage, warn_metadata, kSampleTimestamp, kSampleThread)
+          .ok());
   const auto error_metadata = log_tokenized::Metadata::
       Set<PW_LOG_LEVEL_ERROR, module, flags, line_number>();
-  ASSERT_TRUE(AddLogEntry(kMessage, error_metadata, kSampleTimestamp).ok());
+  ASSERT_TRUE(
+      AddLogEntry(kMessage, error_metadata, kSampleTimestamp, kNewThread).ok());
   const auto different_flags_metadata = log_tokenized::Metadata::
       Set<PW_LOG_LEVEL_ERROR, module, 0x01, line_number>();
   ASSERT_TRUE(
-      AddLogEntry(kMessage, different_flags_metadata, kSampleTimestamp).ok());
+      AddLogEntry(
+          kMessage, different_flags_metadata, kSampleTimestamp, kSampleThread)
+          .ok());
   const auto different_module_metadata = log_tokenized::Metadata::
       Set<PW_LOG_LEVEL_ERROR, 0xabcd, flags, line_number>();
   ASSERT_TRUE(
-      AddLogEntry(kMessage, different_module_metadata, kSampleTimestamp).ok());
+      AddLogEntry(
+          kMessage, different_module_metadata, kSampleTimestamp, kSampleThread)
+          .ok());
+  const auto second_info_metadata = log_tokenized::Metadata::
+      Set<PW_LOG_LEVEL_INFO, module, flags, line_number>();
+  ASSERT_TRUE(
+      AddLogEntry(kMessage, second_info_metadata, kSampleTimestamp, kNewThread)
+          .ok());
+  const auto metadata = log_tokenized::Metadata::
+      Set<PW_LOG_LEVEL_INFO, module, flags, line_number>();
+  ASSERT_TRUE(
+      AddLogEntry(kMessage, metadata, kSampleTimestamp, kInvalidThread).ok());
 
-  Vector<TestLogEntry, 3> expected_messages{
+  Vector<TestLogEntry, 4> expected_messages{
       {.metadata = info_metadata,
        .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))},
+       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+       .thread = kSampleThread},
       {.metadata = warn_metadata,
        .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))},
+       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+       .thread = kSampleThread},
       {.metadata = error_metadata,
        .timestamp = kSampleTimestamp,
-       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage)))},
+       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+       .thread = kNewThread},
+      {.metadata = second_info_metadata,
+       .timestamp = kSampleTimestamp,
+       .tokenized_data = std::as_bytes(std::span(std::string_view(kMessage))),
+       .thread = kNewThread},
   };
 
   // Set up filter rules for drain at drains_[1].
@@ -731,13 +804,26 @@
       .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()}};
+      .module_equals{module_little_endian.begin(), module_little_endian.end()},
+      .thread_equals{kSampleThread.begin(), kSampleThread.end()}};
   rules2_[1] = {
+      .action = Filter::Rule::Action::kKeep,
+      .level_greater_than_or_equal = log::FilterRule::Level::DEBUG_LEVEL,
+      .any_flags_set = flags,
+      .module_equals{module_little_endian.begin(), module_little_endian.end()},
+      .thread_equals{kNewThread.begin(), kNewThread.end()}};
+  rules2_[2] = {
       .action = Filter::Rule::Action::kDrop,
       .level_greater_than_or_equal = log::FilterRule::Level::ANY_LEVEL,
       .any_flags_set = 0,
       .module_equals{},
-  };
+      .thread_equals{}};
+  rules2_[3] = {
+      .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()},
+      .thread_equals{kNewThread.begin(), kNewThread.end()}};
 
   // Request logs.
   LOG_SERVICE_METHOD_CONTEXT context(drain_map_);
@@ -755,7 +841,7 @@
                      entries_found,
                      drop_count_found);
   }
-  EXPECT_EQ(entries_found, 3u);
+  EXPECT_EQ(entries_found, 4u);
   EXPECT_EQ(drop_count_found, 0u);
 }
 
diff --git a/pw_log_rpc/public/pw_log_rpc/internal/config.h b/pw_log_rpc/public/pw_log_rpc/internal/config.h
index 9fbcfbf..0b3bcdc 100644
--- a/pw_log_rpc/public/pw_log_rpc/internal/config.h
+++ b/pw_log_rpc/public/pw_log_rpc/internal/config.h
@@ -25,6 +25,14 @@
 #define PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_MODULE_NAME_SIZE 4
 #endif  // PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_MODULE_NAME_SIZE
 
+// Log filter threads are optionally tokenized,thus their backing on-device
+// container can have different sizes. A token may be represented by a 32-bit
+// integer, usually two. Default the max thread size to
+// 10 bytes.
+#ifndef PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_THREAD_NAME_SIZE
+#define PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_THREAD_NAME_SIZE 10
+#endif  // PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_THREAD_NAME_SIZE
+
 // Log filter IDs are optionally tokenized, and thus their backing on-device
 // container can have different sizes. A token may be represented by a 32-bit
 // integer (though it is usually 2 bytes). Default the max module name size to
@@ -80,4 +88,7 @@
 
 inline constexpr size_t kMaxFilterIdBytes =
     PW_LOG_RPC_CONFIG_MAX_FILTER_ID_SIZE;
-}  // namespace pw::log_rpc::cfg
+
+inline constexpr size_t kMaxThreadNameBytes =
+    PW_LOG_RPC_CONFIG_MAX_FILTER_RULE_THREAD_NAME_SIZE;
+}  // namespace pw::log_rpc::cfg
\ No newline at end of file
diff --git a/pw_log_rpc/public/pw_log_rpc/log_filter.h b/pw_log_rpc/public/pw_log_rpc/log_filter.h
index 56e01f7..e16bb0a 100644
--- a/pw_log_rpc/public/pw_log_rpc/log_filter.h
+++ b/pw_log_rpc/public/pw_log_rpc/log_filter.h
@@ -50,6 +50,9 @@
 
     // Checks if the log entry module equals this value when not empty.
     Vector<std::byte, cfg::kMaxModuleNameBytes> module_equals{};
+
+    // Checks if the log entry thread equals this value when not empty.
+    Vector<std::byte, cfg::kMaxThreadNameBytes> thread_equals{};
   };
 
   Filter(std::span<const std::byte> id, std::span<Rule> rules) : rules_(rules) {