monitor: add journal entries to fan PELs
Adds a new JSON section to the PEL that contains the last 25 systemd
journal entries indicating which system services have
started/stopped/etc.
Signed-off-by: Mike Capps <mikepcapps@gmail.com>
Change-Id: I9f8a7ab8bb7c213cde30496327d83e9f3fab7c94
diff --git a/monitor/fan_error.cpp b/monitor/fan_error.cpp
index 15d8ed0..488bab4 100644
--- a/monitor/fan_error.cpp
+++ b/monitor/fan_error.cpp
@@ -1,5 +1,5 @@
/**
- * Copyright © 2020 IBM Corporation
+ * Copyright © 2022 IBM Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,8 @@
#include "logging.hpp"
#include "sdbusplus.hpp"
+#include <systemd/sd-journal.h>
+
#include <nlohmann/json.hpp>
#include <xyz/openbmc_project/Logging/Create/server.hpp>
@@ -39,6 +41,33 @@
namespace fs = std::filesystem;
using namespace phosphor::fan::util;
+/**
+ * @class JournalCloser
+ *
+ * Automatically closes the journal when the object goes out of scope.
+ */
+class JournalCloser
+{
+ public:
+ // Specify which compiler-generated methods we want
+ JournalCloser() = delete;
+ JournalCloser(const JournalCloser&) = delete;
+ JournalCloser(JournalCloser&&) = delete;
+ JournalCloser& operator=(const JournalCloser&) = delete;
+ JournalCloser& operator=(JournalCloser&&) = delete;
+
+ JournalCloser(sd_journal* journal) : journal{journal}
+ {}
+
+ ~JournalCloser()
+ {
+ sd_journal_close(journal);
+ }
+
+ private:
+ sd_journal* journal{nullptr};
+};
+
FFDCFile::FFDCFile(const fs::path& name) :
_fd(open(name.c_str(), O_RDONLY)), _name(name)
{
@@ -62,6 +91,13 @@
ffdc.emplace_back(FFDCFormat::Text, 0x01, 0x01, logFile->fd());
}
+ // add the previous systemd journal entries as FFDC
+ auto serviceFFDC = makeJsonFFDCFile(getJournalEntries(25));
+ if (serviceFFDC && serviceFFDC->fd() != -1)
+ {
+ ffdc.emplace_back(FFDCFormat::JSON, 0x01, 0x01, serviceFFDC->fd());
+ }
+
// Add the passed in JSON as FFDC
auto ffdcFile = makeJsonFFDCFile(jsonFFDC);
if (ffdcFile && (ffdcFile->fd() != -1))
@@ -165,4 +201,140 @@
return nullptr;
}
+nlohmann::json FanError::getJournalEntries(int numLines) const
+{
+ // Sleep 100ms; otherwise recent journal entries sometimes not available
+ using namespace std::chrono_literals;
+ std::this_thread::sleep_for(100ms);
+
+ std::vector<std::string> entries;
+
+ // Open the journal
+ sd_journal* journal;
+ int rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY);
+ if (rc < 0)
+ {
+ // Build one line string containing field values
+ entries.push_back("[Internal error: sd_journal_open(), rc=" +
+ std::string(strerror(rc)) + "]");
+ return json(entries);
+ }
+
+ // Create object to automatically close journal
+ JournalCloser closer{journal};
+
+ std::string field{"SYSLOG_IDENTIFIER"};
+ std::vector<std::string> executables{"systemd"};
+
+ entries.reserve(2 * numLines);
+
+ for (const auto& executable : executables)
+ {
+ // Add match so we only loop over entries with specified field value
+ std::string match{field + '=' + executable};
+ rc = sd_journal_add_match(journal, match.c_str(), 0);
+ if (rc < 0)
+ {
+ // Build one line string containing field values
+ entries.push_back("[Internal error: sd_journal_add_match(), rc=" +
+ std::string(strerror(rc)) + "]");
+
+ break;
+ }
+
+ int count{1};
+
+ std::string syslogID, pid, message, timeStamp;
+
+ // Loop through journal entries from newest to oldest
+ SD_JOURNAL_FOREACH_BACKWARDS(journal)
+ {
+ // Get relevant journal entry fields
+ timeStamp = getTimeStamp(journal);
+ syslogID = getFieldValue(journal, "SYSLOG_IDENTIFIER");
+ pid = getFieldValue(journal, "_PID");
+ message = getFieldValue(journal, "MESSAGE");
+
+ // Build one line string containing field values
+ entries.push_back(timeStamp + " " + syslogID + "[" + pid +
+ "]: " + message);
+
+ // Stop after number of lines was read
+ if (count++ >= numLines)
+ {
+ break;
+ }
+ }
+ }
+
+ // put the journal entries in chronological order
+ std::reverse(entries.begin(), entries.end());
+
+ return json(entries);
+}
+
+std::string FanError::getTimeStamp(sd_journal* journal) const
+{
+ // Get realtime (wallclock) timestamp of current journal entry. The
+ // timestamp is in microseconds since the epoch.
+ uint64_t usec{0};
+ int rc = sd_journal_get_realtime_usec(journal, &usec);
+ if (rc < 0)
+ {
+ return "[Internal error: sd_journal_get_realtime_usec(), rc=" +
+ std::string(strerror(rc)) + "]";
+ }
+
+ // Convert to number of seconds since the epoch
+ time_t secs = usec / 1000000;
+
+ // Convert seconds to tm struct required by strftime()
+ struct tm* timeStruct = localtime(&secs);
+ if (timeStruct == nullptr)
+ {
+ return "[Internal error: localtime() returned nullptr]";
+ }
+
+ // Convert tm struct into a date/time string
+ char timeStamp[80];
+ strftime(timeStamp, sizeof(timeStamp), "%b %d %H:%M:%S", timeStruct);
+
+ return timeStamp;
+}
+
+std::string FanError::getFieldValue(sd_journal* journal,
+ const std::string& field) const
+{
+ std::string value{};
+
+ // Get field data from current journal entry
+ const void* data{nullptr};
+ size_t length{0};
+ int rc = sd_journal_get_data(journal, field.c_str(), &data, &length);
+ if (rc < 0)
+ {
+ if (-rc == ENOENT)
+ {
+ // Current entry does not include this field; return empty value
+ return value;
+ }
+ else
+ {
+ return "[Internal error: sd_journal_get_data() rc=" +
+ std::string(strerror(rc)) + "]";
+ }
+ }
+
+ // Get value from field data. Field data in format "FIELD=value".
+ std::string dataString{static_cast<const char*>(data), length};
+ std::string::size_type pos = dataString.find('=');
+ if ((pos != std::string::npos) && ((pos + 1) < dataString.size()))
+ {
+ // Value is substring after the '='
+ value = dataString.substr(pos + 1);
+ }
+
+ return value;
+}
+
} // namespace phosphor::fan::monitor
diff --git a/monitor/fan_error.hpp b/monitor/fan_error.hpp
index 79573e1..1f36c2f 100644
--- a/monitor/fan_error.hpp
+++ b/monitor/fan_error.hpp
@@ -138,6 +138,35 @@
private:
/**
+ * @brief returns a JSON structure containing the previous N journal
+ * entries.
+ *
+ * @param[in] numLines - Number of lines of journal to retrieve
+ */
+ nlohmann::json getJournalEntries(int numLines) const;
+
+ /**
+ * Gets the realtime (wallclock) timestamp for the current journal entry.
+ *
+ * @param journal current journal entry
+ * @return timestamp as a date/time string
+ */
+ std::string getTimeStamp(sd_journal* journal) const;
+
+ /**
+ * Gets the value of the specified field for the current journal entry.
+ *
+ * Returns an empty string if the current journal entry does not have the
+ * specified field.
+ *
+ * @param journal current journal entry
+ * @param field journal field name
+ * @return field value
+ */
+ std::string getFieldValue(sd_journal* journal,
+ const std::string& field) const;
+
+ /**
* @brief Returns an FFDCFile holding the Logger contents
*
* @return std::unique_ptr<FFDCFile> - The file object