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