pw_trace_tokenized: Dump trace buffer to log

Adds a support function that dumps the current trace buffer using the
pw_log module.

Change-Id: Ie3b5bb666602ed0b0f294514e8c55646fba5c902
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/18160
Reviewed-by: Paul Mathieu <paulmathieu@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Prashanth Swaminathan <prashanthsw@google.com>
diff --git a/pw_trace_tokenized/BUILD b/pw_trace_tokenized/BUILD
index 6b853ea..00981e7 100644
--- a/pw_trace_tokenized/BUILD
+++ b/pw_trace_tokenized/BUILD
@@ -87,6 +87,22 @@
 )
 
 pw_cc_library(
+    name = "pw_trace_tokenized_buffer_log",
+    hdrs = [
+        "public/pw_trace_tokenized/trace_buffer_log.h",
+    ],
+    srcs = [
+        "trace_buffer_log.cc",
+    ],
+    deps = [
+        ":trace_buffer_headers",
+	"//pw_base64",
+	"//pw_log",
+	"//pw_string",
+    ],
+)
+
+pw_cc_library(
     name = "pw_trace_tokenized_fake_time",
     srcs = [
         "fake_trace_time.cc",
@@ -128,6 +144,20 @@
     ],
 )
 
+pw_cc_test(
+    name = "trace_tokenized_buffer_log_test",
+    srcs = [
+        "trace_buffer_log_test.cc",
+    ],
+    deps = [
+        ":backend",
+        ":facade",
+        ":pw_trace_log",
+        "//pw_preprocessor",
+        "//pw_unit_test",
+    ],
+)
+
 pw_cc_library(
     name = "pw_trace_host_trace_time",
     includes = [ "example/public" ],
diff --git a/pw_trace_tokenized/BUILD.gn b/pw_trace_tokenized/BUILD.gn
index 3f0bed4..d0af5be 100644
--- a/pw_trace_tokenized/BUILD.gn
+++ b/pw_trace_tokenized/BUILD.gn
@@ -37,6 +37,7 @@
   tests = [
     ":trace_tokenized_test",
     ":tokenized_trace_buffer_test",
+    ":tokenized_trace_buffer_log_test",
   ]
 }
 
@@ -96,6 +97,26 @@
   sources = [ "trace_buffer_test.cc" ]
 }
 
+pw_source_set("tokenized_trace_buffer_log") {
+  deps = [
+    "$dir_pw_base64",
+    "$dir_pw_log",
+    "$dir_pw_string",
+  ]
+  public_deps = [ ":tokenized_trace_buffer" ]
+  sources = [ "trace_buffer_log.cc" ]
+  public = [ "public/pw_trace_tokenized/trace_buffer_log.h" ]
+}
+
+pw_test("tokenized_trace_buffer_log_test") {
+  enable_if = pw_trace_tokenizer_time != ""
+  deps = [
+    ":tokenized_trace_buffer_log",
+    "$dir_pw_trace",
+  ]
+  sources = [ "trace_buffer_log_test.cc" ]
+}
+
 pw_source_set("fake_trace_time") {
   deps = [ ":pw_trace_tokenized_core" ]
   sources = [ "fake_trace_time.cc" ]
diff --git a/pw_trace_tokenized/docs.rst b/pw_trace_tokenized/docs.rst
index c588106..6548a03 100644
--- a/pw_trace_tokenized/docs.rst
+++ b/pw_trace_tokenized/docs.rst
@@ -182,6 +182,31 @@
 ``pw_varint``
 
 
+-------
+Logging
+-------
+The optional trace buffer logging adds support to dump trace buffers to the log.
+Buffers are converted to base64-encoding then split across log lines. Trace logs
+are surrounded by 'begin' and 'end' tags.
+
+Ex. Invoking PW_TRACE_INSTANT with 'test1' and 'test2', then calling this
+function would produce this in the output logs:
+
+.. code:: sh
+
+  [TRACE] begin
+  [TRACE] data: BWdDMRoABWj52YMB
+  [TRACE] end
+
+Added dependencies
+------------------
+``pw_base64``
+``pw_log``
+``pw_ring_buffer``
+``pw_string``
+``pw_tokenizer``
+``pw_varint``
+
 --------
 Examples
 --------
diff --git a/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer_log.h b/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer_log.h
new file mode 100644
index 0000000..d6aa4f0
--- /dev/null
+++ b/pw_trace_tokenized/public/pw_trace_tokenized/trace_buffer_log.h
@@ -0,0 +1,37 @@
+// 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.
+//==============================================================================
+//
+// This files provides support to dump the trace buffer to the logging module.
+#pragma once
+
+#include "pw_status/status.h"
+
+namespace pw {
+namespace trace {
+
+// Dumps the trace buffer to the log. The output format to the log is the
+// base64-encoded buffer, split into lines of an implementation-defined length.
+// The trace logs are surrounded by 'begin' and 'end' tags.
+//
+// Ex. Invoking PW_TRACE_INSTANT with 'test1' and 'test2', then calling this
+// function would produce this in the output logs:
+//
+// [TRACE] begin
+// [TRACE] data: BWdDMRoABWj52YMB
+// [TRACE] end
+pw::Status DumpTraceBufferToLog();
+
+}  // namespace trace
+}  // namespace pw
diff --git a/pw_trace_tokenized/trace_buffer_log.cc b/pw_trace_tokenized/trace_buffer_log.cc
new file mode 100644
index 0000000..3e41c3a
--- /dev/null
+++ b/pw_trace_tokenized/trace_buffer_log.cc
@@ -0,0 +1,85 @@
+// 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_trace_tokenized/trace_buffer_log.h"
+
+#include <span>
+
+#include "pw_base64/base64.h"
+#include "pw_log/log.h"
+#include "pw_string/string_builder.h"
+#include "pw_trace_tokenized/trace_buffer.h"
+
+namespace pw {
+namespace trace {
+namespace {
+
+constexpr int kMaxEntrySize = PW_TRACE_BUFFER_MAX_BLOCK_SIZE_BYTES;
+constexpr int kMaxEntrySizeBase64 = pw::base64::EncodedSize(kMaxEntrySize);
+constexpr int kLineLength = 80;
+
+class ScopedTracePause {
+ public:
+  ScopedTracePause() : was_enabled_(pw_trace_IsEnabled()) {
+    PW_TRACE_SET_ENABLED(false);
+  }
+  ~ScopedTracePause() { PW_TRACE_SET_ENABLED(was_enabled_); }
+
+ private:
+  bool was_enabled_;
+};
+
+}  // namespace
+
+pw::Status DumpTraceBufferToLog() {
+  std::byte line_buffer[kLineLength] = {};
+  std::byte entry_buffer[kMaxEntrySize + 1] = {};
+  char entry_base64_buffer[kMaxEntrySizeBase64] = {};
+  pw::StringBuilder line_builder(line_buffer);
+  ScopedTracePause pause_trace;
+  pw::ring_buffer::PrefixedEntryRingBuffer* trace_buffer =
+      pw::trace::GetBuffer();
+  size_t bytes_read = 0;
+  PW_LOG_INFO("[TRACE] begin");
+  while (trace_buffer->PeekFront(std::span(entry_buffer).subspan(1),
+                                 &bytes_read) != pw::Status::OUT_OF_RANGE) {
+    trace_buffer->PopFront();
+    entry_buffer[0] = static_cast<std::byte>(bytes_read);
+    // The entry buffer is formatted as (size, entry) with an extra byte as
+    // a header to the entry. The calcuation of bytes_read + 1 represents
+    // the extra size header.
+    size_t to_write =
+        pw::base64::Encode(std::span(entry_buffer, bytes_read + 1),
+                           std::span(entry_base64_buffer));
+    size_t space_left = line_builder.max_size() - line_builder.size();
+    size_t written = 0;
+    while (to_write - written >= space_left) {
+      line_builder.append(entry_base64_buffer + written, space_left);
+      PW_LOG_INFO("[TRACE] data: %s", line_builder.c_str());
+      line_builder.clear();
+      written += space_left;
+      space_left = line_builder.max_size();
+    }
+    line_builder.append(entry_base64_buffer + written, to_write - written);
+  }
+  if (line_builder.size() > 0) {
+    PW_LOG_INFO("[TRACE] data: %s", line_builder.c_str());
+  }
+  PW_LOG_INFO("[TRACE] end");
+  return pw::Status::OK;
+}
+
+}  // namespace trace
+}  // namespace pw
diff --git a/pw_trace_tokenized/trace_buffer_log_test.cc b/pw_trace_tokenized/trace_buffer_log_test.cc
new file mode 100644
index 0000000..e2327e2
--- /dev/null
+++ b/pw_trace_tokenized/trace_buffer_log_test.cc
@@ -0,0 +1,43 @@
+// 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.
+
+#define PW_TRACE_MODULE_NAME "TST"
+
+#include "pw_trace_tokenized/trace_buffer_log.h"
+
+#include "gtest/gtest.h"
+#include "pw_trace/trace.h"
+
+TEST(TokenizedTrace, DumpSmallBuffer) {
+  // TODO(pwbug/266): This test only verifies that the dump function does not
+  // crash, and requires manual inspection to confirm that the log output is
+  // correct. When there is support to mock and verify the calls to pw_log,
+  // these tests should be improved to validate the output.
+  PW_TRACE_SET_ENABLED(true);
+  PW_TRACE_INSTANT("test1");
+  PW_TRACE_INSTANT("test2");
+  pw::trace::DumpTraceBufferToLog();
+}
+
+TEST(TokenizedTrace, DumpLargeBuffer) {
+  // TODO(pwbug/266): This test only verifies that the dump function does not
+  // crash, and requires manual inspection to confirm that the log output is
+  // correct. When there is support to mock and verify the calls to pw_log,
+  // these tests should be improved to validate the output.
+  PW_TRACE_SET_ENABLED(true);
+  for (int i = 0; i < 100; i++) {
+    PW_TRACE_INSTANT("test");
+  }
+  pw::trace::DumpTraceBufferToLog();
+}