test: apply microsecond precision to test framework logging #32676

pull mzumsande wants to merge 1 commits into bitcoin:master from mzumsande:202506_test_log_micros changing 1 files +10 −2
  1. mzumsande commented at 5:53 pm on June 3, 2025: contributor

    When analyzing functional test logs (produced with combine_logs.py), entries sometimes sort slightly out of order because even though python prints 6 digits for microsecond precision, it fills up the last 3 digits with zeroes. For example, it may look like a message was received by the test framework before it was sent by the node.

    Change this to actually use microsecond precision - this should make combined logs a little bit easier to analyze.

  2. DrahtBot commented at 5:53 pm on June 3, 2025: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32676.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK maflcko, janb84, davidgumberg, achow101

    If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

  3. DrahtBot added the label Tests on Jun 3, 2025
  4. test: apply microsecond precision to test framework logging
    This makes it easier to compare test and bitcoind logs -
    combine_logs.py orders by timestamp and will be more precise.
    ed179e0a65
  5. mzumsande force-pushed on Jun 3, 2025
  6. DrahtBot added the label CI failed on Jun 3, 2025
  7. DrahtBot commented at 6:02 pm on June 3, 2025: contributor

    🚧 At least one of the CI tasks failed. Task lint: https://github.com/bitcoin/bitcoin/runs/43407398087 LLM reason (✨ experimental): The CI failure is caused by a failed lint check due to a dead code warning in Python code.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  8. maflcko commented at 6:55 pm on June 3, 2025: member

    nice!

    review ACK ed179e0a6528c39b3bca76365f256716f917e19b 🗳

    Signature:

    0untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
    1RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
    2trusted comment: review ACK ed179e0a6528c39b3bca76365f256716f917e19b 🗳
    36s+yyc9O+I5NzBwm5FAP966If0KN+FGS6ilbXoVTv+E67Pu+935Z1wWiLOXInAvwBdGTLxmUxUTPtEiyvqVCDQ==
    
  9. in test/functional/test_framework/test_framework.py:850 in ed179e0a65
    847+                dt = datetime.fromtimestamp(record.created, timezone.utc)
    848+                return dt.strftime('%Y-%m-%dT%H:%M:%S.%f')
    849+
    850+        formatter = MicrosecondFormatter(
    851+            fmt='%(asctime)sZ %(name)s (%(levelname)s): %(message)s',
    852+        )
    


    davidgumberg commented at 7:08 pm on June 3, 2025:

    non-blocking question/nit, why is this approach with a derived class of logging.Formatter preferable to modifying the datefmt string?

    0-  formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000Z %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')
    1+  formatter = logging.Formatter(fmt='%(asctime)sZ %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S.%f')
    

    mzumsande commented at 7:13 pm on June 3, 2025:
    I tried that too - it doesn’t work, it will print 2025-06-03T19:12:56.%fZ TestFramework (INFO): See https://stackoverflow.com/questions/75035056/microsecond-do-not-work-in-python-logger-format for some background.

    maflcko commented at 7:14 pm on June 3, 2025:

    non-blocking question/nit, why is this approach with a derived class of logging.Formatter preferable to modifying the datefmt string?

    0-  formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000Z %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')
    1+  formatter = logging.Formatter(fmt='%(asctime)sZ %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S.%f')
    

    Does it work locally? The docs say:

    The %f format directive only applies to strptime(), not to strftime(). However, see also datetime.datetime.strptime() and datetime.datetime.strftime() where the %f format directive applies to microseconds.

    https://docs.python.org/3/library/time.html#time.strftime


    davidgumberg commented at 8:51 pm on June 3, 2025:
    Ah, thanks for the context, I hadn’t checked since I assumed the format specifiers would not differ between datetime.datetime.strftime() and strftime(), but I can confirm that applying the diff I suggest, the logs look exactly as @mzumsande indicated, and the microseconds are not printed.
  10. DrahtBot removed the label CI failed on Jun 3, 2025
  11. in test/functional/test_framework/test_framework.py:844 in ed179e0a65
    837@@ -837,9 +838,16 @@ def _start_logging(self):
    838         # User can provide log level as a number or string (eg DEBUG). loglevel was caught as a string, so try to convert it to an int
    839         ll = int(self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper()
    840         ch.setLevel(ll)
    841+
    842         # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted)
    843-        formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000Z %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S')
    844-        formatter.converter = time.gmtime
    845+        class MicrosecondFormatter(logging.Formatter):
    846+            def formatTime(self, record, _=None):
    


    mzumsande commented at 7:36 pm on June 3, 2025:
    by the way, I switched to _=None because datefmt=None runs into a linter error (unused variable 'datefmt' ), and omitting the arg is not allowed (TypeErrors). Not sure if the linter is correct to complain about this.

    maflcko commented at 7:46 pm on June 3, 2025:
    I can see it fail if python internally passes datefmt as a keyword arg. I can see disabling the linter, because it may be wrong due to this. I can also see just saying assert not datefmt to ensure it is None and ensure the keyword is used. But no strong opinion. I think anything is fine here.

    mzumsande commented at 7:57 pm on June 3, 2025:
    I don’t have a strong opinion either, I just wanted to mention this because _=None may look weird to some - if the current approach is fine I’ll just leave it at this.
  12. davidgumberg commented at 9:04 pm on June 3, 2025: contributor

    Tested ACK https://github.com/bitcoin/bitcoin/pull/32676/commits/ed179e0a6528c39b3bca76365f256716f917e19b

    Added an assert(0) to feature_abortnode.py and on this branch, microsecond times are printed, on master (5471e29d057049d2b5a) they are not.

    Didn’t try to reproduce the described situation in the PR of out-of-order logs, but it makes sense that the existing behavior would result in nonsensical ordering of logs any time you have sub-ms events, and I can recall having run into this before.

    Test logs

     0diff --git a/test/functional/feature_abortnode.py b/test/functional/feature_abortnode.py
     1index a5c8aa163a..8abe487b9e 100755
     2--- a/test/functional/feature_abortnode.py
     3+++ b/test/functional/feature_abortnode.py
     4@@ -37,6 +37,7 @@ class AbortNodeTest(BitcoinTestFramework):
     5             # Check that node0 aborted
     6             self.log.info("Waiting for crash")
     7             self.nodes[0].wait_until_stopped(timeout=5, expect_error=True, expected_stderr="Error: A fatal internal error occurred, see debug.log for details: Failed to disconnect block.")
     8+        assert(0)
     9         self.log.info("Node crashed - now verifying restart fails")
    10         self.nodes[0].assert_start_raises_init_error()
    

    #32676 (https://github.com/bitcoin/bitcoin/pull/32676/commits/ed179e0a6528c39b3bca76365f256716f917e19b)

     0$ git checkout --detach ed179e0
     1$ ./build/test/functional/feature_abortnode.py
     22025-06-03T20:52:18.520787Z TestFramework (INFO): PRNG seed is: 5653719685663637249
     32025-06-03T20:52:18.521155Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_q_44vhww
     42025-06-03T20:52:18.885450Z TestFramework (INFO): Waiting for crash
     52025-06-03T20:52:19.036796Z TestFramework (ERROR): Assertion failed
     6Traceback (most recent call last):
     7  File "/bitcoin/test/functional/test_framework/test_framework.py", line 190, in main
     8    self.run_test()
     9    ~~~~~~~~~~~~~^^
    10  File "/bitcoin./build/test/functional/feature_abortnode.py", line 40, in run_test
    11    assert(0)
    12           ^
    13AssertionError
    142025-06-03T20:52:19.088096Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
    152025-06-03T20:52:19.088240Z TestFramework (WARNING): Not cleaning up dir /tmp/bitcoin_func_test_q_44vhww
    162025-06-03T20:52:19.088274Z TestFramework (ERROR): Test failed. Test logging available at /tmp/bitcoin_func_test_q_44vhww/test_framework.log
    172025-06-03T20:52:19.088372Z TestFramework (ERROR):
    182025-06-03T20:52:19.088455Z TestFramework (ERROR): Hint: Call /bitcoin/test/functional/combine_logs.py '/tmp/bitcoin_func_test_q_44vhww' to consolidate all logs
    192025-06-03T20:52:19.088480Z TestFramework (ERROR):
    202025-06-03T20:52:19.088499Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.
    212025-06-03T20:52:19.088536Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues
    222025-06-03T20:52:19.088554Z TestFramework (ERROR):
    

    master (https://github.com/bitcoin/bitcoin/commit/5471e29d057049d2b5ab8447d8752cce3393086d)

     0$ git checkout --detach 5471e2
     1$ ./build/test/functional/feature_abortnode.py
     22025-06-03T21:00:11.760000Z TestFramework (INFO): PRNG seed is: 6770717479175070298
     32025-06-03T21:00:11.760000Z TestFramework (INFO): Initializing test directory /tmp/bitcoin_func_test_z_r_1o2o
     42025-06-03T21:00:12.124000Z TestFramework (INFO): Waiting for crash
     52025-06-03T21:00:12.276000Z TestFramework (ERROR): Assertion failed
     6Traceback (most recent call last):
     7  File "/bitcoin/test/functional/test_framework/test_framework.py", line 189, in main
     8    self.run_test()
     9    ~~~~~~~~~~~~~^^
    10  File "/bitcoin/./build/test/functional/feature_abortnode.py", line 40, in run_test
    11    assert(0)
    12           ^
    13AssertionError
    142025-06-03T21:00:12.327000Z TestFramework (INFO): Not stopping nodes as test failed. The dangling processes will be cleaned up later.
    152025-06-03T21:00:12.327000Z TestFramework (WARNING): Not cleaning up dir /tmp/bitcoin_func_test_z_r_1o2o
    162025-06-03T21:00:12.327000Z TestFramework (ERROR): Test failed. Test logging available at /tmp/bitcoin_func_test_z_r_1o2o/test_framework.log
    172025-06-03T21:00:12.327000Z TestFramework (ERROR):
    182025-06-03T21:00:12.327000Z TestFramework (ERROR): Hint: Call /bitcoin/test/functional/combine_logs.py '/tmp/bitcoin_func_test_z_r_1o2o' to consolidate all logs
    192025-06-03T21:00:12.327000Z TestFramework (ERROR):
    202025-06-03T21:00:12.327000Z TestFramework (ERROR): If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.
    212025-06-03T21:00:12.327000Z TestFramework (ERROR): https://github.com/bitcoin/bitcoin/issues
    222025-06-03T21:00:12.327000Z TestFramework (ERROR):
    
  13. janb84 commented at 8:00 pm on June 4, 2025: contributor

    ACK ed179e0a6528c39b3bca76365f256716f917e19b

    The new output is of the same length but of higher precision (replacing the unused 0) 👍 (keeping it the same format should prevent any parse problems of 3rd party scripts)

    • code review ✅
    • compiled & tested ✅
  14. achow101 commented at 10:04 pm on June 4, 2025: member
    ACK ed179e0a6528c39b3bca76365f256716f917e19b
  15. achow101 merged this on Jun 4, 2025
  16. achow101 closed this on Jun 4, 2025

  17. mzumsande deleted the branch on Jun 5, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-06-15 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me