Add logger and log_dumper binaries

This enables actually logging data on the roborio and subsequently
actually doing basic viewing of the data in the logfiles.
Tested manually on my local machine.

This also makes an //aos/events/logging folder (this is distinct from
the //aos/logging folder which contains older logging information).

Change-Id: I41f3216d6c56e8330667e8dd6050b1a0dd5b19c9
diff --git a/aos/BUILD b/aos/BUILD
index 2ed8aa9..57c6b1e 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -15,6 +15,7 @@
 filegroup(
     name = "prime_start_binaries",
     srcs = [
+        "//aos/events/logging:logger_main",
         "//aos/logging:binary_log_writer",
     ],
     visibility = ["//visibility:public"],
@@ -35,6 +36,7 @@
 filegroup(
     name = "prime_start_binaries_stripped",
     srcs = [
+        "//aos/events/logging:logger_main.stripped",
         "//aos/logging:binary_log_writer.stripped",
     ],
     visibility = ["//visibility:public"],
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 5aef05a..6ffa526 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -238,7 +238,6 @@
 
 cc_library(
     name = "simulated_event_loop",
-    testonly = True,
     srcs = [
         "event_scheduler.cc",
         "simulated_event_loop.cc",
@@ -255,40 +254,3 @@
         "@com_google_absl//absl/container:btree",
     ],
 )
-
-flatbuffer_cc_library(
-    name = "logger_fbs",
-    srcs = ["logger.fbs"],
-    gen_reflections = 1,
-    includes = [
-        "//aos:configuration_fbs_includes",
-    ],
-)
-
-cc_library(
-    name = "logger",
-    srcs = ["logger.cc"],
-    hdrs = ["logger.h"],
-    deps = [
-        ":event_loop",
-        ":logger_fbs",
-        "//aos:flatbuffer_merge",
-        "//aos/time",
-        "@com_github_google_flatbuffers//:flatbuffers",
-        "@com_google_absl//absl/container:inlined_vector",
-        "@com_google_absl//absl/strings",
-    ],
-)
-
-cc_test(
-    name = "logger_test",
-    srcs = ["logger_test.cc"],
-    data = ["pingpong_config.json"],
-    deps = [
-        ":logger",
-        ":ping_lib",
-        ":pong_lib",
-        ":simulated_event_loop",
-        "//aos/testing:googletest",
-    ],
-)
diff --git a/aos/events/logging/BUILD b/aos/events/logging/BUILD
new file mode 100644
index 0000000..f2012da
--- /dev/null
+++ b/aos/events/logging/BUILD
@@ -0,0 +1,71 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+
+flatbuffer_cc_library(
+    name = "logger_fbs",
+    srcs = ["logger.fbs"],
+    gen_reflections = 1,
+    includes = [
+        "//aos:configuration_fbs_includes",
+    ],
+)
+
+cc_library(
+    name = "logger",
+    srcs = ["logger.cc"],
+    hdrs = ["logger.h"],
+    deps = [
+        ":logger_fbs",
+        "//aos:flatbuffer_merge",
+        "//aos/events:event_loop",
+        "//aos/time",
+        "@com_github_google_flatbuffers//:flatbuffers",
+        "@com_google_absl//absl/container:inlined_vector",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_binary(
+    name = "log_cat",
+    srcs = [
+        "log_cat.cc",
+    ],
+    deps = [
+        ":logger",
+        "//aos:configuration",
+        "//aos:init",
+        "//aos:json_to_flatbuffer",
+        "//aos/events:shm_event_loop",
+        "//aos/events:simulated_event_loop",
+        "@com_github_gflags_gflags//:gflags",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
+cc_binary(
+    name = "logger_main",
+    srcs = [
+        "logger_main.cc",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":logger",
+        "//aos:configuration",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "@com_github_gflags_gflags//:gflags",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
+cc_test(
+    name = "logger_test",
+    srcs = ["logger_test.cc"],
+    data = ["//aos/events:pingpong_config.json"],
+    deps = [
+        ":logger",
+        "//aos/events:ping_lib",
+        "//aos/events:pong_lib",
+        "//aos/events:simulated_event_loop",
+        "//aos/testing:googletest",
+    ],
+)
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
new file mode 100644
index 0000000..c146945
--- /dev/null
+++ b/aos/events/logging/log_cat.cc
@@ -0,0 +1,85 @@
+#include <iostream>
+
+#include "aos/configuration.h"
+#include "aos/events/logging/logger.h"
+#include "aos/events/simulated_event_loop.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "gflags/gflags.h"
+
+DEFINE_string(logfile, "/tmp/logfile.bfbs",
+              "Name of the logfile to read from.");
+DEFINE_string(
+    name, "",
+    "Name to match for printing out channels. Empty means no name filter.");
+DEFINE_string(type, "",
+              "Channel type to match for printing out channels. Empty means no "
+              "type filter.");
+int main(int argc, char **argv) {
+  gflags::SetUsageMessage(
+      "This program provides a basic interface to dump data from a logfile to "
+      "stdout. Given a logfile, channel name filter, and type filter, it will "
+      "print all the messages in the logfile matching the filters. The message "
+      "filters work by taking the values of --name and --type and printing any "
+      "channel whose name contains --name as a substr and whose type contains "
+      "--type as a substr. Not specifying --name or --type leaves them free. "
+      "Calling this program without --name or --type specified prints out all "
+      "the logged data.");
+  aos::InitGoogle(&argc, &argv);
+
+  aos::logger::LogReader reader(FLAGS_logfile);
+  aos::SimulatedEventLoopFactory log_reader_factory(reader.configuration());
+  std::unique_ptr<aos::EventLoop> reader_event_loop =
+      log_reader_factory.MakeEventLoop("log_reader");
+  reader.Register(reader_event_loop.get());
+  // We don't run timing reports when trying to print out logged data, because
+  // otherwise we would end up printing out the timing reports themselves...
+  reader_event_loop->SkipTimingReport();
+
+  std::unique_ptr<aos::EventLoop> printer_event_loop =
+      log_reader_factory.MakeEventLoop("printer");
+  printer_event_loop->SkipTimingReport();
+
+  bool found_channel = false;
+  const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
+      reader.configuration()->channels();
+  for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
+    const aos::Channel *channel = channels->Get(i);
+    const flatbuffers::string_view name = channel->name()->string_view();
+    const flatbuffers::string_view type = channel->type()->string_view();
+    if (name.find(FLAGS_name) != std::string::npos &&
+        type.find(FLAGS_type) != std::string::npos) {
+      LOG(INFO) << "Listening on " << name << " " << type;
+
+      CHECK_NOTNULL(channel->schema());
+      printer_event_loop->MakeRawWatcher(
+          channel, [channel](const aos::Context &context, const void *message) {
+            // Print the flatbuffer out to stdout, both to remove the
+            // unnecessary cruft from glog and to allow the user to readily
+            // redirect just the logged output independent of any debugging
+            // information on stderr.
+            // TODO(james): Also print out realtime time once we support
+            // replaying it.
+            std::cout << channel->name()->c_str() << ' '
+                      << channel->type()->c_str() << " at "
+                      << context.monotonic_sent_time << ": "
+                      << aos::FlatbufferToJson(
+                             channel->schema(),
+                             static_cast<const uint8_t *>(message))
+                      << '\n';
+          });
+      found_channel = true;
+    }
+  }
+
+  if (!found_channel) {
+    LOG(FATAL) << "Could not find any channels";
+  }
+
+  log_reader_factory.Run();
+
+  reader.Deregister();
+
+  aos::Cleanup();
+  return 0;
+}
diff --git a/aos/events/logger.cc b/aos/events/logging/logger.cc
similarity index 99%
rename from aos/events/logger.cc
rename to aos/events/logging/logger.cc
index d39f0a7..5b3d553 100644
--- a/aos/events/logger.cc
+++ b/aos/events/logging/logger.cc
@@ -1,4 +1,4 @@
-#include "aos/events/logger.h"
+#include "aos/events/logging/logger.h"
 
 #include <fcntl.h>
 #include <sys/stat.h>
@@ -9,7 +9,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "aos/events/event_loop.h"
-#include "aos/events/logger_generated.h"
+#include "aos/events/logging/logger_generated.h"
 #include "aos/flatbuffer_merge.h"
 #include "aos/time/time.h"
 #include "flatbuffers/flatbuffers.h"
diff --git a/aos/events/logger.fbs b/aos/events/logging/logger.fbs
similarity index 100%
rename from aos/events/logger.fbs
rename to aos/events/logging/logger.fbs
diff --git a/aos/events/logger.h b/aos/events/logging/logger.h
similarity index 99%
rename from aos/events/logger.h
rename to aos/events/logging/logger.h
index 7873a42..f03aa64 100644
--- a/aos/events/logger.h
+++ b/aos/events/logging/logger.h
@@ -7,7 +7,7 @@
 #include "absl/strings/string_view.h"
 #include "absl/types/span.h"
 #include "aos/events/event_loop.h"
-#include "aos/events/logger_generated.h"
+#include "aos/events/logging/logger_generated.h"
 #include "aos/time/time.h"
 #include "flatbuffers/flatbuffers.h"
 
diff --git a/aos/events/logging/logger_main.cc b/aos/events/logging/logger_main.cc
new file mode 100644
index 0000000..6049f41
--- /dev/null
+++ b/aos/events/logging/logger_main.cc
@@ -0,0 +1,35 @@
+#include "aos/configuration.h"
+#include "aos/events/logging/logger.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+// TODO(james): Write code for managing logfile names.
+DEFINE_string(logfile, "/media/sda1/logfile.bfbs",
+              "Name of the logfile to write to.");
+
+DEFINE_string(config, "config.json", "Config file to use.");
+
+int main(int argc, char *argv[]) {
+  gflags::SetUsageMessage(
+      "This program provides a simple logger binary that logs all SHMEM data "
+      "directly to a file specified at the command line. It does not manage "
+      "filenames, so it will just crash if you attempt to overwrite an "
+      "existing file, and the user must specify the logfile manually at the "
+      "command line.");
+  aos::InitGoogle(&argc, &argv);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(FLAGS_config);
+
+  aos::ShmEventLoop event_loop(&config.message());
+
+  aos::logger::DetachedBufferWriter writer(FLAGS_logfile);
+  aos::logger::Logger logger(&writer, &event_loop);
+
+  event_loop.Run();
+
+  aos::Cleanup();
+  return 0;
+}
diff --git a/aos/events/logger_test.cc b/aos/events/logging/logger_test.cc
similarity index 98%
rename from aos/events/logger_test.cc
rename to aos/events/logging/logger_test.cc
index e0dd7de..1137e66 100644
--- a/aos/events/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -1,4 +1,4 @@
-#include "aos/events/logger.h"
+#include "aos/events/logging/logger.h"
 
 #include "aos/events/event_loop.h"
 #include "aos/events/ping_lib.h"
diff --git a/aos/time/time.cc b/aos/time/time.cc
index 95044a4..6daae7e 100644
--- a/aos/time/time.cc
+++ b/aos/time/time.cc
@@ -5,6 +5,7 @@
 
 #include <atomic>
 #include <chrono>
+#include <ctime>
 
 #ifdef __linux__
 
@@ -54,13 +55,16 @@
 
 namespace aos {
 
-void PrintTo(const monotonic_clock::time_point t, std::ostream *os) {
-  auto s = std::chrono::duration_cast<std::chrono::seconds>(t.time_since_epoch());
-  *os << s.count() << "."
-      << std::chrono::duration_cast<std::chrono::nanoseconds>(
-             t.time_since_epoch() - s)
-             .count()
-      << "sec";
+std::ostream &operator<<(std::ostream &stream,
+                         const aos::monotonic_clock::time_point &now) {
+  auto seconds =
+      std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch());
+  stream << seconds.count() << "."
+         << std::chrono::duration_cast<std::chrono::nanoseconds>(
+                now.time_since_epoch() - seconds)
+                .count()
+         << "sec";
+  return stream;
 }
 
 namespace time {
diff --git a/aos/time/time.h b/aos/time/time.h
index 3cc0c2a..aecddf0 100644
--- a/aos/time/time.h
+++ b/aos/time/time.h
@@ -65,10 +65,10 @@
       time_point(duration(::std::numeric_limits<duration::rep>::max()))};
 };
 
-void PrintTo(const monotonic_clock::time_point t, std::ostream *os);
+std::ostream &operator<<(std::ostream &stream,
+                         const aos::monotonic_clock::time_point &now);
 
 namespace time {
-
 #ifdef __linux__
 
 // Construct a time representing the period of hertz.