ErrorReporting.cpp
Go to the documentation of this file.
1#include "ErrorReporting.h"
2
3#include <iomanip>
4
5#include <boost/lockfree/policies.hpp>
6#include <boost/lockfree/queue.hpp>
7
10
12{
13 std::ostream&
14 operator<<(std::ostream& stream, const Type& rhs)
15 {
16 switch (rhs)
17 {
18 case Type::Bus:
19 stream << "Bus";
20 break;
21 case Type::Slave:
22 stream << "Slave";
23 break;
24 case Type::Device:
25 stream << "Device";
26 break;
27 case Type::General:
28 stream << "General";
29 break;
30 }
31 return stream;
32 }
33
34 std::ostream&
35 operator<<(std::ostream& stream, const Severity& rhs)
36 {
37 switch (rhs)
38 {
39 case Severity::Info:
40 stream << "INFO";
41 break;
42 case Severity::Debug:
43 stream << "DEBUG";
44 break;
46 stream << "WARNING";
47 break;
48 case Severity::Error:
49 stream << "ERROR";
50 break;
51 case Severity::Fatal:
52 stream << "FATAL";
53 break;
54 }
55 return stream;
56 }
57
59 {
60 static constexpr size_t queueSize = 100;
61
62 boost::lockfree::queue<Entry, boost::lockfree::capacity<queueSize>> queue;
63 };
64
65 Reporting::Reporting(std::size_t maxErrorHistorySize) :
66 pimpl(std::make_unique<QueueImpl>()), m_maxErrorHistorySize(maxErrorHistorySize)
67 {
68 setTag("ethercat::Reporting");
69
70 m_timestampReportingStart = armarx::rtNow();
71 m_errorProcessingThread = std::thread(&Reporting::errorProcessingLoop, this);
72 }
73
74 Reporting::~Reporting()
75 {
76 m_errorProcessingRunning.store(false);
77 m_errorProcessingCondition.notify_all();
78 m_errorProcessingThread.join();
79 }
80
83 {
84 static Reporting _instance;
85 return _instance;
86 }
87
90 {
91 return Reporter(this);
92 }
93
96 {
97 static Reporter _instance = Reporter(&getErrorReporting());
98 return _instance;
99 }
100
101 void
103 {
104 error.m_timestamp = armarx::rtNow();
105 bool result = pimpl->queue.push(error);
106 if (not result)
107 {
108 ARMARX_RT_LOGF_WARNING("Logging queue is full!").deactivateSpam(0.1);
109 }
110 m_errorProcessingCondition.notify_all();
111 }
112
113 void
114 Reporting::setMaxErrorHistorySize(std::size_t maxErrorHistorySize)
115 {
116 m_maxErrorHistorySize = maxErrorHistorySize;
117 }
118
119 std::optional<std::filesystem::path>
120 Reporting::dumpToFile(std::filesystem::path file, std::uint64_t lastNSeconds)
121 {
122 const auto endTime = armarx::rtNow();
123 IceUtil::Time startTime =
124 endTime - IceUtil::Time::seconds(static_cast<std::int64_t>(lastNSeconds));
125 if (startTime < m_timestampReportingStart)
126 {
127 startTime = m_timestampReportingStart;
128 }
129 const std::string yearString = startTime.toString("%y-%m-%d");
130 const std::string startTimeString = startTime.toString("%H-%M-%S");
131 const std::string endTimeString = endTime.toString("%H-%M-%S");
132
133 file.replace_filename(std::string(file.stem().c_str()) + "_" + yearString + "__" +
134 startTimeString + "_" + endTimeString);
135 file.replace_extension(".errors");
136
137 if (std::filesystem::exists(file))
138 {
139 file.replace_filename(file.filename().string() + ".new");
140 }
141
142 auto history = m_errorHistory;
143 auto it = history.begin();
144 for (const auto& [key, value] : history)
145 {
146 if (key.timestamp < startTime.toMicroSeconds())
147 {
148 it++;
149 }
150 }
151 history.erase(history.begin(), it);
152
153 if (history.size() > 0)
154 {
155 ARMARX_INFO << history.size() << " errors from " << startTimeString << " to "
156 << endTimeString << " will be saved to file " << file.string();
157 }
158 else
159 {
160 ARMARX_VERBOSE << "No errors happend from " << startTimeString << " to "
161 << endTimeString;
162 return std::nullopt;
163 }
164
165
166 if (!std::filesystem::exists(file.parent_path()) &&
167 !std::filesystem::create_directories(file.parent_path()))
168 {
169 throw std::runtime_error{"Could not create directories for '" + file.string() + "'"};
170 }
171
172 std::ofstream outFile{file.string(), std::ofstream::out};
173 if (!outFile)
174 {
175 throw std::runtime_error{"dumpToFile could not open filestream for '" + file.string() +
176 "'"};
177 }
178
179 for (const auto& [key, value] : history)
180 {
181
182 std::string timeStr = value.m_timestamp.toDateTime();
183 timeStr = timeStr.substr(timeStr.find(' ') + 1);
184
185 outFile << "[" << timeStr << "]";
186 outFile << "[" << value.m_type << "]";
187 outFile << "[" << value.m_severity << "] ";
188 outFile << std::string(value.m_message, value.m_message_size) << "\n";
189 if (value.m_sid.slaveIndex != -1)
190 {
191 outFile << value.m_sid.toString(" ") << "\n";
192 }
193 }
194
195 return std::move(file);
196 }
197
198 void
199 Reporting::errorProcessingLoop()
200 {
201 Entry e;
202 auto handleEntries = [&, this]()
203 {
204 while (pimpl->queue.pop(e))
205 {
206 const std::int64_t timestamp = e.m_timestamp.toMicroSeconds();
207 std::uint16_t i = 0;
208
209 // Check for max size of history
210 while (m_errorHistory.size() >= m_maxErrorHistorySize)
211 {
212 m_errorHistory.erase(m_errorHistory.begin());
213 }
214
215 // Check if there is already an entry with the same timestamp and find correct subindex
216 if (m_errorHistory.begin()->first.timestamp == timestamp)
217 {
218 i = m_errorHistory.begin()->first.subid + 1;
219 }
220 while (m_errorHistory.count({timestamp, i}))
221 {
222 i++;
223 }
224
225 m_errorHistory.insert({{timestamp, i}, e});
226
227 auto logpointer =
228 (loghelper(e.m_metaInfo.file, e.m_metaInfo.line, e.m_metaInfo.function));
229
230 std::string const identifier = e.getIdentifier();
231
232 switch (e.m_severity)
233 {
235 *logpointer->setBacktrace(false)
237 << deactivateSpam(e.m_deactivate_spam_seconds, identifier)
238 << e.toString();
239 return;
241 *logpointer->setBacktrace(false)
243 << deactivateSpam(e.m_deactivate_spam_seconds, identifier)
244 << e.toString();
245 return;
247 *logpointer->setBacktrace(false)
249 << deactivateSpam(e.m_deactivate_spam_seconds, identifier)
250 << e.toString();
251 return;
253 *logpointer->setBacktrace(false)
255 << deactivateSpam(e.m_deactivate_spam_seconds, identifier)
256 << e.toString();
257 return;
259 *logpointer << ::armarx::MessageTypeT::FATAL
260 << deactivateSpam(e.m_deactivate_spam_seconds, identifier)
261 << e.toString();
262 return;
263 }
264 }
265 };
266
267 while (m_errorProcessingRunning.load() == true)
268 {
269 std::unique_lock<std::mutex> lock{m_errorProcessingConditionMutex};
270 m_errorProcessingCondition.wait(
271 lock, [&]() { return !pimpl->queue.empty() || !m_errorProcessingRunning.load(); });
272 handleEntries();
273 }
274
275 // Also handle leftover entries
276 handleEntries();
277 }
278
279 Reporter::ReportWrapper
281 {
282 info.m_severity = Severity::Debug;
283 return Reporter::ReportWrapper{info, this};
284 }
285
286 Reporter::ReportWrapper
288 {
289 info.m_severity = Severity::Info;
290 return Reporter::ReportWrapper{info, this};
291 }
292
293 Reporter::ReportWrapper
295 {
296 error.m_severity = Severity::Warning;
297 m_errorCount++;
298 return Reporter::ReportWrapper{error, this};
299 }
300
301 Reporter::ReportWrapper
303 {
304 error.m_severity = Severity::Error;
305 m_errorCount++;
306 return Reporter::ReportWrapper{error, this};
307 }
308
309 void
311 {
312 error.m_severity = Severity::Fatal;
313 m_errorCount++;
314 m_errorReporting->report(std::move(error));
315 throw std::runtime_error(error.toString());
316 }
317
318 bool
320 {
321 return m_errorCount > 0;
322 }
323
324 Reporter::Reporter(Reporting* errorReporting) : m_errorReporting(errorReporting)
325 {
326 }
327
328 Entry&
330 {
331 m_sid = sid;
332 return *this;
333 }
334
335 Entry&
337 {
338 m_type = type;
339 return *this;
340 }
341
342 Entry&
343 Entry::metaInfo(const char* file, int line, const char* function)
344 {
345 snprintf(m_metaInfo.file, sizeof(m_metaInfo.file), "%s", file);
346 m_metaInfo.line = line;
347 snprintf(m_metaInfo.function, sizeof(m_metaInfo.function), "%s", function);
348 return *this;
349 }
350
351 Entry&
353 {
354 snprintf(m_device_name, sizeof(m_device_name), "%s", deviceName);
355 return *this;
356 }
357
358 std::string
360 {
361 std::stringstream ss;
362 ss << "[" << m_type << "] ";
363 ss << std::string(m_message, m_message_size) << "\n";
364 if (m_sid.slaveIndex != -1)
365 {
366 ss << m_sid.toString(" ");
367 }
368 if (m_device_name[0] != '\0')
369 {
370 ss << " " << std::left << std::setw(16) << "Name: " << std::string(m_device_name);
371 }
372 if (m_bus_iteration_number != 0)
373 {
374 ss << " " << std::left << std::setw(16)
375 << "Bus Iteration: " << std::to_string(m_bus_iteration_number);
376 }
377 ss << "\n";
378 return ss.str();
379 }
380
381 std::string
383 {
384 if (m_identifier_size > 0)
385 {
386 return m_identifier;
387 }
388 else
389 {
390 return std::string{m_metaInfo.file} + "/" + std::string{m_metaInfo.function} + "/" +
391 std::to_string(m_metaInfo.line);
392 }
393 }
394
395 Entry&
396 Entry::busIterationNumber(std::uint64_t iteration)
397 {
398 m_bus_iteration_number = iteration;
399 return *this;
400 }
401
402 Reporter::ReportWrapper::ReportWrapper(Entry& error, Reporter* reporter) :
403 m_error(error), m_reporter(reporter)
404 {
405 }
406
407 Reporter::ReportWrapper::~ReportWrapper()
408 {
409 m_reporter->m_errorReporting->report(std::move(m_error));
410 }
411
412 Reporter::ReportWrapper&
413 Reporter::ReportWrapper::deactivateSpam(float seconds)
414 {
415 m_error.m_deactivate_spam_seconds = std::fmax(0.f, seconds);
416 return *this;
417 }
418
419} // namespace armarx::control::ethercat::reporting
std::string timestamp()
#define ARMARX_RT_LOGF_WARNING(...)
SpamFilterDataPtr deactivateSpam(float deactivationDurationSec=10.0f, const std::string &identifier="", bool deactivate=true) const
disables the logging for the current line for the given amount of seconds.
Definition Logging.cpp:99
LogSenderPtr loghelper(const char *file, int line, const char *function) const
Definition Logging.cpp:113
void setTag(const LogTag &tag)
Definition Logging.cpp:54
The SlaveIdentifier class is a POD-type representing a unique set of values identifying an EtherCAT s...
Brief description of class Entry.
Entry & slaveIdentifier(SlaveIdentifier sid)
Entry & busIterationNumber(std::uint64_t iteration)
Entry & deviceName(const char *deviceName)
Entry & metaInfo(const char *file, int line, const char *function)
Brief description of class Reporter.
Brief description of class Reporting.
std::optional< std::filesystem::path > dumpToFile(std::filesystem::path file, std::uint64_t lastNSeconds=1000000)
void setMaxErrorHistorySize(std::size_t maxErrorHistorySize)
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:181
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
std::ostream & operator<<(std::ostream &stream, const Type &rhs)
reporting::Reporting Reporting
IceUtil::Time rtNow()
Definition RtTiming.h:40
boost::lockfree::queue< Entry, boost::lockfree::capacity< queueSize > > queue