/** * Copyright © 2023 IBM Corporation * * 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 "journal.hpp" #include "util.hpp" #include #include namespace openpower::pels { using namespace phosphor::logging; /** * @class JournalCloser * * Closes the journal on destruction */ class JournalCloser { public: JournalCloser() = delete; JournalCloser(const JournalCloser&) = delete; JournalCloser(JournalCloser&&) = delete; JournalCloser& operator=(const JournalCloser&) = delete; JournalCloser& operator=(JournalCloser&&) = delete; explicit JournalCloser(sd_journal* journal) : journal{journal} {} ~JournalCloser() { sd_journal_close(journal); } private: sd_journal* journal{nullptr}; }; void Journal::sync() const { auto start = std::chrono::steady_clock::now(); util::journalSync(); auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); if (duration.count() > 100) { log( fmt::format("Journal sync took {}ms", duration.count()).c_str()); } } std::vector Journal::getMessages(const std::string& syslogID, size_t maxMessages) const { // The message registry JSON schema will also fail if a zero is in the JSON if (0 == maxMessages) { log( "maxMessages value of zero passed into Journal::getMessages"); return std::vector{}; } sd_journal* journal; int rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY); if (rc < 0) { throw std::runtime_error{std::string{"Failed to open journal: "} + strerror(-rc)}; } JournalCloser closer{journal}; if (!syslogID.empty()) { std::string match{"SYSLOG_IDENTIFIER=" + syslogID}; rc = sd_journal_add_match(journal, match.c_str(), 0); if (rc < 0) { throw std::runtime_error{ std::string{"Failed to add journal match: "} + strerror(-rc)}; } } // Loop through matching entries from newest to oldest std::vector messages; messages.reserve(maxMessages); std::string sID, pid, message, timeStamp, line; SD_JOURNAL_FOREACH_BACKWARDS(journal) { timeStamp = getTimeStamp(journal); sID = getFieldValue(journal, "SYSLOG_IDENTIFIER"); pid = getFieldValue(journal, "_PID"); message = getFieldValue(journal, "MESSAGE"); line = timeStamp + " " + sID + "[" + pid + "]: " + message; messages.emplace(messages.begin(), line); if (messages.size() >= maxMessages) { break; } } return messages; } std::string Journal::getFieldValue(sd_journal* journal, const std::string& field) const { std::string value{}; 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 { throw std::runtime_error{ std::string{"Failed to read journal entry field: "} + strerror(-rc)}; } } // Get value from field data. Field data in format "FIELD=value". std::string dataString{static_cast(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; } std::string Journal::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) { throw std::runtime_error{ std::string{"Failed to get journal entry timestamp: "} + 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) { throw std::runtime_error{ std::string{"Invalid journal entry timestamp: "} + strerror(errno)}; } // Convert tm struct into a date/time string char timeStamp[80]; strftime(timeStamp, sizeof(timeStamp), "%b %d %H:%M:%S", timeStruct); return timeStamp; } } // namespace openpower::pels