Skip to content

Commit 94555e5

Browse files
Molter73ovalenti
andauthored
Output the number of times a log has been throttled (#1876)
Adding the number of times a log message has been throttled can add value in the sense that having the message show up once or twice may not be as worrisome as it showing up thousands of times. In order to achieve this, the log message can be split between its header and the actual message. The header will now be handled by classes separate to LogMessage that will hold the information necessary to print them and use a common ILogHeader interface. This is done in order to reduce the amount of code necessary in macros, there shouldn't be a huge performance difference, but the current approach might take a bit more memory in order to store this data, some testing is needed. Co-authored-by: Olivier Valentin <ovalenti@redhat.com>
1 parent 4d56577 commit 94555e5

File tree

2 files changed

+137
-29
lines changed

2 files changed

+137
-29
lines changed

collector/lib/Logging.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ bool ParseLogLevelName(std::string name, LogLevel* level) {
101101
}
102102

103103
void InspectorLogCallback(std::string&& msg, sinsp_logger::severity severity) {
104+
using namespace collector::logging;
105+
104106
auto collector_severity = (LogLevel)severity;
105107

106108
if (collector_severity == LogLevel::DEBUG && msg.rfind("libbpf:", 0) == 0) {
@@ -109,11 +111,12 @@ void InspectorLogCallback(std::string&& msg, sinsp_logger::severity severity) {
109111
collector_severity = LogLevel::TRACE;
110112
}
111113

112-
if (!collector::logging::CheckLogLevel(collector_severity)) {
114+
if (!CheckLogLevel(collector_severity)) {
113115
return;
114116
}
115117

116-
collector::logging::LogMessage(__FILE__, __LINE__, false, collector_severity) << msg;
118+
static LogHeader header(__FILE__, __LINE__, collector_severity);
119+
LogMessage(header) << msg;
117120
}
118121

119122
const char* GetGlobalLogPrefix() {

collector/lib/Logging.h

Lines changed: 132 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <chrono>
55
#include <cstdint>
6+
#include <filesystem>
67
#include <iomanip>
78
#include <iostream>
89
#include <sstream>
@@ -54,15 +55,35 @@ void WriteTerminationLog(std::string message);
5455

5556
const size_t LevelPaddingWidth = 7;
5657

57-
class LogMessage {
58+
/**
59+
* Interface for log headers.
60+
*
61+
* This class defines how log headers should behave and provides some
62+
* basic, reusable methods.
63+
*/
64+
class ILogHeader {
5865
public:
59-
LogMessage(const char* file, int line, bool throttled, LogLevel level)
60-
: file_(file), line_(line), level_(level), throttled_(throttled) {
61-
// if in debug mode, output file names associated with log messages
62-
include_file_ = CheckLogLevel(LogLevel::DEBUG);
66+
ILogHeader(const char* file, int line, LogLevel level)
67+
: line_(line), level_(level) {
68+
std::filesystem::path p(file);
69+
if (p.has_filename()) {
70+
file_ = p.filename().string();
71+
} else {
72+
file_ = p.string();
73+
}
6374
}
6475

65-
~LogMessage() {
76+
ILogHeader(const ILogHeader&) = delete;
77+
ILogHeader(ILogHeader&&) = delete;
78+
ILogHeader& operator=(const ILogHeader&) = delete;
79+
ILogHeader& operator=(ILogHeader&&) = delete;
80+
virtual ~ILogHeader() = default;
81+
82+
virtual void PrintHeader() = 0;
83+
inline LogLevel GetLogLevel() const { return level_; }
84+
85+
protected:
86+
void PrintPrefix() {
6687
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
6788
auto nowTm = gmtime(&now);
6889

@@ -71,25 +92,106 @@ class LogMessage {
7192
<< std::left << std::setw(LevelPaddingWidth) << GetLogLevelName(level_)
7293
<< " " << std::put_time(nowTm, "%Y/%m/%d %H:%M:%S")
7394
<< "] ";
95+
}
7496

75-
if (throttled_) {
76-
std::cerr << "[Throttled] ";
97+
void PrintFile() {
98+
if (CheckLogLevel(LogLevel::DEBUG)) {
99+
std::cerr << "(" << file_ << ":" << line_ << ") ";
77100
}
101+
}
102+
103+
private:
104+
std::string file_;
105+
int line_;
106+
LogLevel level_;
107+
};
108+
109+
/**
110+
* Basic log header.
111+
*
112+
* Most log lines will use this header.
113+
*
114+
* The format when running in debug level will be:
115+
* [LEVEL YYYY/MM/DD HH:mm:SS] (file:line)
116+
*
117+
* Other log levels exclude the (file:line) part of the header.
118+
*/
119+
class LogHeader : public ILogHeader {
120+
public:
121+
LogHeader(const char* file, int line, LogLevel level)
122+
: ILogHeader(file, line, level) {}
123+
124+
void PrintHeader() override {
125+
PrintPrefix();
126+
PrintFile();
127+
}
128+
};
129+
130+
/**
131+
* Throttled log header.
132+
*
133+
* When the same log message is triggered multiple times, this header
134+
* helps prevent flooding the logs and instead counts the occurrences
135+
* of the message, which will be output after a given time window
136+
* expires.
137+
*
138+
* The format when running in debug level will be:
139+
* [LEVEL YYYY/MM/DD HH:mm:SS] [Throttled COUNT message] (file:line)
140+
*
141+
* Other log levels exclude the (file:line) part of the header.
142+
*/
143+
class ThrottledLogHeader : public ILogHeader {
144+
public:
145+
ThrottledLogHeader(const char* file, int line, LogLevel level, std::chrono::duration<unsigned int> interval)
146+
: ILogHeader(file, line, level), interval_(interval) {}
147+
148+
void PrintHeader() override {
149+
PrintPrefix();
150+
151+
std::cerr << "[Throttled " << count_ << " messages] ";
152+
count_ = 0;
153+
154+
PrintFile();
155+
}
78156

79-
if (include_file_) {
80-
const char* basename = strrchr(file_, '/');
81-
if (!basename) {
82-
basename = file_;
83-
} else {
84-
++basename;
85-
}
86-
std::cerr << "(" << basename << ":" << line_ << ") ";
157+
/**
158+
* Check if the log message should be suppressed.
159+
*
160+
* Throttled logs only output a message every interval_ time windows.
161+
* Every time this method is called, we increment count_ so the next
162+
* time the log is printed we can add the amount of times the log has
163+
* happened.
164+
*
165+
* @returns true if the log has to be suppressed.
166+
*/
167+
bool Suppress() {
168+
std::chrono::duration elapsed = std::chrono::steady_clock::now() - last_log_;
169+
count_++;
170+
if (elapsed < interval_) {
171+
return true;
87172
}
88173

174+
last_log_ = std::chrono::steady_clock::now();
175+
return false;
176+
}
177+
178+
private:
179+
std::chrono::steady_clock::time_point last_log_;
180+
std::chrono::duration<unsigned int> interval_;
181+
unsigned long count_{};
182+
};
183+
184+
class LogMessage {
185+
public:
186+
LogMessage(ILogHeader& ls) : ls_(ls) {}
187+
188+
~LogMessage() {
189+
ls_.PrintHeader();
190+
89191
std::cerr << buf_.str()
90192
<< std::endl;
91193

92-
if (level_ == LogLevel::FATAL) {
194+
if (ls_.GetLogLevel() == LogLevel::FATAL) {
93195
WriteTerminationLog(buf_.str());
94196
exit(1);
95197
}
@@ -102,32 +204,35 @@ class LogMessage {
102204
}
103205

104206
private:
105-
const char* file_;
106-
int line_;
107-
LogLevel level_;
207+
ILogHeader& ls_;
108208
std::stringstream buf_;
109-
bool include_file_;
110-
bool throttled_;
111209
};
112210

113211
} // namespace logging
114212

115213
} // namespace collector
116214

215+
// Helpers for creating unique variables within a compilation unit.
216+
#define CLOG_CAT_(a, b) a##b
217+
#define CLOG_CAT(a, b) CLOG_CAT_(a, b)
218+
#define CLOG_VAR(a) CLOG_CAT(a, __LINE__)
219+
117220
#define CLOG_ENABLED(lvl) (collector::logging::CheckLogLevel(collector::logging::LogLevel::lvl))
118221

119222
#define CLOG_IF(cond, lvl) \
223+
static collector::logging::LogHeader CLOG_VAR(_clog_stmt_)( \
224+
__FILE__, __LINE__, collector::logging::LogLevel::lvl); \
120225
if (collector::logging::CheckLogLevel(collector::logging::LogLevel::lvl) && (cond)) \
121-
collector::logging::LogMessage(__FILE__, __LINE__, false, collector::logging::LogLevel::lvl)
226+
collector::logging::LogMessage(CLOG_VAR(_clog_stmt_))
122227

123228
#define CLOG(lvl) CLOG_IF(true, lvl)
124229

125230
#define CLOG_THROTTLED_IF(cond, lvl, interval) \
126-
static std::chrono::steady_clock::time_point _clog_lastlog_##__LINE__; \
231+
static collector::logging::ThrottledLogHeader CLOG_VAR(_clog_stmt_)( \
232+
__FILE__, __LINE__, collector::logging::LogLevel::lvl, interval); \
127233
if (collector::logging::CheckLogLevel(collector::logging::LogLevel::lvl) && (cond) && \
128-
(std::chrono::steady_clock::now() - _clog_lastlog_##__LINE__ >= interval)) \
129-
_clog_lastlog_##__LINE__ = std::chrono::steady_clock::now(), \
130-
collector::logging::LogMessage(__FILE__, __LINE__, true, collector::logging::LogLevel::lvl)
234+
!CLOG_VAR(_clog_stmt_).Suppress()) \
235+
collector::logging::LogMessage(CLOG_VAR(_clog_stmt_))
131236

132237
#define CLOG_THROTTLED(lvl, interval) CLOG_THROTTLED_IF(true, lvl, interval)
133238

0 commit comments

Comments
 (0)