diff --git a/configure.ac b/configure.ac index 5bc0eafe05c4..d69aaf2bdcd2 100644 --- a/configure.ac +++ b/configure.ac @@ -859,6 +859,22 @@ if test "x$use_thread_local" = xyes || { test "x$use_thread_local" = xauto && te LDFLAGS="$TEMP_LDFLAGS" fi +dnl check for gmtime_r(), fallback to gmtime_s() if that is unavailable +dnl fail if neither are available. +AC_MSG_CHECKING(for gmtime_r) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[ gmtime_r((const time_t *) nullptr, (struct tm *) nullptr); ]])], + [ AC_MSG_RESULT(yes); AC_DEFINE(HAVE_GMTIME_R, 1, [Define this symbol if gmtime_r is available]) ], + [ AC_MSG_RESULT(no); + AC_MSG_CHECKING(for gmtime_s); + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[ gmtime_s((struct tm *) nullptr, (const time_t *) nullptr); ]])], + [ AC_MSG_RESULT(yes)], + [ AC_MSG_RESULT(no); AC_MSG_ERROR(Both gmtime_r and gmtime_s are unavailable) ] + ) + ] +) + # Check for different ways of gathering OS randomness AC_MSG_CHECKING(for Linux getrandom syscall) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include @@ -1452,6 +1468,7 @@ AC_SUBST(SODIUM_LIBS) AC_SUBST(ZMQ_LIBS) AC_SUBST(LIBZCASH_LIBS) AC_SUBST(QR_LIBS) +AC_SUBST(HAVE_GMTIME_R) AC_SUBST(HAVE_FDATASYNC) AC_SUBST(HAVE_FULLFSYNC) AC_SUBST(HAVE_O_CLOEXEC) diff --git a/doc/release-notes.md b/doc/release-notes.md index 9a1b4ed8cec1..fd8261c48c30 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -71,6 +71,15 @@ It is now possible for a single configuration file to set different options for The `addnode=`, `connect=`, `port=`, `bind=`, `rpcport=`, `rpcbind=`, and `wallet=` options will only apply to mainnet when specified in the configuration file, unless a network is specified. +#### Logging + +The log timestamp format is now ISO 8601 (e.g. "2021-02-28T12:34:56Z"). + + +#### Automatic Backup File Naming + +The file extension applied to automatic backups is now in ISO 8601 basic notation (e.g. "20210228T123456Z"). The basic notation is used to prevent illegal `:` characters from appearing in the filename. + *version* Change log ============== diff --git a/src/Makefile.bench.include b/src/Makefile.bench.include index b9c95c51f9f2..05385d36742f 100644 --- a/src/Makefile.bench.include +++ b/src/Makefile.bench.include @@ -20,7 +20,8 @@ bench_bench_pivx_SOURCES = \ bench/lockedpool.cpp \ bench/perf.cpp \ bench/perf.h \ - bench/prevector.cpp + bench/prevector.cpp \ + bench/util_time.cpp nodist_bench_bench_pivx_SOURCES = $(GENERATED_TEST_FILES) diff --git a/src/bench/util_time.cpp b/src/bench/util_time.cpp new file mode 100644 index 000000000000..fd3569c5f0cf --- /dev/null +++ b/src/bench/util_time.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "bench/bench.h" + +#include "utiltime.h" + +static void BenchTimeDeprecated(benchmark::State& state) +{ + while (state.KeepRunning()) { + (void)GetTime(); + } +} + +static void BenchTimeMock(benchmark::State& state) +{ + SetMockTime(111); + while (state.KeepRunning()) { + (void)GetTime(); + } + SetMockTime(0); +} + +static void BenchTimeMillis(benchmark::State& state) +{ + while (state.KeepRunning()) { + (void)GetTime(); + } +} + +static void BenchTimeMillisSys(benchmark::State& state) +{ + while (state.KeepRunning()) { + (void)GetTimeMillis(); + } +} + +BENCHMARK(BenchTimeDeprecated/*, 100000000*/); +BENCHMARK(BenchTimeMillis/*, 6000000*/); +BENCHMARK(BenchTimeMillisSys/*, 6000000*/); +BENCHMARK(BenchTimeMock/*, 300000000*/); diff --git a/src/init.cpp b/src/init.cpp index a376e9cf5ddd..d298f2093b52 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1229,7 +1229,7 @@ bool AppInitMain() LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); #endif if (!g_logger->m_log_timestamps) - LogPrintf("Startup time: %s\n", DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime())); + LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime())); LogPrintf("Default data directory %s\n", GetDefaultDataDir().string()); LogPrintf("Using data directory %s\n", GetDataDir().string()); LogPrintf("Using config file %s\n", GetConfigFile(gArgs.GetArg("-conf", PIVX_CONF_FILENAME)).string()); @@ -1288,7 +1288,7 @@ bool AppInitMain() if (nWalletBackups > 0) { if (fs::exists(backupDir)) { // Create backup of the wallet - std::string dateTimeStr = DateTimeStrFormat(".%Y-%m-%d-%H-%M", GetTime()); + std::string dateTimeStr = FormatISO8601DateTimeForBackup(GetTime()); std::string backupPathStr = backupDir.string(); backupPathStr += "/" + strWalletFile; std::string sourcePathStr = GetDataDir().string(); diff --git a/src/logging.cpp b/src/logging.cpp index c8482fb1b18c..af96e9edb207 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -182,9 +182,19 @@ std::string BCLog::Logger::LogTimestampStr(const std::string &str) if (!m_log_timestamps) return str; - if (m_started_new_line) - strStamped = DateTimeStrFormat("%Y-%m-%d %H:%M:%S", GetTime()) + ' ' + str; - else + if (m_started_new_line) { + int64_t nTimeMicros = GetTimeMicros(); + strStamped = FormatISO8601DateTime(nTimeMicros/1000000); + if (m_log_time_micros) { + strStamped.pop_back(); + strStamped += strprintf(".%06dZ", nTimeMicros % 1000000); + } + int64_t mocktime = GetMockTime(); + if (mocktime) { + strStamped += " (mocktime: " + FormatISO8601DateTime(mocktime) + ")"; + } + strStamped += ' ' + str; + } else strStamped = str; if (!str.empty() && str[str.size()-1] == '\n') diff --git a/src/net.cpp b/src/net.cpp index e865b241d91e..0bf9b374297e 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -2503,7 +2503,7 @@ void CNode::AskFor(const CInv& inv) nRequestTime = it->second; else nRequestTime = 0; - LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, DateTimeStrFormat("%H:%M:%S", nRequestTime / 1000000), id); + LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(), nRequestTime, FormatISO8601Time(nRequestTime / 1000000), id); // Make sure not to reuse time indexes to keep things in the same order int64_t nNow = GetTimeMicros() - 1000000; diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index dcdfdce51754..c5774015c720 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -85,13 +85,24 @@ BOOST_AUTO_TEST_CASE(util_HexStr) } -BOOST_AUTO_TEST_CASE(util_DateTimeStrFormat) +BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTime) { - BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0), "1970-01-01 00:00:00"); - BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 0x7FFFFFFF), "2038-01-19 03:14:07"); - BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", 1317425777), "2011-09-30 23:36:17"); - BOOST_CHECK_EQUAL(DateTimeStrFormat("%Y-%m-%d %H:%M", 1317425777), "2011-09-30 23:36"); - BOOST_CHECK_EQUAL(DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", 1317425777), "Fri, 30 Sep 2011 23:36:17 +0000"); + BOOST_CHECK_EQUAL(FormatISO8601DateTime(1317425777), "2011-09-30T23:36:17Z"); +} + +BOOST_AUTO_TEST_CASE(util_FormatISO8601DateTimeForBackup) +{ + BOOST_CHECK_EQUAL(FormatISO8601DateTimeForBackup(1586310600), ".20200408T0150Z"); +} + +BOOST_AUTO_TEST_CASE(util_FormatISO8601Date) +{ + BOOST_CHECK_EQUAL(FormatISO8601Date(1317425777), "2011-09-30"); +} + +BOOST_AUTO_TEST_CASE(util_FormatISO8601Time) +{ + BOOST_CHECK_EQUAL(FormatISO8601Time(1317425777), "23:36:17Z"); } struct TestArgsManager : public ArgsManager @@ -686,6 +697,27 @@ BOOST_AUTO_TEST_CASE(gettime) BOOST_CHECK((GetTime() & ~0xFFFFFFFFLL) == 0); } +BOOST_AUTO_TEST_CASE(util_time_GetTime) +{ + SetMockTime(111); + // Check that mock time does not change after a sleep + for (const auto& num_sleep : {0, 1}) { + MilliSleep(num_sleep); + BOOST_CHECK_EQUAL(111, GetTime()); // Deprecated time getter + BOOST_CHECK_EQUAL(111, GetTime().count()); + BOOST_CHECK_EQUAL(111000, GetTime().count()); + BOOST_CHECK_EQUAL(111000000, GetTime().count()); + } + + SetMockTime(0); + // Check that system time changes after a sleep + const auto ms_0 = GetTime(); + const auto us_0 = GetTime(); + MilliSleep(1); + BOOST_CHECK(ms_0 < GetTime()); + BOOST_CHECK(us_0 < GetTime()); +} + BOOST_AUTO_TEST_CASE(test_IsDigit) { BOOST_CHECK_EQUAL(IsDigit('0'), true); diff --git a/src/utiltime.cpp b/src/utiltime.cpp index ab59da754057..bacb8024323b 100644 --- a/src/utiltime.cpp +++ b/src/utiltime.cpp @@ -12,9 +12,10 @@ #include "utiltime.h" #include - #include #include +#include +#include static std::atomic nMockTime(0); //!< For unit testing @@ -30,11 +31,30 @@ int64_t GetTime() return now; } +template +T GetTime() +{ + const std::chrono::seconds mocktime{nMockTime.load(std::memory_order_relaxed)}; + + return std::chrono::duration_cast( + mocktime.count() ? + mocktime : + std::chrono::microseconds{GetTimeMicros()}); +} +template std::chrono::seconds GetTime(); +template std::chrono::milliseconds GetTime(); +template std::chrono::microseconds GetTime(); + void SetMockTime(int64_t nMockTimeIn) { nMockTime.store(nMockTimeIn, std::memory_order_relaxed); } +int64_t GetMockTime() +{ + return nMockTime.load(std::memory_order_relaxed); +} + int64_t GetTimeMillis() { int64_t now = (boost::posix_time::microsec_clock::universal_time() - @@ -61,16 +81,6 @@ void MilliSleep(int64_t n) boost::this_thread::sleep_for(boost::chrono::milliseconds(n)); } -std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime) -{ - // std::locale takes ownership of the pointer - std::locale loc(std::locale::classic(), new boost::posix_time::time_facet(pszFormat)); - std::stringstream ss; - ss.imbue(loc); - ss << boost::posix_time::from_time_t(nTime); - return ss.str(); -} - std::string DurationToDHMS(int64_t nDurationTime) { int seconds = nDurationTime % 60; @@ -85,3 +95,55 @@ std::string DurationToDHMS(int64_t nDurationTime) return strprintf("%02dh:%02dm:%02ds", hours, minutes, seconds); return strprintf("%02dm:%02ds", minutes, seconds); } + +std::string FormatISO8601DateTime(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef HAVE_GMTIME_R + if (gmtime_r(&time_val, &ts) == nullptr) { +#else + if (gmtime_s(&ts, &time_val) != 0) { +#endif + return {}; + } + return strprintf("%04i-%02i-%02iT%02i:%02i:%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec); +} + +std::string FormatISO8601DateTimeForBackup(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef HAVE_GMTIME_R + if (gmtime_r(&time_val, &ts) == nullptr) { +#else + if (gmtime_s(&ts, &time_val) != 0) { +#endif + return {}; + } + return strprintf(".%04i%02i%02iT%02i%02iZ", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min); +} + +std::string FormatISO8601Date(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef HAVE_GMTIME_R + if (gmtime_r(&time_val, &ts) == nullptr) { +#else + if (gmtime_s(&ts, &time_val) != 0) { +#endif + return {}; + } + return strprintf("%04i-%02i-%02i", ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday); +} + +std::string FormatISO8601Time(int64_t nTime) { + struct tm ts; + time_t time_val = nTime; +#ifdef HAVE_GMTIME_R + if (gmtime_r(&time_val, &ts) == nullptr) { +#else + if (gmtime_s(&ts, &time_val) != 0) { +#endif + return {}; + } + return strprintf("%02i:%02i:%02iZ", ts.tm_hour, ts.tm_min, ts.tm_sec); +} diff --git a/src/utiltime.h b/src/utiltime.h index d34bd2fab243..b1e6816ec6ca 100644 --- a/src/utiltime.h +++ b/src/utiltime.h @@ -1,6 +1,6 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2014 The Bitcoin developers -// Copyright (c) 2016-2017 The PIVX developers +// Copyright (c) 2016-2019 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -9,25 +9,39 @@ #include #include +#include /** - * GetTimeMicros() and GetTimeMillis() both return the system time, but in - * different units. GetTime() returns the sytem time in seconds, but also - * supports mocktime, where the time can be specified by the user, eg for - * testing (eg with the setmocktime rpc, or -mocktime argument). - * - * TODO: Rework these functions to be type-safe (so that we don't inadvertently - * compare numbers with different units, or compare a mocktime to system time). + * DEPRECATED + * Use either GetSystemTimeInSeconds (not mockable) or GetTime (mockable) */ - int64_t GetTime(); +/** Returns the system time (not mockable) */ int64_t GetTimeMillis(); +/** Returns the system time (not mockable) */ int64_t GetTimeMicros(); +/** Returns the system time (not mockable) */ int64_t GetSystemTimeInSeconds(); // Like GetTime(), but not mockable +/** For testing. Set e.g. with the setmocktime rpc, or -mocktime argument */ void SetMockTime(int64_t nMockTimeIn); +/** For testing */ +int64_t GetMockTime(); + void MilliSleep(int64_t n); -std::string DateTimeStrFormat(const char* pszFormat, int64_t nTime); std::string DurationToDHMS(int64_t nDurationTime); +/** Return system time (or mocked time, if set) */ +template +T GetTime(); + +/** + * ISO 8601 formatting is preferred. Use the FormatISO8601{DateTime,Date,Time} + * helper functions if possible. + */ +std::string FormatISO8601DateTime(int64_t nTime); +std::string FormatISO8601DateTimeForBackup(int64_t nTime); +std::string FormatISO8601Date(int64_t nTime); +std::string FormatISO8601Time(int64_t nTime); + #endif // BITCOIN_UTILTIME_H diff --git a/src/validation.cpp b/src/validation.cpp index 19c3285bbb57..03753ec55933 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1008,14 +1008,13 @@ void static InvalidChainFound(CBlockIndex* pindexNew) LogPrintf("InvalidChainFound: invalid block=%s height=%d log2_work=%.16f date=%s\n", pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, - log(pindexNew->nChainWork.getdouble()) / log(2.0), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", - pindexNew->GetBlockTime())); + log(pindexNew->nChainWork.getdouble()) / log(2.0), FormatISO8601DateTime(pindexNew->GetBlockTime())); const CBlockIndex* pChainTip = chainActive.Tip(); assert(pChainTip); LogPrintf("InvalidChainFound: current best=%s height=%d log2_work=%.16f date=%s\n", - pChainTip->GetBlockHash().GetHex(), pChainTip->nHeight, log(pChainTip->nChainWork.getdouble()) / log(2.0), - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pChainTip->GetBlockTime())); + pChainTip->GetBlockHash().GetHex(), pChainTip->nHeight, log(pChainTip->nChainWork.getdouble()) / log(2.0), + FormatISO8601DateTime(pChainTip->GetBlockTime())); CheckForkWarningConditions(); } @@ -1936,7 +1935,7 @@ void static UpdateTip(CBlockIndex* pindexNew) LogPrintf("%s: new best=%s height=%d version=%d log2_work=%.16f tx=%lu date=%s progress=%f cache=%.1fMiB(%utxo) evodb_cache=%.1fMiB\n", __func__, pChainTip->GetBlockHash().GetHex(), pChainTip->nHeight, pChainTip->nVersion, log(pChainTip->nChainWork.getdouble()) / log(2.0), (unsigned long)pChainTip->nChainTx, - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pChainTip->GetBlockTime()), + FormatISO8601DateTime(pChainTip->GetBlockTime()), Checkpoints::GuessVerificationProgress(pChainTip), pcoinsTip->DynamicMemoryUsage() * (1.0 / (1<<20)), pcoinsTip->GetCacheSize(), evoDb->GetMemoryUsage() * (1.0 / (1<<20))); @@ -3656,7 +3655,7 @@ bool LoadChainTip(const CChainParams& chainparams) LogPrintf("Loaded best chain: hashBestChain=%s height=%d date=%s progress=%f\n", pChainTip->GetBlockHash().GetHex(), pChainTip->nHeight, - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pChainTip->GetBlockTime()), + FormatISO8601DateTime(pChainTip->GetBlockTime()), Checkpoints::GuessVerificationProgress(pChainTip)); return true; } @@ -4194,7 +4193,7 @@ int ActiveProtocol() std::string CBlockFileInfo::ToString() const { - return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, DateTimeStrFormat("%Y-%m-%d", nTimeFirst), DateTimeStrFormat("%Y-%m-%d", nTimeLast)); + return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast)); } CBlockFileInfo* GetBlockFileInfo(size_t n) diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index f1f05a2f562d..d2af3d405655 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -27,11 +27,6 @@ #include -std::string static EncodeDumpTime(int64_t nTime) -{ - return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime); -} - int64_t static DecodeDumpTime(const std::string& str) { static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0); @@ -538,11 +533,11 @@ UniValue dumpwallet(const JSONRPCRequest& request) CBlockIndex* tip = chainActive.Tip(); // produce output file << strprintf("# Wallet dump created by PIVX %s (%s)\n", CLIENT_BUILD, CLIENT_DATE); - file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime())); + file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); if (tip) { file << strprintf("# * Best block at time of backup was %i (%s),\n", tip->nHeight, tip->GetBlockHash().ToString()); - file << strprintf("# mined on %s\n", EncodeDumpTime(tip->GetBlockTime())); + file << strprintf("# mined on %s\n", FormatISO8601DateTime(tip->GetBlockTime())); } else { file << "# Missing tip information\n"; } @@ -563,7 +558,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) for (std::vector >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID& keyid = it->second; - std::string strTime = EncodeDumpTime(it->first); + std::string strTime = FormatISO8601DateTime(it->first); CKey key; if (pwalletMain->GetKey(keyid, key)) { const CKeyMetadata& metadata = pwalletMain->mapKeyMetadata[keyid]; @@ -596,7 +591,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) { auto ivk = extsk.expsk.full_viewing_key().in_viewing_key(); CKeyMetadata keyMeta = pwalletMain->GetSaplingScriptPubKeyMan()->mapSaplingZKeyMetadata[ivk]; - std::string strTime = EncodeDumpTime(keyMeta.nCreateTime); + std::string strTime = FormatISO8601DateTime(keyMeta.nCreateTime); // Keys imported with importsaplingkey do not have key origin metadata file << strprintf("%s %s # shielded_addr=%s%s\n", KeyIO::EncodeSpendingKey(extsk), diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 9bc9805c6050..372f0258888c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -4419,7 +4419,7 @@ bool CWalletTx::AcceptToMemoryPool(CValidationState& state, bool fLimitFree, boo std::string CWallet::GetUniqueWalletBackupName() const { - return strprintf("%s%s", (dbw ? dbw->GetName() : "null"), DateTimeStrFormat(".%Y-%m-%d-%H-%M", GetTime())); + return strprintf("%s%s", (dbw ? dbw->GetName() : "null"), FormatISO8601DateTimeForBackup(GetTime())); } CWallet::CWallet() : dbw(new CWalletDBWrapper()) diff --git a/test/functional/combine_logs.py b/test/functional/combine_logs.py index 6215c208626c..46fcd75c2fdc 100755 --- a/test/functional/combine_logs.py +++ b/test/functional/combine_logs.py @@ -21,7 +21,7 @@ TMPDIR_PREFIX = "pivx_func_test_" # Matches on the date format at the start of the log event -TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{6}") +TIMESTAMP_PATTERN = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z") LogEvent = namedtuple('LogEvent', ['timestamp', 'source', 'event']) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index bc5ffca1cc80..623ce8308410 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -450,7 +450,7 @@ def _start_logging(self): ll = int(self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper() ch.setLevel(ll) # Format logs the same as pivxd's debug.log with microprecision (so log files can be concatenated and sorted) - formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + formatter = logging.Formatter(fmt='%(asctime)s.%(msecs)03d000Z %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S') formatter.converter = time.gmtime fh.setFormatter(formatter) ch.setFormatter(formatter)