Add --json to log_cat to print fully valid json

This makes it easy to use JSON based tooling to dig inside the results.

Change-Id: I483869fe27e117bdf86b9f775371842477ef1cbe
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/aos/events/logging/log_cat.cc b/aos/events/logging/log_cat.cc
index 71293af..60ad0e2 100644
--- a/aos/events/logging/log_cat.cc
+++ b/aos/events/logging/log_cat.cc
@@ -20,6 +20,7 @@
 DEFINE_string(type, "",
               "Channel type to match for printing out channels. Empty means no "
               "type filter.");
+DEFINE_bool(json, false, "If true, print fully valid JSON");
 DEFINE_bool(fetch, false,
             "If true, also print out the messages from before the start of the "
             "log file");
@@ -46,6 +47,30 @@
 DEFINE_bool(channels, false,
             "If true, print out all the configured channels for this log.");
 
+using aos::monotonic_clock;
+namespace chrono = std::chrono;
+
+void StreamSeconds(std::ostream &stream,
+                   const aos::monotonic_clock::time_point now) {
+  if (now < monotonic_clock::epoch()) {
+    chrono::seconds seconds =
+        chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
+
+    stream << "-" << -seconds.count() << "." << std::setfill('0')
+           << std::setw(9)
+           << chrono::duration_cast<chrono::nanoseconds>(seconds -
+                                                         now.time_since_epoch())
+                  .count();
+  } else {
+    chrono::seconds seconds =
+        chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
+    stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
+           << chrono::duration_cast<chrono::nanoseconds>(
+                  now.time_since_epoch() - seconds)
+                  .count();
+  }
+}
+
 // 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.
@@ -64,18 +89,43 @@
       builder, channel->schema(), static_cast<const uint8_t *>(context.data),
       {FLAGS_pretty, static_cast<size_t>(FLAGS_max_vector_size)});
 
-  if (context.monotonic_remote_time != context.monotonic_event_time) {
-    std::cout << node_name << context.realtime_event_time << " ("
-              << context.monotonic_event_time << ") sent "
-              << context.realtime_remote_time << " ("
-              << context.monotonic_remote_time << ") "
-              << channel->name()->c_str() << ' ' << channel->type()->c_str()
-              << ": " << *builder << std::endl;
+  if (FLAGS_json) {
+    std::cout << "{";
+    if (!node_name.empty()) {
+      std::cout << "\"node\": \"" << node_name << "\", ";
+    }
+    std::cout << "\"monotonic_event_time\": ";
+    StreamSeconds(std::cout, context.monotonic_event_time);
+    std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
+              << "\", ";
+
+    if (context.monotonic_remote_time != context.monotonic_event_time) {
+      std::cout << "\"monotonic_remote_time\": ";
+      StreamSeconds(std::cout, context.monotonic_remote_time);
+      std::cout << ", \"realtime_remote_time\": \""
+                << context.realtime_remote_time << "\", ";
+    }
+
+    std::cout << "\"channel\": "
+              << aos::configuration::StrippedChannelToString(channel)
+              << ", \"data\": " << *builder << "}" << std::endl;
   } else {
-    std::cout << node_name << context.realtime_event_time << " ("
-              << context.monotonic_event_time << ") "
-              << channel->name()->c_str() << ' ' << channel->type()->c_str()
-              << ": " << *builder << std::endl;
+    if (!node_name.empty()) {
+      std::cout << node_name << " ";
+    }
+    if (context.monotonic_remote_time != context.monotonic_event_time) {
+      std::cout << context.realtime_event_time << " ("
+                << context.monotonic_event_time << ") sent "
+                << context.realtime_remote_time << " ("
+                << context.monotonic_remote_time << ") "
+                << channel->name()->c_str() << ' ' << channel->type()->c_str()
+                << ": " << *builder << std::endl;
+    } else {
+      std::cout << context.realtime_event_time << " ("
+                << context.monotonic_event_time << ") "
+                << channel->name()->c_str() << ' ' << channel->type()->c_str()
+                << ": " << *builder << std::endl;
+    }
   }
 }
 
@@ -214,7 +264,7 @@
         node_name_(
             event_loop_->node() == nullptr
                 ? ""
-                : std::string(event_loop->node()->name()->string_view()) + " "),
+                : std::string(event_loop->node()->name()->string_view())),
         builder_(builder) {
     event_loop_->SkipTimingReport();
     event_loop_->SkipAosLog();
@@ -259,6 +309,9 @@
   void SetStarted(bool started, aos::monotonic_clock::time_point monotonic_now,
                   aos::realtime_clock::time_point realtime_now) {
     started_ = started;
+    if (FLAGS_json) {
+      return;
+    }
     if (started_) {
       std::cout << std::endl;
       std::cout << (event_loop_->node() != nullptr
diff --git a/aos/flatbuffer_introspection.cc b/aos/flatbuffer_introspection.cc
index 5f6ca45..b7f3727 100644
--- a/aos/flatbuffer_introspection.cc
+++ b/aos/flatbuffer_introspection.cc
@@ -200,9 +200,9 @@
         reflection::BaseType elem_type = type->element();
 
         if (vector->size() > json_options.max_vector_size) {
-          out->Append("[ ... ");
+          out->Append("[ \"... ");
           out->AppendInt(vector->size());
-          out->Append(" elements ... ]");
+          out->Append(" elements ...\" ]");
           break;
         }
 
diff --git a/aos/flatbuffer_introspection_test.cc b/aos/flatbuffer_introspection_test.cc
index fe8460f..e435709 100644
--- a/aos/flatbuffer_introspection_test.cc
+++ b/aos/flatbuffer_introspection_test.cc
@@ -362,7 +362,7 @@
   std::string out =
       FlatbufferToJson(schema_, builder.GetBufferPointer(),
                        {.multi_line = false, .max_vector_size = 100});
-  EXPECT_EQ(out, "{\"vector_foo_int\": [ ... 101 elements ... ]}");
+  EXPECT_EQ(out, "{\"vector_foo_int\": [ \"... 101 elements ...\" ]}");
 }
 
 TEST_F(FlatbufferIntrospectionTest, MultilineTest) {
diff --git a/aos/json_to_flatbuffer.cc b/aos/json_to_flatbuffer.cc
index 11ea3a2..6d4a2f1 100644
--- a/aos/json_to_flatbuffer.cc
+++ b/aos/json_to_flatbuffer.cc
@@ -840,7 +840,7 @@
     }
     if (size > max_vector_size_) {
       ++skip_levels_;
-      to_string_.s += "[ ... " + std::to_string(size) + " elements ... ]";
+      to_string_.s += "[ \"... " + std::to_string(size) + " elements ...\" ]";
       return;
     }
     to_string_.StartVector(size);
diff --git a/aos/json_to_flatbuffer_test.cc b/aos/json_to_flatbuffer_test.cc
index e353b99..42457f6 100644
--- a/aos/json_to_flatbuffer_test.cc
+++ b/aos/json_to_flatbuffer_test.cc
@@ -270,9 +270,9 @@
 
   EXPECT_EQ(json_short, back_json_short_typetable);
   EXPECT_EQ(json_short, back_json_short_reflection);
-  EXPECT_EQ("{ \"vector_foo_int\": [ ... 101 elements ... ] }",
+  EXPECT_EQ("{ \"vector_foo_int\": [ \"... 101 elements ...\" ] }",
             back_json_long_typetable);
-  EXPECT_EQ("{ \"vector_foo_int\": [ ... 101 elements ... ] }",
+  EXPECT_EQ("{ \"vector_foo_int\": [ \"... 101 elements ...\" ] }",
             back_json_long_reflection);
 }