Add failsafe logger for phosphor-pid-control service.

Further optimizations will be handled in b/380946245.

https://fusion2.corp.google.com/6f303831-040f-31c0-a747-2c7c4458b54c
https://fusion2.corp.google.com/b2560a17-2a26-36a0-a1e9-b13a4d395f02
https://fusion2.corp.google.com/b62ed499-47ba-30bc-97c5-4675fafe01cc

Tested:
...
Nov 23 21:40:06 tmddp10-nfd01.prod.google.com swampd[4893]: Zone `0` is in failsafe mode.                With update at `fleeting0`: The sensor has bad readings.
Nov 23 21:40:06 tmddp10-nfd01.prod.google.com swampd[4893]: Zone `1` is in failsafe mode.                With update at `fleeting1`: The sensor has bad readings.
Nov 23 21:40:06 tmddp10-nfd01.prod.google.com swampd[4893]: Zone `1` leaves failsafe mode.                With update at `hotswap_in_Input_Power`: The sensor has recovered.
Nov 23 21:40:06 tmddp10-nfd01.prod.google.com swampd[4893]: Zone `0` leaves failsafe mode.                With update at `hotswap_in_Input_Power`: The sensor has recovered.
...

Platforms-Affected: all platforms
Google-Bug-Id: 355242359
Change-Id: I428dec24501d8036f8478b8cf2aa3c0cfbc2ce80
Signed-off-by: alphetis <alphetis@google.com>
(cherry picked from commit 61f697c8c309327096a7da346ace12b6d6d1d223)
diff --git a/recipes-phosphor/fans/phosphor-pid-control/0001-Add-failsafe-logger-for-zones.patch b/recipes-phosphor/fans/phosphor-pid-control/0001-Add-failsafe-logger-for-zones.patch
new file mode 100644
index 0000000..0bc9ee0
--- /dev/null
+++ b/recipes-phosphor/fans/phosphor-pid-control/0001-Add-failsafe-logger-for-zones.patch
@@ -0,0 +1,645 @@
+From 01dedb7a26b2293044ed4e50ed45999e9fb5b462 Mon Sep 17 00:00:00 2001
+From: James Zheng <alphetis@google.com>
+Date: Sun, 24 Nov 2024 04:01:17 +0000
+Subject: [PATCH] Add failsafe logger for zones
+
+Tested: Manually
+
+Patch Tracking Bug: b/381994545
+Upstream info / review: https://gerrit.openbmc.org/c/openbmc/phosphor-pid-control/+/75964
+Upstream-Status: Pending
+Justification: Enable failsafe logger for each zone in pid control
+
+---
+ dbus/dbuspassive.cpp                        | 34 ++++++++++-
+ failsafeloggers/builder.cpp                 | 63 +++++++++++++++++++++
+ failsafeloggers/builder.hpp                 | 17 ++++++
+ failsafeloggers/failsafe_logger.cpp         | 50 ++++++++++++++++
+ failsafeloggers/failsafe_logger.hpp         | 56 ++++++++++++++++++
+ failsafeloggers/failsafe_logger_utility.cpp | 12 ++++
+ failsafeloggers/failsafe_logger_utility.hpp | 50 ++++++++++++++++
+ main.cpp                                    |  2 +
+ meson.build                                 |  2 +
+ pid/zone.cpp                                | 12 +++-
+ pid/zone.hpp                                | 10 ++++
+ pid/zone_interface.hpp                      |  3 +
+ sensors/host.cpp                            |  4 ++
+ test/dbus_passive_unittest.cpp              |  3 +
+ test/meson.build                            | 11 +++-
+ test/pid_fancontroller_unittest.cpp         |  3 +
+ test/pid_zone_unittest.cpp                  |  3 +
+ test/sensor_host_unittest.cpp               |  3 +
+ test/zone_mock.hpp                          |  2 +
+ 19 files changed, 335 insertions(+), 5 deletions(-)
+ create mode 100644 failsafeloggers/builder.cpp
+ create mode 100644 failsafeloggers/builder.hpp
+ create mode 100644 failsafeloggers/failsafe_logger.cpp
+ create mode 100644 failsafeloggers/failsafe_logger.hpp
+ create mode 100644 failsafeloggers/failsafe_logger_utility.cpp
+ create mode 100644 failsafeloggers/failsafe_logger_utility.hpp
+
+diff --git a/dbus/dbuspassive.cpp b/dbus/dbuspassive.cpp
+index 14f28ea..a782f67 100644
+--- a/dbus/dbuspassive.cpp
++++ b/dbus/dbuspassive.cpp
+@@ -18,6 +18,9 @@
+ #include "dbushelper_interface.hpp"
+ #include "dbuspassiveredundancy.hpp"
+ #include "dbusutil.hpp"
++#include "failsafeloggers/builder.hpp"
++#include "failsafeloggers/failsafe_logger.cpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "util.hpp"
+ 
+ #include <sdbusplus/bus.hpp>
+@@ -210,6 +213,8 @@ bool DbusPassive::getFailed(void) const
+         const std::set<std::string>& failures = redundancy->getFailed();
+         if (failures.find(path) != failures.end())
+         {
++            outputFailsafeLogWithSensor(_id, true, _id,
++                                        "The sensor path is marked redundant.");
+             return true;
+         }
+     }
+@@ -235,6 +240,8 @@ bool DbusPassive::getFailed(void) const
+     // which is set and cleared by other causes.
+     if (_badReading)
+     {
++	outputFailsafeLogWithSensor(_id, true, _id,
++                                    "The sensor has bad readings.");
+         return true;
+     }
+ 
+@@ -244,10 +251,35 @@ bool DbusPassive::getFailed(void) const
+     // they are not cooling the system, enable failsafe mode also.
+     if (_marginHot)
+     {
++        outputFailsafeLogWithSensor(_id, true, _id,
++                                    "The sensor has no thermal margin left.");
+         return true;
+     }
+ 
+-    return _failed || !_available || !_functional;
++    if (_failed)
++    {
++        outputFailsafeLogWithSensor(_id, true, _id,
++                                    "The sensor has failed with a critical issue.");
++        return true;
++    }
++
++    if (!_available)
++    {
++        outputFailsafeLogWithSensor(_id, true, _id,
++                                    "The sensor is unavailable.");
++        return true;
++    }
++
++    if (!_functional) {
++        outputFailsafeLogWithSensor(_id, true, _id,
++                                    "The sensor is not functional.");
++        return true;
++    }
++
++    outputFailsafeLogWithSensor(_id, false, _id,
++                                "The sensor has recovered.");
++
++    return false;
+ }
+ 
+ void DbusPassive::setFailed(bool value)
+diff --git a/failsafeloggers/builder.cpp b/failsafeloggers/builder.cpp
+new file mode 100644
+index 0000000..96f30ff
+--- /dev/null
++++ b/failsafeloggers/builder.cpp
+@@ -0,0 +1,63 @@
++/**
++ * Copyright 2017 Google Inc.
++ *
++ * Licensed under the Apache License, Version 2.0 (the "License");
++ * you may not use this file except in compliance with the License.
++ * You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++#include "failsafeloggers/builder.hpp"
++
++#include <algorithm>
++#include <iostream>
++#include <memory>
++#include <string>
++#include <unordered_map>
++#include <vector>
++
++#include "conf.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
++
++namespace pid_control {
++
++void buildFailsafeLoggers(
++    const std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>& zones,
++    const size_t logMaxCountPerSecond /* = 20 */) {
++  zoneIdToFailsafeLogger =
++      std::unordered_map<int64_t,
++                         std::shared_ptr<pid_control::FailsafeLogger>>();
++  sensorNameToZoneId = std::unordered_map<std::string, std::vector<int64_t>>();
++  for (const auto& zoneIdToZone : zones) {
++    int64_t zoneId = zoneIdToZone.first;
++    // Create a failsafe logger for each zone.
++    zoneIdToFailsafeLogger[zoneId] =
++        std::make_shared<FailsafeLogger>(
++            logMaxCountPerSecond, zoneIdToZone.second->getFailSafeMode());
++
++    // Build the sensor-zone topology map.
++    std::vector<std::string> sensorNames =
++        zoneIdToZone.second->getSensorNames();
++    for (const std::string& sensorName : sensorNames) {
++      if (std::find(sensorNameToZoneId[sensorName].begin(),
++                    sensorNameToZoneId[sensorName].end(),
++                    zoneId) ==
++          sensorNameToZoneId[sensorName].end()) {
++        sensorNameToZoneId[sensorName].push_back(zoneId);
++      }
++    }
++    std::cerr << "Build failsafe logger for Zone " << zoneId << " with initial "
++              << "failsafe mode: " << zoneIdToZone.second->getFailSafeMode()
++              << "\n";
++  }
++}
++
++}  // namespace pid_control
+diff --git a/failsafeloggers/builder.hpp b/failsafeloggers/builder.hpp
+new file mode 100644
+index 0000000..2c11a05
+--- /dev/null
++++ b/failsafeloggers/builder.hpp
+@@ -0,0 +1,17 @@
++#pragma once
++
++#include "conf.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++#include "pid/zone_interface.hpp"
++#include "sensors/manager.hpp"
++
++#include <memory>
++#include <unordered_map>
++
++namespace pid_control
++{
++
++void buildFailsafeLoggers(const std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>& zones,
++                          const size_t logMaxCountPerSecond = 20);
++
++}  // namespace pid_control
+diff --git a/failsafeloggers/failsafe_logger.cpp b/failsafeloggers/failsafe_logger.cpp
+new file mode 100644
+index 0000000..8485c97
+--- /dev/null
++++ b/failsafeloggers/failsafe_logger.cpp
+@@ -0,0 +1,50 @@
++#include "failsafe_logger.hpp"
++
++#include <chrono>
++#include <iostream>
++
++namespace pid_control {
++
++void FailsafeLogger::outputFailsafeLog(const int64_t zoneId, const bool newFailsafeState,
++                                       const std::string location,
++                                       const std::string reason) {
++  // Remove outdated log entries.
++  const auto now = std::chrono::high_resolution_clock::now();
++  uint64_t nowMs = std::chrono::duration_cast<std::chrono::milliseconds>
++      (now.time_since_epoch()).count();
++  // Limit the log output in 1 second.
++  while (!_logTimestamps.empty() && nowMs - _logTimestamps.front() >= 1000) {
++    _logTimestamps.pop_front();
++  }
++
++  // There is a failsafe state change, clear the logs in current state.
++  bool originFailsafeState = _currentFailsafeState;
++  if (newFailsafeState != _currentFailsafeState) {
++    _logsInCurrentState.clear();
++    _currentFailsafeState = newFailsafeState;
++  }
++  // Do not output the log if the capacity is reached, or if the log is already
++  // encountered in the current state.
++  std::string locationReason = location + " @ " + reason;
++  if (_logTimestamps.size() >= _logMaxCountPerSecond ||
++      !_logsInCurrentState.contains(locationReason)) {
++    return;
++  }
++  _logsInCurrentState.insert(locationReason);
++
++  // Only output the log if the zone enters, stays in, or leaves failsafe mode.
++  // No need to output the log if the zone stays in non-failsafe mode.
++  if (newFailsafeState) {
++    std::cerr << "Zone `" << zoneId
++              << "` is in failsafe mode.\t\tWith update at `"
++              << location << "`: " << reason << "\n";
++  } else if (!newFailsafeState && originFailsafeState) {
++    std::cerr << "Zone `" << zoneId
++              << "` leaves failsafe mode.\t\tWith update at `"
++              << location << "`: " << reason << "\n";
++  }
++
++  _logTimestamps.push_back(nowMs);
++}
++
++}  // namespace pid_control
+diff --git a/failsafeloggers/failsafe_logger.hpp b/failsafeloggers/failsafe_logger.hpp
+new file mode 100644
+index 0000000..67fa3eb
+--- /dev/null
++++ b/failsafeloggers/failsafe_logger.hpp
+@@ -0,0 +1,56 @@
++#pragma once
++
++#include <deque>
++#include <memory>
++#include <string>
++#include <unordered_map>
++#include <unordered_set>
++
++namespace pid_control
++{
++
++/**
++ * Log the reason for a zone to enter and leave the failsafe mode.
++ *
++ * Particularly, for entering the failsafe mode:
++ *   1. A sensor is specified in thermal config as an input but missed in DBus
++ *   2. A sensor has null readings in DBus
++ *   3. A sensor is abnormal in DBus (not functional, not enabled, etc)
++ *   4. A sensor's reading is above upper critical (UC) limit
++ *
++ * Among the above reasons:
++ *   1 excludes 2, 3, 4.
++ *   2 excludes 1, 4.
++ *   3 excludes 1.
++ *   4 excludes 1, 2.
++ *
++ * Note that this log is at the zone level, not the sensor level.
++ */
++class FailsafeLogger
++{
++  public:
++    FailsafeLogger(size_t logMaxCountPerSecond = 20,
++                   bool currentFailsafeState = false)
++      : _logMaxCountPerSecond(logMaxCountPerSecond),
++        _currentFailsafeState(currentFailsafeState) {}
++    ~FailsafeLogger() = default;
++
++    /** Attempt to output an entering/leaving-failsafe-mode log.
++      */
++    void outputFailsafeLog(int64_t zoneId, bool newFailsafeState,
++                           const std::string location,
++                           const std::string reason);
++
++  private:
++    // The maximum number of log entries to be output within 1 second.
++    size_t _logMaxCountPerSecond;
++    // Whether the zone is currently in the failsafe mode.
++    bool _currentFailsafeState;
++    // The timestamps of the log entries.
++    std::deque<size_t> _logTimestamps;
++    // The logs already encountered in the current state.
++    std::unordered_set<std::string> _logsInCurrentState;
++};
++
++} // namespace pid_control
++
+diff --git a/failsafeloggers/failsafe_logger_utility.cpp b/failsafeloggers/failsafe_logger_utility.cpp
+new file mode 100644
+index 0000000..3466362
+--- /dev/null
++++ b/failsafeloggers/failsafe_logger_utility.cpp
+@@ -0,0 +1,12 @@
++#include "failsafe_logger_utility.hpp"
++
++#include <string>
++
++
++std::unordered_map<int64_t, std::shared_ptr<pid_control::FailsafeLogger>>
++  zoneIdToFailsafeLogger =
++    std::unordered_map<int64_t, std::shared_ptr<pid_control::FailsafeLogger>>();
++
++std::unordered_map<std::string, std::vector<int64_t>>
++  sensorNameToZoneId = std::unordered_map<std::string, std::vector<int64_t>>();
++
+diff --git a/failsafeloggers/failsafe_logger_utility.hpp b/failsafeloggers/failsafe_logger_utility.hpp
+new file mode 100644
+index 0000000..d886406
+--- /dev/null
++++ b/failsafeloggers/failsafe_logger_utility.hpp
+@@ -0,0 +1,50 @@
++#pragma once
++
++#include "conf.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++
++#include <memory>
++#include <string>
++#include <unordered_map>
++#include <vector>
++
++/** Map of the zone ID to its failsafe logger.
++ */
++extern std::unordered_map<int64_t, std::shared_ptr<pid_control::FailsafeLogger>>
++    zoneIdToFailsafeLogger;
++
++/** Map of the sensor name/ID to its corresponding zone IDs.
++ */
++extern std::unordered_map<std::string, std::vector<int64_t>> sensorNameToZoneId;
++
++namespace pid_control {
++
++    /** Given a sensor name, attempt to output entering/leaving-failsafe-mode
++     * logs for its corresponding zones.
++     */
++    inline void outputFailsafeLogWithSensor(const std::string sensorName,
++                                     const bool newFailsafeState,
++                                     const std::string location,
++                                     const std::string reason) {
++        for (const int64_t zoneId : sensorNameToZoneId[sensorName]) {
++            zoneIdToFailsafeLogger[zoneId]->outputFailsafeLog(zoneId,
++                                               newFailsafeState,
++                                               location,
++                                               reason);
++        }
++    }
++
++    /** Given a zone ID, attempt to output entering/leaving-failsafe-mode
++     * logs for its corresponding zones.
++     */
++    inline void outputFailsafeLogWithZone(const int64_t zoneId,
++                                     const bool newFailsafeState,
++                                     const std::string location,
++                                     const std::string reason) {
++        zoneIdToFailsafeLogger[zoneId]->outputFailsafeLog(zoneId,
++                                                        newFailsafeState,
++                                                        location,
++                                                        reason);
++    }
++}  // namespace pid_control
++
+diff --git a/main.cpp b/main.cpp
+index 911096e..6e7b275 100644
+--- a/main.cpp
++++ b/main.cpp
+@@ -20,6 +20,7 @@
+ #include "conf.hpp"
+ #include "dbus/dbusconfiguration.hpp"
+ #include "dbus/dbuspassive.hpp"
++#include "failsafeloggers/builder.hpp"
+ #include "interfaces.hpp"
+ #include "pid/builder.hpp"
+ #include "pid/buildjson.hpp"
+@@ -167,6 +168,7 @@ void restartControlLoops()
+     state::mgmr = buildSensors(sensorConfig, passiveBus, hostBus);
+     state::zones = buildZones(zoneConfig, zoneDetailsConfig, state::mgmr,
+                               modeControlBus);
++    buildFailsafeLoggers(state::zones, /* logMaxCountPerSecond = */ 20);
+ 
+     if (0 == state::zones.size())
+     {
+diff --git a/meson.build b/meson.build
+index 967087a..fcfa4d0 100644
+--- a/meson.build
++++ b/meson.build
+@@ -100,6 +100,8 @@ libswampd_sources = [
+     'dbus/dbuspassive.cpp',
+     'dbus/dbusactiveread.cpp',
+     'dbus/dbuswrite.cpp',
++    'failsafeloggers/builder.cpp',
++    'failsafeloggers/failsafe_logger_utility.cpp',
+     'sysfs/sysfsread.cpp',
+     'sysfs/sysfswrite.cpp',
+     'sysfs/util.cpp',
+diff --git a/pid/zone.cpp b/pid/zone.cpp
+index 5332efe..4b44ced 100644
+--- a/pid/zone.cpp
++++ b/pid/zone.cpp
+@@ -18,6 +18,7 @@
+ #include "zone.hpp"
+ 
+ #include "conf.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "pid/controller.hpp"
+ #include "pid/ec/pid.hpp"
+ #include "pid/fancontroller.hpp"
+@@ -101,7 +102,11 @@ void DbusPidZone::markSensorMissing(const std::string& name)
+     if (_missingAcceptable.find(name) != _missingAcceptable.end())
+     {
+         // Disallow sensors in MissingIsAcceptable list from causing failsafe
+-        return;
++        outputFailsafeLogWithZone(_zoneId,
++                                  this->getFailSafeMode(),
++                                  name,
++                                  "The sensor is missing but is acceptable.");
++	return;
+     }
+ 
+     _failSafeSensors.emplace(name);
+@@ -530,6 +535,11 @@ Sensor* DbusPidZone::getSensor(const std::string& name)
+     return _mgr.getSensor(name);
+ }
+ 
++std::vector<std::string> DbusPidZone::getSensorNames(void)
++{
++    return _thermalInputs;
++}
++
+ bool DbusPidZone::getRedundantWrite(void) const
+ {
+     return _redundantWrite;
+diff --git a/pid/zone.hpp b/pid/zone.hpp
+index 52180a5..172e9df 100644
+--- a/pid/zone.hpp
++++ b/pid/zone.hpp
+@@ -2,6 +2,7 @@
+ 
+ #include "conf.hpp"
+ #include "controller.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "pidcontroller.hpp"
+ #include "sensors/manager.hpp"
+ #include "sensors/sensor.hpp"
+@@ -88,6 +89,7 @@ class DbusPidZone : public ZoneInterface, public ModeObject
+     uint64_t getUpdateThermalsCycle(void) const override;
+ 
+     Sensor* getSensor(const std::string& name) override;
++    std::vector<std::string> getSensorNames(void) override;
+     void determineMaxSetPointRequest(void) override;
+     void updateFanTelemetry(void) override;
+     void updateSensors(void) override;
+@@ -185,6 +187,10 @@ class DbusPidZone : public ZoneInterface, public ModeObject
+                 {
+                     std::cerr << sensorInput << " sensor timeout\n";
+                 }
++                outputFailsafeLogWithZone(_zoneId,
++                                          this->getFailSafeMode(),
++                                          sensorInput,
++                                          "The sensor has timed out.");
+             }
+             else
+             {
+@@ -199,6 +205,10 @@ class DbusPidZone : public ZoneInterface, public ModeObject
+                     }
+ 
+                     _failSafeSensors.erase(kt);
++		    outputFailsafeLogWithZone(_zoneId,
++                                              this->getFailSafeMode(),
++                                              sensorInput,
++                                              "The sensor has recovered.");
+                 }
+             }
+         }
+diff --git a/pid/zone_interface.hpp b/pid/zone_interface.hpp
+index 31f3256..bb2454c 100644
+--- a/pid/zone_interface.hpp
++++ b/pid/zone_interface.hpp
+@@ -27,6 +27,9 @@ class ZoneInterface
+     /** Return a pointer to the sensor specified by name. */
+     virtual Sensor* getSensor(const std::string& name) = 0;
+ 
++    /** Return the list of sensor names in the zone. */
++    virtual std::vector<std::string> getSensorNames(void) = 0;
++
+     /* updateFanTelemetry() and updateSensors() both clear the failsafe state
+      * for a sensor if it's no longer in that state.
+      */
+diff --git a/sensors/host.cpp b/sensors/host.cpp
+index ae63896..8d43a96 100644
+--- a/sensors/host.cpp
++++ b/sensors/host.cpp
+@@ -16,6 +16,8 @@
+ 
+ #include "host.hpp"
+ 
++#include "failsafeloggers/failsafe_logger_utility.hpp"
++
+ #include <cmath>
+ #include <iostream>
+ #include <memory>
+@@ -103,6 +105,8 @@ bool HostSensor::getFailed(void)
+         return false;
+     }
+ 
++    outputFailsafeLogWithSensor(getName(), true, getName(),
++                                "The sensor has invalid readings.");
+     return true;
+ }
+ 
+diff --git a/test/dbus_passive_unittest.cpp b/test/dbus_passive_unittest.cpp
+index 29c73dc..5b652d5 100644
+--- a/test/dbus_passive_unittest.cpp
++++ b/test/dbus_passive_unittest.cpp
+@@ -1,5 +1,8 @@
+ #include "conf.hpp"
+ #include "dbus/dbuspassive.hpp"
++#include "failsafeloggers/builder.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "test/dbushelper_mock.hpp"
+ 
+ #include <sdbusplus/test/sdbus_mock.hpp>
+diff --git a/test/meson.build b/test/meson.build
+index 50f71a3..fae0312 100644
+--- a/test/meson.build
++++ b/test/meson.build
+@@ -39,7 +39,8 @@ unittest_source = {
+     'dbus_active_unittest': ['../dbus/dbusactiveread.cpp'],
+     'dbus_passive_unittest': ['../dbus/dbuspassive.cpp',
+                               '../dbus/dbuspassiveredundancy.cpp',
+-                              '../dbus/dbusutil.cpp'],
++                              '../dbus/dbusutil.cpp',
++                              '../failsafeloggers/failsafe_logger_utility.cpp'],
+     'dbus_util_unittest': ['../dbus/dbusutil.cpp'],
+     'json_parse_unittest': ['../buildjson/buildjson.cpp'],
+     'pid_json_unittest': ['../pid/buildjson.cpp',
+@@ -60,13 +61,17 @@ unittest_source = {
+                                        '../pid/thermalcontroller.cpp',
+                                        '../pid/tuning.cpp',
+                                        '../pid/util.cpp'],
+-    'pid_zone_unittest': ['../pid/ec/pid.cpp',
++    'pid_zone_unittest': ['../failsafeloggers/failsafe_logger.cpp',
++                          '../failsafeloggers/failsafe_logger_utility.cpp',
++                          '../pid/ec/pid.cpp',
+                           '../pid/ec/logging.cpp',
+                           '../pid/pidcontroller.cpp',
+                           '../pid/tuning.cpp',
+                           '../pid/zone.cpp',
+                           '../sensors/manager.cpp'],
+-    'sensor_host_unittest': ['../sensors/host.cpp'],
++    'sensor_host_unittest': ['../failsafeloggers/failsafe_logger.cpp',
++                             '../failsafeloggers/failsafe_logger_utility.cpp',
++                             '../sensors/host.cpp'],
+     'sensor_manager_unittest': ['../sensors/manager.cpp'],
+     'sensor_pluggable_unittest': ['../sensors/pluggable.cpp'],
+     'sensors_json_unittest': ['../sensors/buildjson.cpp'],
+diff --git a/test/pid_fancontroller_unittest.cpp b/test/pid_fancontroller_unittest.cpp
+index 6075a95..97dda0a 100644
+--- a/test/pid_fancontroller_unittest.cpp
++++ b/test/pid_fancontroller_unittest.cpp
+@@ -1,5 +1,8 @@
+ #include "config.h"
+ 
++#include "failsafeloggers/builder.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "pid/ec/logging.hpp"
+ #include "pid/ec/pid.hpp"
+ #include "pid/fancontroller.hpp"
+diff --git a/test/pid_zone_unittest.cpp b/test/pid_zone_unittest.cpp
+index 1f6e672..5430535 100644
+--- a/test/pid_zone_unittest.cpp
++++ b/test/pid_zone_unittest.cpp
+@@ -1,3 +1,6 @@
++#include "failsafeloggers/builder.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "pid/ec/logging.hpp"
+ #include "pid/ec/pid.hpp"
+ #include "pid/zone.hpp"
+diff --git a/test/sensor_host_unittest.cpp b/test/sensor_host_unittest.cpp
+index cb5ede4..dcde0a6 100644
+--- a/test/sensor_host_unittest.cpp
++++ b/test/sensor_host_unittest.cpp
+@@ -1,3 +1,6 @@
++#include "failsafeloggers/builder.hpp"
++#include "failsafeloggers/failsafe_logger.hpp"
++#include "failsafeloggers/failsafe_logger_utility.hpp"
+ #include "sensors/host.hpp"
+ #include "test/helpers.hpp"
+ 
+diff --git a/test/zone_mock.hpp b/test/zone_mock.hpp
+index 4e34890..fa3e21d 100644
+--- a/test/zone_mock.hpp
++++ b/test/zone_mock.hpp
+@@ -3,6 +3,7 @@
+ #include "pid/zone_interface.hpp"
+ 
+ #include <string>
++#include <vector>
+ 
+ #include <gmock/gmock.h>
+ 
+@@ -49,6 +50,7 @@ class ZoneMock : public ZoneInterface
+     MOCK_CONST_METHOD0(getAccSetPoint, bool());
+ 
+     MOCK_METHOD1(getSensor, Sensor*(const std::string&));
++    MOCK_METHOD0(getSensorNames, std::vector<std::string>());
+ 
+     MOCK_METHOD0(initializeLog, void());
+     MOCK_METHOD1(writeLog, void(const std::string&));
+-- 
+2.47.0.371.ga323438b13-goog
diff --git a/recipes-phosphor/fans/phosphor-pid-control_%.bbappend b/recipes-phosphor/fans/phosphor-pid-control_%.bbappend
index c57fc8e..39e8c0f 100644
--- a/recipes-phosphor/fans/phosphor-pid-control_%.bbappend
+++ b/recipes-phosphor/fans/phosphor-pid-control_%.bbappend
@@ -3,4 +3,5 @@
 SRC_URI:append = " \
   file://0001-Support-temperature-sensor-override-dbus-interface.patch \
   file://0001-set-fan-pwm-forcely.patch \
+  file://0001-Add-failsafe-logger-for-zones.patch \
 "