1 /** 2 * Copyright © 2023 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 #include "journal.hpp" 17 18 #include "util.hpp" 19 20 #include <phosphor-logging/lg2.hpp> 21 22 #include <format> 23 24 namespace openpower::pels 25 { 26 27 using namespace phosphor::logging; 28 29 /** 30 * @class JournalCloser 31 * 32 * Closes the journal on destruction 33 */ 34 class JournalCloser 35 { 36 public: 37 JournalCloser() = delete; 38 JournalCloser(const JournalCloser&) = delete; 39 JournalCloser(JournalCloser&&) = delete; 40 JournalCloser& operator=(const JournalCloser&) = delete; 41 JournalCloser& operator=(JournalCloser&&) = delete; 42 43 explicit JournalCloser(sd_journal* journal) : journal{journal} {} 44 45 ~JournalCloser() 46 { 47 sd_journal_close(journal); 48 } 49 50 private: 51 sd_journal* journal{nullptr}; 52 }; 53 54 void Journal::sync() const 55 { 56 auto start = std::chrono::steady_clock::now(); 57 58 util::journalSync(); 59 60 auto end = std::chrono::steady_clock::now(); 61 auto duration = 62 std::chrono::duration_cast<std::chrono::milliseconds>(end - start); 63 64 if (duration.count() > 100) 65 { 66 lg2::info("Journal sync took {DURATION}ms", "DURATION", 67 duration.count()); 68 } 69 } 70 71 std::vector<std::string> 72 Journal::getMessages(const std::string& syslogID, size_t maxMessages) const 73 { 74 // The message registry JSON schema will also fail if a zero is in the JSON 75 if (0 == maxMessages) 76 { 77 lg2::error( 78 "maxMessages value of zero passed into Journal::getMessages"); 79 return std::vector<std::string>{}; 80 } 81 82 sd_journal* journal; 83 int rc = sd_journal_open(&journal, SD_JOURNAL_LOCAL_ONLY); 84 if (rc < 0) 85 { 86 throw std::runtime_error{ 87 std::string{"Failed to open journal: "} + strerror(-rc)}; 88 } 89 90 JournalCloser closer{journal}; 91 92 if (!syslogID.empty()) 93 { 94 std::string match{"SYSLOG_IDENTIFIER=" + syslogID}; 95 96 rc = sd_journal_add_match(journal, match.c_str(), 0); 97 if (rc < 0) 98 { 99 throw std::runtime_error{ 100 std::string{"Failed to add journal match: "} + strerror(-rc)}; 101 } 102 } 103 104 // Loop through matching entries from newest to oldest 105 std::vector<std::string> messages; 106 messages.reserve(maxMessages); 107 std::string sID, pid, message, timeStamp, line; 108 109 SD_JOURNAL_FOREACH_BACKWARDS(journal) 110 { 111 timeStamp = getTimeStamp(journal); 112 sID = getFieldValue(journal, "SYSLOG_IDENTIFIER"); 113 pid = getFieldValue(journal, "_PID"); 114 message = getFieldValue(journal, "MESSAGE"); 115 116 line = timeStamp + " " + sID + "[" + pid + "]: " + message; 117 messages.emplace(messages.begin(), line); 118 119 if (messages.size() >= maxMessages) 120 { 121 break; 122 } 123 } 124 125 return messages; 126 } 127 128 std::string Journal::getFieldValue(sd_journal* journal, 129 const std::string& field) const 130 { 131 std::string value{}; 132 133 const void* data{nullptr}; 134 size_t length{0}; 135 int rc = sd_journal_get_data(journal, field.c_str(), &data, &length); 136 if (rc < 0) 137 { 138 if (-rc == ENOENT) 139 { 140 // Current entry does not include this field; return empty value 141 return value; 142 } 143 else 144 { 145 throw std::runtime_error{ 146 std::string{"Failed to read journal entry field: "} + 147 strerror(-rc)}; 148 } 149 } 150 151 // Get value from field data. Field data in format "FIELD=value". 152 std::string dataString{static_cast<const char*>(data), length}; 153 std::string::size_type pos = dataString.find('='); 154 if ((pos != std::string::npos) && ((pos + 1) < dataString.size())) 155 { 156 // Value is substring after the '=' 157 value = dataString.substr(pos + 1); 158 } 159 160 return value; 161 } 162 163 std::string Journal::getTimeStamp(sd_journal* journal) const 164 { 165 // Get realtime (wallclock) timestamp of current journal entry. The 166 // timestamp is in microseconds since the epoch. 167 uint64_t usec{0}; 168 int rc = sd_journal_get_realtime_usec(journal, &usec); 169 if (rc < 0) 170 { 171 throw std::runtime_error{ 172 std::string{"Failed to get journal entry timestamp: "} + 173 strerror(-rc)}; 174 } 175 176 // Convert to number of seconds since the epoch 177 time_t secs = usec / 1000000; 178 179 // Convert seconds to tm struct required by strftime() 180 struct tm* timeStruct = gmtime(&secs); 181 if (timeStruct == nullptr) 182 { 183 throw std::runtime_error{ 184 std::string{"Invalid journal entry timestamp: "} + strerror(errno)}; 185 } 186 187 // Convert tm struct into a date/time string 188 char timeStamp[80]; 189 strftime(timeStamp, sizeof(timeStamp), "%b %d %H:%M:%S", timeStruct); 190 191 return timeStamp; 192 } 193 194 } // namespace openpower::pels 195