Added quiet flag for application object

The application object sometimes spews failed to start messages when
executing intensive commands with auto-restart, such as ssh. Although
the nonzero return codes are to be expected, a quiet flag was added as
to reduce the amount of irrelevant messages.

Change-Id: I11b5391b423f1e4763683a2334f9aa75d29b23f8
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/starter/subprocess.cc b/aos/starter/subprocess.cc
index d907d3f..b7a1cf6 100644
--- a/aos/starter/subprocess.cc
+++ b/aos/starter/subprocess.cc
@@ -102,25 +102,29 @@
 Application::Application(std::string_view name,
                          std::string_view executable_name,
                          aos::EventLoop *event_loop,
-                         std::function<void()> on_change)
+                         std::function<void()> on_change,
+                         QuietLogging quiet_flag)
     : name_(name),
       path_(executable_name),
       event_loop_(event_loop),
       start_timer_(event_loop_->AddTimer([this] {
         status_ = aos::starter::State::RUNNING;
-        LOG(INFO) << "Started '" << name_ << "' pid: " << pid_;
+        LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+            << "Started '" << name_ << "' pid: " << pid_;
       })),
       restart_timer_(event_loop_->AddTimer([this] { DoStart(); })),
       stop_timer_(event_loop_->AddTimer([this] {
         if (kill(pid_, SIGKILL) == 0) {
-          LOG(WARNING) << "Failed to stop, sending SIGKILL to '" << name_
-                       << "' pid: " << pid_;
+          LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+              << "Failed to stop, sending SIGKILL to '" << name_
+              << "' pid: " << pid_;
         }
       })),
       pipe_timer_(event_loop_->AddTimer([this]() { FetchOutputs(); })),
       child_status_handler_(
           event_loop_->AddTimer([this]() { MaybeHandleSignal(); })),
-      on_change_(on_change) {
+      on_change_(on_change),
+      quiet_flag_(quiet_flag) {
   event_loop_->OnRun([this]() {
     // Every second poll to check if the child is dead. This is used as a
     // default for the case where the user is not directly catching SIGCHLD and
@@ -132,12 +136,13 @@
 
 Application::Application(const aos::Application *application,
                          aos::EventLoop *event_loop,
-                         std::function<void()> on_change)
+                         std::function<void()> on_change,
+                         QuietLogging quiet_flag)
     : Application(application->name()->string_view(),
                   application->has_executable_name()
                       ? application->executable_name()->string_view()
                       : application->name()->string_view(),
-                  event_loop, on_change) {
+                  event_loop, on_change, quiet_flag) {
   user_name_ = application->has_user() ? application->user()->str() : "";
   user_ = application->has_user() ? FindUid(user_name_.c_str()) : std::nullopt;
   group_ = application->has_user() ? FindPrimaryGidForUser(user_name_.c_str())
@@ -179,7 +184,8 @@
 
   if (pid != 0) {
     if (pid == -1) {
-      PLOG(WARNING) << "Failed to fork '" << name_ << "'";
+      PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+          << "Failed to fork '" << name_ << "'";
       stop_reason_ = aos::starter::LastStopReason::FORK_ERR;
       status_ = aos::starter::State::STOPPED;
     } else {
@@ -187,7 +193,8 @@
       id_ = next_id_++;
       start_time_ = event_loop_->monotonic_now();
       status_ = aos::starter::State::STARTING;
-      LOG(INFO) << "Starting '" << name_ << "' pid " << pid_;
+      LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+          << "Starting '" << name_ << "' pid " << pid_;
 
       // Set up timer which moves application to RUNNING state if it is still
       // alive in 1 second.
@@ -286,7 +293,8 @@
   // If we got here, something went wrong
   status_pipes_.write->Write(
       static_cast<uint32_t>(aos::starter::LastStopReason::EXECV_ERR));
-  PLOG(WARNING) << "Could not execute " << name_ << " (" << path_ << ')';
+  PLOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+      << "Could not execute " << name_ << " (" << path_ << ')';
 
   _exit(EXIT_FAILURE);
 }
@@ -323,8 +331,9 @@
   switch (status_) {
     case aos::starter::State::STARTING:
     case aos::starter::State::RUNNING: {
-      LOG(INFO) << "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
-                << SIGINT;
+      LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+          << "Stopping '" << name_ << "' pid: " << pid_ << " with signal "
+          << SIGINT;
       status_ = aos::starter::State::STOPPING;
 
       kill(pid_, SIGINT);
@@ -369,7 +378,8 @@
 void Application::QueueStart() {
   status_ = aos::starter::State::WAITING;
 
-  LOG(INFO) << "Restarting " << name_ << " in 3 seconds";
+  LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+      << "Restarting " << name_ << " in 3 seconds";
   restart_timer_->Schedule(event_loop_->monotonic_now() +
                            std::chrono::seconds(3));
   start_timer_->Disable();
@@ -531,11 +541,13 @@
   switch (status_) {
     case aos::starter::State::STARTING: {
       if (exit_code_.value() == 0) {
-        LOG(INFO) << "Application '" << name_ << "' pid " << pid_
-                  << " exited with status " << exit_code_.value();
+        LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+            << "Application '" << name_ << "' pid " << pid_
+            << " exited with status " << exit_code_.value();
       } else {
-        LOG(WARNING) << "Failed to start '" << name_ << "' on pid " << pid_
-                     << " : Exited with status " << exit_code_.value();
+        LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+            << "Failed to start '" << name_ << "' on pid " << pid_
+            << " : Exited with status " << exit_code_.value();
       }
       if (autorestart()) {
         QueueStart();
@@ -547,12 +559,13 @@
     }
     case aos::starter::State::RUNNING: {
       if (exit_code_.value() == 0) {
-        LOG(INFO) << "Application '" << name_ << "' pid " << pid_
-                  << " exited with status " << exit_code_.value();
+        LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+            << "Application '" << name_ << "' pid " << pid_
+            << " exited with status " << exit_code_.value();
       } else {
-        LOG(WARNING) << "Application '" << name_ << "' pid " << pid_
-                     << " exited unexpectedly with status "
-                     << exit_code_.value();
+        LOG_IF(WARNING, quiet_flag_ == QuietLogging::kNo)
+            << "Application '" << name_ << "' pid " << pid_
+            << " exited unexpectedly with status " << exit_code_.value();
       }
       if (autorestart()) {
         QueueStart();
@@ -563,8 +576,9 @@
       break;
     }
     case aos::starter::State::STOPPING: {
-      LOG(INFO) << "Successfully stopped '" << name_ << "' pid: " << pid_
-                << " with status " << exit_code_.value();
+      LOG_IF(INFO, quiet_flag_ == QuietLogging::kNo)
+          << "Successfully stopped '" << name_ << "' pid: " << pid_
+          << " with status " << exit_code_.value();
       status_ = aos::starter::State::STOPPED;
 
       // Disable force stop timer since the process already died
diff --git a/aos/starter/subprocess.h b/aos/starter/subprocess.h
index 1f16168..60732c3 100644
--- a/aos/starter/subprocess.h
+++ b/aos/starter/subprocess.h
@@ -55,15 +55,18 @@
 // automatically.
 class Application {
  public:
+  enum class QuietLogging { kYes, kNo };
   Application(const aos::Application *application, aos::EventLoop *event_loop,
-              std::function<void()> on_change);
+              std::function<void()> on_change,
+              QuietLogging quiet_flag = QuietLogging::kNo);
 
   // executable_name is the actual executable path.
   // When sudo is not used, name is used as argv[0] when exec'ing
   // executable_name. When sudo is used it's not possible to pass in a
   // distinct argv[0].
   Application(std::string_view name, std::string_view executable_name,
-              aos::EventLoop *event_loop, std::function<void()> on_change);
+              aos::EventLoop *event_loop, std::function<void()> on_change,
+              QuietLogging quiet_flag = QuietLogging::kNo);
 
   flatbuffers::Offset<aos::starter::ApplicationStatus> PopulateStatus(
       flatbuffers::FlatBufferBuilder *builder, util::Top *top);
@@ -179,6 +182,8 @@
 
   std::unique_ptr<MemoryCGroup> memory_cgroup_;
 
+  QuietLogging quiet_flag_ = QuietLogging::kNo;
+
   DISALLOW_COPY_AND_ASSIGN(Application);
 };
 
diff --git a/aos/starter/subprocess_test.cc b/aos/starter/subprocess_test.cc
index 3633d1f..4aa371b 100644
--- a/aos/starter/subprocess_test.cc
+++ b/aos/starter/subprocess_test.cc
@@ -100,4 +100,83 @@
   ASSERT_EQ(aos::starter::State::STOPPED, echo_stderr.status());
 }
 
+TEST_F(SubprocessTest, UnactiveQuietFlag) {
+  const std::string config_file =
+      ::aos::testing::ArtifactPath("aos/events/pingpong_config.json");
+
+  ::testing::internal::CaptureStderr();
+
+  // Set up application without quiet flag active
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(config_file);
+  aos::ShmEventLoop event_loop(&config.message());
+  bool observed_stopped = false;
+  Application error_out(
+      "false", "false", &event_loop,
+      [&observed_stopped, &error_out]() {
+        if (error_out.status() == aos::starter::State::STOPPED) {
+          observed_stopped = true;
+        }
+      },
+      Application::QuietLogging::kNo);
+  ASSERT_FALSE(error_out.autorestart());
+
+  error_out.Start();
+  aos::TimerHandler *exit_timer =
+      event_loop.AddTimer([&event_loop]() { event_loop.Exit(); });
+  event_loop.OnRun([&event_loop, exit_timer]() {
+    exit_timer->Schedule(event_loop.monotonic_now() +
+                         std::chrono::milliseconds(1500));
+  });
+
+  event_loop.Run();
+
+  // Ensure presence of logs without quiet flag
+  std::string output = ::testing::internal::GetCapturedStderr();
+  std::string expectedStart = "Failed to start 'false'";
+  std::string expectedRun = "exited unexpectedly with status";
+
+  ASSERT_TRUE(output.find(expectedStart) != std::string::npos ||
+              output.find(expectedRun) != std::string::npos);
+  EXPECT_TRUE(observed_stopped);
+  EXPECT_EQ(aos::starter::State::STOPPED, error_out.status());
+}
+
+TEST_F(SubprocessTest, ActiveQuietFlag) {
+  const std::string config_file =
+      ::aos::testing::ArtifactPath("aos/events/pingpong_config.json");
+
+  ::testing::internal::CaptureStderr();
+
+  // Set up application with quiet flag active
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(config_file);
+  aos::ShmEventLoop event_loop(&config.message());
+  bool observed_stopped = false;
+  Application error_out(
+      "false", "false", &event_loop,
+      [&observed_stopped, &error_out]() {
+        if (error_out.status() == aos::starter::State::STOPPED) {
+          observed_stopped = true;
+        }
+      },
+      Application::QuietLogging::kYes);
+  ASSERT_FALSE(error_out.autorestart());
+
+  error_out.Start();
+  aos::TimerHandler *exit_timer =
+      event_loop.AddTimer([&event_loop]() { event_loop.Exit(); });
+  event_loop.OnRun([&event_loop, exit_timer]() {
+    exit_timer->Schedule(event_loop.monotonic_now() +
+                         std::chrono::milliseconds(1500));
+  });
+
+  event_loop.Run();
+
+  // Ensure lack of logs with quiet flag
+  ASSERT_TRUE(::testing::internal::GetCapturedStderr().empty());
+  EXPECT_TRUE(observed_stopped);
+  EXPECT_EQ(aos::starter::State::STOPPED, error_out.status());
+}
+
 }  // namespace aos::starter::testing