upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbitflash-maintainer <noreply@bitflash.invalid>2026-09-03 21:49:35 +0000
committerbitflash-maintainer <noreply@bitflash.invalid>2026-09-03 21:49:35 +0000
commit3c18a66ef601d90f08ea622021cb02bafab1601b (patch)
treeccb8ffec434c2c1eb9e0357c5692ee8aca42e159
parent4c6572f01d995e632338d7fb921962bb8f600eed (diff)
parent74f1d8598a6f603fdfaf6c0487375c40166b76ec (diff)
merge: !2 fix/log-rotation into deploy/all-2026-08-24
-rw-r--r--src/main.cpp6
-rw-r--r--src/main_gui.cpp50
-rw-r--r--src/net.h28
-rw-r--r--src/nostr.cpp2
-rw-r--r--src/util.cpp54
-rw-r--r--src/util.h69
6 files changed, 193 insertions, 16 deletions
diff --git a/src/main.cpp b/src/main.cpp
index be4ab5e..981509c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1713,7 +1713,7 @@ void CWalletTx::RelayWalletTransaction(CTxDB& txdb)
1713 uint256 hash = GetHash(); 1713 uint256 hash = GetHash();
1714 if (!txdb.ContainsTx(hash)) 1714 if (!txdb.ContainsTx(hash))
1715 { 1715 {
1716 if (LogAcceptsCategory("net")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str()); 1716 if (LogAcceptsCategory("relay")) printf("Relaying wtx %s\n", hash.ToString().substr(0,6).c_str());
1717 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this); 1717 RelayMessage(CInv(MSG_TX, hash), (CTransaction)*this);
1718 } 1718 }
1719 } 1719 }
@@ -3264,7 +3264,7 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
3264 vRecv >> *pblock; 3264 vRecv >> *pblock;
3265 3265
3266 //// debug print 3266 //// debug print
3267 if (LogAcceptsCategory("net")) { printf("received block:\n"); pblock->print(); } 3267 if (LogAcceptsCategory("relay")) { printf("received block:\n"); pblock->print(); }
3268 3268
3269 CInv inv(MSG_BLOCK, pblock->GetHash()); 3269 CInv inv(MSG_BLOCK, pblock->GetHash());
3270 pfrom->AddInventoryKnown(inv); 3270 pfrom->AddInventoryKnown(inv);
@@ -4643,7 +4643,7 @@ bool BitcoinMiner(int nThreadId)
4643 //// debug print 4643 //// debug print
4644 if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n"); 4644 if (LogAcceptsCategory("net")) printf("BitcoinMiner:\n");
4645 if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); 4645 if (LogAcceptsCategory("net")) printf("RandomX proof-of-work found \n powhash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
4646 if (LogAcceptsCategory("net")) pblock->print(); 4646 if (LogAcceptsCategory("relay")) pblock->print();
4647 4647
4648 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); 4648 SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL);
4649 CRITICAL_BLOCK(cs_main) 4649 CRITICAL_BLOCK(cs_main)
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 211e1e2..0e64985 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -111,6 +111,8 @@ static void PrintUsage()
111 printf(" /help, -help, --help, /?\n"); 111 printf(" /help, -help, --help, /?\n");
112 printf(" /datadir=PATH\n"); 112 printf(" /datadir=PATH\n");
113 printf(" /debug\n"); 113 printf(" /debug\n");
114 printf(" /debugrelay (/debug plus per-message relay chatter and block dumps)\n");
115 printf(" /maxdebugfilesize=N (roll debug.log at N MB, default 1024; 0 = never)\n");
114 printf(" /gen\n"); 116 printf(" /gen\n");
115 printf(" /nogui or /daemon\n"); 117 printf(" /nogui or /daemon\n");
116 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n"); 118 printf(" /selftest=wallet-keypool, wallet-hd, wallet-format, wallet-crypto, wallet-encrypt,\n");
@@ -191,6 +193,54 @@ static void ParseStartupArguments(int argc, char* argv[])
191 if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug")) 193 if (arg(argc,argv,"/debug") || arg(argc,argv,"-debug"))
192 fDebug = true; 194 fDebug = true;
193 195
196 // /debugrelay -- everything /debug shows, plus the per-message firehose
197 // ("relay" and "nostrdiscovery" categories: sending: tx, Relaying wtx,
198 // raw CBlock dumps, per-peer discovery lines). That traffic was ~90% of
199 // a 13-hour production debug.log (issue #35), so plain /debug leaves it
200 // off; the message text itself is unchanged for anyone grepping it.
201 if (arg(argc,argv,"/debugrelay") || arg(argc,argv,"-debugrelay"))
202 {
203 fDebug = true;
204 fDebugRelay = true;
205 }
206
207 // /maxdebugfilesize=N -- roll debug.log to debug.log.old once it passes
208 // N MB, checked on each write. Default is 1024 (see util.cpp); 0 turns
209 // the check off and leaves rotation entirely to logrotate.
210 string strMaxDebugSize = argval2(argc, argv, "/maxdebugfilesize", "-maxdebugfilesize");
211 if (!strMaxDebugSize.empty())
212 {
213 // Parsed with atoi64() before, which is strtoll() with the endptr
214 // thrown away: it returns 0 for anything it cannot read, so
215 // /maxdebugfilesize=1G and =1024MB both came out as 0 -- "never
216 // roll", identical to asking for that on purpose, with nothing said
217 // to the operator. The old code also multiplied without a range
218 // check, so a big enough N wrapped to a negative cap, which the roll
219 // reads as "off" too. Every rejection is now reported and leaves the
220 // default in place; the value is a plain count of MB, no suffix.
221 const char* psz = strMaxDebugSize.c_str();
222 char* pszEnd = NULL;
223 errno = 0;
224 int64 nMB = strtoll(psz, &pszEnd, 10);
225 // INT64_MAX, without depending on which limits header this tree pulls in.
226 const int64 nMaxMB = (int64)((~(uint64)0) >> 1) / (1024 * 1024);
227 const char* pszWhy = NULL;
228 if (pszEnd == psz)
229 pszWhy = "not a number";
230 else if (*pszEnd != '\0')
231 pszWhy = "trailing characters -- the value is a count of MB, with no unit suffix";
232 else if (errno == ERANGE || nMB < 0)
233 pszWhy = "not a size in MB";
234 else if (nMB > nMaxMB)
235 pszWhy = "larger than a file size can be";
236 if (pszWhy)
237 fprintf(stderr, "Ignoring /maxdebugfilesize=%s: %s (keeping %lld MB)\n",
238 strMaxDebugSize.c_str(), pszWhy,
239 (long long)(nMaxDebugFileSize / 1024 / 1024));
240 else
241 nMaxDebugFileSize = nMB * 1024 * 1024;
242 }
243
194 if (arg(argc,argv,"/gen") || arg(argc,argv,"-gen")) 244 if (arg(argc,argv,"/gen") || arg(argc,argv,"-gen"))
195 { 245 {
196 fGenerateBitcoins = 1; 246 fGenerateBitcoins = 1;
diff --git a/src/net.h b/src/net.h
index 9f3c7e4..600922a 100644
--- a/src/net.h
+++ b/src/net.h
@@ -802,6 +802,18 @@ public:
802 802
803 803
804 804
805 // "sending: tx (N bytes)" alone was ~71% of a 13-hour production
806 // debug.log (issue #35), so tx sends are demoted to the /debugrelay-gated
807 // "relay" category; every other command's send trace stays on "net".
808 // The line is emitted in pieces across BeginMessage/EndMessage/
809 // AbortMessage, so all three must agree on the category or orphaned
810 // "(N bytes)" fragments would leak into a plain -debug log.
811 // Valid only mid-message (nPushPos != -1).
812 const char* SendLogCategory() const
813 {
814 return strcmp(GetMessageCommand(), "tx") == 0 ? "relay" : "net";
815 }
816
805 void BeginMessage(const char* pszCommand) 817 void BeginMessage(const char* pszCommand)
806 { 818 {
807 EnterCriticalSection(&cs_vSend); 819 EnterCriticalSection(&cs_vSend);
@@ -809,17 +821,19 @@ public:
809 AbortMessage(); 821 AbortMessage();
810 nPushPos = vSend.size(); 822 nPushPos = vSend.size();
811 vSend << CMessageHeader(pszCommand, 0); 823 vSend << CMessageHeader(pszCommand, 0);
812 if (LogAcceptsCategory("net")) printf("sending: %-12s ", pszCommand); 824 if (LogAcceptsCategory(SendLogCategory())) printf("sending: %-12s ", pszCommand);
813 } 825 }
814 826
815 void AbortMessage() 827 void AbortMessage()
816 { 828 {
817 if (nPushPos == -1) 829 if (nPushPos == -1)
818 return; 830 return;
831 // Capture before nPushPos is cleared; GetMessageCommand() needs it.
832 const char* pszCategory = SendLogCategory();
819 vSend.resize(nPushPos); 833 vSend.resize(nPushPos);
820 nPushPos = -1; 834 nPushPos = -1;
821 LeaveCriticalSection(&cs_vSend); 835 LeaveCriticalSection(&cs_vSend);
822 if (LogAcceptsCategory("net")) printf("(aborted)\n"); 836 if (LogAcceptsCategory(pszCategory)) printf("(aborted)\n");
823 } 837 }
824 838
825 void EndMessage() 839 void EndMessage()
@@ -827,7 +841,11 @@ public:
827 extern int nDropMessagesTest; 841 extern int nDropMessagesTest;
828 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0) 842 if (nDropMessagesTest > 0 && GetRand(nDropMessagesTest) == 0)
829 { 843 {
830 if (LogAcceptsCategory("net")) printf("dropmessages DROPPING SEND MESSAGE\n"); 844 // Same category as the "sending: " this line interrupts, so a tx
845 // does not announce its own drop under "net" having been gated
846 // off under "relay". nPushPos can be -1 here, and then
847 // GetMessageCommand() returns "" and this is "net" as before.
848 if (LogAcceptsCategory(SendLogCategory())) printf("dropmessages DROPPING SEND MESSAGE\n");
831 AbortMessage(); 849 AbortMessage();
832 return; 850 return;
833 } 851 }
@@ -839,10 +857,10 @@ public:
839 unsigned int nSize = vSend.size() - nPushPos - sizeof(CMessageHeader); 857 unsigned int nSize = vSend.size() - nPushPos - sizeof(CMessageHeader);
840 memcpy((char*)&vSend[nPushPos] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize)); 858 memcpy((char*)&vSend[nPushPos] + offsetof(CMessageHeader, nMessageSize), &nSize, sizeof(nSize));
841 859
842 if (LogAcceptsCategory("net")) printf("(%d bytes) ", nSize); 860 if (LogAcceptsCategory(SendLogCategory())) printf("(%d bytes) ", nSize);
843 //for (int i = nPushPos+sizeof(CMessageHeader); i < min(vSend.size(), nPushPos+sizeof(CMessageHeader)+20U); i++) 861 //for (int i = nPushPos+sizeof(CMessageHeader); i < min(vSend.size(), nPushPos+sizeof(CMessageHeader)+20U); i++)
844 // printf("%02x ", vSend[i] & 0xff); 862 // printf("%02x ", vSend[i] & 0xff);
845 if (LogAcceptsCategory("net")) printf("\n"); 863 if (LogAcceptsCategory(SendLogCategory())) printf("\n");
846 864
847 nPushPos = -1; 865 nPushPos = -1;
848 LeaveCriticalSection(&cs_vSend); 866 LeaveCriticalSection(&cs_vSend);
diff --git a/src/nostr.cpp b/src/nostr.cpp
index 0717e12..f44dc08 100644
--- a/src/nostr.cpp
+++ b/src/nostr.cpp
@@ -1465,7 +1465,7 @@ static bool SeedFromRelay(CNostrKey& key, const string& relay)
1465 else if (createdAt > it->second) it->second = createdAt; // refresh 1465 else if (createdAt > it->second) it->second = createdAt; // refresh
1466 } 1466 }
1467 if (fNew && fDebug) 1467 if (fNew && fDebug)
1468 LogPrint("nostr", "Nostr: discovered .btf peer %s\n", peerAddr.c_str()); 1468 LogPrint("nostrdiscovery", "Nostr: discovered .btf peer %s\n", peerAddr.c_str());
1469 if (++nPeers > 500) break; 1469 if (++nPeers > 500) break;
1470 } 1470 }
1471 else if (type == "EOSE") 1471 else if (type == "EOSE")
diff --git a/src/util.cpp b/src/util.cpp
index 0cd0133..60142be 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -7,6 +7,12 @@
7 7
8 8
9bool fDebug = false; 9bool fDebug = false;
10bool fDebugRelay = false;
11
12// In-process cap for debug.log, in bytes. Default matches the 1G trigger of
13// the shipped logrotate policy so a long-running node stays bounded even
14// without it; -maxdebugfilesize=N (MB) tunes it, 0 disables.
15int64 nMaxDebugFileSize = 1024 * 1024 * (int64)1024;
10 16
11// --------------------------------------------------------------------------- 17// ---------------------------------------------------------------------------
12// Categorized, burst-collapsing logging -- see the LogPrint doc comment in 18// Categorized, burst-collapsing logging -- see the LogPrint doc comment in
@@ -26,10 +32,22 @@ static bool LogCategoryDefaultOn(const char* category)
26 strcmp(category, "payout") == 0; 32 strcmp(category, "payout") == 0;
27} 33}
28 34
35// Categories so noisy they drown out everything else even under -debug:
36// per-message send traces, wallet relay rebroadcasts, raw CBlock dumps and
37// per-address discovery lines made up ~90% of a 13-hour production capture
38// (issue #35). They need /debugrelay, not merely /debug.
39static bool LogCategoryVerboseOnly(const char* category)
40{
41 return strcmp(category, "relay") == 0 ||
42 strcmp(category, "nostrdiscovery") == 0;
43}
44
29bool LogAcceptsCategory(const char* category) 45bool LogAcceptsCategory(const char* category)
30{ 46{
31 if (LogCategoryDefaultOn(category)) 47 if (LogCategoryDefaultOn(category))
32 return true; 48 return true;
49 if (LogCategoryVerboseOnly(category))
50 return fDebugRelay;
33 return fDebug; 51 return fDebug;
34} 52}
35 53
@@ -397,13 +415,37 @@ bool FileExists(const char* psz)
397#endif 415#endif
398} 416}
399 417
400int GetFilesize(FILE* file) 418#ifndef _WIN32
419// If off_t is 32 bits wide, ftello() truncates exactly like the int this
420// function used to return. Fail the build rather than ship the silent
421// version of the bug (32-bit hosts need -D_FILE_OFFSET_BITS=64).
422static_assert(sizeof(off_t) >= 8, "GetFilesize needs a 64-bit off_t");
423#endif
424
425// Returns the file's size in bytes, or -1 if it cannot be determined.
426//
427// This used to hold ftell()'s long in an int, so a file of 2 GiB or more came
428// back negative. Its only caller is the debug.log roll in util.h, which skips
429// the roll on a negative size -- i.e. rotation switched itself off for good at
430// exactly the size the roll exists to prevent, and any cap of 2048 MB or more
431// could never be exceeded at all. Both failures were silent.
432int64 GetFilesize(FILE* file)
401{ 433{
402 int nSavePos = ftell(file); 434#ifdef _WIN32
403 int nFilesize = -1; 435 __int64 nSavePos = _ftelli64(file);
404 if (fseek(file, 0, SEEK_END) == 0) 436 int64 nFilesize = -1;
405 nFilesize = ftell(file); 437 if (nSavePos >= 0 && _fseeki64(file, 0, SEEK_END) == 0)
406 fseek(file, nSavePos, SEEK_SET); 438 nFilesize = _ftelli64(file);
439 if (nSavePos >= 0)
440 _fseeki64(file, nSavePos, SEEK_SET);
441#else
442 off_t nSavePos = ftello(file);
443 int64 nFilesize = -1;
444 if (nSavePos >= 0 && fseeko(file, 0, SEEK_END) == 0)
445 nFilesize = ftello(file);
446 if (nSavePos >= 0)
447 fseeko(file, nSavePos, SEEK_SET);
448#endif
407 return nFilesize; 449 return nFilesize;
408} 450}
409 451
diff --git a/src/util.h b/src/util.h
index 58d1638..dc4536d 100644
--- a/src/util.h
+++ b/src/util.h
@@ -87,6 +87,15 @@ inline T& REF(const T& val)
87 87
88extern bool fDebug; 88extern bool fDebug;
89 89
90// /debugrelay: the per-message firehose ("relay" and "nostrdiscovery"
91// categories) that even -debug keeps off. See LogAcceptsCategory().
92extern bool fDebugRelay;
93
94// debug.log is rolled to debug.log.old once it grows past this many bytes
95// (set from -maxdebugfilesize, in MB; 0 disables the check). See the roll
96// site in OutputDebugStringF() below.
97extern int64 nMaxDebugFileSize;
98
90// --------------------------------------------------------------------------- 99// ---------------------------------------------------------------------------
91// Categorized, burst-collapsing logging 100// Categorized, burst-collapsing logging
92// 101//
@@ -145,7 +154,7 @@ void ParseString(const string& str, char c, vector<string>& v);
145string FormatMoney(int64 n, bool fPlus=false); 154string FormatMoney(int64 n, bool fPlus=false);
146bool ParseMoney(const char* pszIn, int64& nRet); 155bool ParseMoney(const char* pszIn, int64& nRet);
147bool FileExists(const char* psz); 156bool FileExists(const char* psz);
148int GetFilesize(FILE* file); 157int64 GetFilesize(FILE* file);
149uint64 GetRand(uint64 nMax); 158uint64 GetRand(uint64 nMax);
150int64 GetTime(); 159int64 GetTime();
151int64 GetAdjustedTime(); 160int64 GetAdjustedTime();
@@ -390,7 +399,65 @@ inline int OutputDebugStringF(const char* pszFormat, ...)
390 else 399 else
391 strDebugFile = "debug.log"; 400 strDebugFile = "debug.log";
392 } 401 }
402 // Latched by a failed rename in the roll below, which cannot be
403 // retried usefully and must not be retried endlessly.
404 static bool fRollDisabled = false;
393 FILE* fileout = fopen(strDebugFile.c_str(), "a"); 405 FILE* fileout = fopen(strDebugFile.c_str(), "a");
406 if (fileout && nMaxDebugFileSize > 0 && !fRollDisabled)
407 {
408 // Roll an overgrown debug.log before adding to it. Nothing in the
409 // tree shrinks this file once the process is running (there is no
410 // ShrinkDebugFile, and even Bitcoin's only ran at startup), so a
411 // node that stays up for months grows the log without bound --
412 // production hit ~2 GiB. The file is opened and closed per line,
413 // so a rename is safe here: the next line simply reopens the
414 // path. One generation is kept as debug.log.old; logrotate
415 // (contrib/logrotate/) handles anything longer-term.
416 int64 nSize = GetFilesize(fileout);
417 if (nSize >= 0 && nSize > nMaxDebugFileSize)
418 {
419 fclose(fileout);
420 string strOldFile = strDebugFile + ".old";
421 remove(strOldFile.c_str()); // rename() won't clobber on Windows
422 bool fRolled = (rename(strDebugFile.c_str(), strOldFile.c_str()) == 0);
423 int nRenameErrno = errno; // before fopen() can overwrite it
424 fileout = fopen(strDebugFile.c_str(), "a");
425 if (fRolled)
426 {
427 if (fileout)
428 fprintf(fileout,
429 "debug.log grew past %lld MB; previous contents moved to debug.log.old (-maxdebugfilesize)\n",
430 (long long)(nMaxDebugFileSize / 1024 / 1024));
431 }
432 else if (nRenameErrno == ENOENT)
433 {
434 // The file went away between the size check and the
435 // rename, so an external rotator (logrotate) got there
436 // first. Nothing left to roll and nothing wrong: the
437 // fopen above has already recreated it. Latching on this
438 // would let a benign race with logrotate switch the cap
439 // off for the rest of the run.
440 }
441 else
442 {
443 // The file is still over the cap, so this branch would be
444 // reached again on the next line and every line after it.
445 // Unlatched, the complaint about failing to roll becomes
446 // the runaway growth the roll exists to prevent. Say it
447 // once and stop trying -- on stderr too, because whatever
448 // blocked the rename (read-only mount, permissions, EXDEV
449 // across a bind mount) may equally block the log write.
450 fRollDisabled = true;
451 fprintf(stderr,
452 "cannot roll %s (rename to .old failed: %s); -maxdebugfilesize disabled for this run\n",
453 strDebugFile.c_str(), strerror(nRenameErrno));
454 if (fileout)
455 fprintf(fileout,
456 "cannot roll debug.log (rename to .old failed: %s); -maxdebugfilesize disabled for this run\n",
457 strerror(nRenameErrno));
458 }
459 }
460 }
394 if (fileout) 461 if (fileout)
395 { 462 {
396 va_list arg_ptr; 463 va_list arg_ptr;