void Monitor::log_health(
  const health_check_map_t& updated,
  const health_check_map_t& previous,
  MonitorDBStore::TransactionRef t)
{
  if (!g_conf->mon_health_to_clog) {
    return;
  }

  const utime_t now = ceph_clock_now();

  // FIXME: log atomically as part of @t instead of using clog.
  dout(10) << __func__ << " updated " << updated.checks.size()
	   << " previous " << previous.checks.size()
	   << dendl;
  const auto min_log_period = g_conf->get_val<int64_t>(
      "mon_health_log_update_period");
  for (auto& p : updated.checks) {
    auto q = previous.checks.find(p.first);
    bool logged = false;
    if (q == previous.checks.end()) {
      // new
      ostringstream ss;
      ss << "Health check failed: " << p.second.summary << " ("
         << p.first << ")";
      clog->health(p.second.severity) << ss.str();

      logged = true;
    } else {
      if (p.second.summary != q->second.summary ||
	  p.second.severity != q->second.severity) {

        auto status_iter = health_check_log_times.find(p.first);
        if (status_iter != health_check_log_times.end()) {
          if (p.second.severity == q->second.severity &&
              now - status_iter->second.updated_at < min_log_period) {
            // We already logged this recently and the severity is unchanged,
            // so skip emitting an update of the summary string.
            // We'll get an update out of tick() later if the check
            // is still failing.
            continue;
          }
        }

        // summary or severity changed (ignore detail changes at this level)
        ostringstream ss;
        ss << "Health check update: " << p.second.summary << " (" << p.first << ")";
        clog->health(p.second.severity) << ss.str();

        logged = true;
      }
    }
    // Record the time at which we last logged, so that we can check this
    // when considering whether/when to print update messages.
    if (logged) {
      auto iter = health_check_log_times.find(p.first);
      if (iter == health_check_log_times.end()) {
        health_check_log_times.emplace(p.first, HealthCheckLogStatus(
          p.second.severity, p.second.summary, now));
      } else {
        iter->second = HealthCheckLogStatus(
          p.second.severity, p.second.summary, now);
      }
    }
  }
  for (auto& p : previous.checks) {
    if (!updated.checks.count(p.first)) {
      // cleared
      ostringstream ss;
      if (p.first == "DEGRADED_OBJECTS") {
        clog->info() << "All degraded objects recovered";
      } else if (p.first == "OSD_FLAGS") {
        clog->info() << "OSD flags cleared";
      } else {
        clog->info() << "Health check cleared: " << p.first << " (was: "
                     << p.second.summary << ")";
      }

      if (health_check_log_times.count(p.first)) {
        health_check_log_times.erase(p.first);
      }
    }
  }

  if (previous.checks.size() && updated.checks.size() == 0) {
    // We might be going into a fully healthy state, check
    // other subsystems
    bool any_checks = false;
    for (auto& svc : paxos_service) {
      if (&(svc->get_health_checks()) == &(previous)) {
        // Ignore the ones we're clearing right now
        continue;
      }

      if (svc->get_health_checks().checks.size() > 0) {
        any_checks = true;
        break;
      }
    }
    if (!any_checks) {
      clog->info() << "Cluster is now healthy";
    }
  }
}