Skip to content

Commit 296962a

Browse files
authored
Merge pull request #71 from sy-c/master
v2.1.0
2 parents a9f2444 + fbb8ea2 commit 296962a

File tree

5 files changed

+144
-2
lines changed

5 files changed

+144
-2
lines changed

doc/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,14 @@ The InfoLogger library allows to inject messages directly from programs, as show
233233
* InfoLogger library is also available for: Tcl, Python, Go, Node.js.
234234
It allows to log message from scripting languages. A simplified subset of the InfoLogger C++ API is made available through SWIG-generated modules.
235235
Details of the functions accessible from the scripting interface are provided in [a separate document](scriptingAPI.md).
236+
237+
* C++ API provides a mean to automatically control / reduce the verbosity of some log messages.
238+
Auto-mute occurs when a defined amount of messages using the same token in a period of time is exceeded.
239+
The token parameter defines the limits for this maximum verbosity (max messages + time interval), and keeps internal count of usage.
240+
* the time interval starts on the first message, and is reset on the next message after an interval completed. (it is not equal to "X seconds before last message").
241+
* when the number of messages counted in an interval exceeds the threshold, auto-mute triggers ON: next messages (with this token) are discarded.
242+
* when auto-mute is ON, one message is still printed for each time interval, with statistics about the number of discarded messages. The logging rate is effectively limited to a couple of messages per interval.
243+
* the auto-mute triggers OFF when there was no message received for a duration equal to the interval time. (this is equal to "X seconds before last message").
236244
237245
238246

doc/releaseNotes.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,6 @@ This file describes the main feature changes for each InfoLogger released versio
9494

9595
## v2.0.1 - 05/05/2021
9696
- fix reconnect timeout in stdout fallback mode (now 5s).
97+
98+
## v2.1.0 - 11/05/2021
99+
- API: added auto-mute feature for potentially verbose/repetitive messages. To be enabled message per message, using an AutoMuteToken (c++ only, see InfoLogger.hxx and testInfoLogger.cxx).

include/InfoLogger/InfoLogger.hxx

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
#include <list>
2727
#include <utility>
2828
#include <type_traits>
29+
#include <atomic>
30+
#include <chrono>
2931

3032
// here are some macros to help including source code info in infologger messages
3133
// to be used to quickly specify "infoLoggerMessageOption" argument in some logging functions
@@ -248,6 +250,36 @@ class InfoLogger
248250
-1, // sourceLine
249251
};
250252

253+
// Token used to control the flow of messages.
254+
// Allows auto-mute when defined amount of messages using same token in a period of time is exceeded.
255+
//
256+
// Constructor parameters:
257+
// logOptions: Logging options used for the log calls associated to this token.
258+
// maxMsg: Maximum number of messages for the selected time interval, triggering auto-mute.
259+
// interval: Time interval (duration in seconds) used for counting the number of messages.
260+
struct AutoMuteToken {
261+
public:
262+
AutoMuteToken(const InfoLogger::InfoLoggerMessageOption vLogOptions, unsigned int vMaxMsg = 10, unsigned int vInterval = 5) {
263+
count = 0;
264+
logOptions = vLogOptions;
265+
maxMsg = vMaxMsg;
266+
interval = vInterval;
267+
};
268+
~AutoMuteToken() {
269+
};
270+
271+
protected:
272+
friend class InfoLogger;
273+
InfoLogger::InfoLoggerMessageOption logOptions; // options used for associated logs
274+
unsigned int maxMsg; // maximum number of messages for the selected time interval
275+
unsigned int interval; // duration of interval (seconds)
276+
std::atomic<unsigned int> count; // number of messages so far in interval
277+
std::chrono::time_point<std::chrono::steady_clock> t0; // time of beginning of interval
278+
std::chrono::time_point<std::chrono::steady_clock> t1; // time of previous message
279+
unsigned int ndiscarded; // number of messages discarded in last interval
280+
unsigned int ndiscardedtotal; // number of messages discarded since beginning of verbose phase
281+
};
282+
251283
/// Convert a string to an infologger severity
252284
/// \param text NUL-terminated word to convert to InfoLogger severity type. Current implementation is not exact-match, it takes closest based on first-letter value
253285
/// \return Corresponding severity (InfoLogger::Undefined if no match found)
@@ -261,13 +293,30 @@ class InfoLogger
261293
static int setMessageOption(const char* fieldName, const char* fieldValue, InfoLoggerMessageOption& output);
262294

263295
/// extended log function, with all extra fields, including a specific context
264-
/// \return 0 on success, an error code otherwise (but never throw exceptions)..
296+
/// \return 0 on success, an error code otherwise (but never throw exceptions).
265297
int log(const InfoLoggerMessageOption& options, const InfoLoggerContext& context, const char* message, ...) __attribute__((format(printf, 4, 5)));
266298

267299
/// extended log function, with all extra fields, using default context
268-
/// \return 0 on success, an error code otherwise (but never throw exceptions)..
300+
/// \return 0 on success, an error code otherwise (but never throw exceptions).
269301
int log(const InfoLoggerMessageOption& options, const char* message, ...) __attribute__((format(printf, 3, 4)));
270302

303+
/// extended log function, with all extra fields, using default context
304+
/// with automatic message muting, according to the usage of the token parameter provided.
305+
/// Auto-mute occurs when defined amount of messages using same token in a period of time is exceeded.
306+
/// The token parameter defines the limits for this maximum verbosity, and keeps internal count of usage.
307+
/// The token should be the same variable used between consecutive calls for a given type of messages,
308+
/// typically a static variable properly initialized with desired settings, close to the log call(s) to be controlled.
309+
///
310+
///
311+
/// Auto-mute behavior is as follows:
312+
/// - the time interval starts on the first message, and is reset on the next message after an interval completed. (it is not equal to "X seconds before last message").
313+
/// - when the number of messages counted in an interval exceeds the threshold, auto-mute triggers ON: next messages (with this token) are discarded
314+
/// - when auto-mute is on, one message is still printed for each time interval, with statistics about the number of discarded messages -> the logging rate is effectively limited to a couple of messages per interval
315+
/// - the auto-mute triggers off when there was no message received for a duration equal to the interval time. (this is equal to "X seconds before last message").
316+
///
317+
/// \return 0 on success, an error code otherwise (but never throw exceptions).
318+
int log(AutoMuteToken &token, const char* message, ...) __attribute__((format(printf, 3, 4)));
319+
271320
//////////////////////////
272321
// iostream-like interface
273322
//////////////////////////

src/InfoLogger.cxx

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,67 @@ void InfoLogger::filterReset() {
11101110
mPimpl->filterDiscardLevel = InfoLogger::undefinedMessageOption.level;
11111111
}
11121112

1113+
int InfoLogger::log(AutoMuteToken &limit, const char* message, ...) {
1114+
if (mPimpl->magicTag != InfoLoggerMagicNumber) {
1115+
return __LINE__;
1116+
}
11131117

1118+
unsigned int n = ++limit.count; // number of messages so far
1119+
auto now= std::chrono::steady_clock::now(); // current clock
1120+
1121+
// start interval timer
1122+
if (n == 1) {
1123+
limit.t0 = now;
1124+
limit.ndiscarded = 0;
1125+
}
1126+
// end of interval flag
1127+
bool endOfInterval = ((n > 1) && ((std::chrono::duration_cast<std::chrono::seconds>(now - limit.t0)).count() >= limit.interval));
1128+
1129+
// discard when number of messages exceeded in interval
1130+
if ((n>limit.maxMsg) && (!endOfInterval)) {
1131+
limit.ndiscarded++;
1132+
limit.ndiscardedtotal++;
1133+
limit.t1 = now;
1134+
return -1;
1135+
}
1136+
1137+
#define LOG_MUTE_PREFIX "[auto-mute] "
1138+
1139+
if (endOfInterval) {
1140+
// report once per interval about excess logs
1141+
log(LOG_MUTE_PREFIX "%lu similar messages discarded since last sample (total: %u / %u)", limit.ndiscarded, limit.ndiscardedtotal, n-1);
1142+
if ((limit.ndiscarded == 0) || ((std::chrono::duration_cast<std::chrono::seconds>(now - limit.t1)).count() >= limit.interval)) {
1143+
// no message discarded / no message at all for one period, reset full count
1144+
limit.count = 1;
1145+
log(limit.logOptions, LOG_MUTE_PREFIX "It was quiet for a while, restoring full logging for similar messages");
1146+
limit.ndiscardedtotal = 0;
1147+
}
1148+
// reset period
1149+
limit.ndiscarded = 0;
1150+
limit.t0 = now;
1151+
}
1152+
1153+
// log message
1154+
// forward variable list of arguments to logV method
1155+
int err;
1156+
va_list ap;
1157+
va_start(ap, message);
1158+
err = mPimpl->logV(limit.logOptions, mPimpl->currentContext, message, ap);
1159+
va_end(ap);
1160+
1161+
if (!endOfInterval) {
1162+
// warn on limit on first excess
1163+
if ( n == limit.maxMsg) {
1164+
log(limit.logOptions, LOG_MUTE_PREFIX "Verbosity threshold exceeded (%u msg < %u s), discarding next similar messages", n, limit.interval);
1165+
limit.t0 = now;
1166+
limit.ndiscarded = 0;
1167+
limit.ndiscardedtotal = 0;
1168+
}
1169+
}
1170+
limit.t1 = now;
1171+
1172+
return err;
1173+
}
11141174

11151175
// end of namespace
11161176
} // namespace InfoLogger

test/testInfoLogger.cxx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,27 @@ int main()
7272
theLog.log(LogInfoDevel, "Info message with severity filter - you should see this");
7373
theLog.filterReset();
7474
theLog.log(LogDebugDevel, "Debug message with severity filter - you should see this again");
75+
76+
// message verbosity control with auto-mute
77+
const int limitN = 5; // max number of messages ...
78+
const int limitT = 3; // ... for given time interval
79+
theLog.log("Will now test auto-mute: limit = %d msg / %d s", limitN, limitT);
80+
// define a static variable token, and pass it to all relevant log() calls
81+
static InfoLogger::AutoMuteToken msgLimit(LogInfoDevel_(1234), limitN, limitT);
82+
// a couple of loops to show behavior
83+
for (int j=0; j<2; j++) {
84+
const int nmsg = 20 * limitN;
85+
const float msgRate = 10;
86+
theLog.log("Injecting %d msgs at %.2f Hz", nmsg, msgRate);
87+
for (int i=1; i<= nmsg; i++) {
88+
theLog.log(msgLimit, "This is message loop %d", i);
89+
usleep((int)(1000000/msgRate));
90+
}
91+
const int pauseTime = limitT+1;
92+
theLog.log("Pause %d s", pauseTime);
93+
usleep(pauseTime*1000000);
94+
}
95+
theLog.log(msgLimit, "Final message test for auto-mute");
96+
theLog.log("End of test");
7597
return 0;
7698
}

0 commit comments

Comments
 (0)