/** * Copyright © 2020 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 #include #include #include #include #include #include namespace phosphor::power::regulators { /** * @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}; }; std::vector SystemdJournal::getMessages(const std::string& field, const std::string& fieldValue, unsigned int max) { // Sleep 100ms; otherwise recent journal entries sometimes not available using namespace std::chrono_literals; std::this_thread::sleep_for(100ms); // Open the journal 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)}; } // Create object to automatically close journal JournalCloser closer{journal}; // Add match so we only loop over entries with specified field value std::string match{field + '=' + fieldValue}; 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((max != 0) ? max : 10); std::string syslogID, pid, message, timeStamp, line; 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 line = timeStamp + " " + syslogID + "[" + pid + "]: " + message; messages.emplace(messages.begin(), line); // Stop looping if a max was specified and we have reached it if ((max != 0) && (messages.size() >= max)) { break; } } return messages; } std::string SystemdJournal::getFieldValue(sd_journal* journal, const std::string& field) { 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 { 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 SystemdJournal::getTimeStamp(sd_journal* journal) { // 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 phosphor::power::regulators