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