44// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
66#include < logging.h>
7+ #include < memusage.h>
78#include < util/fs.h>
89#include < util/string.h>
910#include < util/threadnames.h>
1415#include < optional>
1516
1617using util::Join;
17- using util::RemovePrefix;
18- using util::ToString;
18+ using util::RemovePrefixView;
1919
2020const char * const DEFAULT_DEBUGLOGFILE = " debug.log" ;
2121constexpr auto MAX_USER_SETABLE_SEVERITY_LEVEL{BCLog::Level::Info};
@@ -43,7 +43,7 @@ BCLog::Logger& LogInstance()
4343
4444bool fLogIPs = DEFAULT_LOGIPS;
4545
46- static int FileWriteStr (const std::string & str, FILE *fp)
46+ static int FileWriteStr (std::string_view str, FILE *fp)
4747{
4848 return fwrite (str.data (), 1 , str.size (), fp);
4949}
@@ -71,17 +71,22 @@ bool BCLog::Logger::StartLogging()
7171
7272 // dump buffered messages from before we opened the log
7373 m_buffering = false ;
74+ if (m_buffer_lines_discarded > 0 ) {
75+ LogPrintStr_ (strprintf (" Early logging buffer overflowed, %d log lines discarded.\n " , m_buffer_lines_discarded), __func__, __FILE__, __LINE__, BCLog::ALL, Level::Info);
76+ }
7477 while (!m_msgs_before_open.empty ()) {
75- const std::string& s = m_msgs_before_open.front ();
78+ const auto & buflog = m_msgs_before_open.front ();
79+ std::string s{buflog.str };
80+ FormatLogStrInPlace (s, buflog.category , buflog.level , buflog.source_file , buflog.source_line , buflog.logging_function , buflog.threadname , buflog.now , buflog.mocktime );
81+ m_msgs_before_open.pop_front ();
7682
7783 if (m_print_to_file) FileWriteStr (s, m_fileout);
7884 if (m_print_to_console) fwrite (s.data (), 1 , s.size (), stdout);
7985 for (const auto & cb : m_print_callbacks) {
8086 cb (s);
8187 }
82-
83- m_msgs_before_open.pop_front ();
8488 }
89+ m_cur_buffer_memusage = 0 ;
8590 if (m_print_to_console) fflush (stdout);
8691
8792 return true ;
@@ -94,14 +99,31 @@ void BCLog::Logger::DisconnectTestLogger()
9499 if (m_fileout != nullptr ) fclose (m_fileout);
95100 m_fileout = nullptr ;
96101 m_print_callbacks.clear ();
102+ m_max_buffer_memusage = DEFAULT_MAX_LOG_BUFFER;
103+ m_cur_buffer_memusage = 0 ;
104+ m_buffer_lines_discarded = 0 ;
105+ m_msgs_before_open.clear ();
106+
107+ }
108+
109+ void BCLog::Logger::DisableLogging ()
110+ {
111+ {
112+ StdLockGuard scoped_lock (m_cs);
113+ assert (m_buffering);
114+ assert (m_print_callbacks.empty ());
115+ }
116+ m_print_to_file = false ;
117+ m_print_to_console = false ;
118+ StartLogging ();
97119}
98120
99121void BCLog::Logger::EnableCategory (BCLog::LogFlags flag)
100122{
101123 m_categories |= flag;
102124}
103125
104- bool BCLog::Logger::EnableCategory (const std::string& str)
126+ bool BCLog::Logger::EnableCategory (std::string_view str)
105127{
106128 BCLog::LogFlags flag;
107129 if (!GetLogCategory (flag, str)) return false ;
@@ -114,7 +136,7 @@ void BCLog::Logger::DisableCategory(BCLog::LogFlags flag)
114136 m_categories &= ~flag;
115137}
116138
117- bool BCLog::Logger::DisableCategory (const std::string& str)
139+ bool BCLog::Logger::DisableCategory (std::string_view str)
118140{
119141 BCLog::LogFlags flag;
120142 if (!GetLogCategory (flag, str)) return false ;
@@ -145,7 +167,7 @@ bool BCLog::Logger::DefaultShrinkDebugFile() const
145167 return m_categories == BCLog::NONE;
146168}
147169
148- static const std::map<std::string, BCLog::LogFlags> LOG_CATEGORIES_BY_STR{
170+ static const std::map<std::string, BCLog::LogFlags, std::less<> > LOG_CATEGORIES_BY_STR{
149171 {" 0" , BCLog::NONE},
150172 {" " , BCLog::NONE},
151173 {" net" , BCLog::NET},
@@ -185,7 +207,7 @@ static const std::map<std::string, BCLog::LogFlags> LOG_CATEGORIES_BY_STR{
185207
186208static const std::unordered_map<BCLog::LogFlags, std::string> LOG_CATEGORIES_BY_FLAG{
187209 // Swap keys and values from LOG_CATEGORIES_BY_STR.
188- [](const std::map<std::string, BCLog::LogFlags> & in) {
210+ [](const auto & in) {
189211 std::unordered_map<BCLog::LogFlags, std::string> out;
190212 for (const auto & [k, v] : in) {
191213 switch (v) {
@@ -198,7 +220,7 @@ static const std::unordered_map<BCLog::LogFlags, std::string> LOG_CATEGORIES_BY_
198220 }(LOG_CATEGORIES_BY_STR)
199221};
200222
201- bool GetLogCategory (BCLog::LogFlags& flag, const std::string& str)
223+ bool GetLogCategory (BCLog::LogFlags& flag, std::string_view str)
202224{
203225 if (str.empty ()) {
204226 flag = BCLog::ALL;
@@ -236,7 +258,7 @@ std::string LogCategoryToStr(BCLog::LogFlags category)
236258 return it->second ;
237259}
238260
239- static std::optional<BCLog::Level> GetLogLevel (const std::string& level_str)
261+ static std::optional<BCLog::Level> GetLogLevel (std::string_view level_str)
240262{
241263 if (level_str == " trace" ) {
242264 return BCLog::Level::Trace;
@@ -276,28 +298,23 @@ std::string BCLog::Logger::LogLevelsString() const
276298 return Join (std::vector<BCLog::Level>{levels.begin (), levels.end ()}, " , " , [](BCLog::Level level) { return LogLevelToStr (level); });
277299}
278300
279- std::string BCLog::Logger::LogTimestampStr (const std::string& str)
301+ std::string BCLog::Logger::LogTimestampStr (SystemClock::time_point now, std::chrono::seconds mocktime) const
280302{
281303 std::string strStamped;
282304
283305 if (!m_log_timestamps)
284- return str;
285-
286- if (m_started_new_line) {
287- const auto now{SystemClock::now ()};
288- const auto now_seconds{std::chrono::time_point_cast<std::chrono::seconds>(now)};
289- strStamped = FormatISO8601DateTime (TicksSinceEpoch<std::chrono::seconds>(now_seconds));
290- if (m_log_time_micros && !strStamped.empty ()) {
291- strStamped.pop_back ();
292- strStamped += strprintf (" .%06dZ" , Ticks<std::chrono::microseconds>(now - now_seconds));
293- }
294- std::chrono::seconds mocktime = GetMockTime ();
295- if (mocktime > 0s) {
296- strStamped += " (mocktime: " + FormatISO8601DateTime (count_seconds (mocktime)) + " )" ;
297- }
298- strStamped += ' ' + str;
299- } else
300- strStamped = str;
306+ return strStamped;
307+
308+ const auto now_seconds{std::chrono::time_point_cast<std::chrono::seconds>(now)};
309+ strStamped = FormatISO8601DateTime (TicksSinceEpoch<std::chrono::seconds>(now_seconds));
310+ if (m_log_time_micros && !strStamped.empty ()) {
311+ strStamped.pop_back ();
312+ strStamped += strprintf (" .%06dZ" , Ticks<std::chrono::microseconds>(now - now_seconds));
313+ }
314+ if (mocktime > 0s) {
315+ strStamped += " (mocktime: " + FormatISO8601DateTime (count_seconds (mocktime)) + " )" ;
316+ }
317+ strStamped += ' ' ;
301318
302319 return strStamped;
303320}
@@ -310,7 +327,7 @@ namespace BCLog {
310327 * It escapes instead of removes them to still allow for troubleshooting
311328 * issues where they accidentally end up in strings.
312329 */
313- std::string LogEscapeMessage (const std::string& str) {
330+ std::string LogEscapeMessage (std::string_view str) {
314331 std::string ret;
315332 for (char ch_in : str) {
316333 uint8_t ch = (uint8_t )ch_in;
@@ -350,34 +367,84 @@ std::string BCLog::Logger::GetLogPrefix(BCLog::LogFlags category, BCLog::Level l
350367 return s;
351368}
352369
353- void BCLog::Logger::LogPrintStr (const std::string& str, const std::string& logging_function, const std::string& source_file, int source_line, BCLog::LogFlags category, BCLog::Level level )
370+ static size_t MemUsage (const BCLog::Logger::BufferedLog& buflog )
354371{
355- StdLockGuard scoped_lock (m_cs );
356- std::string str_prefixed = LogEscapeMessage (str);
372+ return buflog. str . size () + buflog. logging_function . size () + buflog. source_file . size () + buflog. threadname . size () + memusage::MallocUsage ( sizeof (memusage::list_node<BCLog::Logger::BufferedLog>) );
373+ }
357374
358- if (m_started_new_line) {
359- str_prefixed. insert ( 0 , GetLogPrefix (category, level));
360- }
375+ void BCLog::Logger::FormatLogStrInPlace (std::string& str, BCLog::LogFlags category, BCLog::Level level, std::string_view source_file, int source_line, std::string_view logging_function, std::string_view threadname, SystemClock::time_point now, std::chrono::seconds mocktime) const
376+ {
377+ str. insert ( 0 , GetLogPrefix (category, level));
361378
362- if (m_log_sourcelocations && m_started_new_line ) {
363- str_prefixed .insert (0 , " [ " + RemovePrefix (source_file, " ./" ) + " : " + ToString ( source_line) + " ] [ " + logging_function + " ] " );
379+ if (m_log_sourcelocations) {
380+ str .insert (0 , strprintf ( " [%s:%d] [%s] " , RemovePrefixView (source_file, " ./" ), source_line, logging_function) );
364381 }
365382
366- if (m_log_threadnames && m_started_new_line) {
367- const auto & threadname = util::ThreadGetInternalName ();
368- str_prefixed.insert (0 , " [" + (threadname.empty () ? " unknown" : threadname) + " ] " );
383+ if (m_log_threadnames) {
384+ str.insert (0 , strprintf (" [%s] " , (threadname.empty () ? " unknown" : threadname)));
369385 }
370386
371- str_prefixed = LogTimestampStr (str_prefixed);
387+ str.insert (0 , LogTimestampStr (now, mocktime));
388+ }
389+
390+ void BCLog::Logger::LogPrintStr (std::string_view str, std::string_view logging_function, std::string_view source_file, int source_line, BCLog::LogFlags category, BCLog::Level level)
391+ {
392+ StdLockGuard scoped_lock (m_cs);
393+ return LogPrintStr_ (str, logging_function, source_file, source_line, category, level);
394+ }
372395
396+ void BCLog::Logger::LogPrintStr_ (std::string_view str, std::string_view logging_function, std::string_view source_file, int source_line, BCLog::LogFlags category, BCLog::Level level)
397+ {
398+ std::string str_prefixed = LogEscapeMessage (str);
399+
400+ const bool starts_new_line = m_started_new_line;
373401 m_started_new_line = !str.empty () && str[str.size ()-1 ] == ' \n ' ;
374402
375403 if (m_buffering) {
376- // buffer if we haven't started logging yet
377- m_msgs_before_open.push_back (str_prefixed);
404+ if (!starts_new_line) {
405+ if (!m_msgs_before_open.empty ()) {
406+ m_msgs_before_open.back ().str += str_prefixed;
407+ m_cur_buffer_memusage += str_prefixed.size ();
408+ return ;
409+ } else {
410+ // unlikely edge case; add a marker that something was trimmed
411+ str_prefixed.insert (0 , " [...] " );
412+ }
413+ }
414+
415+ {
416+ BufferedLog buf{
417+ .now =SystemClock::now (),
418+ .mocktime =GetMockTime (),
419+ .str =str_prefixed,
420+ .logging_function =std::string (logging_function),
421+ .source_file =std::string (source_file),
422+ .threadname =util::ThreadGetInternalName (),
423+ .source_line =source_line,
424+ .category =category,
425+ .level =level,
426+ };
427+ m_cur_buffer_memusage += MemUsage (buf);
428+ m_msgs_before_open.push_back (std::move (buf));
429+ }
430+
431+ while (m_cur_buffer_memusage > m_max_buffer_memusage) {
432+ if (m_msgs_before_open.empty ()) {
433+ m_cur_buffer_memusage = 0 ;
434+ break ;
435+ }
436+ m_cur_buffer_memusage -= MemUsage (m_msgs_before_open.front ());
437+ m_msgs_before_open.pop_front ();
438+ ++m_buffer_lines_discarded;
439+ }
440+
378441 return ;
379442 }
380443
444+ if (starts_new_line) {
445+ FormatLogStrInPlace (str_prefixed, category, level, source_file, source_line, logging_function, util::ThreadGetInternalName (), SystemClock::now (), GetMockTime ());
446+ }
447+
381448 if (m_print_to_console) {
382449 // print to console
383450 fwrite (str_prefixed.data (), 1 , str_prefixed.size (), stdout);
@@ -444,15 +511,15 @@ void BCLog::Logger::ShrinkDebugFile()
444511 fclose (file);
445512}
446513
447- bool BCLog::Logger::SetLogLevel (const std::string& level_str)
514+ bool BCLog::Logger::SetLogLevel (std::string_view level_str)
448515{
449516 const auto level = GetLogLevel (level_str);
450517 if (!level.has_value () || level.value () > MAX_USER_SETABLE_SEVERITY_LEVEL) return false ;
451518 m_log_level = level.value ();
452519 return true ;
453520}
454521
455- bool BCLog::Logger::SetCategoryLogLevel (const std::string& category_str, const std::string& level_str)
522+ bool BCLog::Logger::SetCategoryLogLevel (std::string_view category_str, std::string_view level_str)
456523{
457524 BCLog::LogFlags flag;
458525 if (!GetLogCategory (flag, category_str)) return false ;
0 commit comments