Create binary for extracting configs from logfiles.

Change-Id: I64b2c061312bb05f5914701e8af43a731d5dfc94
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/events/logging/BUILD b/aos/events/logging/BUILD
index 07a751c..9b179ba 100644
--- a/aos/events/logging/BUILD
+++ b/aos/events/logging/BUILD
@@ -729,3 +729,21 @@
         "@com_google_absl//absl/strings",
     ],
 )
+
+cc_binary(
+    name = "log_config_extractor",
+    srcs = [
+        "log_config_extractor.cc",
+    ],
+    target_compatible_with = ["@platforms//os:linux"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos:aos_cli_utils",
+        "//aos:configuration",
+        "//aos:init",
+        "//aos:json_to_flatbuffer",
+        "//aos/events/logging:log_reader",
+        "@com_github_gflags_gflags//:gflags",
+        "@com_github_google_glog//:glog",
+    ],
+)
diff --git a/aos/events/logging/log_config_extractor.cc b/aos/events/logging/log_config_extractor.cc
new file mode 100644
index 0000000..b7a937d
--- /dev/null
+++ b/aos/events/logging/log_config_extractor.cc
@@ -0,0 +1,122 @@
+#include <iostream>
+#include <filesystem>
+#include <vector>
+
+#include "aos/configuration_generated.h"
+#include "aos/events/logging/log_reader.h"
+#include "aos/events/logging/logfile_sorting.h"
+#include "aos/flatbuffer_merge.h"
+#include "aos/init.h"
+#include "aos/json_to_flatbuffer.h"
+#include "flatbuffers/flatbuffers.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+DEFINE_string(output_path, "/tmp/",
+              "Destination folder for output files. If this flag is not used, "
+              "it stores the files in /tmp/.");
+DEFINE_bool(convert_to_json, false,
+            "If true, can be used to convert bfbs to json.");
+DEFINE_bool(bfbs, false,
+            "If true, write as a binary flatbuffer inside the output_path.");
+DEFINE_bool(json, false, "If true, write as a json inside the output_path.");
+DEFINE_bool(stripped, false,
+            "If true, write as a stripped json inside the output_path.");
+DEFINE_bool(quiet, false,
+            "If true, do not print configuration to stdout. If false, print "
+            "stripped json");
+
+namespace aos {
+
+void WriteConfig(const aos::Configuration *config, std::string output_path) {
+  auto config_flatbuffer = RecursiveCopyFlatBuffer(config);
+
+  if (FLAGS_bfbs) {
+    WriteFlatbufferToFile(output_path + ".bfbs", config_flatbuffer);
+    LOG(INFO) << "Done writing bfbs to " << output_path << ".bfbs";
+  }
+
+  if (FLAGS_json) {
+    WriteFlatbufferToJson(output_path + ".json", config_flatbuffer);
+    LOG(INFO) << "Done writing json to " << output_path << ".json";
+  }
+
+  if (FLAGS_stripped || !FLAGS_quiet) {
+    auto *channels = config_flatbuffer.mutable_message()->mutable_channels();
+    for (size_t i = 0; i < channels->size(); i++) {
+      channels->GetMutableObject(i)->clear_schema();
+    }
+    if (FLAGS_stripped) {
+      WriteFlatbufferToJson(output_path + ".stripped.json", config_flatbuffer);
+      LOG(INFO) << "Done writing stripped json to " << output_path
+                << ".stripped.json";
+    }
+    if (!FLAGS_quiet) {
+      std::cout << FlatbufferToJson(config_flatbuffer) << std::endl;
+    }
+  }
+}
+
+int Main(int argc, char *argv[]) {
+  CHECK(argc > 1) << "Must provide an argument";
+
+  std::string output_path = FLAGS_output_path;
+  if (output_path.back() != '/') {
+    output_path += "/";
+  }
+  if (!std::filesystem::exists(output_path)) {
+    LOG(ERROR)
+        << "Output path is invalid. Make sure the path exists before running.";
+    return EXIT_FAILURE;
+  }
+  output_path += "aos_config";
+
+  std::shared_ptr<const aos::Configuration> config;
+  // Check if the user wants to use stdin (denoted by '-') which will help
+  // convert configs in json to bfbs (see example in SetUsageMessage)
+  std::string_view arg{argv[1]};
+  if (arg == "-") {
+    // Read in everything from stdin, blocks when there's no data on stdin
+    std::string stdin_data(std::istreambuf_iterator(std::cin), {});
+    aos::FlatbufferDetachedBuffer<aos::Configuration> buffer(
+        aos::JsonToFlatbuffer(stdin_data, aos::ConfigurationTypeTable()));
+    WriteConfig(&buffer.message(), output_path);
+  } else if (FLAGS_convert_to_json) {
+    aos::FlatbufferDetachedBuffer config = aos::configuration::ReadConfig(arg);
+    WriteFlatbufferToJson(output_path + ".json", config);
+    LOG(INFO) << "Done writing json to " << output_path << ".json";
+  } else {
+    const std::vector<std::string> unsorted_logfiles =
+        aos::logger::FindLogs(argc, argv);
+
+    const std::vector<aos::logger::LogFile> logfiles =
+        aos::logger::SortParts(unsorted_logfiles);
+
+    WriteConfig(logfiles[0].config.get(), output_path);
+  }
+  return EXIT_SUCCESS;
+}
+
+}  // namespace aos
+
+int main(int argc, char *argv[]) {
+  gflags::SetUsageMessage(
+      "Binary to output the configuration of a log.\n"
+      "# print config as stripped json to stdout\n"
+      "# path to log should always be absolute path.\n"
+      "log_config_extractor /path/to/log\n"
+      "# write config to ~/work/results/aos_config.bfbs and "
+      "~/work/results/aos_config.json with no stdout "
+      "output\n"
+      "# make sure the output paths are valid and absolute paths.\n"
+      "log_config_extractor /path/to/log --output_path=~/work/results/ --bfbs "
+      "--json --quiet\n"
+      "# pass json config by stdin and output as bfbs\n"
+      "cat aos_config.json | log_config_extractor - --output_path "
+      "/absolute/path/to/dir --bfbs\n"
+      "# This can also be used to convert a bfbs file of config to json\n"
+      "log_config_extractor /path/to/config.bfbs --convert_to_json");
+
+  aos::InitGoogle(&argc, &argv);
+  return aos::Main(argc, argv);
+}