1 /** 2 * Copyright © 2020 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "journal.hpp" 18 19 #include <errno.h> 20 #include <stdint.h> 21 #include <string.h> 22 #include <time.h> 23 24 #include <chrono> 25 #include <stdexcept> 26 #include <thread> 27 28 namespace phosphor::power::regulators 29 { 30 31 /** 32 * @class JournalCloser 33 * 34 * Automatically closes the journal when the object goes out of scope. 35 */ 36 class JournalCloser 37 { 38 public: 39 // Specify which compiler-generated methods we want 40 JournalCloser() = delete; 41 JournalCloser(const JournalCloser&) = delete; 42 JournalCloser(JournalCloser&&) = delete; 43 JournalCloser& operator=(const JournalCloser&) = delete; 44 JournalCloser& operator=(JournalCloser&&) = delete; 45 46 JournalCloser(sd_journal* journal) : journal{journal} 47 { 48 } 49 50 ~JournalCloser() 51 { 52 sd_journal_close(journal); 53 } 54 55 private: 56 sd_journal* journal{nullptr}; 57 }; 58 59 std::vector<std::string> 60 SystemdJournal::getMessages(const std::string& field, 61 const std::string& fieldValue, unsigned int max) 62 { 63 // Sleep 100ms; otherwise recent journal entries sometimes not available 64 using namespace std::chrono_literals; 65 std::this_thread::sleep_for(100ms); 66 67 // Open the journal 68 sd_journal* journal; 69 int rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY); 70 if (rc < 0) 71 { 72 throw std::runtime_error{std::string{"Failed to open journal: "} + 73 strerror(-rc)}; 74 } 75 76 // Create object to automatically close journal 77 JournalCloser closer{journal}; 78 79 // Add match so we only loop over entries with specified field value 80 std::string match{field + '=' + fieldValue}; 81 rc = sd_journal_add_match(journal, match.c_str(), 0); 82 if (rc < 0) 83 { 84 throw std::runtime_error{std::string{"Failed to add journal match: "} + 85 strerror(-rc)}; 86 } 87 88 // Loop through matching entries from newest to oldest 89 std::vector<std::string> messages; 90 messages.reserve((max != 0) ? max : 10); 91 std::string syslogID, pid, message, timeStamp, line; 92 SD_JOURNAL_FOREACH_BACKWARDS(journal) 93 { 94 // Get relevant journal entry fields 95 timeStamp = getTimeStamp(journal); 96 syslogID = getFieldValue(journal, "SYSLOG_IDENTIFIER"); 97 pid = getFieldValue(journal, "_PID"); 98 message = getFieldValue(journal, "MESSAGE"); 99 100 // Build one line string containing field values 101 line = timeStamp + " " + syslogID + "[" + pid + "]: " + message; 102 messages.emplace(messages.begin(), line); 103 104 // Stop looping if a max was specified and we have reached it 105 if ((max != 0) && (messages.size() >= max)) 106 { 107 break; 108 } 109 } 110 111 return messages; 112 } 113 114 std::string SystemdJournal::getFieldValue(sd_journal* journal, 115 const std::string& field) 116 { 117 std::string value{}; 118 119 // Get field data from current journal entry 120 const void* data{nullptr}; 121 size_t length{0}; 122 int rc = sd_journal_get_data(journal, field.c_str(), &data, &length); 123 if (rc < 0) 124 { 125 if (-rc == ENOENT) 126 { 127 // Current entry does not include this field; return empty value 128 return value; 129 } 130 else 131 { 132 throw std::runtime_error{ 133 std::string{"Failed to read journal entry field: "} + 134 strerror(-rc)}; 135 } 136 } 137 138 // Get value from field data. Field data in format "FIELD=value". 139 std::string dataString{static_cast<const char*>(data), length}; 140 std::string::size_type pos = dataString.find('='); 141 if ((pos != std::string::npos) && ((pos + 1) < dataString.size())) 142 { 143 // Value is substring after the '=' 144 value = dataString.substr(pos + 1); 145 } 146 147 return value; 148 } 149 150 std::string SystemdJournal::getTimeStamp(sd_journal* journal) 151 { 152 // Get realtime (wallclock) timestamp of current journal entry. The 153 // timestamp is in microseconds since the epoch. 154 uint64_t usec{0}; 155 int rc = sd_journal_get_realtime_usec(journal, &usec); 156 if (rc < 0) 157 { 158 throw std::runtime_error{ 159 std::string{"Failed to get journal entry timestamp: "} + 160 strerror(-rc)}; 161 } 162 163 // Convert to number of seconds since the epoch 164 time_t secs = usec / 1000000; 165 166 // Convert seconds to tm struct required by strftime() 167 struct tm* timeStruct = localtime(&secs); 168 if (timeStruct == nullptr) 169 { 170 throw std::runtime_error{ 171 std::string{"Invalid journal entry timestamp: "} + strerror(errno)}; 172 } 173 174 // Convert tm struct into a date/time string 175 char timeStamp[80]; 176 strftime(timeStamp, sizeof(timeStamp), "%b %d %H:%M:%S", timeStruct); 177 178 return timeStamp; 179 } 180 181 } // namespace phosphor::power::regulators 182