Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Output the number of times a log has been throttled #1876

Merged
merged 5 commits into from
Oct 8, 2024

Conversation

Molter73
Copy link
Collaborator

@Molter73 Molter73 commented Oct 4, 2024

Description

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. Calling virtual methods for printing the header could also have a performance impact, but given we don't print that many logs in info severity, would not consider this a problem.

Supersedes #1520.

Checklist

  • Investigated and inspected CI test results
  • Updated documentation accordingly

Automated testing

  • Added unit tests
  • Added integration tests
  • Added regression tests

If any of these don't apply, please comment below.

Testing Performed

Manually checked logging still works as expected.

Added a throttle message in system-inspector::Service::GetNext and checked the output happened every 10 seconds:
image

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 <[email protected]>
@Molter73 Molter73 requested a review from a team as a code owner October 4, 2024 09:13
Copy link
Contributor

@ovalenti ovalenti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is much cleaner than the macro version, I like it !

Not too worried by the extra cost of the method calls for the same reasons you expose.

collector/lib/Logging.h Show resolved Hide resolved
@Molter73
Copy link
Collaborator Author

Molter73 commented Oct 7, 2024

Checked memory used on a deployed collector with kubectl top and this version has a similar usage to the one that is currently on the master branch of the main repo, so I think we are ok on that front as well.

Copy link
Contributor

@erthalion erthalion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, thanks. Couple commentaries below, but feel free to decide which of them sound important enough for you to include.


if (include_file_) {
void PrintFile() {
if (CheckLogLevel(LogLevel::DEBUG)) {
const char* basename = strrchr(file_, '/');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's strictly speaking beyond this PR, but since we have c++17, why don't we use something fancy here, like std::filesystem::path instead of a plain string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want to do too many changes here outside of counting logs, but I agree that using std::filesystem::path makes more sense here, I'll take a look into it.


bool ShouldPrint() {
std::chrono::duration elapsed = std::chrono::steady_clock::now() - last_log_;
count_++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might cause some surprise in the future (imagine someone else reshuffling this code), that ShouldPrint function actually increments the counter. Maybe it would be more obvious to introduce another method Suppress, that will increment the count? In that case the macro would look like:

if (log_level & cond & CLOG_VAR(stmt).ShouldPrint) {
    LogMessage;
} else {
    CLOG_VAR(stmt).Suppress();
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I didn't really like this approach but I couldn't think of a better solution. I like your suggestion though, I'll add that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I remember why this is a pain, the macro needs to end with LogMessage(...) so we can directly << into it. I could do something similar to what you proposed by not using braces around the if/else statements, but that causes a bunch of warnings about a dangling else condition and it's overall ugly.

Best I can think of is renaming ShouldPrint to Suppress, turn the logic of the returned value around and use that, so at least it's a bit more obvious that Suppress is telling the macro to not output the log message and increment the count.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. IIUC currently it does

if (log_level && cond && CLOG_VAR(stmt).ShouldPrint())
    LogMessage()

How does it return LogMessage(...) if the if condition is false?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, it's a macro, so LogMessage() and everything to the ; is skipped when the condition is false.

image

Copy link
Contributor

@erthalion erthalion Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't then reversing the if condition solve this conundrum? Like

if (!log_level ||!cond || CLOG_VAR(stmt).ShouldPrint()) {
    Suppress()
} else
    LogMessage(...)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Problem with that is that missing the log level or having the condition be false would also add to the count of message, I think we need something like:

if (log_level && cond) 
  if (!CLOG_VAR(stmt).ShouldPrint()) {
    Suppress()
  } else
    LogMessage(...)

But then you have a dangling else and the logic becomes difficult to follow.

You also get this really nice warning a bunch of times when compiling:

In file included from /home/mmoltras/go/src/github.com/stackrox/collector/collector/lib/system-inspector/EventExtractor.h:10,
                 from /home/mmoltras/go/src/github.com/stackrox/collector/collector/lib/ContainerMetadata.cpp:5:
/home/mmoltras/go/src/github.com/stackrox/collector/collector/lib/system-inspector/EventExtractor.h: In member function ‘const int64_t* collector::system_inspector::EventExtractor::get_event_rawres(sinsp_evt*)’:
/home/mmoltras/go/src/github.com/stackrox/collector/collector/lib/Logging.h:224:6: warning: suggest explicit braces to avoid ambiguous ‘else’ [-Wdangling-else]
  224 |   if (collector::logging::CheckLogLevel(collector::logging::LogLevel::lvl) && (cond)) \
      |      ^
/home/mmoltras/go/src/github.com/stackrox/collector/collector/lib/Logging.h:230:39: note: in expansion of macro ‘CLOG_THROTTLED_IF’
  230 | #define CLOG_THROTTLED(lvl, interval) CLOG_THROTTLED_IF(true, lvl, interval)
      |                                       ^~~~~~~~~~~~~~~~~

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, ok, then just go with whatever is easier + loud commentaries around to explain what this function does :)

LogLevel level_;
};

class LogHeader : public ILogHeader {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future us would appreciate a few lines of comments, explaining what's the difference between this and the following classes.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I'll document them.

Add documentation to ILogHeader and its implementations.
Use std::filesystem::path for retrieving filename.
Rename ShouldPrint to Suppress.
@Molter73 Molter73 merged commit 94555e5 into master Oct 8, 2024
12 of 13 checks passed
@Molter73 Molter73 deleted the mauro/count-throttled-logs branch October 8, 2024 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants