diff --git a/src/init.cpp b/src/init.cpp
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1761,11 +1761,11 @@
     if (gArgs.GetBoolArg("-shrinkdebugfile", logCategories != BCLog::NONE)) {
         // Do this first since it both loads a bunch of debug.log into memory,
         // and because this needs to happen before any other debug.log printing.
-        ShrinkDebugFile();
+        logger.ShrinkDebugFile();
     }
 
     if (logger.fPrintToDebugLog) {
-        OpenDebugLog();
+        logger.OpenDebugLog();
     }
 
     if (!logger.fLogTimestamps) {
diff --git a/src/logging.h b/src/logging.h
--- a/src/logging.h
+++ b/src/logging.h
@@ -9,15 +9,17 @@
 
 #include <atomic>
 #include <cstdint>
+#include <list>
 #include <memory>
 #include <string>
 
+#include <boost/thread/mutex.hpp>
+
 static const bool DEFAULT_LOGTIMEMICROS = false;
 static const bool DEFAULT_LOGIPS = false;
 static const bool DEFAULT_LOGTIMESTAMPS = true;
 
 extern bool fLogIPs;
-extern std::atomic<bool> fReopenDebugLog;
 
 extern std::atomic<uint32_t> logCategories;
 
@@ -51,6 +53,10 @@
 
 class Logger {
 private:
+    FILE *fileout = nullptr;
+    boost::mutex mutexDebugLog;
+    std::list<std::string> vMsgsBeforeOpenLog;
+
     /**
      * fStartedNewLine is a state variable that will suppress printing of the
      * timestamp when multiple calls are made that don't end in a newline.
@@ -68,8 +74,13 @@
 
     std::atomic<bool> fReopenDebugLog{false};
 
+    ~Logger();
+
     /** Send a string to the log output */
     int LogPrintStr(const std::string &str);
+
+    void OpenDebugLog();
+    void ShrinkDebugFile();
 };
 
 } // namespace BCLog
@@ -99,7 +110,4 @@
         GetLogger().LogPrintStr(tfm::format(__VA_ARGS__));                     \
     } while (0)
 
-void OpenDebugLog();
-void ShrinkDebugFile();
-
 #endif // BITCOIN_LOGGING_H
diff --git a/src/logging.cpp b/src/logging.cpp
--- a/src/logging.cpp
+++ b/src/logging.cpp
@@ -9,9 +9,6 @@
 #include "utilstrencodings.h"
 #include "utiltime.h"
 
-#include <boost/thread/mutex.hpp>
-#include <boost/thread/once.hpp>
-
 bool fLogIPs = DEFAULT_LOGIPS;
 
 /**
@@ -38,60 +35,25 @@
  */
 std::atomic<uint32_t> logCategories(0);
 
-/**
- * LogPrintf() has been broken a couple of times now by well-meaning people
- * adding mutexes in the most straightforward way. It breaks because it may be
- * called by global destructors during shutdown. Since the order of destruction
- * of static/global objects is undefined, defining a mutex as a global object
- * doesn't work (the mutex gets destroyed, and then some later destructor calls
- * OutputDebugStringF, maybe indirectly, and you get a core dump at shutdown
- * trying to lock the mutex).
- */
-static boost::once_flag debugPrintInitFlag = BOOST_ONCE_INIT;
-
-/**
- * We use boost::call_once() to make sure mutexDebugLog and vMsgsBeforeOpenLog
- * are initialized in a thread-safe manner.
- *
- * NOTE: fileout, mutexDebugLog and sometimes vMsgsBeforeOpenLog are leaked on
- * exit. This is ugly, but will be cleaned up by the OS/libc. When the shutdown
- * sequence is fully audited and tested, explicit destruction of these objects
- * can be implemented.
- */
-static FILE *fileout = nullptr;
-static boost::mutex *mutexDebugLog = nullptr;
-static std::list<std::string> *vMsgsBeforeOpenLog;
-
 static int FileWriteStr(const std::string &str, FILE *fp) {
     return fwrite(str.data(), 1, str.size(), fp);
 }
 
-static void DebugPrintInit() {
-    assert(mutexDebugLog == nullptr);
-    mutexDebugLog = new boost::mutex();
-    vMsgsBeforeOpenLog = new std::list<std::string>;
-}
-
-void OpenDebugLog() {
-    boost::call_once(&DebugPrintInit, debugPrintInitFlag);
-    boost::mutex::scoped_lock scoped_lock(*mutexDebugLog);
+void BCLog::Logger::OpenDebugLog() {
+    boost::mutex::scoped_lock scoped_lock(mutexDebugLog);
 
     assert(fileout == nullptr);
-    assert(vMsgsBeforeOpenLog);
     fs::path pathDebug = GetDataDir() / "debug.log";
     fileout = fsbridge::fopen(pathDebug, "a");
     if (fileout) {
         // Unbuffered.
         setbuf(fileout, nullptr);
         // Dump buffered messages from before we opened the log.
-        while (!vMsgsBeforeOpenLog->empty()) {
-            FileWriteStr(vMsgsBeforeOpenLog->front(), fileout);
-            vMsgsBeforeOpenLog->pop_front();
+        while (!vMsgsBeforeOpenLog.empty()) {
+            FileWriteStr(vMsgsBeforeOpenLog.front(), fileout);
+            vMsgsBeforeOpenLog.pop_front();
         }
     }
-
-    delete vMsgsBeforeOpenLog;
-    vMsgsBeforeOpenLog = nullptr;
 }
 
 struct CLogCategoryDesc {
@@ -157,6 +119,12 @@
     return ret;
 }
 
+BCLog::Logger::~Logger() {
+    if (fileout) {
+        fclose(fileout);
+    }
+}
+
 std::string BCLog::Logger::LogTimestampStr(const std::string &str) {
     std::string strStamped;
 
@@ -191,14 +159,12 @@
         ret = fwrite(strTimestamped.data(), 1, strTimestamped.size(), stdout);
         fflush(stdout);
     } else if (fPrintToDebugLog) {
-        boost::call_once(&DebugPrintInit, debugPrintInitFlag);
-        boost::mutex::scoped_lock scoped_lock(*mutexDebugLog);
+        boost::mutex::scoped_lock scoped_lock(mutexDebugLog);
 
         // Buffer if we haven't opened the log yet.
         if (fileout == nullptr) {
-            assert(vMsgsBeforeOpenLog);
             ret = strTimestamped.length();
-            vMsgsBeforeOpenLog->push_back(strTimestamped);
+            vMsgsBeforeOpenLog.push_back(strTimestamped);
         } else {
             // Reopen the log file, if requested.
             if (fReopenDebugLog) {
@@ -216,7 +182,7 @@
     return ret;
 }
 
-void ShrinkDebugFile() {
+void BCLog::Logger::ShrinkDebugFile() {
     // Amount of debug.log to save at end when shrinking (must fit in memory)
     constexpr size_t RECENT_DEBUG_HISTORY_SIZE = 10 * 1000000;
     // Scroll debug.log if it's getting too big.