diff --git a/src/common/byte_stream.cpp b/src/common/byte_stream.cpp
index d0578ec93..50524e806 100644
--- a/src/common/byte_stream.cpp
+++ b/src/common/byte_stream.cpp
@@ -283,11 +283,11 @@ public:
 #if defined(_WIN32)
       // delete the temporary file
       if (!DeleteFileW(FileSystem::GetWin32Path(m_temporaryFileName).c_str()))
-        Log_WarningFmt("Failed to delete temporary file '{}'", m_temporaryFileName);
+        WARNING_LOG("Failed to delete temporary file '{}'", m_temporaryFileName);
 #else
       // delete the temporary file
       if (remove(m_temporaryFileName.c_str()) < 0)
-        Log_WarningFmt("Failed to delete temporary file '{}'", m_temporaryFileName);
+        WARNING_LOG("Failed to delete temporary file '{}'", m_temporaryFileName);
 #endif
     }
     else if (!m_committed)
@@ -311,7 +311,7 @@ public:
     if (!MoveFileExW(FileSystem::GetWin32Path(m_temporaryFileName).c_str(),
                      FileSystem::GetWin32Path(m_originalFileName).c_str(), MOVEFILE_REPLACE_EXISTING))
     {
-      Log_WarningFmt("Failed to rename temporary file '{}' to '{}'", m_temporaryFileName, m_originalFileName);
+      WARNING_LOG("Failed to rename temporary file '{}' to '{}'", m_temporaryFileName, m_originalFileName);
       m_discarded = true;
     }
     else
@@ -322,7 +322,7 @@ public:
     // move the atomic file name to the original file name
     if (rename(m_temporaryFileName.c_str(), m_originalFileName.c_str()) < 0)
     {
-      Log_WarningFmt("Failed to rename temporary file '{}' to '{}'", m_temporaryFileName, m_originalFileName);
+      WARNING_LOG("Failed to rename temporary file '{}' to '{}'", m_temporaryFileName, m_originalFileName);
       m_discarded = true;
     }
     else
diff --git a/src/common/dynamic_library.cpp b/src/common/dynamic_library.cpp
index 520bee21f..36a07b650 100644
--- a/src/common/dynamic_library.cpp
+++ b/src/common/dynamic_library.cpp
@@ -30,7 +30,7 @@ DynamicLibrary::DynamicLibrary(const char* filename)
 {
   Error error;
   if (!Open(filename, &error))
-    Log_ErrorPrint(error.GetDescription());
+    ERROR_LOG(error.GetDescription());
 }
 
 DynamicLibrary::DynamicLibrary(DynamicLibrary&& move) : m_handle(move.m_handle)
diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp
index e8ff08f36..2ceef316a 100644
--- a/src/common/file_system.cpp
+++ b/src/common/file_system.cpp
@@ -266,7 +266,7 @@ bool FileSystem::GetWin32Path(std::wstring* dest, std::string_view str)
     }
     else [[unlikely]]
     {
-      Log_ErrorFmt("PathCchCanonicalizeEx() returned {:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("PathCchCanonicalizeEx() returned {:08X}", static_cast<unsigned>(hr));
       _freea(wstr_buf);
       return false;
     }
@@ -2407,13 +2407,13 @@ static bool SetLock(int fd, bool lock)
   const off_t offs = lseek(fd, 0, SEEK_CUR);
   if (offs < 0)
   {
-    Log_ErrorFmt("lseek({}) failed: {}", fd, errno);
+    ERROR_LOG("lseek({}) failed: {}", fd, errno);
     return false;
   }
 
   if (offs != 0 && lseek(fd, 0, SEEK_SET) < 0)
   {
-    Log_ErrorFmt("lseek({}, 0) failed: {}", fd, errno);
+    ERROR_LOG("lseek({}, 0) failed: {}", fd, errno);
     return false;
   }
 
@@ -2422,7 +2422,7 @@ static bool SetLock(int fd, bool lock)
     Panic("Repositioning file descriptor after lock failed.");
 
   if (!res)
-    Log_ErrorFmt("lockf() for {} failed: {}", lock ? "lock" : "unlock", errno);
+    ERROR_LOG("lockf() for {} failed: {}", lock ? "lock" : "unlock", errno);
 
   return res;
 }
diff --git a/src/common/log.cpp b/src/common/log.cpp
index f7266cdac..a825eb008 100644
--- a/src/common/log.cpp
+++ b/src/common/log.cpp
@@ -59,7 +59,7 @@ static void FormatLogMessageAndPrintW(const char* channelName, const char* funct
                                       const T& callback);
 #endif
 
-static const char s_log_level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'P', 'I', 'V', 'D', 'R', 'B', 'T'};
+static const char s_log_level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'I', 'V', 'D', 'B', 'T'};
 
 static std::vector<RegisteredCallback> s_callbacks;
 static std::mutex s_callback_mutex;
@@ -154,11 +154,9 @@ ALWAYS_INLINE_RELEASE void Log::FormatLogMessageForDisplay(fmt::memory_buffer& b
     "\033[0m"sv,    // NONE
     "\033[1;31m"sv, // ERROR
     "\033[1;33m"sv, // WARNING
-    "\033[1;35m"sv, // PERF
     "\033[1;37m"sv, // INFO
     "\033[1;32m"sv, // VERBOSE
     "\033[0;37m"sv, // DEV
-    "\033[1;36m"sv, // PROFILE
     "\033[0;32m"sv, // DEBUG
     "\033[0;34m"sv, // TRACE
   };
@@ -174,7 +172,7 @@ ALWAYS_INLINE_RELEASE void Log::FormatLogMessageForDisplay(fmt::memory_buffer& b
     // find time since start of process
     const float message_time = Log::GetCurrentMessageTime();
 
-    if (level <= LOGLEVEL_PERF)
+    if (functionName)
     {
       fmt::format_to(appender, "[{:10.4f}] {}{}({}): {}{}{}", message_time, color_start, s_log_level_characters[level],
                      functionName, message, color_end, message_end);
@@ -187,7 +185,7 @@ ALWAYS_INLINE_RELEASE void Log::FormatLogMessageForDisplay(fmt::memory_buffer& b
   }
   else
   {
-    if (level <= LOGLEVEL_PERF)
+    if (functionName)
     {
       fmt::format_to(appender, "{}{}({}): {}{}{}", color_start, s_log_level_characters[level], functionName, message,
                      color_end, message_end);
@@ -480,6 +478,15 @@ ALWAYS_INLINE_RELEASE bool Log::FilterTest(LOGLEVEL level, const char* channelNa
   return (level <= s_log_level && s_log_filter.find(channelName) == std::string::npos);
 }
 
+void Log::Write(const char* channelName, LOGLEVEL level, std::string_view message)
+{
+  std::unique_lock lock(s_callback_mutex);
+  if (!FilterTest(level, channelName, lock))
+    return;
+
+  ExecuteCallbacks(channelName, nullptr, level, message, lock);
+}
+
 void Log::Write(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message)
 {
   std::unique_lock lock(s_callback_mutex);
@@ -489,45 +496,16 @@ void Log::Write(const char* channelName, const char* functionName, LOGLEVEL leve
   ExecuteCallbacks(channelName, functionName, level, message, lock);
 }
 
-void Log::Writef(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, ...)
-{
-  std::va_list ap;
-  va_start(ap, format);
-  Writev(channelName, functionName, level, format, ap);
-  va_end(ap);
-}
-
-void Log::Writev(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, va_list ap)
+void Log::WriteFmtArgs(const char* channelName, LOGLEVEL level, fmt::string_view fmt, fmt::format_args args)
 {
   std::unique_lock lock(s_callback_mutex);
   if (!FilterTest(level, channelName, lock))
     return;
 
-  std::va_list apCopy;
-  va_copy(apCopy, ap);
+  fmt::memory_buffer buffer;
+  fmt::vformat_to(std::back_inserter(buffer), fmt, args);
 
-#ifdef _WIN32
-  u32 requiredSize = static_cast<u32>(_vscprintf(format, apCopy));
-#else
-  u32 requiredSize = std::vsnprintf(nullptr, 0, format, apCopy);
-#endif
-  va_end(apCopy);
-
-  if (requiredSize < 512)
-  {
-    char buffer[512];
-    const int len = std::vsnprintf(buffer, countof(buffer), format, ap);
-    if (len > 0)
-      ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer, static_cast<size_t>(len)), lock);
-  }
-  else
-  {
-    char* buffer = new char[requiredSize + 1];
-    const int len = std::vsnprintf(buffer, requiredSize + 1, format, ap);
-    if (len > 0)
-      ExecuteCallbacks(channelName, functionName, level, std::string_view(buffer, static_cast<size_t>(len)), lock);
-    delete[] buffer;
-  }
+  ExecuteCallbacks(channelName, nullptr, level, std::string_view(buffer.data(), buffer.size()), lock);
 }
 
 void Log::WriteFmtArgs(const char* channelName, const char* functionName, LOGLEVEL level, fmt::string_view fmt,
diff --git a/src/common/log.h b/src/common/log.h
index 5e8c2abbc..154f29103 100644
--- a/src/common/log.h
+++ b/src/common/log.h
@@ -14,17 +14,16 @@
 
 enum LOGLEVEL
 {
-  LOGLEVEL_NONE = 0,    // Silences all log traffic
-  LOGLEVEL_ERROR = 1,   // "ErrorPrint"
-  LOGLEVEL_WARNING = 2, // "WarningPrint"
-  LOGLEVEL_PERF = 3,    // "PerfPrint" // TODO: Purge
-  LOGLEVEL_INFO = 4,    // "InfoPrint"
-  LOGLEVEL_VERBOSE = 5, // "VerbosePrint"
-  LOGLEVEL_DEV = 6,     // "DevPrint"
-  LOGLEVEL_PROFILE = 7, // "ProfilePrint" // TODO: Purge
-  LOGLEVEL_DEBUG = 8,   // "DebugPrint"
-  LOGLEVEL_TRACE = 9,   // "TracePrint"
-  LOGLEVEL_COUNT = 10
+  LOGLEVEL_NONE, // Silences all log traffic
+  LOGLEVEL_ERROR,
+  LOGLEVEL_WARNING,
+  LOGLEVEL_INFO,
+  LOGLEVEL_VERBOSE,
+  LOGLEVEL_DEV,
+  LOGLEVEL_DEBUG,
+  LOGLEVEL_TRACE,
+
+  LOGLEVEL_COUNT
 };
 
 namespace Log {
@@ -65,91 +64,57 @@ void SetLogLevel(LOGLEVEL level);
 void SetLogFilter(std::string_view filter);
 
 // writes a message to the log
+void Write(const char* channelName, LOGLEVEL level, std::string_view message);
 void Write(const char* channelName, const char* functionName, LOGLEVEL level, std::string_view message);
-void Writef(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, ...)
-  printflike(4, 5);
-void Writev(const char* channelName, const char* functionName, LOGLEVEL level, const char* format, va_list ap);
+void WriteFmtArgs(const char* channelName, LOGLEVEL level, fmt::string_view fmt, fmt::format_args args);
 void WriteFmtArgs(const char* channelName, const char* functionName, LOGLEVEL level, fmt::string_view fmt,
                   fmt::format_args args);
 
-template<typename... T>
-ALWAYS_INLINE static void WriteFmt(const char* channelName, const char* functionName, LOGLEVEL level,
-                                   fmt::format_string<T...> fmt, T&&... args)
+ALWAYS_INLINE static void FastWrite(const char* channelName, LOGLEVEL level, std::string_view message)
 {
   if (level <= GetLogLevel())
-    return WriteFmtArgs(channelName, functionName, level, fmt, fmt::make_format_args(args...));
+    Write(channelName, level, message);
+}
+ALWAYS_INLINE static void FastWrite(const char* channelName, const char* functionName, LOGLEVEL level,
+                                    std::string_view message)
+{
+  if (level <= GetLogLevel())
+    Write(channelName, functionName, level, message);
+}
+template<typename... T>
+ALWAYS_INLINE static void FastWrite(const char* channelName, LOGLEVEL level, fmt::format_string<T...> fmt, T&&... args)
+{
+  if (level <= GetLogLevel())
+    WriteFmtArgs(channelName, level, fmt, fmt::make_format_args(args...));
+}
+template<typename... T>
+ALWAYS_INLINE static void FastWrite(const char* channelName, const char* functionName, LOGLEVEL level,
+                                    fmt::format_string<T...> fmt, T&&... args)
+{
+  if (level <= GetLogLevel())
+    WriteFmtArgs(channelName, functionName, level, fmt, fmt::make_format_args(args...));
 }
 } // namespace Log
 
 // log wrappers
-#define Log_SetChannel(ChannelName)                                                                                    \
-  [[maybe_unused]] [[maybe_unused]] static const char* ___LogChannel___ = #ChannelName;
-#define Log_ErrorPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_ERROR, msg)
-#define Log_ErrorPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
-#define Log_ErrorFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
-#define Log_WarningPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_WARNING, msg)
-#define Log_WarningPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_WARNING, __VA_ARGS__)
-#define Log_WarningFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_WARNING, __VA_ARGS__)
-#define Log_PerfPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_PERF, msg)
-#define Log_PerfPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_PERF, __VA_ARGS__)
-#define Log_PerfFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_PERF, __VA_ARGS__)
-#define Log_InfoPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_INFO, msg)
-#define Log_InfoPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_INFO, __VA_ARGS__)
-#define Log_InfoFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_INFO, __VA_ARGS__)
-#define Log_VerbosePrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_VERBOSE, msg)
-#define Log_VerbosePrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_VERBOSE, __VA_ARGS__)
-#define Log_VerboseFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_VERBOSE, __VA_ARGS__)
-#define Log_DevPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_DEV, msg)
-#define Log_DevPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_DEV, __VA_ARGS__)
-#define Log_DevFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_DEV, __VA_ARGS__)
-#define Log_ProfilePrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_PROFILE, msg)
-#define Log_ProfilePrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_PROFILE, __VA_ARGS__)
-#define Log_ProfileFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_PROFILE, __VA_ARGS__)
+#define Log_SetChannel(ChannelName) [[maybe_unused]] static const char* ___LogChannel___ = #ChannelName;
 
-#define Log_ErrorVisible() Log::IsLogVisible(LOGLEVEL_ERROR, ___LogChannel___)
-#define Log_WarningVisible() Log::IsLogVisible(LOGLEVEL_WARNING, ___LogChannel___)
-#define Log_PerfVisible() Log::IsLogVisible(LOGLEVEL_PERF, ___LogChannel___)
-#define Log_InfoVisible() Log::IsLogVisible(LOGLEVEL_INFO, ___LogChannel___)
-#define Log_VerboseVisible() Log::IsLogVisible(LOGLEVEL_VERBOSE, ___LogChannel___)
-#define Log_DevVisible() Log::IsLogVisible(LOGLEVEL_DEV, ___LogChannel___)
-#define Log_ProfileVisible() Log::IsLogVisible(LOGLEVEL_PROFILE, ___LogChannel___)
+#define ERROR_LOG(...) Log::FastWrite(___LogChannel___, __func__, LOGLEVEL_ERROR, __VA_ARGS__)
+#define WARNING_LOG(...) Log::FastWrite(___LogChannel___, __func__, LOGLEVEL_WARNING, __VA_ARGS__)
+#define INFO_LOG(...) Log::FastWrite(___LogChannel___, LOGLEVEL_INFO, __VA_ARGS__)
+#define VERBOSE_LOG(...) Log::FastWrite(___LogChannel___, LOGLEVEL_VERBOSE, __VA_ARGS__)
+#define DEV_LOG(...) Log::FastWrite(___LogChannel___, LOGLEVEL_DEV, __VA_ARGS__)
 
 #ifdef _DEBUG
-#define Log_DebugPrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_DEBUG, msg)
-#define Log_DebugPrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_DEBUG, __VA_ARGS__)
-#define Log_DebugFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_DEBUG, __VA_ARGS__)
-#define Log_TracePrint(msg) Log::Write(___LogChannel___, __func__, LOGLEVEL_TRACE, msg)
-#define Log_TracePrintf(...) Log::Writef(___LogChannel___, __func__, LOGLEVEL_TRACE, __VA_ARGS__)
-#define Log_TraceFmt(...) Log::WriteFmt(___LogChannel___, __func__, LOGLEVEL_TRACE, __VA_ARGS__)
-
-#define Log_DebugVisible() Log::IsLogVisible(LOGLEVEL_DEBUG, ___LogChannel___)
-#define Log_TraceVisible() Log::IsLogVisible(LOGLEVEL_TRACE, ___LogChannel___)
+#define DEBUG_LOG(...) Log::FastWrite(___LogChannel___, LOGLEVEL_DEBUG, __VA_ARGS__)
+#define TRACE_LOG(...) Log::FastWrite(___LogChannel___, LOGLEVEL_TRACE, __VA_ARGS__)
 #else
-#define Log_DebugPrint(msg)                                                                                            \
+#define DEBUG_LOG(...)                                                                                                 \
   do                                                                                                                   \
   {                                                                                                                    \
   } while (0)
-#define Log_DebugPrintf(...)                                                                                           \
+#define TRACE_LOG(...)                                                                                                 \
   do                                                                                                                   \
   {                                                                                                                    \
   } while (0)
-#define Log_DebugFmt(...)                                                                                              \
-  do                                                                                                                   \
-  {                                                                                                                    \
-  } while (0)
-#define Log_TracePrint(msg)                                                                                            \
-  do                                                                                                                   \
-  {                                                                                                                    \
-  } while (0)
-#define Log_TracePrintf(...)                                                                                           \
-  do                                                                                                                   \
-  {                                                                                                                    \
-  } while (0)
-#define Log_TraceFmt(...)                                                                                              \
-  do                                                                                                                   \
-  {                                                                                                                    \
-  } while (0)
-
-#define Log_DebugVisible() false
-#define Log_TraceVisible() false
 #endif
diff --git a/src/common/memmap.cpp b/src/common/memmap.cpp
index 2ed2adbfc..1cdd91a52 100644
--- a/src/common/memmap.cpp
+++ b/src/common/memmap.cpp
@@ -36,7 +36,7 @@ bool MemMap::MemProtect(void* baseaddr, size_t size, PageProtect mode)
   DWORD old_protect;
   if (!VirtualProtect(baseaddr, size, static_cast<DWORD>(mode), &old_protect))
   {
-    Log_ErrorFmt("VirtualProtect() failed with error {}", GetLastError());
+    ERROR_LOG("VirtualProtect() failed with error {}", GetLastError());
     return false;
   }
 
@@ -205,7 +205,7 @@ u8* SharedMemoryMappingArea::Map(void* file_handle, size_t file_offset, void* ma
   if (!MapViewOfFile3(static_cast<HANDLE>(file_handle), GetCurrentProcess(), map_base, file_offset, map_size,
                       MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0))
   {
-    Log_ErrorFmt("MapViewOfFile3() failed: {}", GetLastError());
+    ERROR_LOG("MapViewOfFile3() failed: {}", GetLastError());
     return nullptr;
   }
 
@@ -231,7 +231,7 @@ bool SharedMemoryMappingArea::Unmap(void* map_base, size_t map_size)
   // unmap the specified range
   if (!UnmapViewOfFile2(GetCurrentProcess(), map_base, MEM_PRESERVE_PLACEHOLDER))
   {
-    Log_ErrorFmt("UnmapViewOfFile2() failed: {}", GetLastError());
+    ERROR_LOG("UnmapViewOfFile2() failed: {}", GetLastError());
     return false;
   }
 
@@ -287,7 +287,7 @@ bool MemMap::MemProtect(void* baseaddr, size_t size, PageProtect mode)
   const int result = mprotect(baseaddr, size, static_cast<int>(mode));
   if (result != 0) [[unlikely]]
   {
-    Log_ErrorFmt("mprotect() for {} at {} failed", size, baseaddr);
+    ERROR_LOG("mprotect() for {} at {} failed", size, baseaddr);
     return false;
   }
 
diff --git a/src/common/progress_callback.cpp b/src/common/progress_callback.cpp
index 3c9533afe..3f7b0d085 100644
--- a/src/common/progress_callback.cpp
+++ b/src/common/progress_callback.cpp
@@ -10,7 +10,9 @@
 #include <limits>
 Log_SetChannel(ProgressCallback);
 
-ProgressCallback::~ProgressCallback() {}
+ProgressCallback::~ProgressCallback()
+{
+}
 
 void ProgressCallback::SetFormattedStatusText(const char* Format, ...)
 {
@@ -133,18 +135,18 @@ public:
   void SetProgressValue(u32 value) override {}
   void IncrementProgressValue() override {}
 
-  void DisplayError(const char* message) override { Log_ErrorPrint(message); }
-  void DisplayWarning(const char* message) override { Log_WarningPrint(message); }
-  void DisplayInformation(const char* message) override { Log_InfoPrint(message); }
-  void DisplayDebugMessage(const char* message) override { Log_DevPrint(message); }
+  void DisplayError(const char* message) override { ERROR_LOG(message); }
+  void DisplayWarning(const char* message) override { WARNING_LOG(message); }
+  void DisplayInformation(const char* message) override { INFO_LOG(message); }
+  void DisplayDebugMessage(const char* message) override { DEV_LOG(message); }
 
-  void ModalError(const char* message) override { Log_ErrorPrint(message); }
+  void ModalError(const char* message) override { ERROR_LOG(message); }
   bool ModalConfirmation(const char* message) override
   {
-    Log_InfoPrint(message);
+    INFO_LOG(message);
     return false;
   }
-  void ModalInformation(const char* message) override { Log_InfoPrint(message); }
+  void ModalInformation(const char* message) override { INFO_LOG(message); }
 };
 
 static NullProgressCallbacks s_nullProgressCallbacks;
@@ -365,42 +367,42 @@ void ConsoleProgressCallback::Redraw(bool update_value_only)
 void ConsoleProgressCallback::DisplayError(const char* message)
 {
   Clear();
-  Log_ErrorPrint(message);
+  ERROR_LOG(message);
   Redraw(false);
 }
 
 void ConsoleProgressCallback::DisplayWarning(const char* message)
 {
   Clear();
-  Log_WarningPrint(message);
+  WARNING_LOG(message);
   Redraw(false);
 }
 
 void ConsoleProgressCallback::DisplayInformation(const char* message)
 {
   Clear();
-  Log_InfoPrint(message);
+  INFO_LOG(message);
   Redraw(false);
 }
 
 void ConsoleProgressCallback::DisplayDebugMessage(const char* message)
 {
   Clear();
-  Log_DevPrint(message);
+  DEV_LOG(message);
   Redraw(false);
 }
 
 void ConsoleProgressCallback::ModalError(const char* message)
 {
   Clear();
-  Log_ErrorPrint(message);
+  ERROR_LOG(message);
   Redraw(false);
 }
 
 bool ConsoleProgressCallback::ModalConfirmation(const char* message)
 {
   Clear();
-  Log_InfoPrint(message);
+  INFO_LOG(message);
   Redraw(false);
   return false;
 }
@@ -408,6 +410,6 @@ bool ConsoleProgressCallback::ModalConfirmation(const char* message)
 void ConsoleProgressCallback::ModalInformation(const char* message)
 {
   Clear();
-  Log_InfoPrint(message);
+  INFO_LOG(message);
   Redraw(false);
 }
diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp
index c82d1a85c..ec6b9471d 100644
--- a/src/core/achievements.cpp
+++ b/src/core/achievements.cpp
@@ -245,7 +245,7 @@ const rc_client_user_game_summary_t& Achievements::GetGameSummary()
 void Achievements::ReportError(std::string_view sv)
 {
   std::string error = fmt::format("Achievements error: {}", sv);
-  Log_ErrorPrint(error.c_str());
+  ERROR_LOG(error.c_str());
   Host::AddOSDMessage(std::move(error), Host::OSD_CRITICAL_ERROR_DURATION);
 }
 
@@ -278,7 +278,7 @@ std::string Achievements::GetGameHash(CDImage* image)
     std::memcpy(&header, executable_data.data(), sizeof(header));
   if (!BIOS::IsValidPSExeHeader(header, static_cast<u32>(executable_data.size())))
   {
-    Log_ErrorFmt("PS-EXE header is invalid in '{}' ({} bytes)", executable_name, executable_data.size());
+    ERROR_LOG("PS-EXE header is invalid in '{}' ({} bytes)", executable_name, executable_data.size());
     return {};
   }
 
@@ -300,8 +300,8 @@ std::string Achievements::GetGameHash(CDImage* image)
                 hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7], hash[8], hash[9], hash[10],
                 hash[11], hash[12], hash[13], hash[14], hash[15]);
 
-  Log_InfoFmt("Hash for '{}' ({} bytes, {} bytes hashed): {}", executable_name, executable_data.size(), hash_size,
-              hash_str);
+  INFO_LOG("Hash for '{}' ({} bytes, {} bytes hashed): {}", executable_name, executable_data.size(), hash_size,
+           hash_str);
   return hash_str;
 }
 
@@ -314,7 +314,7 @@ void Achievements::DownloadImage(std::string url, std::string cache_filename)
 
     if (!FileSystem::WriteBinaryFile(cache_filename.c_str(), data.data(), data.size()))
     {
-      Log_ErrorFmt("Failed to write badge image to '{}'", cache_filename);
+      ERROR_LOG("Failed to write badge image to '{}'", cache_filename);
       return;
     }
 
@@ -420,7 +420,7 @@ bool Achievements::Initialize()
   std::string api_token = Host::GetBaseStringSettingValue("Cheevos", "Token");
   if (!username.empty() && !api_token.empty())
   {
-    Log_InfoFmt("Attempting login with user '{}'...", username);
+    INFO_LOG("Attempting login with user '{}'...", username);
     s_login_request = rc_client_begin_login_with_token(s_client, username.c_str(), api_token.c_str(),
                                                        ClientLoginWithTokenCallback, nullptr);
   }
@@ -588,7 +588,7 @@ void Achievements::EnsureCacheDirectoriesExist()
 
 void Achievements::ClientMessageCallback(const char* message, const rc_client_t* client)
 {
-  Log_DevPrint(message);
+  DEV_LOG(message);
 }
 
 uint32_t Achievements::ClientReadMemory(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client)
@@ -769,7 +769,7 @@ void Achievements::ClientEventHandler(const rc_client_event_t* event, rc_client_
       break;
 
     default:
-      [[unlikely]] Log_ErrorFmt("Unhandled event: {}", event->type);
+      [[unlikely]] ERROR_LOG("Unhandled event: {}", event->type);
       break;
   }
 }
@@ -793,7 +793,7 @@ void Achievements::UpdateRichPresence(std::unique_lock<std::recursive_mutex>& lo
 
   s_rich_presence_string.assign(sv);
 
-  Log_InfoFmt("Rich presence updated: {}", s_rich_presence_string);
+  INFO_LOG("Rich presence updated: {}", s_rich_presence_string);
   Host::OnAchievementsRefreshed();
 
 #ifdef ENABLE_DISCORD_PRESENCE
@@ -817,7 +817,7 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image)
 {
   if (s_game_path == path)
   {
-    Log_WarningPrint("Game path is unchanged.");
+    WARNING_LOG("Game path is unchanged.");
     return;
   }
 
@@ -828,7 +828,7 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image)
     temp_image = CDImage::Open(path.c_str(), g_settings.cdrom_load_image_patches, nullptr);
     image = temp_image.get();
     if (!temp_image)
-      Log_ErrorFmt("Failed to open temporary CD image '{}'", path);
+      ERROR_LOG("Failed to open temporary CD image '{}'", path);
   }
 
   std::string game_hash;
@@ -838,7 +838,7 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image)
   if (s_game_hash == game_hash)
   {
     // only the path has changed - different format/save state/etc.
-    Log_InfoFmt("Detected path change from '{}' to '{}'", s_game_path, path);
+    INFO_LOG("Detected path change from '{}' to '{}'", s_game_path, path);
     s_game_path = path;
     return;
   }
@@ -861,7 +861,7 @@ void Achievements::IdentifyGame(const std::string& path, CDImage* image)
   // bail out if we're not logged in, just save the hash
   if (!IsLoggedInOrLoggingIn())
   {
-    Log_InfoPrint("Skipping load game because we're not logged in.");
+    INFO_LOG("Skipping load game because we're not logged in.");
     DisableHardcoreMode();
     return;
   }
@@ -903,7 +903,7 @@ void Achievements::ClientLoadGameCallback(int result, const char* error_message,
   if (result == RC_NO_GAME_LOADED)
   {
     // Unknown game.
-    Log_InfoFmt("Unknown game '%s', disabling achievements.", s_game_hash);
+    INFO_LOG("Unknown game '%s', disabling achievements.", s_game_hash);
     DisableHardcoreMode();
     return;
   }
@@ -1053,7 +1053,7 @@ void Achievements::DisplayHardcoreDeferredMessage()
 void Achievements::HandleResetEvent(const rc_client_event_t* event)
 {
   // We handle system resets ourselves, but still need to reset the client's state.
-  Log_InfoPrint("Resetting runtime due to reset event");
+  INFO_LOG("Resetting runtime due to reset event");
   rc_client_reset(s_client);
 
   if (HasActiveGame())
@@ -1065,7 +1065,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
   const rc_client_achievement_t* cheevo = event->achievement;
   DebugAssert(cheevo);
 
-  Log_InfoFmt("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_game_id);
+  INFO_LOG("Achievement {} ({}) for game {} unlocked", cheevo->title, cheevo->id, s_game_id);
   UpdateGameSummary();
 
   if (g_settings.achievements_notifications && FullscreenUI::Initialize())
@@ -1089,7 +1089,7 @@ void Achievements::HandleUnlockEvent(const rc_client_event_t* event)
 
 void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
 {
-  Log_InfoFmt("Game {} complete", s_game_id);
+  INFO_LOG("Game {} complete", s_game_id);
   UpdateGameSummary();
 
   if (g_settings.achievements_notifications && FullscreenUI::Initialize())
@@ -1108,7 +1108,7 @@ void Achievements::HandleGameCompleteEvent(const rc_client_event_t* event)
 
 void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title);
+  DEV_LOG("Leaderboard {} ({}) started", event->leaderboard->id, event->leaderboard->title);
 
   if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
   {
@@ -1123,7 +1123,7 @@ void Achievements::HandleLeaderboardStartedEvent(const rc_client_event_t* event)
 
 void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title);
+  DEV_LOG("Leaderboard {} ({}) failed", event->leaderboard->id, event->leaderboard->title);
 
   if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
   {
@@ -1138,7 +1138,7 @@ void Achievements::HandleLeaderboardFailedEvent(const rc_client_event_t* event)
 
 void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title);
+  DEV_LOG("Leaderboard {} ({}) submitted", event->leaderboard->id, event->leaderboard->title);
 
   if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
   {
@@ -1167,8 +1167,8 @@ void Achievements::HandleLeaderboardSubmittedEvent(const rc_client_event_t* even
 
 void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
-             event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
+  DEV_LOG("Leaderboard {} scoreboard rank {} of {}", event->leaderboard_scoreboard->leaderboard_id,
+          event->leaderboard_scoreboard->new_rank, event->leaderboard_scoreboard->num_entries);
 
   if (g_settings.achievements_leaderboard_notifications && FullscreenUI::Initialize())
   {
@@ -1195,8 +1195,7 @@ void Achievements::HandleLeaderboardScoreboardEvent(const rc_client_event_t* eve
 
 void Achievements::HandleLeaderboardTrackerShowEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Showing leaderboard tracker: {}: {}", event->leaderboard_tracker->id,
-             event->leaderboard_tracker->display);
+  DEV_LOG("Showing leaderboard tracker: {}: {}", event->leaderboard_tracker->id, event->leaderboard_tracker->display);
 
   TinyString width_string;
   width_string.append(ICON_FA_STOPWATCH);
@@ -1219,7 +1218,7 @@ void Achievements::HandleLeaderboardTrackerHideEvent(const rc_client_event_t* ev
   if (it == s_active_leaderboard_trackers.end())
     return;
 
-  Log_DevFmt("Hiding leaderboard tracker: {}", id);
+  DEV_LOG("Hiding leaderboard tracker: {}", id);
   it->active = false;
   it->show_hide_time.Reset();
 }
@@ -1232,8 +1231,7 @@ void Achievements::HandleLeaderboardTrackerUpdateEvent(const rc_client_event_t*
   if (it == s_active_leaderboard_trackers.end())
     return;
 
-  Log_DevFmt("Updating leaderboard tracker: {}: {}", event->leaderboard_tracker->id,
-             event->leaderboard_tracker->display);
+  DEV_LOG("Updating leaderboard tracker: {}: {}", event->leaderboard_tracker->id, event->leaderboard_tracker->display);
 
   it->text = event->leaderboard_tracker->display;
   it->active = true;
@@ -1257,7 +1255,7 @@ void Achievements::HandleAchievementChallengeIndicatorShowEvent(const rc_client_
   indicator.active = true;
   s_active_challenge_indicators.push_back(std::move(indicator));
 
-  Log_DevFmt("Show challenge indicator for {} ({})", event->achievement->id, event->achievement->title);
+  DEV_LOG("Show challenge indicator for {} ({})", event->achievement->id, event->achievement->title);
 }
 
 void Achievements::HandleAchievementChallengeIndicatorHideEvent(const rc_client_event_t* event)
@@ -1268,15 +1266,15 @@ void Achievements::HandleAchievementChallengeIndicatorHideEvent(const rc_client_
   if (it == s_active_challenge_indicators.end())
     return;
 
-  Log_DevFmt("Hide challenge indicator for {} ({})", event->achievement->id, event->achievement->title);
+  DEV_LOG("Hide challenge indicator for {} ({})", event->achievement->id, event->achievement->title);
   it->show_hide_time.Reset();
   it->active = false;
 }
 
 void Achievements::HandleAchievementProgressIndicatorShowEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Showing progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title,
-             event->achievement->measured_progress);
+  DEV_LOG("Showing progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title,
+          event->achievement->measured_progress);
 
   if (!s_active_progress_indicator.has_value())
     s_active_progress_indicator.emplace();
@@ -1294,15 +1292,15 @@ void Achievements::HandleAchievementProgressIndicatorHideEvent(const rc_client_e
   if (!s_active_progress_indicator.has_value())
     return;
 
-  Log_DevPrint("Hiding progress indicator");
+  DEV_LOG("Hiding progress indicator");
   s_active_progress_indicator->show_hide_time.Reset();
   s_active_progress_indicator->active = false;
 }
 
 void Achievements::HandleAchievementProgressIndicatorUpdateEvent(const rc_client_event_t* event)
 {
-  Log_DevFmt("Updating progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title,
-             event->achievement->measured_progress);
+  DEV_LOG("Updating progress indicator: {} ({}): {}", event->achievement->id, event->achievement->title,
+          event->achievement->measured_progress);
   s_active_progress_indicator->achievement = event->achievement;
   s_active_progress_indicator->active = true;
 }
@@ -1313,13 +1311,13 @@ void Achievements::HandleServerErrorEvent(const rc_client_event_t* event)
     fmt::format(TRANSLATE_FS("Achievements", "Server error in {}:\n{}"),
                 event->server_error->api ? event->server_error->api : "UNKNOWN",
                 event->server_error->error_message ? event->server_error->error_message : "UNKNOWN");
-  Log_ErrorPrint(message.c_str());
+  ERROR_LOG(message.c_str());
   Host::AddOSDMessage(std::move(message), Host::OSD_ERROR_DURATION);
 }
 
 void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
 {
-  Log_WarningPrint("Server disconnected.");
+  WARNING_LOG("Server disconnected.");
 
   if (FullscreenUI::Initialize())
   {
@@ -1333,7 +1331,7 @@ void Achievements::HandleServerDisconnectedEvent(const rc_client_event_t* event)
 
 void Achievements::HandleServerReconnectedEvent(const rc_client_event_t* event)
 {
-  Log_WarningPrint("Server reconnected.");
+  WARNING_LOG("Server reconnected.");
 
   if (FullscreenUI::Initialize())
   {
@@ -1356,7 +1354,7 @@ void Achievements::ResetClient()
   if (!IsActive())
     return;
 
-  Log_DevPrint("Reset client");
+  DEV_LOG("Reset client");
   rc_client_reset(s_client);
 }
 
@@ -1474,7 +1472,7 @@ bool Achievements::DoState(StateWrapper& sw)
     if (data_size == 0)
     {
       // reset runtime, no data (state might've been created without cheevos)
-      Log_DevPrint("State is missing cheevos data, resetting runtime");
+      DEV_LOG("State is missing cheevos data, resetting runtime");
 #ifdef ENABLE_RAINTEGRATION
       if (IsUsingRAIntegration())
         RA_OnReset();
@@ -1502,7 +1500,7 @@ bool Achievements::DoState(StateWrapper& sw)
       const int result = rc_client_deserialize_progress(s_client, data.get());
       if (result != RC_OK)
       {
-        Log_WarningFmt("Failed to deserialize cheevos state ({}), resetting", result);
+        WARNING_LOG("Failed to deserialize cheevos state ({}), resetting", result);
         rc_client_reset(s_client);
       }
     }
@@ -1526,7 +1524,7 @@ bool Achievements::DoState(StateWrapper& sw)
       const int result = RA_CaptureState(reinterpret_cast<char*>(data.get()), static_cast<int>(data_size));
       if (result != static_cast<int>(data_size))
       {
-        Log_WarningPrint("Failed to serialize cheevos state from RAIntegration.");
+        WARNING_LOG("Failed to serialize cheevos state from RAIntegration.");
         data_size = 0;
       }
     }
@@ -1543,7 +1541,7 @@ bool Achievements::DoState(StateWrapper& sw)
       if (result != RC_OK)
       {
         // set data to zero, effectively serializing nothing
-        Log_WarningFmt("Failed to serialize cheevos state ({})", result);
+        WARNING_LOG("Failed to serialize cheevos state ({})", result);
         data_size = 0;
       }
     }
@@ -1679,7 +1677,7 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error
 
   if (result != RC_OK)
   {
-    Log_ErrorFmt("Login failed: {}: {}", rc_error_str(result), error_message ? error_message : "Unknown");
+    ERROR_LOG("Login failed: {}: {}", rc_error_str(result), error_message ? error_message : "Unknown");
     Error::SetString(params->error,
                      fmt::format("{}: {}", rc_error_str(result), error_message ? error_message : "Unknown"));
     params->result = false;
@@ -1690,7 +1688,7 @@ void Achievements::ClientLoginWithPasswordCallback(int result, const char* error
   const rc_client_user_t* user = rc_client_get_user_info(client);
   if (!user || !user->token)
   {
-    Log_ErrorPrint("rc_client_get_user_info() returned NULL");
+    ERROR_LOG("rc_client_get_user_info() returned NULL");
     Error::SetString(params->error, "rc_client_get_user_info() returned NULL");
     params->result = false;
     return;
@@ -1801,11 +1799,11 @@ void Achievements::Logout()
     if (HasActiveGame())
       ClearGameInfo();
 
-    Log_InfoPrint("Logging out...");
+    INFO_LOG("Logging out...");
     rc_client_logout(s_client);
   }
 
-  Log_InfoPrint("Clearing credentials...");
+  INFO_LOG("Clearing credentials...");
   Host::DeleteBaseSettingValue("Cheevos", "Username");
   Host::DeleteBaseSettingValue("Cheevos", "Token");
   Host::DeleteBaseSettingValue("Cheevos", "LoginTimestamp");
@@ -1959,7 +1957,7 @@ void Achievements::DrawGameOverlays()
 
       if (!indicator.active && opacity <= 0.01f)
       {
-        Log_DevPrint("Remove challenge indicator");
+        DEV_LOG("Remove challenge indicator");
         it = s_active_challenge_indicators.erase(it);
       }
       else
@@ -2003,7 +2001,7 @@ void Achievements::DrawGameOverlays()
 
     if (!indicator.active && opacity <= 0.01f)
     {
-      Log_DevPrint("Remove progress indicator");
+      DEV_LOG("Remove progress indicator");
       s_active_progress_indicator.reset();
     }
 
@@ -2046,7 +2044,7 @@ void Achievements::DrawGameOverlays()
 
       if (!indicator.active && opacity <= 0.01f)
       {
-        Log_DevPrint("Remove tracker indicator");
+        DEV_LOG("Remove tracker indicator");
         it = s_active_leaderboard_trackers.erase(it);
       }
       else
@@ -2158,7 +2156,7 @@ bool Achievements::PrepareAchievementsWindow()
     RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS /*RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE*/);
   if (!s_achievement_list)
   {
-    Log_ErrorPrint("rc_client_create_achievement_list() returned null");
+    ERROR_LOG("rc_client_create_achievement_list() returned null");
     return false;
   }
 
@@ -2499,7 +2497,7 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
   if (clicked)
   {
     const SmallString url = SmallString::from_format(fmt::runtime(ACHEIVEMENT_DETAILS_URL_TEMPLATE), cheevo->id);
-    Log_InfoFmt("Opening achievement details: {}", url);
+    INFO_LOG("Opening achievement details: {}", url);
     Host::OpenURL(url);
   }
 
@@ -2518,7 +2516,7 @@ bool Achievements::PrepareLeaderboardsWindow()
   s_leaderboard_list = rc_client_create_leaderboard_list(client, RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE);
   if (!s_leaderboard_list)
   {
-    Log_ErrorPrint("rc_client_create_leaderboard_list() returned null");
+    ERROR_LOG("rc_client_create_leaderboard_list() returned null");
     return false;
   }
 
@@ -2974,7 +2972,7 @@ void Achievements::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboar
 
 void Achievements::OpenLeaderboard(const rc_client_leaderboard_t* lboard)
 {
-  Log_DevFmt("Opening leaderboard '{}' ({})", lboard->title, lboard->id);
+  DEV_LOG("Opening leaderboard '{}' ({})", lboard->title, lboard->id);
 
   CloseLeaderboard();
 
@@ -3058,7 +3056,7 @@ void Achievements::FetchNextLeaderboardEntries()
   for (rc_client_leaderboard_entry_list_t* list : s_leaderboard_entry_lists)
     start += list->num_entries;
 
-  Log_DevFmt("Fetching entries {} to {}", start, start + LEADERBOARD_ALL_FETCH_SIZE);
+  DEV_LOG("Fetching entries {} to {}", start, start + LEADERBOARD_ALL_FETCH_SIZE);
 
   if (s_leaderboard_fetch_handle)
     rc_client_abort_async(s_client, s_leaderboard_fetch_handle);
diff --git a/src/core/analog_controller.cpp b/src/core/analog_controller.cpp
index 98fc8351a..17bebfd47 100644
--- a/src/core/analog_controller.cpp
+++ b/src/core/analog_controller.cpp
@@ -296,7 +296,7 @@ void AnalogController::SetAnalogMode(bool enabled, bool show_message)
   if (m_analog_mode == enabled)
     return;
 
-  Log_InfoFmt("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
+  INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
   if (show_message)
   {
     Host::AddIconOSDMessage(
@@ -432,12 +432,12 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
 
       if (data_in == 0x01)
       {
-        Log_DebugPrint("ACK controller access");
+        DEBUG_LOG("ACK controller access");
         m_command = Command::Ready;
         return true;
       }
 
-      Log_DevFmt("Unknown data_in = 0x{:02X}", data_in);
+      DEV_LOG("Unknown data_in = 0x{:02X}", data_in);
       return false;
     }
     break;
@@ -508,7 +508,7 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
       else
       {
         if (m_configuration_mode)
-          Log_ErrorFmt("Unimplemented config mode command 0x{:02X}", data_in);
+          ERROR_LOG("Unimplemented config mode command 0x{:02X}", data_in);
 
         *data_out = 0xFF;
         return false;
@@ -658,7 +658,7 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
           m_status_byte = 0x5A;
         }
 
-        Log_DevFmt("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave");
+        DEV_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave");
       }
     }
     break;
@@ -667,14 +667,14 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
     {
       if (m_command_step == 2)
       {
-        Log_DevFmt("analog mode val 0x{:02x}", data_in);
+        DEV_LOG("analog mode val 0x{:02x}", data_in);
 
         if (data_in == 0x00 || data_in == 0x01)
           SetAnalogMode((data_in == 0x01), true);
       }
       else if (m_command_step == 3)
       {
-        Log_DevFmt("analog mode lock 0x{:02x}", data_in);
+        DEV_LOG("analog mode lock 0x{:02x}", data_in);
 
         if (data_in == 0x02 || data_in == 0x03)
           m_analog_locked = (data_in == 0x03);
@@ -771,10 +771,10 @@ bool AnalogController::Transfer(const u8 data_in, u8* data_out)
   {
     m_command = Command::Idle;
 
-    Log_DebugFmt("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1],
-                 m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]);
-    Log_DebugFmt("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1],
-                 m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]);
+    DEBUG_LOG("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1],
+              m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]);
+    DEBUG_LOG("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1],
+              m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]);
 
     m_rx_buffer.fill(0x00);
     m_tx_buffer.fill(0x00);
diff --git a/src/core/analog_joystick.cpp b/src/core/analog_joystick.cpp
index 828bc3712..fb01e9d46 100644
--- a/src/core/analog_joystick.cpp
+++ b/src/core/analog_joystick.cpp
@@ -240,7 +240,7 @@ void AnalogJoystick::ToggleAnalogMode()
 {
   m_analog_mode = !m_analog_mode;
 
-  Log_InfoFmt("Joystick {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
+  INFO_LOG("Joystick {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
   Host::AddIconOSDMessage(
     fmt::format("analog_mode_toggle_{}", m_index), ICON_FA_GAMEPAD,
     m_analog_mode ? fmt::format(TRANSLATE_FS("Controller", "Controller {} switched to analog mode."), m_index + 1u) :
diff --git a/src/core/bios.cpp b/src/core/bios.cpp
index f40083d08..cf191fc47 100644
--- a/src/core/bios.cpp
+++ b/src/core/bios.cpp
@@ -196,7 +196,7 @@ std::optional<BIOS::Image> BIOS::LoadImageFromFile(const char* filename, Error*
     return std::nullopt;
   }
 
-  Log_DevPrint(
+  DEV_LOG(
     fmt::format("Hash for BIOS '{}': {}", FileSystem::GetDisplayNameFromPath(filename), GetImageHash(ret).ToString())
       .c_str());
   return ret;
@@ -223,7 +223,7 @@ const BIOS::ImageInfo* BIOS::GetInfoForImage(const Image& image, const Hash& has
       return &ii;
   }
 
-  Log_WarningFmt("Unknown BIOS hash: {}", hash.ToString());
+  WARNING_LOG("Unknown BIOS hash: {}", hash.ToString());
   return nullptr;
 }
 
@@ -246,14 +246,14 @@ void BIOS::PatchBIOS(u8* image, u32 image_size, u32 address, u32 value, u32 mask
   SmallString old_disasm, new_disasm;
   CPU::DisassembleInstruction(&old_disasm, address, existing_value);
   CPU::DisassembleInstruction(&new_disasm, address, new_value);
-  Log_DevFmt("BIOS-Patch 0x{:08X} (+0x{:X}): 0x{:08X} {} -> {:08X} {}", address, offset, existing_value, old_disasm,
-             new_value, new_disasm);
+  DEV_LOG("BIOS-Patch 0x{:08X} (+0x{:X}): 0x{:08X} {} -> {:08X} {}", address, offset, existing_value, old_disasm,
+          new_value, new_disasm);
 }
 
 bool BIOS::PatchBIOSFastBoot(u8* image, u32 image_size)
 {
   // Replace the shell entry point with a return back to the bootstrap.
-  Log_InfoPrint("Patching BIOS to skip intro");
+  INFO_LOG("Patching BIOS to skip intro");
   PatchBIOS(image, image_size, 0x1FC18000, 0x3C011F80); // lui at, 1f80
   PatchBIOS(image, image_size, 0x1FC18004, 0x3C0A0300); // lui t2, 0300h
   PatchBIOS(image, image_size, 0x1FC18008, 0xAC2A1814); // sw zero, 1814h(at)        ; turn the display on
@@ -308,8 +308,8 @@ bool BIOS::IsValidPSExeHeader(const PSEXEHeader& header, u32 file_size)
 
   if ((header.file_size + sizeof(PSEXEHeader)) > file_size)
   {
-    Log_WarningFmt("Incorrect file size in PS-EXE header: {} bytes should not be greater than {} bytes",
-                   header.file_size, static_cast<unsigned>(file_size - sizeof(PSEXEHeader)));
+    WARNING_LOG("Incorrect file size in PS-EXE header: {} bytes should not be greater than {} bytes", header.file_size,
+                static_cast<unsigned>(file_size - sizeof(PSEXEHeader)));
   }
 
   return true;
@@ -369,9 +369,8 @@ std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region, Error* e
     const ImageInfo* ii = GetInfoForImage(image.value());
     if (!ii || !IsValidBIOSForRegion(region, ii->region))
     {
-      Log_WarningFmt("BIOS region {} does not match requested region {}. This may cause issues.",
-                     ii ? Settings::GetConsoleRegionName(ii->region) : "UNKNOWN",
-                     Settings::GetConsoleRegionName(region));
+      WARNING_LOG("BIOS region {} does not match requested region {}. This may cause issues.",
+                  ii ? Settings::GetConsoleRegionName(ii->region) : "UNKNOWN", Settings::GetConsoleRegionName(region));
     }
   }
 
@@ -380,7 +379,7 @@ std::optional<std::vector<u8>> BIOS::GetBIOSImage(ConsoleRegion region, Error* e
 
 std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion region, const char* directory, Error* error)
 {
-  Log_InfoFmt("Searching for a {} BIOS in '{}'...", Settings::GetConsoleRegionName(region), directory);
+  INFO_LOG("Searching for a {} BIOS in '{}'...", Settings::GetConsoleRegionName(region), directory);
 
   FileSystem::FindResultsArray results;
   FileSystem::FindFiles(
@@ -394,7 +393,7 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
   {
     if (fd.Size != BIOS_SIZE && fd.Size != BIOS_SIZE_PS2 && fd.Size != BIOS_SIZE_PS3)
     {
-      Log_WarningFmt("Skipping '{}': incorrect size", fd.FileName.c_str());
+      WARNING_LOG("Skipping '{}': incorrect size", fd.FileName.c_str());
       continue;
     }
 
@@ -406,7 +405,7 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
     const ImageInfo* ii = GetInfoForImage(found_image.value());
     if (ii && IsValidBIOSForRegion(region, ii->region))
     {
-      Log_InfoFmt("Using BIOS '{}': {}", fd.FileName.c_str(), ii->description);
+      INFO_LOG("Using BIOS '{}': {}", fd.FileName.c_str(), ii->description);
       fallback_image = std::move(found_image);
       return fallback_image;
     }
@@ -429,12 +428,12 @@ std::optional<std::vector<u8>> BIOS::FindBIOSImageInDirectory(ConsoleRegion regi
 
   if (!fallback_info)
   {
-    Log_WarningFmt("Using unknown BIOS '{}'. This may crash.", Path::GetFileName(fallback_path));
+    WARNING_LOG("Using unknown BIOS '{}'. This may crash.", Path::GetFileName(fallback_path));
   }
   else
   {
-    Log_WarningFmt("Falling back to possibly-incompatible image '{}': {}", Path::GetFileName(fallback_path),
-                   fallback_info->description);
+    WARNING_LOG("Falling back to possibly-incompatible image '{}': {}", Path::GetFileName(fallback_path),
+                fallback_info->description);
   }
 
   return fallback_image;
diff --git a/src/core/bus.cpp b/src/core/bus.cpp
index a25d56c49..8955a998b 100644
--- a/src/core/bus.cpp
+++ b/src/core/bus.cpp
@@ -200,7 +200,7 @@ bool Bus::AllocateMemory(Error* error)
     return false;
   }
 
-  Log_VerboseFmt("RAM is mapped at {}.", static_cast<void*>(g_ram));
+  VERBOSE_LOG("RAM is mapped at {}.", static_cast<void*>(g_ram));
 
   g_bios = static_cast<u8*>(MemMap::MapSharedMemory(s_shmem_handle, MemoryMap::BIOS_OFFSET, nullptr,
                                                     MemoryMap::BIOS_SIZE, PageProtect::ReadWrite));
@@ -211,7 +211,7 @@ bool Bus::AllocateMemory(Error* error)
     return false;
   }
 
-  Log_VerboseFmt("BIOS is mapped at {}.", static_cast<void*>(g_bios));
+  VERBOSE_LOG("BIOS is mapped at {}.", static_cast<void*>(g_bios));
 
   g_memory_handlers = static_cast<void**>(MemMap::MapSharedMemory(s_shmem_handle, MemoryMap::LUT_OFFSET, nullptr,
                                                                   MemoryMap::LUT_SIZE, PageProtect::ReadWrite));
@@ -222,7 +222,7 @@ bool Bus::AllocateMemory(Error* error)
     return false;
   }
 
-  Log_VerboseFmt("LUTs are mapped at {}.", static_cast<void*>(g_memory_handlers));
+  VERBOSE_LOG("LUTs are mapped at {}.", static_cast<void*>(g_memory_handlers));
   g_memory_handlers_isc = g_memory_handlers + MEMORY_LUT_SLOTS;
   SetHandlers();
 
@@ -234,7 +234,7 @@ bool Bus::AllocateMemory(Error* error)
     return false;
   }
 
-  Log_InfoFmt("Fastmem base: {}", static_cast<void*>(s_fastmem_arena.BasePointer()));
+  INFO_LOG("Fastmem base: {}", static_cast<void*>(s_fastmem_arena.BasePointer()));
 #endif
 
 #ifndef __ANDROID__
@@ -351,7 +351,7 @@ void Bus::AddTTYCharacter(char ch)
   {
     if (!s_tty_line_buffer.empty())
     {
-      Log::WriteFmt("TTY", "", LOGLEVEL_INFO, "\033[1;34m{}\033[0m", s_tty_line_buffer);
+      Log::FastWrite("TTY", "", LOGLEVEL_INFO, "\033[1;34m{}\033[0m", s_tty_line_buffer);
 #ifdef _DEBUG
       if (CPU::IsTraceEnabled())
         CPU::WriteToExecutionLog("TTY: %s\n", s_tty_line_buffer.c_str());
@@ -392,7 +392,7 @@ bool Bus::DoState(StateWrapper& sw)
 
   if (sw.GetVersion() < 58)
   {
-    Log_WarningPrint("Overwriting loaded BIOS with old save state.");
+    WARNING_LOG("Overwriting loaded BIOS with old save state.");
     sw.DoBytes(g_bios, BIOS_SIZE);
   }
 
@@ -452,15 +452,15 @@ void Bus::RecalculateMemoryTimings()
   std::tie(g_spu_access_time[0], g_spu_access_time[1], g_spu_access_time[2]) =
     CalculateMemoryTiming(s_MEMCTRL.spu_delay_size, s_MEMCTRL.common_delay);
 
-  Log_TraceFmt("BIOS Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
-               s_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, g_bios_access_time[0] + 1, g_bios_access_time[1] + 1,
-               g_bios_access_time[2] + 1);
-  Log_TraceFmt("CDROM Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
-               s_MEMCTRL.cdrom_delay_size.data_bus_16bit ? 16 : 8, g_cdrom_access_time[0] + 1,
-               g_cdrom_access_time[1] + 1, g_cdrom_access_time[2] + 1);
-  Log_TraceFmt("SPU Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
-               s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
-               g_spu_access_time[2] + 1);
+  TRACE_LOG("BIOS Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
+            s_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, g_bios_access_time[0] + 1, g_bios_access_time[1] + 1,
+            g_bios_access_time[2] + 1);
+  TRACE_LOG("CDROM Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
+            s_MEMCTRL.cdrom_delay_size.data_bus_16bit ? 16 : 8, g_cdrom_access_time[0] + 1, g_cdrom_access_time[1] + 1,
+            g_cdrom_access_time[2] + 1);
+  TRACE_LOG("SPU Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
+            s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
+            g_spu_access_time[2] + 1);
 }
 
 CPUFastmemMode Bus::GetFastmemMode()
@@ -506,8 +506,8 @@ void Bus::UpdateFastmemViews(CPUFastmemMode mode)
       u8* map_address = s_fastmem_arena.BasePointer() + base_address;
       if (!s_fastmem_arena.Map(s_shmem_handle, 0, map_address, g_ram_size, PageProtect::ReadWrite)) [[unlikely]]
       {
-        Log_ErrorFmt("Failed to map RAM at fastmem area {} (offset 0x{:08X})", static_cast<void*>(map_address),
-                     g_ram_size);
+        ERROR_LOG("Failed to map RAM at fastmem area {} (offset 0x{:08X})", static_cast<void*>(map_address),
+                  g_ram_size);
         return;
       }
 
@@ -519,7 +519,7 @@ void Bus::UpdateFastmemViews(CPUFastmemMode mode)
           u8* page_address = map_address + (i * HOST_PAGE_SIZE);
           if (!MemMap::MemProtect(page_address, HOST_PAGE_SIZE, PageProtect::ReadOnly)) [[unlikely]]
           {
-            Log_ErrorFmt("Failed to write-protect code page at {}", static_cast<void*>(page_address));
+            ERROR_LOG("Failed to write-protect code page at {}", static_cast<void*>(page_address));
             s_fastmem_arena.Unmap(map_address, g_ram_size);
             return;
           }
@@ -547,7 +547,7 @@ void Bus::UpdateFastmemViews(CPUFastmemMode mode)
     s_fastmem_lut = static_cast<u8**>(std::malloc(sizeof(u8*) * FASTMEM_LUT_SLOTS));
     Assert(s_fastmem_lut);
 
-    Log_InfoFmt("Fastmem base (software): {}", static_cast<void*>(s_fastmem_lut));
+    INFO_LOG("Fastmem base (software): {}", static_cast<void*>(s_fastmem_lut));
   }
 
   // This assumes the top 4KB of address space is not mapped. It shouldn't be on any sane OSes.
@@ -637,9 +637,9 @@ void Bus::SetRAMPageWritable(u32 page_index, bool writable)
   if (!MemMap::MemProtect(&g_ram[page_index * HOST_PAGE_SIZE], HOST_PAGE_SIZE,
                           writable ? PageProtect::ReadWrite : PageProtect::ReadOnly)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to set RAM host page {} ({}) to {}", page_index,
-                 reinterpret_cast<const void*>(&g_ram[page_index * HOST_PAGE_SIZE]),
-                 writable ? "read-write" : "read-only");
+    ERROR_LOG("Failed to set RAM host page {} ({}) to {}", page_index,
+              reinterpret_cast<const void*>(&g_ram[page_index * HOST_PAGE_SIZE]),
+              writable ? "read-write" : "read-only");
   }
 
 #ifdef ENABLE_MMAP_FASTMEM
@@ -653,8 +653,8 @@ void Bus::SetRAMPageWritable(u32 page_index, bool writable)
       u8* page_address = it.first + (page_index * HOST_PAGE_SIZE);
       if (!MemMap::MemProtect(page_address, HOST_PAGE_SIZE, protect)) [[unlikely]]
       {
-        Log_ErrorFmt("Failed to {} code page {} (0x{:08X}) @ {}", writable ? "unprotect" : "protect", page_index,
-                     page_index * static_cast<u32>(HOST_PAGE_SIZE), static_cast<void*>(page_address));
+        ERROR_LOG("Failed to {} code page {} (0x{:08X}) @ {}", writable ? "unprotect" : "protect", page_index,
+                  page_index * static_cast<u32>(HOST_PAGE_SIZE), static_cast<void*>(page_address));
       }
     }
 
@@ -668,7 +668,7 @@ void Bus::ClearRAMCodePageFlags()
   g_ram_code_bits.reset();
 
   if (!MemMap::MemProtect(g_ram, RAM_8MB_SIZE, PageProtect::ReadWrite))
-    Log_ErrorPrint("Failed to restore RAM protection to read-write.");
+    ERROR_LOG("Failed to restore RAM protection to read-write.");
 
 #ifdef ENABLE_MMAP_FASTMEM
   if (s_fastmem_mode == CPUFastmemMode::MMap)
@@ -677,7 +677,7 @@ void Bus::ClearRAMCodePageFlags()
     for (const auto& it : s_fastmem_ram_views)
     {
       if (!MemMap::MemProtect(it.first, it.second, PageProtect::ReadWrite))
-        Log_ErrorFmt("Failed to unprotect code pages for fastmem view @ %p", static_cast<void*>(it.first));
+        ERROR_LOG("Failed to unprotect code pages for fastmem view @ %p", static_cast<void*>(it.first));
     }
   }
 #endif
@@ -881,8 +881,8 @@ template<MemoryAccessSize size>
 u32 Bus::UnknownReadHandler(VirtualMemoryAddress address)
 {
   static constexpr const char* sizes[3] = {"byte", "halfword", "word"};
-  Log_ErrorFmt("Invalid {} read at address 0x{:08X}, pc 0x{:08X}", sizes[static_cast<u32>(size)], address,
-               CPU::g_state.pc);
+  ERROR_LOG("Invalid {} read at address 0x{:08X}, pc 0x{:08X}", sizes[static_cast<u32>(size)], address,
+            CPU::g_state.pc);
   return 0xFFFFFFFFu;
 }
 
@@ -890,8 +890,8 @@ template<MemoryAccessSize size>
 void Bus::UnknownWriteHandler(VirtualMemoryAddress address, u32 value)
 {
   static constexpr const char* sizes[3] = {"byte", "halfword", "word"};
-  Log_ErrorFmt("Invalid {} write at address 0x{:08X}, value 0x{:08X}, pc 0x{:08X}", sizes[static_cast<u32>(size)],
-               address, value, CPU::g_state.pc);
+  ERROR_LOG("Invalid {} write at address 0x{:08X}, value 0x{:08X}, pc 0x{:08X}", sizes[static_cast<u32>(size)], address,
+            value, CPU::g_state.pc);
   CPU::g_state.bus_error = true;
 }
 
@@ -1042,7 +1042,7 @@ void Bus::CacheControlWriteHandler(VirtualMemoryAddress address, u32 value)
   if (address != 0xFFFE0130)
     return UnknownWriteHandler<size>(address, value);
 
-  Log_DevFmt("Cache control <- 0x{:08X}", value);
+  DEV_LOG("Cache control <- 0x{:08X}", value);
   CPU::g_state.cache_control.bits = value;
 }
 
@@ -1122,7 +1122,7 @@ u32 Bus::EXP1ReadHandler(VirtualMemoryAddress address)
 template<MemoryAccessSize size>
 void Bus::EXP1WriteHandler(VirtualMemoryAddress address, u32 value)
 {
-  Log_WarningFmt("EXP1 write: 0x{:08X} <- 0x{:08X}", address, value);
+  WARNING_LOG("EXP1 write: 0x{:08X} <- 0x{:08X}", address, value);
 }
 
 template<MemoryAccessSize size>
@@ -1145,7 +1145,7 @@ u32 Bus::EXP2ReadHandler(VirtualMemoryAddress address)
   }
   else
   {
-    Log_WarningFmt("EXP2 read: 0x{:08X}", address);
+    WARNING_LOG("EXP2 read: 0x{:08X}", address);
     value = UINT32_C(0xFFFFFFFF);
   }
 
@@ -1162,11 +1162,11 @@ void Bus::EXP2WriteHandler(VirtualMemoryAddress address, u32 value)
   }
   else if (offset == 0x41 || offset == 0x42)
   {
-    Log_DevFmt("BIOS POST status: {:02X}", value & UINT32_C(0x0F));
+    DEV_LOG("BIOS POST status: {:02X}", value & UINT32_C(0x0F));
   }
   else if (offset == 0x70)
   {
-    Log_DevFmt("BIOS POST2 status: {:02X}", value & UINT32_C(0x0F));
+    DEV_LOG("BIOS POST2 status: {:02X}", value & UINT32_C(0x0F));
   }
 #if 0
   // TODO: Put behind configuration variable
@@ -1187,14 +1187,14 @@ void Bus::EXP2WriteHandler(VirtualMemoryAddress address, u32 value)
 #endif
   else
   {
-    Log_WarningFmt("EXP2 write: 0x{:08X} <- 0x{:08X}", address, value);
+    WARNING_LOG("EXP2 write: 0x{:08X} <- 0x{:08X}", address, value);
   }
 }
 
 template<MemoryAccessSize size>
 u32 Bus::EXP3ReadHandler(VirtualMemoryAddress address)
 {
-  Log_WarningFmt("EXP3 read: 0x{:08X}", address);
+  WARNING_LOG("EXP3 read: 0x{:08X}", address);
   return UINT32_C(0xFFFFFFFF);
 }
 
@@ -1203,7 +1203,7 @@ void Bus::EXP3WriteHandler(VirtualMemoryAddress address, u32 value)
 {
   const u32 offset = address & EXP3_MASK;
   if (offset == 0)
-    Log_WarningFmt("BIOS POST3 status: {:02X}", value & UINT32_C(0x0F));
+    WARNING_LOG("BIOS POST3 status: {:02X}", value & UINT32_C(0x0F));
 }
 
 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1762,7 +1762,7 @@ void** Bus::GetMemoryHandlers(bool isolate_cache, bool swap_caches)
 
 #ifdef _DEBUG
   if (swap_caches)
-    Log_WarningPrint("Cache isolated and swapped, icache will be written instead of scratchpad?");
+    WARNING_LOG("Cache isolated and swapped, icache will be written instead of scratchpad?");
 #endif
 
   return g_memory_handlers_isc;
diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp
index 94af2637b..6c8283ecd 100644
--- a/src/core/cdrom.cpp
+++ b/src/core/cdrom.cpp
@@ -571,8 +571,8 @@ void CDROM::SoftReset(TickCount ticks_late)
     const TickCount speed_change_ticks = was_double_speed ? GetTicksForSpeedChange() : 0;
     const TickCount seek_ticks = (s_current_lba != 0) ? GetTicksForSeek(0) : 0;
     const TickCount total_ticks = std::max<TickCount>(speed_change_ticks + seek_ticks, INIT_TICKS) - ticks_late;
-    Log_DevFmt("CDROM init total disc ticks = {} (speed change = {}, seek = {})", total_ticks, speed_change_ticks,
-               seek_ticks);
+    DEV_LOG("CDROM init total disc ticks = {} (speed change = {}, seek = {})", total_ticks, speed_change_ticks,
+            seek_ticks);
 
     if (s_current_lba != 0)
     {
@@ -746,8 +746,8 @@ void CDROM::InsertMedia(std::unique_ptr<CDImage> media, DiscRegion region)
   if (CanReadMedia())
     RemoveMedia(true);
 
-  Log_InfoFmt("Inserting new media, disc region: {}, console region: {}", Settings::GetDiscRegionName(region),
-              Settings::GetConsoleRegionName(System::GetRegion()));
+  INFO_LOG("Inserting new media, disc region: {}, console region: {}", Settings::GetDiscRegionName(region),
+           Settings::GetConsoleRegionName(System::GetRegion()));
 
   s_disc_region = region;
   s_reader.SetMedia(std::move(media));
@@ -771,7 +771,7 @@ std::unique_ptr<CDImage> CDROM::RemoveMedia(bool for_disc_swap)
   if (for_disc_swap)
     stop_ticks += System::ScaleTicksToOverclock(System::MASTER_CLOCK * 2);
 
-  Log_InfoPrint("Removing CD...");
+  INFO_LOG("Removing CD...");
   std::unique_ptr<CDImage> image = s_reader.RemoveMedia();
 
   if (s_show_current_file)
@@ -863,20 +863,20 @@ u8 CDROM::ReadRegister(u32 offset)
   switch (offset)
   {
     case 0: // status register
-      Log_TraceFmt("CDROM read status register -> 0x{:08X}", s_status.bits);
+      TRACE_LOG("CDROM read status register -> 0x{:08X}", s_status.bits);
       return s_status.bits;
 
     case 1: // always response FIFO
     {
       if (s_response_fifo.IsEmpty())
       {
-        Log_DevPrint("Response FIFO empty on read");
+        DEV_LOG("Response FIFO empty on read");
         return 0x00;
       }
 
       const u8 value = s_response_fifo.Pop();
       UpdateStatusRegister();
-      Log_DebugFmt("CDROM read response FIFO -> 0x{:08X}", ZeroExtend32(value));
+      DEBUG_LOG("CDROM read response FIFO -> 0x{:08X}", ZeroExtend32(value));
       return value;
     }
 
@@ -884,7 +884,7 @@ u8 CDROM::ReadRegister(u32 offset)
     {
       const u8 value = s_data_fifo.Pop();
       UpdateStatusRegister();
-      Log_DebugFmt("CDROM read data FIFO -> 0x{:08X}", ZeroExtend32(value));
+      DEBUG_LOG("CDROM read data FIFO -> 0x{:08X}", ZeroExtend32(value));
       return value;
     }
 
@@ -893,13 +893,13 @@ u8 CDROM::ReadRegister(u32 offset)
       if (s_status.index & 1)
       {
         const u8 value = s_interrupt_flag_register | ~INTERRUPT_REGISTER_MASK;
-        Log_DebugFmt("CDROM read interrupt flag register -> 0x{:02X}", value);
+        DEBUG_LOG("CDROM read interrupt flag register -> 0x{:02X}", value);
         return value;
       }
       else
       {
         const u8 value = s_interrupt_enable_register | ~INTERRUPT_REGISTER_MASK;
-        Log_DebugFmt("CDROM read interrupt enable register -> 0x{:02X}", value);
+        DEBUG_LOG("CDROM read interrupt enable register -> 0x{:02X}", value);
         return value;
       }
     }
@@ -908,8 +908,8 @@ u8 CDROM::ReadRegister(u32 offset)
     default:
       [[unlikely]]
       {
-        Log_ErrorFmt("Unknown CDROM register read: offset=0x{:02X}, index={}", offset,
-                     ZeroExtend32(s_status.index.GetValue()));
+        ERROR_LOG("Unknown CDROM register read: offset=0x{:02X}, index={}", offset,
+                  ZeroExtend32(s_status.index.GetValue()));
         Panic("Unknown CDROM register");
       }
   }
@@ -919,7 +919,7 @@ void CDROM::WriteRegister(u32 offset, u8 value)
 {
   if (offset == 0)
   {
-    Log_TraceFmt("CDROM status register <- 0x{:02X}", value);
+    TRACE_LOG("CDROM status register <- 0x{:02X}", value);
     s_status.bits = (s_status.bits & static_cast<u8>(~3)) | (value & u8(3));
     return;
   }
@@ -929,7 +929,7 @@ void CDROM::WriteRegister(u32 offset, u8 value)
   {
     case 0:
     {
-      Log_DebugFmt("CDROM command register <- 0x{:02X} ({})", value, s_command_info[value].name);
+      DEBUG_LOG("CDROM command register <- 0x{:02X} ({})", value, s_command_info[value].name);
       BeginCommand(static_cast<Command>(value));
       return;
     }
@@ -938,7 +938,7 @@ void CDROM::WriteRegister(u32 offset, u8 value)
     {
       if (s_param_fifo.IsFull())
       {
-        Log_WarningPrint("Parameter FIFO overflow");
+        WARNING_LOG("Parameter FIFO overflow");
         s_param_fifo.RemoveOne();
       }
 
@@ -949,14 +949,14 @@ void CDROM::WriteRegister(u32 offset, u8 value)
 
     case 2:
     {
-      Log_DebugFmt("Request register <- 0x{:02X}", value);
+      DEBUG_LOG("Request register <- 0x{:02X}", value);
       const RequestRegister rr{value};
 
       // Sound map is not currently implemented, haven't found anything which uses it.
       if (rr.SMEN)
-        Log_ErrorPrint("Sound map enable set");
+        ERROR_LOG("Sound map enable set");
       if (rr.BFWR)
-        Log_ErrorPrint("Buffer write enable set");
+        ERROR_LOG("Buffer write enable set");
 
       if (rr.BFRD)
       {
@@ -964,7 +964,7 @@ void CDROM::WriteRegister(u32 offset, u8 value)
       }
       else
       {
-        Log_DebugPrint("Clearing data FIFO");
+        DEBUG_LOG("Clearing data FIFO");
         s_data_fifo.Clear();
       }
 
@@ -974,13 +974,13 @@ void CDROM::WriteRegister(u32 offset, u8 value)
 
     case 3:
     {
-      Log_ErrorFmt("Sound map data out <- 0x{:02X}", value);
+      ERROR_LOG("Sound map data out <- 0x{:02X}", value);
       return;
     }
 
     case 4:
     {
-      Log_DebugFmt("Interrupt enable register <- 0x{:02X}", value);
+      DEBUG_LOG("Interrupt enable register <- 0x{:02X}", value);
       s_interrupt_enable_register = value & INTERRUPT_REGISTER_MASK;
       UpdateInterruptRequest();
       return;
@@ -988,7 +988,7 @@ void CDROM::WriteRegister(u32 offset, u8 value)
 
     case 5:
     {
-      Log_DebugFmt("Interrupt flag register <- 0x{:02X}", value);
+      DEBUG_LOG("Interrupt flag register <- 0x{:02X}", value);
       s_interrupt_flag_register &= ~(value & INTERRUPT_REGISTER_MASK);
       if (s_interrupt_flag_register == 0)
       {
@@ -1011,41 +1011,41 @@ void CDROM::WriteRegister(u32 offset, u8 value)
 
     case 6:
     {
-      Log_ErrorFmt("Sound map coding info <- 0x{:02X}", value);
+      ERROR_LOG("Sound map coding info <- 0x{:02X}", value);
       return;
     }
 
     case 7:
     {
-      Log_DebugFmt("Audio volume for left-to-left output <- 0x{:02X}", value);
+      DEBUG_LOG("Audio volume for left-to-left output <- 0x{:02X}", value);
       s_next_cd_audio_volume_matrix[0][0] = value;
       return;
     }
 
     case 8:
     {
-      Log_DebugFmt("Audio volume for left-to-right output <- 0x{:02X}", value);
+      DEBUG_LOG("Audio volume for left-to-right output <- 0x{:02X}", value);
       s_next_cd_audio_volume_matrix[0][1] = value;
       return;
     }
 
     case 9:
     {
-      Log_DebugFmt("Audio volume for right-to-right output <- 0x{:02X}", value);
+      DEBUG_LOG("Audio volume for right-to-right output <- 0x{:02X}", value);
       s_next_cd_audio_volume_matrix[1][1] = value;
       return;
     }
 
     case 10:
     {
-      Log_DebugFmt("Audio volume for right-to-left output <- 0x{:02X}", value);
+      DEBUG_LOG("Audio volume for right-to-left output <- 0x{:02X}", value);
       s_next_cd_audio_volume_matrix[1][0] = value;
       return;
     }
 
     case 11:
     {
-      Log_DebugFmt("Audio volume apply changes <- 0x{:02X}", value);
+      DEBUG_LOG("Audio volume apply changes <- 0x{:02X}", value);
 
       const bool adpcm_muted = ConvertToBoolUnchecked(value & u8(0x01));
       if (adpcm_muted != s_adpcm_muted ||
@@ -1066,8 +1066,8 @@ void CDROM::WriteRegister(u32 offset, u8 value)
     default:
       [[unlikely]]
       {
-        Log_ErrorFmt("Unknown CDROM register write: offset=0x{:02X}, index={}, reg={}, value=0x{:02X}", offset,
-                     s_status.index.GetValue(), reg, value);
+        ERROR_LOG("Unknown CDROM register write: offset=0x{:02X}, index={}, reg={}, value=0x{:02X}", offset,
+                  s_status.index.GetValue(), reg, value);
         return;
       }
   }
@@ -1078,7 +1078,7 @@ void CDROM::DMARead(u32* words, u32 word_count)
   const u32 words_in_fifo = s_data_fifo.GetSize() / 4;
   if (words_in_fifo < word_count)
   {
-    Log_ErrorPrint("DMA read on empty/near-empty data FIFO");
+    ERROR_LOG("DMA read on empty/near-empty data FIFO");
     std::memset(words + words_in_fifo, 0, sizeof(u32) * (word_count - words_in_fifo));
   }
 
@@ -1112,8 +1112,7 @@ void CDROM::SetAsyncInterrupt(Interrupt interrupt)
 {
   if (s_interrupt_flag_register == static_cast<u8>(interrupt))
   {
-    Log_DevFmt("Not setting async interrupt {} because there is already one unacknowledged",
-               static_cast<u8>(interrupt));
+    DEV_LOG("Not setting async interrupt {} because there is already one unacknowledged", static_cast<u8>(interrupt));
     s_async_response_fifo.Clear();
     return;
   }
@@ -1152,8 +1151,8 @@ void CDROM::QueueDeliverAsyncInterrupt()
   }
   else
   {
-    Log_DevFmt("Delaying async interrupt {} because it's been {} cycles since last interrupt",
-               s_pending_async_interrupt, diff);
+    DEV_LOG("Delaying async interrupt {} because it's been {} cycles since last interrupt", s_pending_async_interrupt,
+            diff);
     s_async_interrupt_event->Schedule(INTERRUPT_DELAY_CYCLES);
   }
 }
@@ -1171,7 +1170,7 @@ void CDROM::DeliverAsyncInterrupt(void*, TickCount ticks, TickCount ticks_late)
     s_async_interrupt_event->Deactivate();
 
     Assert(s_pending_async_interrupt != 0 && !HasPendingInterrupt());
-    Log_DebugFmt("Delivering async interrupt {}", s_pending_async_interrupt);
+    DEBUG_LOG("Delivering async interrupt {}", s_pending_async_interrupt);
 
     if (s_pending_async_interrupt == static_cast<u8>(Interrupt::DataReady))
       s_current_read_sector_buffer = s_current_write_sector_buffer;
@@ -1335,13 +1334,13 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change)
     const TickCount remaining_change_ticks = s_drive_event->GetTicksUntilNextExecution();
     ticks += remaining_change_ticks;
 
-    Log_DevFmt("Seek time for {} LBAs: {} ({:.3f} ms) ({} for speed change/implicit TOC read)", lba_diff, ticks,
-               (static_cast<float>(ticks) / static_cast<float>(ticks_per_second)) * 1000.0f, remaining_change_ticks);
+    DEV_LOG("Seek time for {} LBAs: {} ({:.3f} ms) ({} for speed change/implicit TOC read)", lba_diff, ticks,
+            (static_cast<float>(ticks) / static_cast<float>(ticks_per_second)) * 1000.0f, remaining_change_ticks);
   }
   else
   {
-    Log_DevFmt("Seek time for {} LBAs: {} ({:.3f} ms)", lba_diff, ticks,
-               (static_cast<float>(ticks) / static_cast<float>(ticks_per_second)) * 1000.0f);
+    DEV_LOG("Seek time for {} LBAs: {} ({:.3f} ms)", lba_diff, ticks,
+            (static_cast<float>(ticks) / static_cast<float>(ticks_per_second)) * 1000.0f);
   }
 
   if (g_settings.cdrom_seek_speedup > 1)
@@ -1396,16 +1395,16 @@ void CDROM::BeginCommand(Command command)
     if (s_command_info[static_cast<u8>(s_command)].min_parameters >
         s_command_info[static_cast<u8>(command)].min_parameters)
     {
-      Log_WarningFmt("Ignoring command 0x{:02X} ({}) and emptying FIFO as 0x{:02X} ({}) is still pending",
-                     static_cast<u8>(command), s_command_info[static_cast<u8>(command)].name,
-                     static_cast<u8>(s_command), s_command_info[static_cast<u8>(s_command)].name);
+      WARNING_LOG("Ignoring command 0x{:02X} ({}) and emptying FIFO as 0x{:02X} ({}) is still pending",
+                  static_cast<u8>(command), s_command_info[static_cast<u8>(command)].name, static_cast<u8>(s_command),
+                  s_command_info[static_cast<u8>(s_command)].name);
       s_param_fifo.Clear();
       return;
     }
 
-    Log_WarningFmt("Cancelling pending command 0x{:02X} ({}) for new command 0x{:02X} ({})", static_cast<u8>(s_command),
-                   s_command_info[static_cast<u8>(s_command)].name, static_cast<u8>(command),
-                   s_command_info[static_cast<u8>(command)].name);
+    WARNING_LOG("Cancelling pending command 0x{:02X} ({}) for new command 0x{:02X} ({})", static_cast<u8>(s_command),
+                s_command_info[static_cast<u8>(s_command)].name, static_cast<u8>(command),
+                s_command_info[static_cast<u8>(command)].name);
 
     // subtract the currently-elapsed ack ticks from the new command
     if (s_command_event->IsActive())
@@ -1434,19 +1433,19 @@ void CDROM::EndCommand()
 void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 {
   const CommandInfo& ci = s_command_info[static_cast<u8>(s_command)];
-  if (Log_DevVisible()) [[unlikely]]
+  if (Log::IsLogVisible(LOGLEVEL_DEV, ___LogChannel___)) [[unlikely]]
   {
     SmallString params;
     for (u32 i = 0; i < s_param_fifo.GetSize(); i++)
       params.append_format("{}0x{:02X}", (i == 0) ? "" : ", ", s_param_fifo.Peek(i));
-    Log_DevFmt("CDROM executing command 0x{:02X} ({}), stat = 0x{:02X}, params = [{}]", static_cast<u8>(s_command),
-               ci.name, s_secondary_status.bits, params);
+    DEV_LOG("CDROM executing command 0x{:02X} ({}), stat = 0x{:02X}, params = [{}]", static_cast<u8>(s_command),
+            ci.name, s_secondary_status.bits, params);
   }
 
   if (s_param_fifo.GetSize() < ci.min_parameters || s_param_fifo.GetSize() > ci.max_parameters) [[unlikely]]
   {
-    Log_WarningFmt("Incorrect parameters for command 0x{:02X} ({}), expecting {}-{} got {}", static_cast<u8>(s_command),
-                   ci.name, ci.min_parameters, ci.max_parameters, s_param_fifo.GetSize());
+    WARNING_LOG("Incorrect parameters for command 0x{:02X} ({}), expecting {}-{} got {}", static_cast<u8>(s_command),
+                ci.name, ci.min_parameters, ci.max_parameters, s_param_fifo.GetSize());
     SendErrorResponse(STAT_ERROR, ERROR_REASON_INCORRECT_NUMBER_OF_PARAMETERS);
     EndCommand();
     return;
@@ -1454,7 +1453,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
   if (!s_response_fifo.IsEmpty())
   {
-    Log_DebugPrint("Response FIFO not empty on command begin");
+    DEBUG_LOG("Response FIFO not empty on command begin");
     s_response_fifo.Clear();
   }
 
@@ -1462,7 +1461,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
   {
     case Command::Getstat:
     {
-      Log_DebugPrint("CDROM Getstat command");
+      DEBUG_LOG("CDROM Getstat command");
 
       // if bit 0 or 2 is set, send an additional byte
       SendACKAndStat();
@@ -1484,7 +1483,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::GetID:
     {
-      Log_DebugPrint("CDROM GetID command");
+      DEBUG_LOG("CDROM GetID command");
       ClearCommandSecondResponse();
 
       if (!CanReadMedia())
@@ -1503,7 +1502,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::ReadTOC:
     {
-      Log_DebugPrint("CDROM ReadTOC command");
+      DEBUG_LOG("CDROM ReadTOC command");
       ClearCommandSecondResponse();
 
       if (!CanReadMedia())
@@ -1525,7 +1524,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     {
       const u8 file = s_param_fifo.Peek(0);
       const u8 channel = s_param_fifo.Peek(1);
-      Log_DebugFmt("CDROM setfilter command 0x{:02X} 0x{:02X}", ZeroExtend32(file), ZeroExtend32(channel));
+      DEBUG_LOG("CDROM setfilter command 0x{:02X} 0x{:02X}", ZeroExtend32(file), ZeroExtend32(channel));
       s_xa_filter_file_number = file;
       s_xa_filter_channel_number = channel;
       s_xa_current_set = false;
@@ -1538,7 +1537,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     {
       const u8 mode = s_param_fifo.Peek(0);
       const bool speed_change = (mode & 0x80) != (s_mode.bits & 0x80);
-      Log_DevFmt("CDROM setmode command 0x{:02X}", ZeroExtend32(mode));
+      DEV_LOG("CDROM setmode command 0x{:02X}", ZeroExtend32(mode));
 
       s_mode.bits = mode;
       SendACKAndStat();
@@ -1551,7 +1550,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
           // cancel the speed change if it's less than a quarter complete
           if (s_drive_event->GetTicksUntilNextExecution() >= (GetTicksForSpeedChange() / 4))
           {
-            Log_DevPrint("Cancelling speed change event");
+            DEV_LOG("Cancelling speed change event");
             ClearDriveState();
           }
         }
@@ -1561,14 +1560,14 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
           const TickCount change_ticks = GetTicksForSpeedChange();
           if (s_drive_state != DriveState::Idle)
           {
-            Log_DevFmt("Drive is {}, delaying event by {} ticks for speed change to {}-speed",
-                       s_drive_state_names[static_cast<u8>(s_drive_state)], change_ticks,
-                       s_mode.double_speed ? "double" : "single");
+            DEV_LOG("Drive is {}, delaying event by {} ticks for speed change to {}-speed",
+                    s_drive_state_names[static_cast<u8>(s_drive_state)], change_ticks,
+                    s_mode.double_speed ? "double" : "single");
             s_drive_event->Delay(change_ticks);
           }
           else
           {
-            Log_DevFmt("Drive is idle, speed change takes {} ticks", change_ticks);
+            DEV_LOG("Drive is idle, speed change takes {} ticks", change_ticks);
             s_drive_state = DriveState::ChangingSpeedOrTOCRead;
             s_drive_event->Schedule(change_ticks);
           }
@@ -1583,13 +1582,13 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
       const u8 mm = s_param_fifo.Peek(0);
       const u8 ss = s_param_fifo.Peek(1);
       const u8 ff = s_param_fifo.Peek(2);
-      Log_DevFmt("CDROM setloc command ({:02X}, {:02X}, {:02X})", mm, ss, ff);
+      DEV_LOG("CDROM setloc command ({:02X}, {:02X}, {:02X})", mm, ss, ff);
 
       // MM must be BCD, SS must be BCD and <0x60, FF must be BCD and <0x75
       if (((mm & 0x0F) > 0x09) || (mm > 0x99) || ((ss & 0x0F) > 0x09) || (ss >= 0x60) || ((ff & 0x0F) > 0x09) ||
           (ff >= 0x75))
       {
-        Log_ErrorFmt("Invalid/out of range seek to {:02X}:{:02X}:{:02X}", mm, ss, ff);
+        ERROR_LOG("Invalid/out of range seek to {:02X}:{:02X}:{:02X}", mm, ss, ff);
         SendErrorResponse(STAT_ERROR, ERROR_REASON_INVALID_ARGUMENT);
       }
       else
@@ -1610,7 +1609,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     case Command::SeekP:
     {
       const bool logical = (s_command == Command::SeekL);
-      Log_DebugFmt("CDROM {} command", logical ? "SeekL" : "SeekP");
+      DEBUG_LOG("CDROM {} command", logical ? "SeekL" : "SeekP");
 
       if (IsSeeking())
         UpdatePositionWhileSeeking();
@@ -1632,7 +1631,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     case Command::ReadT:
     {
       const u8 session = s_param_fifo.Peek(0);
-      Log_DebugFmt("CDROM ReadT command, session={}", session);
+      DEBUG_LOG("CDROM ReadT command, session={}", session);
 
       if (!CanReadMedia() || s_drive_state == DriveState::Reading || s_drive_state == DriveState::Playing)
       {
@@ -1659,7 +1658,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     case Command::ReadN:
     case Command::ReadS:
     {
-      Log_DebugPrint("CDROM read command");
+      DEBUG_LOG("CDROM read command");
       if (!CanReadMedia())
       {
         SendErrorResponse(STAT_ERROR, ERROR_REASON_NOT_READY);
@@ -1675,8 +1674,8 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
         if ((!s_setloc_pending || s_setloc_position.ToLBA() == GetNextSectorToBeRead()) &&
             (s_drive_state == DriveState::Reading || (IsSeeking() && s_read_after_seek)))
         {
-          Log_DevFmt("Ignoring read command with {} setloc, already reading/reading after seek",
-                     s_setloc_pending ? "pending" : "same");
+          DEV_LOG("Ignoring read command with {} setloc, already reading/reading after seek",
+                  s_setloc_pending ? "pending" : "same");
           s_setloc_pending = false;
         }
         else
@@ -1695,7 +1694,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     case Command::Play:
     {
       const u8 track = s_param_fifo.IsEmpty() ? 0 : PackedBCDToBinary(s_param_fifo.Peek(0));
-      Log_DebugFmt("CDROM play command, track={}", track);
+      DEBUG_LOG("CDROM play command, track={}", track);
 
       if (!CanReadMedia())
       {
@@ -1708,7 +1707,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
         if (track == 0 && (!s_setloc_pending || s_setloc_position.ToLBA() == GetNextSectorToBeRead()) &&
             (s_drive_state == DriveState::Playing || (IsSeeking() && s_play_after_seek)))
         {
-          Log_DevPrint("Ignoring play command with no/same setloc, already playing/playing after seek");
+          DEV_LOG("Ignoring play command with no/same setloc, already playing/playing after seek");
           s_fast_forward_rate = 0;
         }
         else
@@ -1778,8 +1777,8 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
       {
         // TODO: On console, this returns an error. But perhaps only during the coarse/fine seek part? Needs more
         // hardware tests.
-        Log_WarningFmt("CDROM Pause command while seeking from {} to {} - jumping to seek target", s_seek_start_lba,
-                       s_seek_end_lba);
+        WARNING_LOG("CDROM Pause command while seeking from {} to {} - jumping to seek target", s_seek_start_lba,
+                    s_seek_end_lba);
         s_read_after_seek = false;
         s_play_after_seek = false;
         CompleteSeek();
@@ -1816,7 +1815,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::Init:
     {
-      Log_DebugPrint("CDROM init command");
+      DEBUG_LOG("CDROM init command");
 
       if (s_command_second_response == Command::Init)
       {
@@ -1839,7 +1838,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::MotorOn:
     {
-      Log_DebugPrint("CDROM motor on command");
+      DEBUG_LOG("CDROM motor on command");
       if (IsMotorOn())
       {
         SendErrorResponse(STAT_ERROR, ERROR_REASON_INCORRECT_NUMBER_OF_PARAMETERS);
@@ -1868,7 +1867,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::Mute:
     {
-      Log_DebugPrint("CDROM mute command");
+      DEBUG_LOG("CDROM mute command");
       s_muted = true;
       SendACKAndStat();
       EndCommand();
@@ -1877,7 +1876,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::Demute:
     {
-      Log_DebugPrint("CDROM demute command");
+      DEBUG_LOG("CDROM demute command");
       s_muted = false;
       SendACKAndStat();
       EndCommand();
@@ -1888,15 +1887,15 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     {
       if (!s_last_sector_header_valid)
       {
-        Log_DevFmt("CDROM GetlocL command - header invalid, status 0x{:02X}", s_secondary_status.bits);
+        DEV_LOG("CDROM GetlocL command - header invalid, status 0x{:02X}", s_secondary_status.bits);
         SendErrorResponse(STAT_ERROR, ERROR_REASON_NOT_READY);
       }
       else
       {
         UpdatePhysicalPosition(true);
 
-        Log_DebugFmt("CDROM GetlocL command - [{:02X}:{:02X}:{:02X}]", s_last_sector_header.minute,
-                     s_last_sector_header.second, s_last_sector_header.frame);
+        DEBUG_LOG("CDROM GetlocL command - [{:02X}:{:02X}:{:02X}]", s_last_sector_header.minute,
+                  s_last_sector_header.second, s_last_sector_header.frame);
 
         s_response_fifo.PushRange(reinterpret_cast<const u8*>(&s_last_sector_header), sizeof(s_last_sector_header));
         s_response_fifo.PushRange(reinterpret_cast<const u8*>(&s_last_sector_subheader),
@@ -1912,7 +1911,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     {
       if (!CanReadMedia())
       {
-        Log_DebugPrint("CDROM GetlocP command - not ready");
+        DEBUG_LOG("CDROM GetlocP command - not ready");
         SendErrorResponse(STAT_ERROR, ERROR_REASON_NOT_READY);
       }
       else
@@ -1922,10 +1921,10 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
         else
           UpdatePhysicalPosition(false);
 
-        Log_DevFmt("CDROM GetlocP command - T{:02x} I{:02x} R[{:02x}:{:02x}:{:02x}] A[{:02x}:{:02x}:{:02x}]",
-                   s_last_subq.track_number_bcd, s_last_subq.index_number_bcd, s_last_subq.relative_minute_bcd,
-                   s_last_subq.relative_second_bcd, s_last_subq.relative_frame_bcd, s_last_subq.absolute_minute_bcd,
-                   s_last_subq.absolute_second_bcd, s_last_subq.absolute_frame_bcd);
+        DEV_LOG("CDROM GetlocP command - T{:02x} I{:02x} R[{:02x}:{:02x}:{:02x}] A[{:02x}:{:02x}:{:02x}]",
+                s_last_subq.track_number_bcd, s_last_subq.index_number_bcd, s_last_subq.relative_minute_bcd,
+                s_last_subq.relative_second_bcd, s_last_subq.relative_frame_bcd, s_last_subq.absolute_minute_bcd,
+                s_last_subq.absolute_second_bcd, s_last_subq.absolute_frame_bcd);
 
         s_response_fifo.Push(s_last_subq.track_number_bcd);
         s_response_fifo.Push(s_last_subq.index_number_bcd);
@@ -1944,11 +1943,11 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::GetTN:
     {
-      Log_DebugPrint("CDROM GetTN command");
+      DEBUG_LOG("CDROM GetTN command");
       if (CanReadMedia())
       {
-        Log_DevFmt("GetTN -> {} {}", s_reader.GetMedia()->GetFirstTrackNumber(),
-                   s_reader.GetMedia()->GetLastTrackNumber());
+        DEV_LOG("GetTN -> {} {}", s_reader.GetMedia()->GetFirstTrackNumber(),
+                s_reader.GetMedia()->GetLastTrackNumber());
 
         s_response_fifo.Push(s_secondary_status.bits);
         s_response_fifo.Push(BinaryToBCD(Truncate8(s_reader.GetMedia()->GetFirstTrackNumber())));
@@ -1966,7 +1965,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::GetTD:
     {
-      Log_DebugPrint("CDROM GetTD command");
+      DEBUG_LOG("CDROM GetTD command");
       Assert(s_param_fifo.GetSize() >= 1);
 
       if (!CanReadMedia())
@@ -1979,7 +1978,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
       const u8 track_bcd = s_param_fifo.Peek();
       if (!IsValidPackedBCD(track_bcd))
       {
-        Log_ErrorFmt("Invalid track number in GetTD: {:02X}", track_bcd);
+        ERROR_LOG("Invalid track number in GetTD: {:02X}", track_bcd);
         SendErrorResponse(STAT_ERROR, ERROR_REASON_INVALID_ARGUMENT);
         EndCommand();
         return;
@@ -2001,7 +2000,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
         s_response_fifo.Push(s_secondary_status.bits);
         s_response_fifo.Push(BinaryToBCD(Truncate8(pos.minute)));
         s_response_fifo.Push(BinaryToBCD(Truncate8(pos.second)));
-        Log_DevFmt("GetTD {} -> {} {}", track, pos.minute, pos.second);
+        DEV_LOG("GetTD {} -> {} {}", track, pos.minute, pos.second);
 
         SetInterrupt(Interrupt::ACK);
       }
@@ -2012,7 +2011,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::Getmode:
     {
-      Log_DebugPrint("CDROM Getmode command");
+      DEBUG_LOG("CDROM Getmode command");
 
       s_response_fifo.Push(s_secondary_status.bits);
       s_response_fifo.Push(s_mode.bits);
@@ -2026,7 +2025,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::Sync:
     {
-      Log_DebugPrint("CDROM sync command");
+      DEBUG_LOG("CDROM sync command");
 
       SendErrorResponse(STAT_ERROR, ERROR_REASON_INVALID_COMMAND);
       EndCommand();
@@ -2035,7 +2034,7 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
 
     case Command::VideoCD:
     {
-      Log_DebugPrint("CDROM VideoCD command");
+      DEBUG_LOG("CDROM VideoCD command");
       SendErrorResponse(STAT_ERROR, ERROR_REASON_INVALID_COMMAND);
 
       // According to nocash this doesn't clear the parameter FIFO.
@@ -2048,8 +2047,8 @@ void CDROM::ExecuteCommand(void*, TickCount ticks, TickCount ticks_late)
     default:
       [[unlikely]]
       {
-        Log_ErrorFmt("Unknown CDROM command 0x{:04X} with {} parameters, please report", static_cast<u16>(s_command),
-                     s_param_fifo.GetSize());
+        ERROR_LOG("Unknown CDROM command 0x{:04X} with {} parameters, please report", static_cast<u16>(s_command),
+                  s_param_fifo.GetSize());
         SendErrorResponse(STAT_ERROR, ERROR_REASON_INVALID_COMMAND);
         EndCommand();
       }
@@ -2063,7 +2062,7 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
   {
     case 0x04: // Reset SCEx counters
     {
-      Log_DebugPrint("Reset SCEx counters");
+      DEBUG_LOG("Reset SCEx counters");
       s_secondary_status.motor_on = true;
       s_response_fifo.Push(s_secondary_status.bits);
       SetInterrupt(Interrupt::ACK);
@@ -2073,7 +2072,7 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
 
     case 0x05: // Read SCEx counters
     {
-      Log_DebugPrint("Read SCEx counters");
+      DEBUG_LOG("Read SCEx counters");
       s_response_fifo.Push(s_secondary_status.bits);
       s_response_fifo.Push(0); // # of TOC reads?
       s_response_fifo.Push(0); // # of SCEx strings received
@@ -2084,7 +2083,7 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
 
     case 0x20: // Get CDROM BIOS Date/Version
     {
-      Log_DebugPrint("Get CDROM BIOS Date/Version");
+      DEBUG_LOG("Get CDROM BIOS Date/Version");
 
       static constexpr const u8 version_table[][4] = {
         {0x94, 0x09, 0x19, 0xC0}, // PSX (PU-7)               19 Sep 1994, version vC0 (a)
@@ -2111,7 +2110,7 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
 
     case 0x22:
     {
-      Log_DebugPrint("Get CDROM region ID string");
+      DEBUG_LOG("Get CDROM region ID string");
 
       switch (System::GetRegion())
       {
@@ -2146,7 +2145,7 @@ void CDROM::ExecuteTestCommand(u8 subcommand)
     default:
       [[unlikely]]
       {
-        Log_ErrorFmt("Unknown test command 0x{:02X}, %u parameters", subcommand, s_param_fifo.GetSize());
+        ERROR_LOG("Unknown test command 0x{:02X}, %u parameters", subcommand, s_param_fifo.GetSize());
         SendErrorResponse(STAT_ERROR, ERROR_REASON_INVALID_COMMAND);
         EndCommand();
         return;
@@ -2189,8 +2188,8 @@ void CDROM::ClearCommandSecondResponse()
 {
   if (s_command_second_response != Command::None)
   {
-    Log_DevFmt("Cancelling pending command 0x{:02X} ({}) second response", static_cast<u16>(s_command_second_response),
-               s_command_info[static_cast<u16>(s_command_second_response)].name);
+    DEV_LOG("Cancelling pending command 0x{:02X} ({}) second response", static_cast<u16>(s_command_second_response),
+            s_command_info[static_cast<u16>(s_command_second_response)].name);
   }
 
   s_command_second_response_event->Deactivate();
@@ -2304,8 +2303,8 @@ void CDROM::BeginReading(TickCount ticks_late /* = 0 */, bool after_seek /* = fa
   // Fixes crash in Disney's The Lion King - Simba's Mighty Adventure.
   if (IsSeeking())
   {
-    Log_DevFmt("Read command while seeking, scheduling read after seek {} -> {} finishes in {} ticks", s_seek_start_lba,
-               s_seek_end_lba, s_drive_event->GetTicksUntilNextExecution());
+    DEV_LOG("Read command while seeking, scheduling read after seek {} -> {} finishes in {} ticks", s_seek_start_lba,
+            s_seek_end_lba, s_drive_event->GetTicksUntilNextExecution());
 
     // Implicit seeks won't trigger the read, so swap it for a logical.
     if (s_drive_state == DriveState::SeekingImplicit)
@@ -2316,7 +2315,7 @@ void CDROM::BeginReading(TickCount ticks_late /* = 0 */, bool after_seek /* = fa
     return;
   }
 
-  Log_DebugFmt("Starting reading @ LBA {}", s_current_lba);
+  DEBUG_LOG("Starting reading @ LBA {}", s_current_lba);
 
   const TickCount ticks = GetTicksForRead();
   const TickCount first_sector_ticks = ticks + (after_seek ? 0 : GetTicksForSeek(s_current_lba)) - ticks_late;
@@ -2336,7 +2335,7 @@ void CDROM::BeginReading(TickCount ticks_late /* = 0 */, bool after_seek /* = fa
 
 void CDROM::BeginPlaying(u8 track, TickCount ticks_late /* = 0 */, bool after_seek /* = false */)
 {
-  Log_DebugFmt("Starting playing CDDA track {}", track);
+  DEBUG_LOG("Starting playing CDDA track {}", track);
   s_last_cdda_report_frame_nibble = 0xFF;
   s_play_track_number_bcd = track;
   s_fast_forward_rate = 0;
@@ -2381,7 +2380,7 @@ void CDROM::BeginPlaying(u8 track, TickCount ticks_late /* = 0 */, bool after_se
 void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_seek)
 {
   if (!s_setloc_pending)
-    Log_WarningPrint("Seeking without setloc set");
+    WARNING_LOG("Seeking without setloc set");
 
   s_read_after_seek = read_after_seek;
   s_play_after_seek = play_after_seek;
@@ -2389,8 +2388,8 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
   // TODO: Pending should stay set on seek command.
   s_setloc_pending = false;
 
-  Log_DebugFmt("Seeking to [{:02d}:{:02d}:{:02d}] (LBA {}) ({})", s_setloc_position.minute, s_setloc_position.second,
-               s_setloc_position.frame, s_setloc_position.ToLBA(), logical ? "logical" : "physical");
+  DEBUG_LOG("Seeking to [{:02d}:{:02d}:{:02d}] (LBA {}) ({})", s_setloc_position.minute, s_setloc_position.second,
+            s_setloc_position.frame, s_setloc_position.ToLBA(), logical ? "logical" : "physical");
 
   const CDImage::LBA seek_lba = s_setloc_position.ToLBA();
   const TickCount seek_time = GetTicksForSeek(seek_lba, play_after_seek);
@@ -2439,13 +2438,13 @@ void CDROM::UpdatePositionWhileSeeking()
     return;
   }
 
-  Log_DevFmt("Update position while seeking from {} to {} - {} ({:.2f})", s_seek_start_lba, s_seek_end_lba, current_lba,
-             completed_frac);
+  DEV_LOG("Update position while seeking from {} to {} - {} ({:.2f})", s_seek_start_lba, s_seek_end_lba, current_lba,
+          completed_frac);
 
   // access the image directly since we want to preserve the cached data for the seek complete
   CDImage::SubChannelQ subq;
   if (!s_reader.ReadSectorUncached(current_lba, &subq, nullptr))
-    Log_ErrorFmt("Failed to read subq for sector {} for physical position", current_lba);
+    ERROR_LOG("Failed to read subq for sector {} for physical position", current_lba);
   else if (subq.IsCRCValid())
     s_last_subq = subq;
 
@@ -2466,8 +2465,8 @@ void CDROM::UpdatePhysicalPosition(bool update_logical)
     if ((s_secondary_status.bits & (STAT_READING | STAT_PLAYING_CDDA | STAT_MOTOR_ON)) == STAT_MOTOR_ON &&
         s_current_lba != s_physical_lba)
     {
-      Log_WarningFmt("Jumping to hold position [{}->{}] while {} first sector", s_physical_lba, s_current_lba,
-                     (s_drive_state == DriveState::Reading) ? "reading" : "playing");
+      WARNING_LOG("Jumping to hold position [{}->{}] while {} first sector", s_physical_lba, s_current_lba,
+                  (s_drive_state == DriveState::Reading) ? "reading" : "playing");
       SetHoldPosition(s_current_lba, true);
     }
 
@@ -2507,8 +2506,8 @@ void CDROM::UpdatePhysicalPosition(bool update_logical)
     const CDImage::LBA new_offset = (old_offset + sector_diff) % sectors_per_track;
     const CDImage::LBA new_physical_lba = base + new_offset;
 #ifdef _DEBUG
-    Log_DevFmt("Tick diff {}, sector diff {}, old pos {}, new pos {}", diff, sector_diff,
-               LBAToMSFString(s_physical_lba), LBAToMSFString(new_physical_lba));
+    DEV_LOG("Tick diff {}, sector diff {}, old pos {}, new pos {}", diff, sector_diff, LBAToMSFString(s_physical_lba),
+            LBAToMSFString(new_physical_lba));
 #endif
     if (s_physical_lba != new_physical_lba)
     {
@@ -2518,7 +2517,7 @@ void CDROM::UpdatePhysicalPosition(bool update_logical)
       CDROMAsyncReader::SectorBuffer raw_sector;
       if (!s_reader.ReadSectorUncached(new_physical_lba, &subq, update_logical ? &raw_sector : nullptr))
       {
-        Log_ErrorFmt("Failed to read subq for sector {} for physical position", new_physical_lba);
+        ERROR_LOG("Failed to read subq for sector {} for physical position", new_physical_lba);
       }
       else
       {
@@ -2541,7 +2540,7 @@ void CDROM::SetHoldPosition(CDImage::LBA lba, bool update_subq)
   {
     CDImage::SubChannelQ subq;
     if (!s_reader.ReadSectorUncached(lba, &subq, nullptr))
-      Log_ErrorFmt("Failed to read subq for sector {} for physical position", lba);
+      ERROR_LOG("Failed to read subq for sector {} for physical position", lba);
     else if (subq.IsCRCValid())
       s_last_subq = subq;
   }
@@ -2592,8 +2591,8 @@ bool CDROM::CompleteSeek()
         {
           if (logical)
           {
-            Log_WarningFmt("Logical seek to non-data sector [{:02x}:{:02x}:{:02x}]{}", seek_mm, seek_ss, seek_ff,
-                           s_read_after_seek ? ", reading after seek" : "");
+            WARNING_LOG("Logical seek to non-data sector [{:02x}:{:02x}:{:02x}]{}", seek_mm, seek_ss, seek_ff,
+                        s_read_after_seek ? ", reading after seek" : "");
 
             // If CDDA mode isn't enabled and we're reading an audio sector, we need to fail the seek.
             // Test cases:
@@ -2606,7 +2605,7 @@ bool CDROM::CompleteSeek()
 
         if (subq.track_number_bcd == CDImage::LEAD_OUT_TRACK_NUMBER)
         {
-          Log_WarningFmt("Invalid seek to lead-out area (LBA {})", s_reader.GetLastReadSector());
+          WARNING_LOG("Invalid seek to lead-out area (LBA {})", s_reader.GetLastReadSector());
           seek_okay = false;
         }
       }
@@ -2646,8 +2645,8 @@ void CDROM::DoSeekComplete(TickCount ticks_late)
   }
   else
   {
-    Log_WarningFmt("{} seek to [{}] failed", logical ? "Logical" : "Physical",
-                   LBAToMSFString(s_reader.GetLastReadSector()));
+    WARNING_LOG("{} seek to [{}] failed", logical ? "Logical" : "Physical",
+                LBAToMSFString(s_reader.GetLastReadSector()));
     s_secondary_status.ClearActiveBits();
     SendAsyncErrorResponse(STAT_SEEK_ERROR, 0x04);
     s_last_sector_header_valid = false;
@@ -2675,7 +2674,7 @@ void CDROM::DoStatSecondResponse()
 
 void CDROM::DoChangeSessionComplete()
 {
-  Log_DebugPrint("Changing session complete");
+  DEBUG_LOG("Changing session complete");
   ClearDriveState();
   s_secondary_status.ClearActiveBits();
   s_secondary_status.motor_on = true;
@@ -2695,7 +2694,7 @@ void CDROM::DoChangeSessionComplete()
 
 void CDROM::DoSpinUpComplete()
 {
-  Log_DebugPrint("Spinup complete");
+  DEBUG_LOG("Spinup complete");
   s_drive_state = DriveState::Idle;
   s_drive_event->Deactivate();
   s_secondary_status.ClearActiveBits();
@@ -2704,14 +2703,14 @@ void CDROM::DoSpinUpComplete()
 
 void CDROM::DoSpeedChangeOrImplicitTOCReadComplete()
 {
-  Log_DebugPrint("Speed change/implicit TOC read complete");
+  DEBUG_LOG("Speed change/implicit TOC read complete");
   s_drive_state = DriveState::Idle;
   s_drive_event->Deactivate();
 }
 
 void CDROM::DoIDRead()
 {
-  Log_DebugPrint("ID read complete");
+  DEBUG_LOG("ID read complete");
   s_secondary_status.ClearActiveBits();
   s_secondary_status.motor_on = CanReadMedia();
 
@@ -2765,11 +2764,11 @@ void CDROM::StartMotor()
 {
   if (s_drive_state == DriveState::SpinningUp)
   {
-    Log_DevPrint("Starting motor - already spinning up");
+    DEV_LOG("Starting motor - already spinning up");
     return;
   }
 
-  Log_DevPrint("Starting motor");
+  DEV_LOG("Starting motor");
   s_drive_state = DriveState::SpinningUp;
   s_drive_event->Schedule(GetTicksForSpinUp());
 }
@@ -2805,12 +2804,12 @@ void CDROM::DoSectorRead()
   }
   else
   {
-    Log_DevFmt("Sector {} [{}] has invalid subchannel Q", s_current_lba, LBAToMSFString(s_current_lba));
+    DEV_LOG("Sector {} [{}] has invalid subchannel Q", s_current_lba, LBAToMSFString(s_current_lba));
   }
 
   if (subq.track_number_bcd == CDImage::LEAD_OUT_TRACK_NUMBER)
   {
-    Log_DevFmt("Read reached lead-out area of disc at LBA {}, stopping", s_reader.GetLastReadSector());
+    DEV_LOG("Read reached lead-out area of disc at LBA {}, stopping", s_reader.GetLastReadSector());
     StopReadingWithDataEnd();
     StopMotor();
     return;
@@ -2823,12 +2822,12 @@ void CDROM::DoSectorRead()
     {
       // track number was not specified, but we've found the track now
       s_play_track_number_bcd = subq.track_number_bcd;
-      Log_DebugFmt("Setting playing track number to {}", s_play_track_number_bcd);
+      DEBUG_LOG("Setting playing track number to {}", s_play_track_number_bcd);
     }
     else if (s_mode.auto_pause && subq.track_number_bcd != s_play_track_number_bcd)
     {
       // we don't want to update the position if the track changes, so we check it before reading the actual sector.
-      Log_DevFmt("Auto pause at the start of track {:02x} (LBA {})", s_last_subq.track_number_bcd, s_current_lba);
+      DEV_LOG("Auto pause at the start of track {:02x} (LBA {})", s_last_subq.track_number_bcd, s_current_lba);
       StopReadingWithDataEnd();
       return;
     }
@@ -2857,8 +2856,8 @@ void CDROM::DoSectorRead()
   }
   else
   {
-    Log_WarningFmt("Skipping sector {} as it is a {} sector and we're not {}", s_current_lba,
-                   is_data_sector ? "data" : "audio", is_data_sector ? "reading" : "playing");
+    WARNING_LOG("Skipping sector {} as it is a {} sector and we're not {}", s_current_lba,
+                is_data_sector ? "data" : "audio", is_data_sector ? "reading" : "playing");
   }
 
   s_requested_lba = next_sector;
@@ -2876,9 +2875,8 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessDataSectorHeader(const u8* raw_sector)
 ALWAYS_INLINE_RELEASE void CDROM::ProcessDataSector(const u8* raw_sector, const CDImage::SubChannelQ& subq)
 {
   const u32 sb_num = (s_current_write_sector_buffer + 1) % NUM_SECTOR_BUFFERS;
-  Log_DevFmt("Read sector {} [{}]: mode {} submode 0x{:02X} into buffer {}", s_current_lba,
-             LBAToMSFString(s_current_lba), s_last_sector_header.sector_mode,
-             ZeroExtend32(s_last_sector_subheader.submode.bits), sb_num);
+  DEV_LOG("Read sector {} [{}]: mode {} submode 0x{:02X} into buffer {}", s_current_lba, LBAToMSFString(s_current_lba),
+          s_last_sector_header.sector_mode, ZeroExtend32(s_last_sector_subheader.submode.bits), sb_num);
 
   if (s_mode.xa_enable && s_last_sector_header.sector_mode == 2)
   {
@@ -2895,12 +2893,12 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessDataSector(const u8* raw_sector, const
   SectorBuffer* sb = &s_sector_buffers[sb_num];
   if (sb->size > 0)
   {
-    Log_DevFmt("Sector buffer {} was not read, previous sector dropped",
-               (s_current_write_sector_buffer - 1) % NUM_SECTOR_BUFFERS);
+    DEV_LOG("Sector buffer {} was not read, previous sector dropped",
+            (s_current_write_sector_buffer - 1) % NUM_SECTOR_BUFFERS);
   }
 
   if (s_mode.ignore_bit)
-    Log_WarningFmt("SetMode.4 bit set on read of sector {}", s_current_lba);
+    WARNING_LOG("SetMode.4 bit set on read of sector {}", s_current_lba);
 
   if (s_mode.read_raw_sector)
   {
@@ -2912,7 +2910,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessDataSector(const u8* raw_sector, const
     // TODO: This should actually depend on the mode...
     if (s_last_sector_header.sector_mode != 2)
     {
-      Log_WarningFmt("Ignoring non-mode2 sector at {}", s_current_lba);
+      WARNING_LOG("Ignoring non-mode2 sector at {}", s_current_lba);
       return;
     }
 
@@ -2925,7 +2923,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessDataSector(const u8* raw_sector, const
   // Deliver to CPU
   if (HasPendingAsyncInterrupt())
   {
-    Log_WarningPrint("Data interrupt was not delivered");
+    WARNING_LOG("Data interrupt was not delivered");
     ClearAsyncInterrupt();
   }
 
@@ -2933,7 +2931,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessDataSector(const u8* raw_sector, const
   {
     const u32 sectors_missed = (s_current_write_sector_buffer - s_current_read_sector_buffer) % NUM_SECTOR_BUFFERS;
     if (sectors_missed > 1)
-      Log_WarningFmt("Interrupt not processed in time, missed {} sectors", sectors_missed - 1);
+      WARNING_LOG("Interrupt not processed in time, missed {} sectors", sectors_missed - 1);
   }
 
   s_async_response_fifo.Push(s_secondary_status.bits);
@@ -3006,7 +3004,7 @@ void CDROM::ResampleXAADPCM(const s16* frames_in, u32 num_frames_in)
   // the SPU will over-read in the next batch to catch up.
   if (s_audio_fifo.GetSize() > AUDIO_FIFO_LOW_WATERMARK)
   {
-    Log_DevFmt("Dropping {} XA frames because audio FIFO still has {} frames", num_frames_in, s_audio_fifo.GetSize());
+    DEV_LOG("Dropping {} XA frames because audio FIFO still has {} frames", num_frames_in, s_audio_fifo.GetSize());
     return;
   }
 
@@ -3076,9 +3074,8 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessXAADPCMSector(const u8* raw_sector, con
   if (s_mode.xa_filter && (s_last_sector_subheader.file_number != s_xa_filter_file_number ||
                            s_last_sector_subheader.channel_number != s_xa_filter_channel_number))
   {
-    Log_DebugFmt("Skipping sector due to filter mismatch (expected {}/{} got {}/{})", s_xa_filter_file_number,
-                 s_xa_filter_channel_number, s_last_sector_subheader.file_number,
-                 s_last_sector_subheader.channel_number);
+    DEBUG_LOG("Skipping sector due to filter mismatch (expected {}/{} got {}/{})", s_xa_filter_file_number,
+              s_xa_filter_channel_number, s_last_sector_subheader.file_number, s_last_sector_subheader.channel_number);
     return;
   }
 
@@ -3091,9 +3088,9 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessXAADPCMSector(const u8* raw_sector, con
     // TODO: Verify with a hardware test.
     if (s_last_sector_subheader.channel_number == 255 && (!s_mode.xa_filter || s_xa_filter_channel_number != 255))
     {
-      Log_WarningFmt("Skipping XA file with file number {} and channel number {} (submode 0x{:02X} coding 0x{:02X})",
-                     s_last_sector_subheader.file_number, s_last_sector_subheader.channel_number,
-                     s_last_sector_subheader.submode.bits, s_last_sector_subheader.codinginfo.bits);
+      WARNING_LOG("Skipping XA file with file number {} and channel number {} (submode 0x{:02X} coding 0x{:02X})",
+                  s_last_sector_subheader.file_number, s_last_sector_subheader.channel_number,
+                  s_last_sector_subheader.submode.bits, s_last_sector_subheader.codinginfo.bits);
       return;
     }
 
@@ -3104,9 +3101,8 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessXAADPCMSector(const u8* raw_sector, con
   else if (s_last_sector_subheader.file_number != s_xa_current_file_number ||
            s_last_sector_subheader.channel_number != s_xa_current_channel_number)
   {
-    Log_DebugFmt("Skipping sector due to current file mismatch (expected {}/{} got {}/{})", s_xa_current_file_number,
-                 s_xa_current_channel_number, s_last_sector_subheader.file_number,
-                 s_last_sector_subheader.channel_number);
+    DEBUG_LOG("Skipping sector due to current file mismatch (expected {}/{} got {}/{})", s_xa_current_file_number,
+              s_xa_current_channel_number, s_last_sector_subheader.file_number, s_last_sector_subheader.channel_number);
     return;
   }
 
@@ -3195,7 +3191,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessCDDASector(const u8* raw_sector, const
                                                     bool subq_valid)
 {
   // For CDDA sectors, the whole sector contains the audio data.
-  Log_DevFmt("Read sector {} as CDDA", s_current_lba);
+  DEV_LOG("Read sector {} as CDDA", s_current_lba);
 
   // The reporting doesn't happen if we're reading with the CDDA mode bit set.
   if (s_drive_state == DriveState::Playing && s_mode.report_audio && subq_valid)
@@ -3231,7 +3227,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessCDDASector(const u8* raw_sector, const
       s_async_response_fifo.Push(Truncate8(peak_value >> 8)); // peak high
       SetAsyncInterrupt(Interrupt::DataReady);
 
-      Log_DevFmt(
+      DEV_LOG(
         "CDDA report at track[{:02x}] index[{:02x}] rel[{:02x}:{:02x}:{:02x}] abs[{:02x}:{:02x}:{:02x}] peak[{}:{}]",
         subq.track_number_bcd, subq.index_number_bcd, subq.relative_minute_bcd, subq.relative_second_bcd,
         subq.relative_frame_bcd, subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd, channel,
@@ -3250,7 +3246,7 @@ ALWAYS_INLINE_RELEASE void CDROM::ProcessCDDASector(const u8* raw_sector, const
   const u32 remaining_space = s_audio_fifo.GetSpace();
   if (remaining_space < num_samples)
   {
-    Log_WarningFmt("Dropping {} frames from audio FIFO", num_samples - remaining_space);
+    WARNING_LOG("Dropping {} frames from audio FIFO", num_samples - remaining_space);
     s_audio_fifo.Remove(num_samples - remaining_space);
   }
 
@@ -3269,7 +3265,7 @@ void CDROM::LoadDataFIFO()
 {
   if (!s_data_fifo.IsEmpty())
   {
-    Log_DevPrint("Load data fifo when not empty");
+    DEV_LOG("Load data fifo when not empty");
     return;
   }
 
@@ -3277,7 +3273,7 @@ void CDROM::LoadDataFIFO()
   SectorBuffer& sb = s_sector_buffers[s_current_read_sector_buffer];
   if (sb.size == 0)
   {
-    Log_WarningPrint("Attempting to load empty sector buffer");
+    WARNING_LOG("Attempting to load empty sector buffer");
     s_data_fifo.PushRange(sb.data.data(), RAW_SECTOR_OUTPUT_SIZE);
   }
   else
@@ -3286,12 +3282,12 @@ void CDROM::LoadDataFIFO()
     sb.size = 0;
   }
 
-  Log_DebugFmt("Loaded %u bytes to data FIFO from buffer {}", s_data_fifo.GetSize(), s_current_read_sector_buffer);
+  DEBUG_LOG("Loaded %u bytes to data FIFO from buffer {}", s_data_fifo.GetSize(), s_current_read_sector_buffer);
 
   SectorBuffer& next_sb = s_sector_buffers[s_current_write_sector_buffer];
   if (next_sb.size > 0)
   {
-    Log_DevFmt("Sending additional INT1 for missed sector in buffer {}", s_current_write_sector_buffer);
+    DEV_LOG("Sending additional INT1 for missed sector in buffer {}", s_current_write_sector_buffer);
     s_async_response_fifo.Push(s_secondary_status.bits);
     SetAsyncInterrupt(Interrupt::DataReady);
   }
@@ -3316,14 +3312,14 @@ void CDROM::CreateFileMap()
   IsoReader iso;
   if (!iso.Open(media, 1))
   {
-    Log_ErrorFmt("Failed to open ISO filesystem.");
+    ERROR_LOG("Failed to open ISO filesystem.");
     return;
   }
 
-  Log_DevFmt("Creating file map for {}...", media->GetFileName());
+  DEV_LOG("Creating file map for {}...", media->GetFileName());
   s_file_map.emplace(iso.GetPVDLBA(), std::make_pair(iso.GetPVDLBA(), std::string("PVD")));
   CreateFileMap(iso, std::string_view());
-  Log_DevFmt("Found {} files", s_file_map.size());
+  DEV_LOG("Found {} files", s_file_map.size());
 }
 
 void CDROM::CreateFileMap(IsoReader& iso, std::string_view dir)
@@ -3332,7 +3328,7 @@ void CDROM::CreateFileMap(IsoReader& iso, std::string_view dir)
   {
     if (entry.IsDirectory())
     {
-      Log_DevFmt("{}-{} = {}", entry.location_le, entry.location_le + entry.GetSizeInSectors() - 1, path);
+      DEV_LOG("{}-{} = {}", entry.location_le, entry.location_le + entry.GetSizeInSectors() - 1, path);
       s_file_map.emplace(entry.location_le, std::make_pair(entry.location_le + entry.GetSizeInSectors() - 1,
                                                            fmt::format("<DIR> {}", path)));
 
@@ -3340,7 +3336,7 @@ void CDROM::CreateFileMap(IsoReader& iso, std::string_view dir)
       continue;
     }
 
-    Log_DevFmt("{}-{} = {}", entry.location_le, entry.location_le + entry.GetSizeInSectors() - 1, path);
+    DEV_LOG("{}-{} = {}", entry.location_le, entry.location_le + entry.GetSizeInSectors() - 1, path);
     s_file_map.emplace(entry.location_le,
                        std::make_pair(entry.location_le + entry.GetSizeInSectors() - 1, std::move(path)));
   }
diff --git a/src/core/cdrom_async_reader.cpp b/src/core/cdrom_async_reader.cpp
index cc40d9d09..f9d15247b 100644
--- a/src/core/cdrom_async_reader.cpp
+++ b/src/core/cdrom_async_reader.cpp
@@ -25,7 +25,7 @@ void CDROMAsyncReader::StartThread(u32 readahead_count)
 
   m_shutdown_flag.store(false);
   m_read_thread = std::thread(&CDROMAsyncReader::WorkerThreadEntryPoint, this);
-  Log_InfoFmt("Read thread started with readahead of {} sectors", readahead_count);
+  INFO_LOG("Read thread started with readahead of {} sectors", readahead_count);
 }
 
 void CDROMAsyncReader::StopThread()
@@ -82,7 +82,7 @@ bool CDROMAsyncReader::Precache(ProgressCallback* callback)
       const CDImage::LBA lba = m_media->GetPositionOnDisc();
       if (!memory_image->Seek(lba)) [[unlikely]]
       {
-        Log_ErrorFmt("Failed to seek to LBA {} in memory image", lba);
+        ERROR_LOG("Failed to seek to LBA {} in memory image", lba);
         return false;
       }
 
@@ -115,7 +115,7 @@ void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba)
     const u32 buffer_front = m_buffer_front.load();
     if (m_buffers[buffer_front].lba == lba)
     {
-      Log_DebugFmt("Skipping re-reading same sector {}", lba);
+      DEBUG_LOG("Skipping re-reading same sector {}", lba);
       return;
     }
 
@@ -124,7 +124,7 @@ void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba)
     if (m_buffer_count > 1 && m_buffers[next_buffer].lba == lba)
     {
       // great, don't need a seek, but still kick the thread to start reading ahead again
-      Log_DebugFmt("Readahead buffer hit for sector {}", lba);
+      DEBUG_LOG("Readahead buffer hit for sector {}", lba);
       m_buffer_front.store(next_buffer);
       m_buffer_count.fetch_sub(1);
       m_can_readahead.store(true);
@@ -134,7 +134,7 @@ void CDROMAsyncReader::QueueReadSector(CDImage::LBA lba)
   }
 
   // we need to toss away our readahead and start fresh
-  Log_DebugFmt("Readahead buffer miss, queueing seek to {}", lba);
+  DEBUG_LOG("Readahead buffer miss, queueing seek to {}", lba);
   std::unique_lock<std::mutex> lock(m_mutex);
   m_next_position_set.store(true);
   m_next_position = lba;
@@ -156,7 +156,7 @@ bool CDROMAsyncReader::ReadSectorUncached(CDImage::LBA lba, CDImage::SubChannelQ
   const bool result = InternalReadSectorUncached(lba, subq, data);
   if (!m_media->Seek(prev_lba)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to re-seek to cached position {}", prev_lba);
+    ERROR_LOG("Failed to re-seek to cached position {}", prev_lba);
     m_can_readahead.store(false);
   }
 
@@ -167,13 +167,13 @@ bool CDROMAsyncReader::InternalReadSectorUncached(CDImage::LBA lba, CDImage::Sub
 {
   if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba)) [[unlikely]]
   {
-    Log_WarningFmt("Seek to LBA {} failed", lba);
+    WARNING_LOG("Seek to LBA {} failed", lba);
     return false;
   }
 
   if (!m_media->ReadRawSector(data, subq)) [[unlikely]]
   {
-    Log_WarningFmt("Read of LBA {} failed", lba);
+    WARNING_LOG("Read of LBA {} failed", lba);
     return false;
   }
 
@@ -185,12 +185,12 @@ bool CDROMAsyncReader::WaitForReadToComplete()
   // Safe without locking with memory_order_seq_cst.
   if (!m_next_position_set.load() && m_buffer_count.load() > 0)
   {
-    Log_TraceFmt("Returning sector {}", m_buffers[m_buffer_front.load()].lba);
+    TRACE_LOG("Returning sector {}", m_buffers[m_buffer_front.load()].lba);
     return m_buffers[m_buffer_front.load()].result;
   }
 
   Common::Timer wait_timer;
-  Log_DebugPrint("Sector read pending, waiting");
+  DEBUG_LOG("Sector read pending, waiting");
 
   std::unique_lock<std::mutex> lock(m_mutex);
   m_notify_read_complete_cv.wait(
@@ -204,9 +204,9 @@ bool CDROMAsyncReader::WaitForReadToComplete()
   const u32 front = m_buffer_front.load();
   const double wait_time = wait_timer.GetTimeMilliseconds();
   if (wait_time > 1.0f) [[unlikely]]
-    Log_WarningFmt("Had to wait {:.2f} msec for LBA {}", wait_time, m_buffers[front].lba);
+    WARNING_LOG("Had to wait {:.2f} msec for LBA {}", wait_time, m_buffers[front].lba);
 
-  Log_TraceFmt("Returning sector {} after waiting", m_buffers[front].lba);
+  TRACE_LOG("Returning sector {} after waiting", m_buffers[front].lba);
   return m_buffers[front].result;
 }
 
@@ -238,18 +238,18 @@ bool CDROMAsyncReader::ReadSectorIntoBuffer(std::unique_lock<std::mutex>& lock)
   m_is_reading.store(true);
   lock.unlock();
 
-  Log_TraceFmt("Reading LBA {}...", buffer.lba);
+  TRACE_LOG("Reading LBA {}...", buffer.lba);
 
   buffer.result = m_media->ReadRawSector(buffer.data.data(), &buffer.subq);
   if (buffer.result) [[likely]]
   {
     const double read_time = timer.GetTimeMilliseconds();
     if (read_time > 1.0f) [[unlikely]]
-      Log_DevFmt("Read LBA {} took {:.2f} msec", buffer.lba, read_time);
+      DEV_LOG("Read LBA {} took {:.2f} msec", buffer.lba, read_time);
   }
   else
   {
-    Log_ErrorFmt("Read of LBA {} failed", buffer.lba);
+    ERROR_LOG("Read of LBA {} failed", buffer.lba);
   }
 
   lock.lock();
@@ -269,7 +269,7 @@ void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba)
 
   if (m_media->GetPositionOnDisc() != lba && !m_media->Seek(lba))
   {
-    Log_WarningFmt("Seek to LBA {} failed", lba);
+    WARNING_LOG("Seek to LBA {} failed", lba);
     m_seek_error.store(true);
     return;
   }
@@ -277,18 +277,18 @@ void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba)
   BufferSlot& buffer = m_buffers.front();
   buffer.lba = m_media->GetPositionOnDisc();
 
-  Log_TraceFmt("Reading LBA {}...", buffer.lba);
+  TRACE_LOG("Reading LBA {}...", buffer.lba);
 
   buffer.result = m_media->ReadRawSector(buffer.data.data(), &buffer.subq);
   if (buffer.result) [[likely]]
   {
     const double read_time = timer.GetTimeMilliseconds();
     if (read_time > 1.0f) [[unlikely]]
-      Log_DevFmt("Read LBA {} took {:.2f} msec", buffer.lba, read_time);
+      DEV_LOG("Read LBA {} took {:.2f} msec", buffer.lba, read_time);
   }
   else
   {
-    Log_ErrorFmt("Read of LBA {} failed", buffer.lba);
+    ERROR_LOG("Read of LBA {} failed", buffer.lba);
   }
 
   m_buffer_count.fetch_add(1);
@@ -296,7 +296,7 @@ void CDROMAsyncReader::ReadSectorNonThreaded(CDImage::LBA lba)
 
 void CDROMAsyncReader::CancelReadahead()
 {
-  Log_DevPrint("Cancelling readahead");
+  DEV_LOG("Cancelling readahead");
 
   std::unique_lock lock(m_mutex);
 
@@ -332,7 +332,7 @@ void CDROMAsyncReader::WorkerThreadEntryPoint()
         lock.unlock();
 
         // seek without lock held in case it takes time
-        Log_DebugFmt("Seeking to LBA {}...", seek_location);
+        DEBUG_LOG("Seeking to LBA {}...", seek_location);
         const bool seek_result = (m_media->GetPositionOnDisc() == seek_location || m_media->Seek(seek_location));
 
         lock.lock();
@@ -346,7 +346,7 @@ void CDROMAsyncReader::WorkerThreadEntryPoint()
         if (!seek_result) [[unlikely]]
         {
           // add the error result, and don't try to read ahead
-          Log_WarningFmt("Seek to LBA {} failed", seek_location);
+          WARNING_LOG("Seek to LBA {} failed", seek_location);
           m_seek_error.store(true);
           m_notify_read_complete_cv.notify_all();
           break;
@@ -360,7 +360,7 @@ void CDROMAsyncReader::WorkerThreadEntryPoint()
         break;
 
       // readahead time! read as many sectors as we have space for
-      Log_DebugFmt("Reading ahead {} sectors...", static_cast<u32>(m_buffers.size()) - m_buffer_count.load());
+      DEBUG_LOG("Reading ahead {} sectors...", static_cast<u32>(m_buffers.size()) - m_buffer_count.load());
       while (m_buffer_count.load() < static_cast<u32>(m_buffers.size()))
       {
         if (m_next_position_set.load())
diff --git a/src/core/cheats.cpp b/src/core/cheats.cpp
index dc8354e81..ec68caa32 100644
--- a/src/core/cheats.cpp
+++ b/src/core/cheats.cpp
@@ -311,7 +311,7 @@ bool CheatList::LoadFromPCSXRString(const std::string& str)
     m_codes.push_back(std::move(current_code));
   }
 
-  Log_InfoFmt("Loaded {} cheats (PCSXR format)", m_codes.size());
+  INFO_LOG("Loaded {} cheats (PCSXR format)", m_codes.size());
   return !m_codes.empty();
 }
 
@@ -402,7 +402,7 @@ bool CheatList::LoadFromLibretroString(const std::string& str)
     const std::string* enable = FindKey(kvp, TinyString::from_format("cheat{}_enable", i));
     if (!desc || !code || !enable)
     {
-      Log_WarningFmt("Missing desc/code/enable for cheat {}", i);
+      WARNING_LOG("Missing desc/code/enable for cheat {}", i);
       continue;
     }
 
@@ -414,7 +414,7 @@ bool CheatList::LoadFromLibretroString(const std::string& str)
       m_codes.push_back(std::move(cc));
   }
 
-  Log_InfoFmt("Loaded {} cheats (libretro format)", m_codes.size());
+  INFO_LOG("Loaded {} cheats (libretro format)", m_codes.size());
   return !m_codes.empty();
 }
 
@@ -501,7 +501,7 @@ bool CheatList::LoadFromEPSXeString(const std::string& str)
   if (current_code.Valid())
     m_codes.push_back(std::move(current_code));
 
-  Log_InfoFmt("Loaded {} cheats (EPSXe format)", m_codes.size());
+  INFO_LOG("Loaded {} cheats (EPSXe format)", m_codes.size());
   return !m_codes.empty();
 }
 
@@ -523,7 +523,7 @@ bool CheatList::ParseLibretroCheat(CheatCode* cc, const char* line)
     {
       if (!IsLibretroSeparator(*end_ptr))
       {
-        Log_WarningFmt("Malformed code '{}'", line);
+        WARNING_LOG("Malformed code '{}'", line);
         break;
       }
 
@@ -536,7 +536,7 @@ bool CheatList::ParseLibretroCheat(CheatCode* cc, const char* line)
       {
         if (!IsLibretroSeparator(*end_ptr))
         {
-          Log_WarningFmt("Malformed code '{}'", line);
+          WARNING_LOG("Malformed code '{}'", line);
           break;
         }
 
@@ -791,11 +791,11 @@ bool CheatList::LoadFromPackage(const std::string& serial)
     if (current_code.Valid())
       m_codes.push_back(std::move(current_code));
 
-    Log_InfoFmt("Loaded {} codes from package for {}", m_codes.size(), serial);
+    INFO_LOG("Loaded {} codes from package for {}", m_codes.size(), serial);
     return !m_codes.empty();
   }
 
-  Log_WarningFmt("No codes found in package for {}", serial);
+  WARNING_LOG("No codes found in package for {}", serial);
   return false;
 }
 
@@ -1266,7 +1266,7 @@ void CheatCode::Apply() const
 
         if ((index + 4) >= instructions.size())
         {
-          Log_ErrorPrint("Incomplete find/replace instruction");
+          ERROR_LOG("Incomplete find/replace instruction");
           return;
         }
         const Instruction& inst2 = instructions[index + 1];
@@ -1754,7 +1754,7 @@ void CheatCode::Apply() const
                       break;
                     }
                     default:
-                      Log_ErrorPrint("Incorrect conditional instruction (see chtdb.txt for supported instructions)");
+                      ERROR_LOG("Incorrect conditional instruction (see chtdb.txt for supported instructions)");
                       return;
                   }
                 }
@@ -1866,14 +1866,14 @@ void CheatCode::Apply() const
                       break;
                     }
                     default:
-                      Log_ErrorPrint("Incorrect conditional instruction (see chtdb.txt for supported instructions)");
+                      ERROR_LOG("Incorrect conditional instruction (see chtdb.txt for supported instructions)");
                       return;
                   }
                 }
               }
               else
               {
-                Log_ErrorPrint("Incomplete multi conditional instruction");
+                ERROR_LOG("Incomplete multi conditional instruction");
                 return;
               }
               if (conditions_check == true)
@@ -2518,7 +2518,7 @@ void CheatCode::Apply() const
       {
         if ((index + 1) >= instructions.size())
         {
-          Log_ErrorPrint("Incomplete slide instruction");
+          ERROR_LOG("Incomplete slide instruction");
           return;
         }
 
@@ -2550,7 +2550,7 @@ void CheatCode::Apply() const
         }
         else
         {
-          Log_ErrorFmt("Invalid command in second slide parameter 0x{:02X}", static_cast<unsigned>(write_type));
+          ERROR_LOG("Invalid command in second slide parameter 0x{:02X}", static_cast<unsigned>(write_type));
         }
 
         index += 2;
@@ -2561,7 +2561,7 @@ void CheatCode::Apply() const
       {
         if ((index + 1) >= instructions.size())
         {
-          Log_ErrorPrint("Incomplete slide instruction");
+          ERROR_LOG("Incomplete slide instruction");
           return;
         }
 
@@ -2622,7 +2622,7 @@ void CheatCode::Apply() const
         }
         else
         {
-          Log_ErrorFmt("Invalid command in second slide parameter 0x{:02X}", static_cast<unsigned>(write_type));
+          ERROR_LOG("Invalid command in second slide parameter 0x{:02X}", static_cast<unsigned>(write_type));
         }
 
         index += 2;
@@ -2633,7 +2633,7 @@ void CheatCode::Apply() const
       {
         if ((index + 1) >= instructions.size())
         {
-          Log_ErrorPrint("Incomplete memory copy instruction");
+          ERROR_LOG("Incomplete memory copy instruction");
           return;
         }
 
@@ -2656,8 +2656,8 @@ void CheatCode::Apply() const
 
       default:
       {
-        Log_ErrorFmt("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
-                     inst.first, inst.second);
+        ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
+                  inst.first, inst.second);
         index++;
       }
       break;
@@ -2757,8 +2757,8 @@ void CheatCode::ApplyOnDisable() const
 
         [[unlikely]] default:
         {
-          Log_ErrorFmt("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
-                       inst.first, inst.second);
+          ERROR_LOG("Unhandled instruction code 0x{:02X} ({:08X} {:08X})", static_cast<u8>(inst.code.GetValue()),
+                    inst.first, inst.second);
           index++;
         }
         break;
diff --git a/src/core/cpu_code_cache.cpp b/src/core/cpu_code_cache.cpp
index e2d9dd122..9658abd35 100644
--- a/src/core/cpu_code_cache.cpp
+++ b/src/core/cpu_code_cache.cpp
@@ -483,8 +483,7 @@ CPU::CodeCache::Block* CPU::CodeCache::CreateBlock(u32 pc, const BlockInstructio
   }
   else if (block->compile_count >= RECOMPILE_COUNT_FOR_INTERPRETER_FALLBACK)
   {
-    Log_DevFmt("{} recompiles in {} frames to block 0x{:08X}, not caching.", block->compile_count, frame_delta,
-               block->pc);
+    DEV_LOG("{} recompiles in {} frames to block 0x{:08X}, not caching.", block->compile_count, frame_delta, block->pc);
     block->size = 0;
   }
 
@@ -531,7 +530,7 @@ bool CPU::CodeCache::RevalidateBlock(Block* block)
   if (!IsBlockCodeCurrent(block))
   {
     // changed, needs recompiling
-    Log_DebugFmt("Block at PC {:08X} has changed and needs recompiling", block->pc);
+    DEBUG_LOG("Block at PC {:08X} has changed and needs recompiling", block->pc);
     return false;
   }
 
@@ -614,8 +613,8 @@ void CPU::CodeCache::InvalidateBlocksWithPageIndex(u32 index)
   }
   else if (ppi.invalidate_count > INVALIDATE_COUNT_FOR_MANUAL_PROTECTION)
   {
-    Log_DevFmt("{} invalidations in {} frames to page {} [0x{:08X} -> 0x{:08X}], switching to manual protection",
-               ppi.invalidate_count, frame_delta, index, (index * HOST_PAGE_SIZE), ((index + 1) * HOST_PAGE_SIZE));
+    DEV_LOG("{} invalidations in {} frames to page {} [0x{:08X} -> 0x{:08X}], switching to manual protection",
+            ppi.invalidate_count, frame_delta, index, (index * HOST_PAGE_SIZE), ((index + 1) * HOST_PAGE_SIZE));
     ppi.mode = PageProtectionMode::ManualCheck;
     new_block_state = BlockState::NeedsRecompile;
   }
@@ -727,8 +726,7 @@ PageFaultHandler::HandlerResult PageFaultHandler::HandlePageFault(void* exceptio
     DebugAssert(is_write);
     const u32 guest_address = static_cast<u32>(static_cast<const u8*>(fault_address) - Bus::g_ram);
     const u32 page_index = Bus::GetRAMCodePageIndex(guest_address);
-    Log_DevFmt("Page fault on protected RAM @ 0x{:08X} (page #{}), invalidating code cache.", guest_address,
-               page_index);
+    DEV_LOG("Page fault on protected RAM @ 0x{:08X} (page #{}), invalidating code cache.", guest_address, page_index);
     CPU::CodeCache::InvalidateBlocksWithPageIndex(page_index);
     return PageFaultHandler::HandlerResult::ContinueExecution;
   }
@@ -899,7 +897,7 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
         // if we're just crossing the page and not in a branch delay slot, jump directly to the next block
         if (!is_branch_delay_slot)
         {
-          Log_DevFmt("Breaking block 0x{:08X} at 0x{:08X} due to page crossing", start_pc, pc);
+          DEV_LOG("Breaking block 0x{:08X} at 0x{:08X} due to page crossing", start_pc, pc);
           metadata->flags |= BlockFlags::SpansPages;
           break;
         }
@@ -907,8 +905,8 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
         {
           // otherwise, we need to use manual protection in case the delay slot changes.
           // may as well keep going then, since we're doing manual check anyways.
-          Log_DevFmt("Block 0x{:08X} has branch delay slot crossing page at 0x{:08X}, forcing manual protection",
-                     start_pc, pc);
+          DEV_LOG("Block 0x{:08X} has branch delay slot crossing page at 0x{:08X}, forcing manual protection", start_pc,
+                  pc);
           metadata->flags |= BlockFlags::BranchDelaySpansPages;
         }
       }
@@ -954,18 +952,18 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
       const BlockInstructionInfoPair& prev = instructions->back();
       if (!prev.second.is_unconditional_branch_instruction || !prev.second.is_direct_branch_instruction)
       {
-        Log_WarningFmt("Conditional or indirect branch delay slot at {:08X}, skipping block", info.pc);
+        WARNING_LOG("Conditional or indirect branch delay slot at {:08X}, skipping block", info.pc);
         return false;
       }
       if (!IsDirectBranchInstruction(instruction))
       {
-        Log_WarningFmt("Indirect branch in delay slot at {:08X}, skipping block", info.pc);
+        WARNING_LOG("Indirect branch in delay slot at {:08X}, skipping block", info.pc);
         return false;
       }
 
       // we _could_ fetch the delay slot from the first branch's target, but it's probably in a different
       // page, and that's an invalidation nightmare. so just fallback to the int, this is very rare anyway.
-      Log_WarningFmt("Direct branch in delay slot at {:08X}, skipping block", info.pc);
+      WARNING_LOG("Direct branch in delay slot at {:08X}, skipping block", info.pc);
       return false;
     }
 
@@ -990,7 +988,7 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
 
   if (instructions->empty())
   {
-    Log_WarningFmt("Empty block compiled at 0x{:08X}", start_pc);
+    WARNING_LOG("Empty block compiled at 0x{:08X}", start_pc);
     return false;
   }
 
@@ -998,12 +996,12 @@ bool CPU::CodeCache::ReadBlockInstructions(u32 start_pc, BlockInstructionList* i
 
 #ifdef _DEBUG
   SmallString disasm;
-  Log_DebugFmt("Block at 0x{:08X}", start_pc);
+  DEBUG_LOG("Block at 0x{:08X}", start_pc);
   for (const auto& cbi : *instructions)
   {
     CPU::DisassembleInstruction(&disasm, cbi.second.pc, cbi.first.bits);
-    Log_DebugFmt("[{} {} 0x{:08X}] {:08X} {}", cbi.second.is_branch_delay_slot ? "BD" : "  ",
-                 cbi.second.is_load_delay_slot ? "LD" : "  ", cbi.second.pc, cbi.first.bits, disasm);
+    DEBUG_LOG("[{} {} 0x{:08X}] {:08X} {}", cbi.second.is_branch_delay_slot ? "BD" : "  ",
+              cbi.second.is_load_delay_slot ? "LD" : "  ", cbi.second.pc, cbi.first.bits, disasm);
   }
 #endif
 
@@ -1164,7 +1162,7 @@ void CPU::CodeCache::FillBlockRegInfo(Block* block)
             break;
 
           default:
-            Log_ErrorFmt("Unknown funct {}", static_cast<u32>(iinst->r.funct.GetValue()));
+            ERROR_LOG("Unknown funct {}", static_cast<u32>(iinst->r.funct.GetValue()));
             break;
         }
       }
@@ -1263,7 +1261,7 @@ void CPU::CodeCache::FillBlockRegInfo(Block* block)
           break;
 
         default:
-          Log_ErrorFmt("Unknown op {}", static_cast<u32>(iinst->r.funct.GetValue()));
+          ERROR_LOG("Unknown op {}", static_cast<u32>(iinst->r.funct.GetValue()));
           break;
       }
     } // end switch
@@ -1308,7 +1306,7 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc)
   BlockMetadata metadata = {};
   if (!ReadBlockInstructions(start_pc, &s_block_instructions, &metadata))
   {
-    Log_ErrorFmt("Failed to read block at 0x{:08X}, falling back to uncached interpreter", start_pc);
+    ERROR_LOG("Failed to read block at 0x{:08X}, falling back to uncached interpreter", start_pc);
     SetCodeLUT(start_pc, g_interpret_block);
     BacklinkBlocks(start_pc, g_interpret_block);
     MemMap::EndCodeWrite();
@@ -1321,14 +1319,14 @@ void CPU::CodeCache::CompileOrRevalidateBlock(u32 start_pc)
   if (s_code_buffer.GetFreeCodeSpace() < (block_size * Recompiler::MAX_NEAR_HOST_BYTES_PER_INSTRUCTION) ||
       s_code_buffer.GetFreeFarCodeSpace() < (block_size * Recompiler::MAX_FAR_HOST_BYTES_PER_INSTRUCTION))
   {
-    Log_ErrorFmt("Out of code space while compiling {:08X}. Resetting code cache.", start_pc);
+    ERROR_LOG("Out of code space while compiling {:08X}. Resetting code cache.", start_pc);
     CodeCache::Reset();
   }
 
   if ((block = CreateBlock(start_pc, s_block_instructions, metadata)) == nullptr || block->size == 0 ||
       !CompileBlock(block))
   {
-    Log_ErrorFmt("Failed to compile block at 0x{:08X}, falling back to uncached interpreter", start_pc);
+    ERROR_LOG("Failed to compile block at 0x{:08X}, falling back to uncached interpreter", start_pc);
     SetCodeLUT(start_pc, g_interpret_block);
     BacklinkBlocks(start_pc, g_interpret_block);
     MemMap::EndCodeWrite();
@@ -1344,7 +1342,7 @@ void CPU::CodeCache::DiscardAndRecompileBlock(u32 start_pc)
 {
   MemMap::BeginCodeWrite();
 
-  Log_DevFmt("Discard block {:08X} with manual protection", start_pc);
+  DEV_LOG("Discard block {:08X} with manual protection", start_pc);
   Block* block = LookupBlock(start_pc);
   DebugAssert(block && block->state == BlockState::Valid);
   InvalidateBlock(block, BlockState::NeedsRecompile);
@@ -1380,8 +1378,8 @@ const void* CPU::CodeCache::CreateBlockLink(Block* block, void* code, u32 newpc)
     block->exit_links[block->num_exit_links++] = iter;
   }
 
-  Log_DebugFmt("Linking {} with dst pc {:08X} to {}{}", code, newpc, dst,
-               (dst == g_compile_or_revalidate_block) ? "[compiler]" : "");
+  DEBUG_LOG("Linking {} with dst pc {:08X} to {}{}", code, newpc, dst,
+            (dst == g_compile_or_revalidate_block) ? "[compiler]" : "");
   return dst;
 }
 
@@ -1393,8 +1391,8 @@ void CPU::CodeCache::BacklinkBlocks(u32 pc, const void* dst)
   const auto link_range = s_block_links.equal_range(pc);
   for (auto it = link_range.first; it != link_range.second; ++it)
   {
-    Log_DebugFmt("Backlinking {} with dst pc {:08X} to {}{}", it->second, pc, dst,
-                 (dst == g_compile_or_revalidate_block) ? "[compiler]" : "");
+    DEBUG_LOG("Backlinking {} with dst pc {:08X} to {}{}", it->second, pc, dst,
+              (dst == g_compile_or_revalidate_block) ? "[compiler]" : "");
     EmitJump(it->second, dst, true);
   }
 }
@@ -1480,7 +1478,7 @@ bool CPU::CodeCache::CompileBlock(Block* block)
 
   if (!host_code)
   {
-    Log_ErrorFmt("Failed to compile host code for block at 0x{:08X}", block->pc);
+    ERROR_LOG("Failed to compile host code for block at 0x{:08X}", block->pc);
     block->state = BlockState::FallbackToInterpreter;
     return false;
   }
@@ -1574,7 +1572,7 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
     // TODO: path for manual protection to return back to read-only pages
     if (is_write && !g_state.cop0_regs.sr.Isc && AddressInRAM(guest_address))
     {
-      Log_DevFmt("Ignoring fault due to RAM write @ 0x{:08X}", guest_address);
+      DEV_LOG("Ignoring fault due to RAM write @ 0x{:08X}", guest_address);
       InvalidateBlocksWithPageIndex(Bus::GetRAMCodePageIndex(guest_address));
       return PageFaultHandler::HandlerResult::ContinueExecution;
     }
@@ -1586,21 +1584,21 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
     guest_address = std::numeric_limits<PhysicalMemoryAddress>::max();
   }
 
-  Log_DevFmt("Page fault handler invoked at PC={} Address={} {}, fastmem offset {:08X}", exception_pc, fault_address,
-             is_write ? "(write)" : "(read)", guest_address);
+  DEV_LOG("Page fault handler invoked at PC={} Address={} {}, fastmem offset {:08X}", exception_pc, fault_address,
+          is_write ? "(write)" : "(read)", guest_address);
 
   auto iter = s_fastmem_backpatch_info.find(exception_pc);
   if (iter == s_fastmem_backpatch_info.end())
   {
-    Log_ErrorFmt("No backpatch info found for {}", exception_pc);
+    ERROR_LOG("No backpatch info found for {}", exception_pc);
     return PageFaultHandler::HandlerResult::ExecuteNextHandler;
   }
 
   LoadstoreBackpatchInfo& info = iter->second;
-  Log_DevFmt("Backpatching {} at {}[{}] (pc {:08X} addr {:08X}): Bitmask {:08X} Addr {} Data {} Size {} Signed {:02X}",
-             info.is_load ? "load" : "store", exception_pc, info.code_size, info.guest_pc, guest_address,
-             info.gpr_bitmask, static_cast<unsigned>(info.address_register), static_cast<unsigned>(info.data_register),
-             info.AccessSizeInBytes(), static_cast<unsigned>(info.is_signed));
+  DEV_LOG("Backpatching {} at {}[{}] (pc {:08X} addr {:08X}): Bitmask {:08X} Addr {} Data {} Size {} Signed {:02X}",
+          info.is_load ? "load" : "store", exception_pc, info.code_size, info.guest_pc, guest_address, info.gpr_bitmask,
+          static_cast<unsigned>(info.address_register), static_cast<unsigned>(info.data_register),
+          info.AccessSizeInBytes(), static_cast<unsigned>(info.is_signed));
 
   MemMap::BeginCodeWrite();
 
@@ -1613,7 +1611,7 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
     if (block)
     {
       // This is a bit annoying, we have to remove it from the page list if it's a RAM block.
-      Log_DevFmt("Queuing block {:08X} for recompilation due to backpatch", block->pc);
+      DEV_LOG("Queuing block {:08X} for recompilation due to backpatch", block->pc);
       RemoveBlockFromPageList(block);
       InvalidateBlock(block, BlockState::NeedsRecompile);
 
diff --git a/src/core/cpu_core.cpp b/src/core/cpu_core.cpp
index aab3de65c..2c548250e 100644
--- a/src/core/cpu_core.cpp
+++ b/src/core/cpu_core.cpp
@@ -322,10 +322,10 @@ ALWAYS_INLINE_RELEASE void CPU::RaiseException(u32 CAUSE_bits, u32 EPC, u32 vect
   if (g_state.cop0_regs.cause.Excode != Exception::INT && g_state.cop0_regs.cause.Excode != Exception::Syscall &&
       g_state.cop0_regs.cause.Excode != Exception::BP)
   {
-    Log_DevFmt("Exception {} at 0x{:08X} (epc=0x{:08X}, BD={}, CE={})",
-               static_cast<u8>(g_state.cop0_regs.cause.Excode.GetValue()), g_state.current_instruction_pc,
-               g_state.cop0_regs.EPC, g_state.cop0_regs.cause.BD ? "true" : "false",
-               g_state.cop0_regs.cause.CE.GetValue());
+    DEV_LOG("Exception {} at 0x{:08X} (epc=0x{:08X}, BD={}, CE={})",
+            static_cast<u8>(g_state.cop0_regs.cause.Excode.GetValue()), g_state.current_instruction_pc,
+            g_state.cop0_regs.EPC, g_state.cop0_regs.cause.BD ? "true" : "false",
+            g_state.cop0_regs.cause.CE.GetValue());
     DisassembleAndPrint(g_state.current_instruction_pc, 4u, 0u);
     if (s_trace_to_log)
     {
@@ -523,14 +523,14 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value)
     case Cop0Reg::BPC:
     {
       g_state.cop0_regs.BPC = value;
-      Log_DevFmt("COP0 BPC <- {:08X}", value);
+      DEV_LOG("COP0 BPC <- {:08X}", value);
     }
     break;
 
     case Cop0Reg::BPCM:
     {
       g_state.cop0_regs.BPCM = value;
-      Log_DevFmt("COP0 BPCM <- {:08X}", value);
+      DEV_LOG("COP0 BPCM <- {:08X}", value);
       if (UpdateDebugDispatcherFlag())
         ExitExecution();
     }
@@ -539,20 +539,20 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value)
     case Cop0Reg::BDA:
     {
       g_state.cop0_regs.BDA = value;
-      Log_DevFmt("COP0 BDA <- {:08X}", value);
+      DEV_LOG("COP0 BDA <- {:08X}", value);
     }
     break;
 
     case Cop0Reg::BDAM:
     {
       g_state.cop0_regs.BDAM = value;
-      Log_DevFmt("COP0 BDAM <- {:08X}", value);
+      DEV_LOG("COP0 BDAM <- {:08X}", value);
     }
     break;
 
     case Cop0Reg::JUMPDEST:
     {
-      Log_WarningPrint("Ignoring write to Cop0 JUMPDEST");
+      WARNING_LOG("Ignoring write to Cop0 JUMPDEST");
     }
     break;
 
@@ -560,7 +560,7 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value)
     {
       g_state.cop0_regs.dcic.bits =
         (g_state.cop0_regs.dcic.bits & ~Cop0Registers::DCIC::WRITE_MASK) | (value & Cop0Registers::DCIC::WRITE_MASK);
-      Log_DevFmt("COP0 DCIC <- {:08X} (now {:08X})", value, g_state.cop0_regs.dcic.bits);
+      DEV_LOG("COP0 DCIC <- {:08X} (now {:08X})", value, g_state.cop0_regs.dcic.bits);
       if (UpdateDebugDispatcherFlag())
         ExitExecution();
     }
@@ -570,7 +570,7 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value)
     {
       g_state.cop0_regs.sr.bits =
         (g_state.cop0_regs.sr.bits & ~Cop0Registers::SR::WRITE_MASK) | (value & Cop0Registers::SR::WRITE_MASK);
-      Log_DebugFmt("COP0 SR <- {:08X} (now {:08X})", value, g_state.cop0_regs.sr.bits);
+      DEBUG_LOG("COP0 SR <- {:08X} (now {:08X})", value, g_state.cop0_regs.sr.bits);
       UpdateMemoryPointers();
       CheckForPendingInterrupt();
     }
@@ -580,12 +580,12 @@ ALWAYS_INLINE_RELEASE void CPU::WriteCop0Reg(Cop0Reg reg, u32 value)
     {
       g_state.cop0_regs.cause.bits =
         (g_state.cop0_regs.cause.bits & ~Cop0Registers::CAUSE::WRITE_MASK) | (value & Cop0Registers::CAUSE::WRITE_MASK);
-      Log_DebugFmt("COP0 CAUSE <- {:08X} (now {:08X})", value, g_state.cop0_regs.cause.bits);
+      DEBUG_LOG("COP0 CAUSE <- {:08X} (now {:08X})", value, g_state.cop0_regs.cause.bits);
       CheckForPendingInterrupt();
     }
     break;
 
-      [[unlikely]] default : Log_DevFmt("Unknown COP0 reg write {} ({:08X})", static_cast<u8>(reg), value);
+      [[unlikely]] default : DEV_LOG("Unknown COP0 reg write {} ({:08X})", static_cast<u8>(reg), value);
       break;
   }
 }
@@ -631,7 +631,7 @@ ALWAYS_INLINE_RELEASE void CPU::Cop0ExecutionBreakpointCheck()
   if (bpcm == 0 || ((pc ^ bpc) & bpcm) != 0u)
     return;
 
-  Log_DevFmt("Cop0 execution breakpoint at {:08X}", pc);
+  DEV_LOG("Cop0 execution breakpoint at {:08X}", pc);
   g_state.cop0_regs.dcic.status_any_break = true;
   g_state.cop0_regs.dcic.status_bpc_code_break = true;
   DispatchCop0Breakpoint();
@@ -657,7 +657,7 @@ ALWAYS_INLINE_RELEASE void CPU::Cop0DataBreakpointCheck(VirtualMemoryAddress add
   if (bdam == 0 || ((address ^ bda) & bdam) != 0u)
     return;
 
-  Log_DevFmt("Cop0 data breakpoint for {:08X} at {:08X}", address, g_state.current_instruction_pc);
+  DEV_LOG("Cop0 data breakpoint for {:08X} at {:08X}", address, g_state.current_instruction_pc);
 
   g_state.cop0_regs.dcic.status_any_break = true;
   g_state.cop0_regs.dcic.status_bda_data_break = true;
@@ -710,7 +710,7 @@ void CPU::PrintInstruction(u32 bits, u32 pc, bool regs, const char* prefix)
     }
   }
 
-  Log_DevFmt("{}{:08x}: {:08x} %s", prefix, pc, bits, instr);
+  DEV_LOG("{}{:08x}: {:08x} %s", prefix, pc, bits, instr);
 }
 
 void CPU::LogInstruction(u32 bits, u32 pc, bool regs)
@@ -1744,7 +1744,7 @@ restart_instruction:
     {
       if (InUserMode() && !g_state.cop0_regs.sr.CU0)
       {
-        Log_WarningPrint("Coprocessor 0 not present in user mode");
+        WARNING_LOG("Coprocessor 0 not present in user mode");
         RaiseException(Exception::CpU);
         return;
       }
@@ -1774,8 +1774,8 @@ restart_instruction:
           break;
 
           default:
-            [[unlikely]] Log_ErrorFmt("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc,
-                                      inst.bits);
+            [[unlikely]] ERROR_LOG("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc,
+                                   inst.bits);
             break;
         }
       }
@@ -1800,8 +1800,8 @@ restart_instruction:
             break;
 
           default:
-            [[unlikely]] Log_ErrorFmt("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc,
-                                      inst.bits);
+            [[unlikely]] ERROR_LOG("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc,
+                                   inst.bits);
             break;
         }
       }
@@ -1812,7 +1812,7 @@ restart_instruction:
     {
       if (!g_state.cop0_regs.sr.CE2)
       {
-        Log_WarningPrint("Coprocessor 2 not enabled");
+        WARNING_LOG("Coprocessor 2 not enabled");
         RaiseException(Exception::CpU);
         return;
       }
@@ -1865,8 +1865,8 @@ restart_instruction:
           break;
 
           default:
-            [[unlikely]] Log_ErrorFmt("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc,
-                                      inst.bits);
+            [[unlikely]] ERROR_LOG("Unhandled instruction at {:08X}: {:08X}", g_state.current_instruction_pc,
+                                   inst.bits);
             break;
         }
       }
@@ -1881,7 +1881,7 @@ restart_instruction:
     {
       if (!g_state.cop0_regs.sr.CE2)
       {
-        Log_WarningPrint("Coprocessor 2 not enabled");
+        WARNING_LOG("Coprocessor 2 not enabled");
         RaiseException(Exception::CpU);
         return;
       }
@@ -1903,7 +1903,7 @@ restart_instruction:
     {
       if (!g_state.cop0_regs.sr.CE2)
       {
-        Log_WarningPrint("Coprocessor 2 not enabled");
+        WARNING_LOG("Coprocessor 2 not enabled");
         RaiseException(Exception::CpU);
         return;
       }
@@ -1939,8 +1939,8 @@ restart_instruction:
       if (SafeReadInstruction(g_state.current_instruction_pc, &ram_value) &&
           ram_value != g_state.current_instruction.bits) [[unlikely]]
       {
-        Log_ErrorFmt("Stale icache at 0x{:08X} - ICache: {:08X} RAM: {:08X}", g_state.current_instruction_pc,
-                     g_state.current_instruction.bits, ram_value);
+        ERROR_LOG("Stale icache at 0x{:08X} - ICache: {:08X} RAM: {:08X}", g_state.current_instruction_pc,
+                  g_state.current_instruction.bits, ram_value);
         g_state.current_instruction.bits = ram_value;
         goto restart_instruction;
       }
@@ -1986,7 +1986,7 @@ bool CPU::UpdateDebugDispatcherFlag()
   if (use_debug_dispatcher == g_state.use_debug_dispatcher)
     return false;
 
-  Log_DevFmt("{} debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using");
+  DEV_LOG("{} debug dispatcher", use_debug_dispatcher ? "Now using" : "No longer using");
   g_state.use_debug_dispatcher = use_debug_dispatcher;
   return true;
 }
@@ -2061,8 +2061,8 @@ bool CPU::AddBreakpoint(BreakpointType type, VirtualMemoryAddress address, bool
   if (HasBreakpointAtAddress(type, address))
     return false;
 
-  Log_InfoFmt("Adding {} breakpoint at {:08X}, auto clear = %u", GetBreakpointTypeName(type), address,
-              static_cast<unsigned>(auto_clear));
+  INFO_LOG("Adding {} breakpoint at {:08X}, auto clear = %u", GetBreakpointTypeName(type), address,
+           static_cast<unsigned>(auto_clear));
 
   Breakpoint bp{address, nullptr, auto_clear ? 0 : s_breakpoint_counter++, 0, type, auto_clear, enabled};
   GetBreakpointList(type).push_back(std::move(bp));
@@ -2082,7 +2082,7 @@ bool CPU::AddBreakpointWithCallback(BreakpointType type, VirtualMemoryAddress ad
   if (HasBreakpointAtAddress(type, address))
     return false;
 
-  Log_InfoFmt("Adding {} breakpoint with callback at {:08X}", GetBreakpointTypeName(type), address);
+  INFO_LOG("Adding {} breakpoint with callback at {:08X}", GetBreakpointTypeName(type), address);
 
   Breakpoint bp{address, callback, 0, 0, type, false, true};
   GetBreakpointList(type).push_back(std::move(bp));
@@ -2382,7 +2382,7 @@ void CPU::Execute()
       if (!SafeReadInstruction(g_state.pc, &g_state.next_instruction.bits)) [[unlikely]]
       {
         g_state.next_instruction.bits = 0;
-        Log_ErrorFmt("Failed to read current instruction from 0x{:08X}", g_state.pc);
+        ERROR_LOG("Failed to read current instruction from 0x{:08X}", g_state.pc);
       }
 
       g_state.npc = g_state.pc + sizeof(Instruction);
diff --git a/src/core/cpu_newrec_compiler.cpp b/src/core/cpu_newrec_compiler.cpp
index 560780405..2556bbd6e 100644
--- a/src/core/cpu_newrec_compiler.cpp
+++ b/src/core/cpu_newrec_compiler.cpp
@@ -71,7 +71,7 @@ void CPU::NewRec::Compiler::BeginBlock()
 
   if (m_block->protection == CodeCache::PageProtectionMode::ManualCheck)
   {
-    Log_DebugFmt("Generate manual protection for PC {:08X}", m_block->pc);
+    DEBUG_LOG("Generate manual protection for PC {:08X}", m_block->pc);
     const u8* ram_ptr = Bus::g_ram + VirtualAddressToPhysical(m_block->pc);
     const u8* shadow_ptr = reinterpret_cast<const u8*>(m_block->Instructions());
     GenerateBlockProtectCheck(ram_ptr, shadow_ptr, m_block->size * sizeof(Instruction));
@@ -103,7 +103,7 @@ const void* CPU::NewRec::Compiler::CompileBlock(CodeCache::Block* block, u32* ho
   Reset(block, buffer.GetFreeCodePointer(), buffer.GetFreeCodeSpace(), buffer.GetFreeFarCodePointer(),
         buffer.GetFreeFarCodeSpace());
 
-  Log_DebugFmt("Block range: {:08X} -> {:08X}", block->pc, block->pc + block->size * 4);
+  DEBUG_LOG("Block range: {:08X} -> {:08X}", block->pc, block->pc + block->size * 4);
 
   BeginBlock();
 
@@ -167,8 +167,8 @@ void CPU::NewRec::Compiler::SetConstantReg(Reg r, u32 v)
 
   if (const std::optional<u32> hostreg = CheckHostReg(0, HR_TYPE_CPU_REG, r); hostreg.has_value())
   {
-    Log_DebugFmt("Discarding guest register {} in host register {} due to constant set", GetRegName(r),
-                 GetHostRegName(hostreg.value()));
+    DEBUG_LOG("Discarding guest register {} in host register {} due to constant set", GetRegName(r),
+              GetHostRegName(hostreg.value()));
     FreeHostReg(hostreg.value());
   }
 }
@@ -178,7 +178,7 @@ void CPU::NewRec::Compiler::CancelLoadDelaysToReg(Reg reg)
   if (m_load_delay_register != reg)
     return;
 
-  Log_DebugFmt("Cancelling load delay to {}", GetRegName(reg));
+  DEBUG_LOG("Cancelling load delay to {}", GetRegName(reg));
   m_load_delay_register = Reg::count;
   if (m_load_delay_value_register != NUM_HOST_REGS)
     ClearHostReg(m_load_delay_value_register);
@@ -196,7 +196,7 @@ void CPU::NewRec::Compiler::UpdateLoadDelay()
     // thankfully since this only happens on the first instruction, we can get away with just killing anything which
     // isn't in write mode, because nothing could've been written before it, and the new value overwrites any
     // load-delayed value
-    Log_DebugPrint("Invalidating non-dirty registers, and flushing load delay from state");
+    DEBUG_LOG("Invalidating non-dirty registers, and flushing load delay from state");
 
     constexpr u32 req_flags = (HR_ALLOCATED | HR_MODE_WRITE);
 
@@ -206,7 +206,7 @@ void CPU::NewRec::Compiler::UpdateLoadDelay()
       if (ra.type != HR_TYPE_CPU_REG || !IsHostRegAllocated(i) || ((ra.flags & req_flags) == req_flags))
         continue;
 
-      Log_DebugFmt("Freeing non-dirty cached register {} in {}", GetRegName(ra.reg), GetHostRegName(i));
+      DEBUG_LOG("Freeing non-dirty cached register {} in {}", GetRegName(ra.reg), GetHostRegName(i));
       DebugAssert(!(ra.flags & HR_MODE_WRITE));
       ClearHostReg(i);
     }
@@ -217,7 +217,7 @@ void CPU::NewRec::Compiler::UpdateLoadDelay()
       if (!HasConstantReg(static_cast<Reg>(i)) || HasDirtyConstantReg(static_cast<Reg>(i)))
         continue;
 
-      Log_DebugFmt("Clearing non-dirty constant {}", GetRegName(static_cast<Reg>(i)));
+      DEBUG_LOG("Clearing non-dirty constant {}", GetRegName(static_cast<Reg>(i)));
       ClearConstantReg(static_cast<Reg>(i));
     }
 
@@ -264,8 +264,8 @@ void CPU::NewRec::Compiler::FinishLoadDelay()
   // kill any (old) cached value for this register
   DeleteMIPSReg(m_load_delay_register, false);
 
-  Log_DebugFmt("Finished delayed load to {} in host register {}", GetRegName(m_load_delay_register),
-               GetHostRegName(m_load_delay_value_register));
+  DEBUG_LOG("Finished delayed load to {} in host register {}", GetRegName(m_load_delay_register),
+            GetHostRegName(m_load_delay_value_register));
 
   // and swap the mode over so it gets written back later
   HostRegAlloc& ra = m_host_regs[m_load_delay_value_register];
@@ -275,7 +275,7 @@ void CPU::NewRec::Compiler::FinishLoadDelay()
   ra.type = HR_TYPE_CPU_REG;
 
   // constants are gone
-  Log_DebugFmt("Clearing constant in {} due to load delay", GetRegName(m_load_delay_register));
+  DEBUG_LOG("Clearing constant in {} due to load delay", GetRegName(m_load_delay_register));
   ClearConstantReg(m_load_delay_register);
 
   m_load_delay_register = Reg::count;
@@ -490,7 +490,7 @@ bool CPU::NewRec::Compiler::TrySwapDelaySlot(Reg rs, Reg rt, Reg rd)
 
 is_safe:
 #ifdef _DEBUG
-  Log_DebugFmt("Swapping delay slot {:08X} {}", m_current_instruction_pc + 4, disasm);
+  DEBUG_LOG("Swapping delay slot {:08X} {}", m_current_instruction_pc + 4, disasm);
 #endif
 
   CompileBranchDelaySlot();
@@ -502,7 +502,7 @@ is_safe:
 
 is_unsafe:
 #ifdef _DEBUG
-  Log_DebugFmt("NOT swapping delay slot {:08X} {}", m_current_instruction_pc + 4, disasm);
+  DEBUG_LOG("NOT swapping delay slot {:08X} {}", m_current_instruction_pc + 4, disasm);
 #endif
 
   return false;
@@ -598,8 +598,8 @@ u32 CPU::NewRec::Compiler::GetFreeHostReg(u32 flags)
 
         if (caller_saved_lowest_count < lowest_count)
         {
-          Log_DebugFmt("Moving caller-saved host register {} with MIPS register {} to {} for allocation",
-                       GetHostRegName(lowest), GetRegName(ra.reg), GetHostRegName(caller_saved_lowest));
+          DEBUG_LOG("Moving caller-saved host register {} with MIPS register {} to {} for allocation",
+                    GetHostRegName(lowest), GetRegName(ra.reg), GetHostRegName(caller_saved_lowest));
           if (IsHostRegAllocated(caller_saved_lowest))
             FreeHostReg(caller_saved_lowest);
           CopyHostReg(caller_saved_lowest, lowest);
@@ -609,20 +609,19 @@ u32 CPU::NewRec::Compiler::GetFreeHostReg(u32 flags)
         }
       }
 
-      Log_DebugFmt("Freeing register {} in host register {} for allocation", GetRegName(ra.reg),
-                   GetHostRegName(lowest));
+      DEBUG_LOG("Freeing register {} in host register {} for allocation", GetRegName(ra.reg), GetHostRegName(lowest));
     }
     break;
     case HR_TYPE_LOAD_DELAY_VALUE:
     {
-      Log_DebugFmt("Freeing load delay register {} in host register {} for allocation", GetHostRegName(lowest),
-                   GetRegName(ra.reg));
+      DEBUG_LOG("Freeing load delay register {} in host register {} for allocation", GetHostRegName(lowest),
+                GetRegName(ra.reg));
     }
     break;
     case HR_TYPE_NEXT_LOAD_DELAY_VALUE:
     {
-      Log_DebugFmt("Freeing next load delay register {} in host register {} due for allocation", GetRegName(ra.reg),
-                   GetHostRegName(lowest));
+      DEBUG_LOG("Freeing next load delay register {} in host register {} due for allocation", GetRegName(ra.reg),
+                GetHostRegName(lowest));
     }
     break;
     default:
@@ -680,8 +679,8 @@ u32 CPU::NewRec::Compiler::AllocateHostReg(u32 flags, HostRegAllocType type /* =
     {
       DebugAssert(reg != Reg::zero);
 
-      Log_DebugFmt("Allocate host reg {} to guest reg {} in {} mode", GetHostRegName(hreg), GetRegName(reg),
-                   GetReadWriteModeString(flags));
+      DEBUG_LOG("Allocate host reg {} to guest reg {} in {} mode", GetHostRegName(hreg), GetRegName(reg),
+                GetReadWriteModeString(flags));
 
       if (flags & HR_MODE_READ)
       {
@@ -690,7 +689,7 @@ u32 CPU::NewRec::Compiler::AllocateHostReg(u32 flags, HostRegAllocType type /* =
         if (HasConstantReg(reg))
         {
           // may as well flush it now
-          Log_DebugFmt("Flush constant register in guest reg {} to host reg {}", GetRegName(reg), GetHostRegName(hreg));
+          DEBUG_LOG("Flush constant register in guest reg {} to host reg {}", GetRegName(reg), GetHostRegName(hreg));
           LoadHostRegWithConstant(hreg, GetConstantRegU32(reg));
           m_constant_regs_dirty.reset(static_cast<u8>(reg));
           ra.flags |= HR_MODE_WRITE;
@@ -704,8 +703,8 @@ u32 CPU::NewRec::Compiler::AllocateHostReg(u32 flags, HostRegAllocType type /* =
       if (flags & HR_MODE_WRITE && HasConstantReg(reg))
       {
         DebugAssert(reg != Reg::zero);
-        Log_DebugFmt("Clearing constant register in guest reg {} due to write mode in {}", GetRegName(reg),
-                     GetHostRegName(hreg));
+        DEBUG_LOG("Clearing constant register in guest reg {} due to write mode in {}", GetRegName(reg),
+                  GetHostRegName(hreg));
 
         ClearConstantReg(reg);
       }
@@ -715,8 +714,8 @@ u32 CPU::NewRec::Compiler::AllocateHostReg(u32 flags, HostRegAllocType type /* =
     case HR_TYPE_LOAD_DELAY_VALUE:
     {
       DebugAssert(!m_load_delay_dirty && (!HasLoadDelay() || !(flags & HR_MODE_WRITE)));
-      Log_DebugFmt("Allocating load delayed guest register {} in host reg {} in {} mode", GetRegName(reg),
-                   GetHostRegName(hreg), GetReadWriteModeString(flags));
+      DEBUG_LOG("Allocating load delayed guest register {} in host reg {} in {} mode", GetRegName(reg),
+                GetHostRegName(hreg), GetReadWriteModeString(flags));
       m_load_delay_register = reg;
       m_load_delay_value_register = hreg;
       if (flags & HR_MODE_READ)
@@ -726,8 +725,8 @@ u32 CPU::NewRec::Compiler::AllocateHostReg(u32 flags, HostRegAllocType type /* =
 
     case HR_TYPE_NEXT_LOAD_DELAY_VALUE:
     {
-      Log_DebugFmt("Allocating next load delayed guest register {} in host reg {} in {} mode", GetRegName(reg),
-                   GetHostRegName(hreg), GetReadWriteModeString(flags));
+      DEBUG_LOG("Allocating next load delayed guest register {} in host reg {} in {} mode", GetRegName(reg),
+                GetHostRegName(hreg), GetReadWriteModeString(flags));
       m_next_load_delay_register = reg;
       m_next_load_delay_value_register = hreg;
       if (flags & HR_MODE_READ)
@@ -738,7 +737,7 @@ u32 CPU::NewRec::Compiler::AllocateHostReg(u32 flags, HostRegAllocType type /* =
     case HR_TYPE_TEMP:
     {
       DebugAssert(!(flags & (HR_MODE_READ | HR_MODE_WRITE)));
-      Log_DebugFmt("Allocate host reg {} as temporary", GetHostRegName(hreg));
+      DEBUG_LOG("Allocate host reg {} as temporary", GetHostRegName(hreg));
     }
     break;
 
@@ -764,13 +763,13 @@ std::optional<u32> CPU::NewRec::Compiler::CheckHostReg(u32 flags, HostRegAllocTy
     {
       DebugAssert(type == HR_TYPE_CPU_REG);
       if (!(ra.flags & HR_MODE_WRITE))
-        Log_DebugFmt("Switch guest reg {} from read to read-write in host reg {}", GetRegName(reg), GetHostRegName(i));
+        DEBUG_LOG("Switch guest reg {} from read to read-write in host reg {}", GetRegName(reg), GetHostRegName(i));
 
       if (HasConstantReg(reg))
       {
         DebugAssert(reg != Reg::zero);
-        Log_DebugFmt("Clearing constant register in guest reg {} due to write mode in {}", GetRegName(reg),
-                     GetHostRegName(i));
+        DEBUG_LOG("Clearing constant register in guest reg {} due to write mode in {}", GetRegName(reg),
+                  GetHostRegName(i));
 
         ClearConstantReg(reg);
       }
@@ -784,7 +783,7 @@ std::optional<u32> CPU::NewRec::Compiler::CheckHostReg(u32 flags, HostRegAllocTy
     {
       // Need to move it to one which is
       const u32 new_reg = GetFreeHostReg(HR_CALLEE_SAVED);
-      Log_DebugFmt("Rename host reg {} to {} for callee saved", GetHostRegName(i), GetHostRegName(new_reg));
+      DEBUG_LOG("Rename host reg {} to {} for callee saved", GetHostRegName(i), GetHostRegName(new_reg));
 
       CopyHostReg(new_reg, i);
       SwapHostRegAlloc(i, new_reg);
@@ -826,7 +825,7 @@ void CPU::NewRec::Compiler::FlushHostReg(u32 reg)
       case HR_TYPE_CPU_REG:
       {
         DebugAssert(ra.reg > Reg::zero && ra.reg < Reg::count);
-        Log_DebugFmt("Flushing register {} in host register {} to state", GetRegName(ra.reg), GetHostRegName(reg));
+        DEBUG_LOG("Flushing register {} in host register {} to state", GetRegName(ra.reg), GetHostRegName(reg));
         StoreHostRegToCPUPointer(reg, &g_state.regs.r[static_cast<u8>(ra.reg)]);
       }
       break;
@@ -834,8 +833,8 @@ void CPU::NewRec::Compiler::FlushHostReg(u32 reg)
       case HR_TYPE_LOAD_DELAY_VALUE:
       {
         DebugAssert(m_load_delay_value_register == reg);
-        Log_DebugFmt("Flushing load delayed register {} in host register {} to state", GetRegName(ra.reg),
-                     GetHostRegName(reg));
+        DEBUG_LOG("Flushing load delayed register {} in host register {} to state", GetRegName(ra.reg),
+                  GetHostRegName(reg));
 
         StoreHostRegToCPUPointer(reg, &g_state.load_delay_value);
         m_load_delay_value_register = NUM_HOST_REGS;
@@ -845,8 +844,8 @@ void CPU::NewRec::Compiler::FlushHostReg(u32 reg)
       case HR_TYPE_NEXT_LOAD_DELAY_VALUE:
       {
         DebugAssert(m_next_load_delay_value_register == reg);
-        Log_WarningFmt("Flushing NEXT load delayed register {} in host register {} to state", GetRegName(ra.reg),
-                       GetHostRegName(reg));
+        WARNING_LOG("Flushing NEXT load delayed register {} in host register {} to state", GetRegName(ra.reg),
+                    GetHostRegName(reg));
 
         StoreHostRegToCPUPointer(reg, &g_state.next_load_delay_value);
         m_next_load_delay_value_register = NUM_HOST_REGS;
@@ -864,7 +863,7 @@ void CPU::NewRec::Compiler::FlushHostReg(u32 reg)
 void CPU::NewRec::Compiler::FreeHostReg(u32 reg)
 {
   DebugAssert(IsHostRegAllocated(reg));
-  Log_DebugFmt("Freeing host register {}", GetHostRegName(reg));
+  DEBUG_LOG("Freeing host register {}", GetHostRegName(reg));
   FlushHostReg(reg);
   ClearHostReg(reg);
 }
@@ -906,18 +905,18 @@ void CPU::NewRec::Compiler::RenameHostReg(u32 reg, u32 new_flags, HostRegAllocTy
 
   if (new_type == HR_TYPE_CPU_REG)
   {
-    Log_DebugFmt("Renaming host reg {} to guest reg {}", GetHostRegName(reg), GetRegName(new_reg));
+    DEBUG_LOG("Renaming host reg {} to guest reg {}", GetHostRegName(reg), GetRegName(new_reg));
   }
   else if (new_type == HR_TYPE_NEXT_LOAD_DELAY_VALUE)
   {
-    Log_DebugFmt("Renaming host reg {} to load delayed guest reg {}", GetHostRegName(reg), GetRegName(new_reg));
+    DEBUG_LOG("Renaming host reg {} to load delayed guest reg {}", GetHostRegName(reg), GetRegName(new_reg));
     DebugAssert(m_next_load_delay_register == Reg::count && m_next_load_delay_value_register == NUM_HOST_REGS);
     m_next_load_delay_register = new_reg;
     m_next_load_delay_value_register = reg;
   }
   else
   {
-    Log_DebugFmt("Renaming host reg {} to temp", GetHostRegName(reg));
+    DEBUG_LOG("Renaming host reg {} to temp", GetHostRegName(reg));
   }
 
   HostRegAlloc& ra = m_host_regs[reg];
@@ -983,7 +982,7 @@ bool CPU::NewRec::Compiler::TryRenameMIPSReg(Reg to, Reg from, u32 fromhost, Reg
   if (to == from || to == other || !iinfo->RenameTest(from))
     return false;
 
-  Log_DebugFmt("Renaming MIPS register {} to {}", GetRegName(from), GetRegName(to));
+  DEBUG_LOG("Renaming MIPS register {} to {}", GetRegName(from), GetRegName(to));
 
   if (iinfo->LiveTest(from))
     FlushHostReg(fromhost);
@@ -1090,8 +1089,8 @@ void CPU::NewRec::Compiler::Flush(u32 flags)
 void CPU::NewRec::Compiler::FlushConstantReg(Reg r)
 {
   DebugAssert(m_constant_regs_valid.test(static_cast<u32>(r)));
-  Log_DebugFmt("Writing back register {} with constant value 0x{:08X}", GetRegName(r),
-               m_constant_reg_values[static_cast<u32>(r)]);
+  DEBUG_LOG("Writing back register {} with constant value 0x{:08X}", GetRegName(r),
+            m_constant_reg_values[static_cast<u32>(r)]);
   StoreConstantToCPUPointer(m_constant_reg_values[static_cast<u32>(r)], &g_state.regs.r[static_cast<u32>(r)]);
   m_constant_regs_dirty.reset(static_cast<u32>(r));
 }
@@ -1179,8 +1178,8 @@ void CPU::NewRec::Compiler::CompileInstruction()
 #ifdef _DEBUG
   TinyString str;
   DisassembleInstruction(&str, m_current_instruction_pc, inst->bits);
-  Log_DebugFmt("Compiling{} {:08X}: {}", m_current_instruction_branch_delay_slot ? " branch delay slot" : "",
-               m_current_instruction_pc, str);
+  DEBUG_LOG("Compiling{} {:08X}: {}", m_current_instruction_branch_delay_slot ? " branch delay slot" : "",
+            m_current_instruction_pc, str);
 #endif
 
   m_cycles++;
@@ -1390,7 +1389,7 @@ void CPU::NewRec::Compiler::CompileTemplate(void (Compiler::*const_func)(Compile
   if (!(tflags & TF_NO_NOP) && (!g_settings.cpu_recompiler_memory_exceptions || !(tflags & TF_CAN_OVERFLOW)) &&
       ((tflags & TF_WRITES_T && rt == Reg::zero) || (tflags & TF_WRITES_D && rd == Reg::zero)))
   {
-    Log_DebugPrint("Skipping instruction because it writes to zero");
+    DEBUG_LOG("Skipping instruction because it writes to zero");
     return;
   }
 
@@ -1437,7 +1436,7 @@ void CPU::NewRec::Compiler::CompileTemplate(void (Compiler::*const_func)(Compile
   if (tflags & TF_COMMUTATIVE && !(tflags & TF_WRITES_T) &&
       ((HasConstantReg(rs) && !HasConstantReg(rt)) || (tflags & TF_WRITES_D && rd == rt)))
   {
-    Log_DebugFmt("Swapping S:{} and T:{} due to commutative op and constants", GetRegName(rs), GetRegName(rt));
+    DEBUG_LOG("Swapping S:{} and T:{} due to commutative op and constants", GetRegName(rs), GetRegName(rt));
     std::swap(rs, rt);
   }
 
@@ -1638,7 +1637,7 @@ void CPU::NewRec::Compiler::CompileLoadStoreTemplate(void (Compiler::*func)(Comp
 
     if (!Bus::CanUseFastmemForAddress(addr.value()))
     {
-      Log_DebugFmt("Not using fastmem for {:08X}", addr.value());
+      DEBUG_LOG("Not using fastmem for {:08X}", addr.value());
       use_fastmem = false;
     }
   }
@@ -1647,7 +1646,7 @@ void CPU::NewRec::Compiler::CompileLoadStoreTemplate(void (Compiler::*func)(Comp
     spec_addr = SpecExec_LoadStoreAddr();
     if (use_fastmem && spec_addr.has_value() && !Bus::CanUseFastmemForAddress(spec_addr.value()))
     {
-      Log_DebugFmt("Not using fastmem for speculative {:08X}", spec_addr.value());
+      DEBUG_LOG("Not using fastmem for speculative {:08X}", spec_addr.value());
       use_fastmem = false;
     }
 
@@ -1707,9 +1706,9 @@ void CPU::NewRec::Compiler::CompileLoadStoreTemplate(void (Compiler::*func)(Comp
     if (phys_spec_addr >= VirtualAddressToPhysical(m_block->pc) &&
         phys_spec_addr < VirtualAddressToPhysical(m_block->pc + (m_block->size * sizeof(Instruction))))
     {
-      Log_WarningFmt("Instruction {:08X} speculatively writes to {:08X} inside block {:08X}-{:08X}. Truncating block.",
-                     m_current_instruction_pc, phys_spec_addr, m_block->pc,
-                     m_block->pc + (m_block->size * sizeof(Instruction)));
+      WARNING_LOG("Instruction {:08X} speculatively writes to {:08X} inside block {:08X}-{:08X}. Truncating block.",
+                  m_current_instruction_pc, phys_spec_addr, m_block->pc,
+                  m_block->pc + (m_block->size * sizeof(Instruction)));
       TruncateBlock();
     }
   }
@@ -2198,7 +2197,7 @@ void CPU::NewRec::Compiler::Compile_mfc0(CompileFlags cf)
   const u32* ptr = GetCop0RegPtr(r);
   if (!ptr)
   {
-    Log_ErrorFmt("Read from unknown cop0 reg {}", static_cast<u32>(r));
+    ERROR_LOG("Read from unknown cop0 reg {}", static_cast<u32>(r));
     Compile_Fallback();
     return;
   }
@@ -2304,7 +2303,7 @@ void CPU::NewRec::Compiler::AddGTETicks(TickCount ticks)
 {
   // TODO: check, int has +1 here
   m_gte_done_cycle = m_cycles + ticks;
-  Log_DebugFmt("Adding {} GTE ticks", ticks);
+  DEBUG_LOG("Adding {} GTE ticks", ticks);
 }
 
 void CPU::NewRec::Compiler::StallUntilGTEComplete()
@@ -2319,14 +2318,14 @@ void CPU::NewRec::Compiler::StallUntilGTEComplete()
     // simple case - in block scheduling
     if (m_gte_done_cycle > m_cycles)
     {
-      Log_DebugFmt("Stalling for {} ticks from GTE", m_gte_done_cycle - m_cycles);
+      DEBUG_LOG("Stalling for {} ticks from GTE", m_gte_done_cycle - m_cycles);
       m_cycles += (m_gte_done_cycle - m_cycles);
     }
   }
   else
   {
     // switch to in block scheduling
-    Log_DebugPrint("Flushing GTE stall from state");
+    DEBUG_LOG("Flushing GTE stall from state");
     Flush(FLUSH_GTE_STALL_FROM_STATE);
   }
 
diff --git a/src/core/cpu_newrec_compiler_aarch32.cpp b/src/core/cpu_newrec_compiler_aarch32.cpp
index 84909fb73..30af0cc9b 100644
--- a/src/core/cpu_newrec_compiler_aarch32.cpp
+++ b/src/core/cpu_newrec_compiler_aarch32.cpp
@@ -438,7 +438,7 @@ void CPU::NewRec::AArch32Compiler::EndAndLinkBlock(const std::optional<u32>& new
     if (newpc.value() == m_block->pc)
     {
       // Special case: ourselves! No need to backlink then.
-      Log_DebugFmt("Linking block at {:08X} to self", m_block->pc);
+      DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
       armEmitJmp(armAsm, armAsm->GetBuffer()->GetStartAddress<const void*>(), true);
     }
     else
@@ -575,7 +575,7 @@ void CPU::NewRec::AArch32Compiler::MoveSToReg(const vixl::aarch32::Register& dst
   }
   else
   {
-    Log_WarningFmt("Hit memory path in MoveSToReg() for {}", GetRegName(cf.MipsS()));
+    WARNING_LOG("Hit memory path in MoveSToReg() for {}", GetRegName(cf.MipsS()));
     armAsm->ldr(dst, PTR(&g_state.regs.r[cf.mips_s]));
   }
 }
@@ -594,7 +594,7 @@ void CPU::NewRec::AArch32Compiler::MoveTToReg(const vixl::aarch32::Register& dst
   }
   else
   {
-    Log_WarningFmt("Hit memory path in MoveTToReg() for {}", GetRegName(cf.MipsT()));
+    WARNING_LOG("Hit memory path in MoveTToReg() for {}", GetRegName(cf.MipsT()));
     armAsm->ldr(dst, PTR(&g_state.regs.r[cf.mips_t]));
   }
 }
@@ -1927,7 +1927,7 @@ void CPU::NewRec::AArch32Compiler::Compile_mtc0(CompileFlags cf)
   if (mask == 0)
   {
     // if it's a read-only register, ignore
-    Log_DebugFmt("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
+    DEBUG_LOG("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
     return;
   }
 
@@ -1984,7 +1984,7 @@ void CPU::NewRec::AArch32Compiler::Compile_mtc0(CompileFlags cf)
   if (reg == Cop0Reg::DCIC && g_settings.cpu_recompiler_memory_exceptions)
   {
     // TODO: DCIC handling for debug breakpoints
-    Log_WarningPrint("TODO: DCIC handling for debug breakpoints");
+    WARNING_LOG("TODO: DCIC handling for debug breakpoints");
   }
 }
 
diff --git a/src/core/cpu_newrec_compiler_aarch64.cpp b/src/core/cpu_newrec_compiler_aarch64.cpp
index a8029245d..2f9be38ad 100644
--- a/src/core/cpu_newrec_compiler_aarch64.cpp
+++ b/src/core/cpu_newrec_compiler_aarch64.cpp
@@ -410,7 +410,7 @@ void CPU::NewRec::AArch64Compiler::EndAndLinkBlock(const std::optional<u32>& new
     if (newpc.value() == m_block->pc)
     {
       // Special case: ourselves! No need to backlink then.
-      Log_DebugFmt("Linking block at {:08X} to self", m_block->pc);
+      DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
       armEmitJmp(armAsm, armAsm->GetBuffer()->GetStartAddress<const void*>(), true);
     }
     else
@@ -542,7 +542,7 @@ void CPU::NewRec::AArch64Compiler::MoveSToReg(const vixl::aarch64::WRegister& ds
   }
   else
   {
-    Log_WarningFmt("Hit memory path in MoveSToReg() for {}", GetRegName(cf.MipsS()));
+    WARNING_LOG("Hit memory path in MoveSToReg() for {}", GetRegName(cf.MipsS()));
     armAsm->ldr(dst, PTR(&g_state.regs.r[cf.mips_s]));
   }
 }
@@ -564,7 +564,7 @@ void CPU::NewRec::AArch64Compiler::MoveTToReg(const vixl::aarch64::WRegister& ds
   }
   else
   {
-    Log_WarningFmt("Hit memory path in MoveTToReg() for {}", GetRegName(cf.MipsT()));
+    WARNING_LOG("Hit memory path in MoveTToReg() for {}", GetRegName(cf.MipsT()));
     armAsm->ldr(dst, PTR(&g_state.regs.r[cf.mips_t]));
   }
 }
@@ -1906,7 +1906,7 @@ void CPU::NewRec::AArch64Compiler::Compile_mtc0(CompileFlags cf)
   if (mask == 0)
   {
     // if it's a read-only register, ignore
-    Log_DebugFmt("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
+    DEBUG_LOG("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
     return;
   }
 
@@ -1961,7 +1961,7 @@ void CPU::NewRec::AArch64Compiler::Compile_mtc0(CompileFlags cf)
   if (reg == Cop0Reg::DCIC && g_settings.cpu_recompiler_memory_exceptions)
   {
     // TODO: DCIC handling for debug breakpoints
-    Log_WarningPrint("TODO: DCIC handling for debug breakpoints");
+    WARNING_LOG("TODO: DCIC handling for debug breakpoints");
   }
 }
 
diff --git a/src/core/cpu_newrec_compiler_riscv64.cpp b/src/core/cpu_newrec_compiler_riscv64.cpp
index bf2e33fe0..ac100ffc0 100644
--- a/src/core/cpu_newrec_compiler_riscv64.cpp
+++ b/src/core/cpu_newrec_compiler_riscv64.cpp
@@ -173,11 +173,11 @@ void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
     size_t instlen;
     inst_fetch(cur, &inst, &instlen);
     disasm_inst(buf, std::size(buf), rv64, static_cast<u64>(reinterpret_cast<uintptr_t>(cur)), inst);
-    Log_DebugFmt("\t0x{:016X}\t{}", static_cast<u64>(reinterpret_cast<uintptr_t>(cur)), buf);
+    DEBUG_LOG("\t0x{:016X}\t{}", static_cast<u64>(reinterpret_cast<uintptr_t>(cur)), buf);
     cur += instlen;
   }
 #else
-  Log_ErrorPrint("Not compiled with ENABLE_HOST_DISASSEMBLY.");
+  ERROR_LOG("Not compiled with ENABLE_HOST_DISASSEMBLY.");
 #endif
 }
 
@@ -197,7 +197,7 @@ u32 CPU::CodeCache::GetHostInstructionCount(const void* start, u32 size)
   }
   return icount;
 #else
-  Log_ErrorPrint("Not compiled with ENABLE_HOST_DISASSEMBLY.");
+  ERROR_LOG("Not compiled with ENABLE_HOST_DISASSEMBLY.");
   return 0;
 #endif
 }
@@ -665,7 +665,7 @@ void CPU::NewRec::RISCV64Compiler::EndAndLinkBlock(const std::optional<u32>& new
     if (newpc.value() == m_block->pc)
     {
       // Special case: ourselves! No need to backlink then.
-      Log_DebugFmt("Linking block at {:08X} to self", m_block->pc);
+      DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
       rvEmitJmp(rvAsm, rvAsm->GetBufferPointer(0));
     }
     else
@@ -754,7 +754,7 @@ biscuit::GPR CPU::NewRec::RISCV64Compiler::CFGetSafeRegS(CompileFlags cf, const
   }
   else
   {
-    Log_WarningFmt("Hit memory path in CFGetSafeRegS() for {}", GetRegName(cf.MipsS()));
+    WARNING_LOG("Hit memory path in CFGetSafeRegS() for {}", GetRegName(cf.MipsS()));
     rvAsm->LW(temp_reg, PTR(&g_state.regs.r[cf.mips_s]));
     return temp_reg;
   }
@@ -776,7 +776,7 @@ biscuit::GPR CPU::NewRec::RISCV64Compiler::CFGetSafeRegT(CompileFlags cf, const
   }
   else
   {
-    Log_WarningFmt("Hit memory path in CFGetSafeRegT() for {}", GetRegName(cf.MipsT()));
+    WARNING_LOG("Hit memory path in CFGetSafeRegT() for {}", GetRegName(cf.MipsT()));
     rvAsm->LW(temp_reg, PTR(&g_state.regs.r[cf.mips_t]));
     return temp_reg;
   }
@@ -825,7 +825,7 @@ void CPU::NewRec::RISCV64Compiler::MoveSToReg(const biscuit::GPR& dst, CompileFl
   }
   else
   {
-    Log_WarningFmt("Hit memory path in MoveSToReg() for {}", GetRegName(cf.MipsS()));
+    WARNING_LOG("Hit memory path in MoveSToReg() for {}", GetRegName(cf.MipsS()));
     rvAsm->LW(dst, PTR(&g_state.regs.r[cf.mips_s]));
   }
 }
@@ -843,7 +843,7 @@ void CPU::NewRec::RISCV64Compiler::MoveTToReg(const biscuit::GPR& dst, CompileFl
   }
   else
   {
-    Log_WarningFmt("Hit memory path in MoveTToReg() for {}", GetRegName(cf.MipsT()));
+    WARNING_LOG("Hit memory path in MoveTToReg() for {}", GetRegName(cf.MipsT()));
     rvAsm->LW(dst, PTR(&g_state.regs.r[cf.mips_t]));
   }
 }
@@ -2214,7 +2214,7 @@ void CPU::NewRec::RISCV64Compiler::Compile_mtc0(CompileFlags cf)
   if (mask == 0)
   {
     // if it's a read-only register, ignore
-    Log_DebugFmt("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
+    DEBUG_LOG("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
     return;
   }
 
@@ -2273,7 +2273,7 @@ void CPU::NewRec::RISCV64Compiler::Compile_mtc0(CompileFlags cf)
   if (reg == Cop0Reg::DCIC && g_settings.cpu_recompiler_memory_exceptions)
   {
     // TODO: DCIC handling for debug breakpoints
-    Log_WarningPrint("TODO: DCIC handling for debug breakpoints");
+    WARNING_LOG("TODO: DCIC handling for debug breakpoints");
   }
 }
 
diff --git a/src/core/cpu_newrec_compiler_x64.cpp b/src/core/cpu_newrec_compiler_x64.cpp
index a090284b2..58b862b73 100644
--- a/src/core/cpu_newrec_compiler_x64.cpp
+++ b/src/core/cpu_newrec_compiler_x64.cpp
@@ -312,7 +312,7 @@ void CPU::NewRec::X64Compiler::EndAndLinkBlock(const std::optional<u32>& newpc,
     if (newpc.value() == m_block->pc)
     {
       // Special case: ourselves! No need to backlink then.
-      Log_DebugFmt("Linking block at {:08X} to self", m_block->pc);
+      DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
       cg->jmp(cg->getCode());
     }
     else
@@ -1877,7 +1877,7 @@ void CPU::NewRec::X64Compiler::Compile_mtc0(CompileFlags cf)
   if (mask == 0)
   {
     // if it's a read-only register, ignore
-    Log_DebugFmt("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
+    DEBUG_LOG("Ignoring write to read-only cop0 reg {}", static_cast<u32>(reg));
     return;
   }
 
@@ -1940,7 +1940,7 @@ void CPU::NewRec::X64Compiler::Compile_mtc0(CompileFlags cf)
   if (reg == Cop0Reg::DCIC && g_settings.cpu_recompiler_memory_exceptions)
   {
     // TODO: DCIC handling for debug breakpoints
-    Log_WarningPrint("TODO: DCIC handling for debug breakpoints");
+    WARNING_LOG("TODO: DCIC handling for debug breakpoints");
   }
 }
 
diff --git a/src/core/cpu_pgxp.cpp b/src/core/cpu_pgxp.cpp
index e3679ea84..f27ff4ab9 100644
--- a/src/core/cpu_pgxp.cpp
+++ b/src/core/cpu_pgxp.cpp
@@ -150,7 +150,7 @@ void CPU::PGXP::Initialize()
     s_vertex_cache = static_cast<PGXP_value*>(std::calloc(VERTEX_CACHE_SIZE, sizeof(PGXP_value)));
     if (!s_vertex_cache)
     {
-      Log_ErrorPrint("Failed to allocate memory for vertex cache, disabling.");
+      ERROR_LOG("Failed to allocate memory for vertex cache, disabling.");
       g_settings.gpu_pgxp_vertex_cache = false;
     }
   }
diff --git a/src/core/cpu_recompiler_code_generator.cpp b/src/core/cpu_recompiler_code_generator.cpp
index a16cf9998..fe2bb27f0 100644
--- a/src/core/cpu_recompiler_code_generator.cpp
+++ b/src/core/cpu_recompiler_code_generator.cpp
@@ -980,7 +980,7 @@ void CodeGenerator::BlockPrologue()
 
   if (m_block->protection == CodeCache::PageProtectionMode::ManualCheck)
   {
-    Log_DebugFmt("Generate manual protection for PC {:08X}", m_block->pc);
+    DEBUG_LOG("Generate manual protection for PC {:08X}", m_block->pc);
     const u8* ram_ptr = Bus::g_ram + VirtualAddressToPhysical(m_block->pc);
     const u8* shadow_ptr = reinterpret_cast<const u8*>(m_block->Instructions());
     EmitBlockProtectCheck(ram_ptr, shadow_ptr, m_block->size * sizeof(Instruction));
@@ -1079,7 +1079,7 @@ void CodeGenerator::InstructionEpilogue(Instruction instruction, const CodeCache
   if (m_load_delay_dirty)
   {
     // we have to invalidate the register cache, since the load delayed register might've been cached
-    Log_DebugPrint("Emitting delay slot flush");
+    DEBUG_LOG("Emitting delay slot flush");
     EmitFlushInterpreterLoadDelay();
     m_register_cache.InvalidateAllNonDirtyGuestRegisters();
     m_load_delay_dirty = false;
@@ -1088,7 +1088,7 @@ void CodeGenerator::InstructionEpilogue(Instruction instruction, const CodeCache
   // copy if the previous instruction was a load, reset the current value on the next instruction
   if (m_next_load_delay_dirty)
   {
-    Log_DebugPrint("Emitting delay slot flush (with move next)");
+    DEBUG_LOG("Emitting delay slot flush (with move next)");
     EmitMoveNextInterpreterLoadDelay();
     m_next_load_delay_dirty = false;
     m_load_delay_dirty = true;
@@ -1097,7 +1097,7 @@ void CodeGenerator::InstructionEpilogue(Instruction instruction, const CodeCache
 
 void CodeGenerator::TruncateBlockAtCurrentInstruction()
 {
-  Log_DevFmt("Truncating block {:08X} at {:08X}", m_block->pc, m_current_instruction.info->pc);
+  DEV_LOG("Truncating block {:08X} at {:08X}", m_block->pc, m_current_instruction.info->pc);
   m_block_end.instruction = m_current_instruction.instruction + 1;
   m_block_end.info = m_current_instruction.info + 1;
   WriteNewPC(CalculatePC(), true);
@@ -1141,7 +1141,7 @@ void CodeGenerator::AddPendingCycles(bool commit)
 void CodeGenerator::AddGTETicks(TickCount ticks)
 {
   m_gte_done_cycle = m_delayed_cycles_add + ticks;
-  Log_DebugFmt("Adding {} GTE ticks", ticks);
+  DEBUG_LOG("Adding {} GTE ticks", ticks);
 }
 
 void CodeGenerator::StallUntilGTEComplete()
@@ -1151,7 +1151,7 @@ void CodeGenerator::StallUntilGTEComplete()
     // simple case - in block scheduling
     if (m_gte_done_cycle > m_delayed_cycles_add)
     {
-      Log_DebugFmt("Stalling for {} ticks from GTE", m_gte_done_cycle - m_delayed_cycles_add);
+      DEBUG_LOG("Stalling for {} ticks from GTE", m_gte_done_cycle - m_delayed_cycles_add);
       m_delayed_cycles_add += (m_gte_done_cycle - m_delayed_cycles_add);
     }
 
@@ -1667,9 +1667,8 @@ bool CodeGenerator::Compile_Store(Instruction instruction, const CodeCache::Inst
         VirtualAddressToPhysical(m_block->pc + (m_block->size * sizeof(Instruction)));
       if (phys_addr >= block_start && phys_addr < block_end)
       {
-        Log_WarningFmt(
-          "Instruction {:08X} speculatively writes to {:08X} inside block {:08X}-{:08X}. Truncating block.", info.pc,
-          phys_addr, block_start, block_end);
+        WARNING_LOG("Instruction {:08X} speculatively writes to {:08X} inside block {:08X}-{:08X}. Truncating block.",
+                    info.pc, phys_addr, block_start, block_end);
         TruncateBlockAtCurrentInstruction();
       }
     }
@@ -1710,7 +1709,7 @@ bool CodeGenerator::Compile_LoadLeftRight(Instruction instruction, const CodeCac
     // we don't actually care if it's our target reg or not, if it's not, it won't affect anything
     if (m_load_delay_dirty)
     {
-      Log_DevFmt("Flushing interpreter load delay for lwl/lwr instruction at 0x{:08X}", info.pc);
+      DEV_LOG("Flushing interpreter load delay for lwl/lwr instruction at 0x{:08X}", info.pc);
       EmitFlushInterpreterLoadDelay();
       m_register_cache.InvalidateGuestRegister(instruction.r.rt);
       m_load_delay_dirty = false;
@@ -2387,8 +2386,8 @@ bool CodeGenerator::Compile_Branch(Instruction instruction, const CodeCache::Ins
 
       if (branch_target.IsConstant())
       {
-        Log_WarningFmt("Misaligned constant target branch 0x{:08X}, this is strange",
-                       Truncate32(branch_target.constant_value));
+        WARNING_LOG("Misaligned constant target branch 0x{:08X}, this is strange",
+                    Truncate32(branch_target.constant_value));
       }
       else
       {
diff --git a/src/core/cpu_recompiler_code_generator_aarch32.cpp b/src/core/cpu_recompiler_code_generator_aarch32.cpp
index b1d7cb989..6f0c996be 100644
--- a/src/core/cpu_recompiler_code_generator_aarch32.cpp
+++ b/src/core/cpu_recompiler_code_generator_aarch32.cpp
@@ -123,7 +123,8 @@ void CPU::Recompiler::armEmitCall(vixl::aarch32::Assembler* armAsm, const void*
   }
 }
 
-void CPU::Recompiler::armEmitCondBranch(vixl::aarch32::Assembler* armAsm, vixl::aarch32::Condition cond, const void* ptr)
+void CPU::Recompiler::armEmitCondBranch(vixl::aarch32::Assembler* armAsm, vixl::aarch32::Condition cond,
+                                        const void* ptr)
 {
   const s32 displacement = armGetPCDisplacement(armAsm->GetCursorAddress<const void*>(), ptr);
   if (!armIsPCDisplacementInImmediateRange(displacement))
@@ -145,7 +146,7 @@ void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
   dis.SetCodeAddress(reinterpret_cast<uintptr_t>(start));
   dis.DisassembleA32Buffer(static_cast<const u32*>(start), size);
 #else
-  Log_ErrorPrint("Not compiled with ENABLE_HOST_DISASSEMBLY.");
+  ERROR_LOG("Not compiled with ENABLE_HOST_DISASSEMBLY.");
 #endif
 }
 
@@ -1766,7 +1767,7 @@ void CodeGenerator::EmitStoreGuestMemorySlowmem(Instruction instruction, const C
 
 void CodeGenerator::BackpatchLoadStore(void* host_pc, const CodeCache::LoadstoreBackpatchInfo& lbi)
 {
-  Log_DevFmt("Backpatching {} (guest PC 0x{:08X}) to slowmem at {}", host_pc, lbi.guest_pc, lbi.thunk_address);
+  DEV_LOG("Backpatching {} (guest PC 0x{:08X}) to slowmem at {}", host_pc, lbi.guest_pc, lbi.thunk_address);
 
   // turn it into a jump to the slowmem handler
   vixl::aarch32::MacroAssembler emit(static_cast<vixl::byte*>(host_pc), lbi.code_size, a32::A32);
diff --git a/src/core/cpu_recompiler_code_generator_aarch64.cpp b/src/core/cpu_recompiler_code_generator_aarch64.cpp
index 5c9dc3824..89a2088a3 100644
--- a/src/core/cpu_recompiler_code_generator_aarch64.cpp
+++ b/src/core/cpu_recompiler_code_generator_aarch64.cpp
@@ -279,8 +279,7 @@ void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
   protected:
     void ProcessOutput(const a64::Instruction* instr) override
     {
-      Log_DebugFmt("0x{:016X}  {:08X}\t\t{}", reinterpret_cast<uint64_t>(instr), instr->GetInstructionBits(),
-                   GetOutput());
+      DEBUG_LOG("0x{:016X}  {:08X}\t\t{}", reinterpret_cast<uint64_t>(instr), instr->GetInstructionBits(), GetOutput());
     }
   };
 
@@ -290,7 +289,7 @@ void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
   decoder.Decode(static_cast<const a64::Instruction*>(start),
                  reinterpret_cast<const a64::Instruction*>(static_cast<const u8*>(start) + size));
 #else
-  Log_ErrorPrint("Not compiled with ENABLE_HOST_DISASSEMBLY.");
+  ERROR_LOG("Not compiled with ENABLE_HOST_DISASSEMBLY.");
 #endif
 }
 
@@ -2059,7 +2058,7 @@ void CodeGenerator::EmitUpdateFastmemBase()
 
 void CodeGenerator::BackpatchLoadStore(void* host_pc, const CodeCache::LoadstoreBackpatchInfo& lbi)
 {
-  Log_DevFmt("Backpatching {} (guest PC 0x{:08X}) to slowmem at {}", host_pc, lbi.guest_pc, lbi.thunk_address);
+  DEV_LOG("Backpatching {} (guest PC 0x{:08X}) to slowmem at {}", host_pc, lbi.guest_pc, lbi.thunk_address);
 
   // check jump distance
   const s64 jump_distance =
diff --git a/src/core/cpu_recompiler_code_generator_generic.cpp b/src/core/cpu_recompiler_code_generator_generic.cpp
index 6fa7023d1..0b3f262c9 100644
--- a/src/core/cpu_recompiler_code_generator_generic.cpp
+++ b/src/core/cpu_recompiler_code_generator_generic.cpp
@@ -70,14 +70,14 @@ Value CodeGenerator::EmitLoadGuestMemory(Instruction instruction, const CodeCach
   {
     if (!use_fastmem)
     {
-      Log_ProfileFmt("Non-constant load at 0x{:08X}, speculative address 0x{:08X}, using fastmem = {}", info.pc,
-                     *address_spec, use_fastmem ? "yes" : "no");
+      DEBUG_LOG("Non-constant load at 0x{:08X}, speculative address 0x{:08X}, using fastmem = {}", info.pc,
+                *address_spec, use_fastmem ? "yes" : "no");
     }
   }
   else
   {
-    Log_ProfileFmt("Non-constant load at 0x{:08X}, speculative address UNKNOWN, using fastmem = {}", info.pc,
-                   use_fastmem ? "yes" : "no");
+    DEBUG_LOG("Non-constant load at 0x{:08X}, speculative address UNKNOWN, using fastmem = {}", info.pc,
+              use_fastmem ? "yes" : "no");
   }
 
   if (CodeCache::IsUsingFastmem() && use_fastmem)
@@ -145,14 +145,14 @@ void CodeGenerator::EmitStoreGuestMemory(Instruction instruction, const CodeCach
   {
     if (!use_fastmem)
     {
-      Log_ProfileFmt("Non-constant store at 0x{:08X}, speculative address 0x{:08X}, using fastmem = {}", info.pc,
-                     *address_spec, use_fastmem ? "yes" : "no");
+      DEBUG_LOG("Non-constant store at 0x{:08X}, speculative address 0x{:08X}, using fastmem = {}", info.pc,
+                *address_spec, use_fastmem ? "yes" : "no");
     }
   }
   else
   {
-    Log_ProfileFmt("Non-constant store at 0x{:08X}, speculative address UNKNOWN, using fastmem = {}", info.pc,
-                   use_fastmem ? "yes" : "no");
+    DEBUG_LOG("Non-constant store at 0x{:08X}, speculative address UNKNOWN, using fastmem = {}", info.pc,
+              use_fastmem ? "yes" : "no");
   }
 
   if (CodeCache::IsUsingFastmem() && use_fastmem)
diff --git a/src/core/cpu_recompiler_code_generator_x64.cpp b/src/core/cpu_recompiler_code_generator_x64.cpp
index 39374ab5a..89ed92e11 100644
--- a/src/core/cpu_recompiler_code_generator_x64.cpp
+++ b/src/core/cpu_recompiler_code_generator_x64.cpp
@@ -259,8 +259,8 @@ void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
         else
           hex.append("   ");
       }
-      Log::WriteFmt("HostCode", "", LOGLEVEL_DEBUG, "  {:016X} {} {}",
-                    static_cast<u64>(reinterpret_cast<uintptr_t>(ptr)), hex, buffer);
+      Log::FastWrite("HostCode", "", LOGLEVEL_DEBUG, "  {:016X} {} {}",
+                     static_cast<u64>(reinterpret_cast<uintptr_t>(ptr)), hex, buffer);
     }
 
     ptr += disas_instruction.length;
@@ -293,12 +293,12 @@ u32 CPU::CodeCache::GetHostInstructionCount(const void* start, u32 size)
 
 void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
 {
-  Log_ErrorPrint("Not compiled with ENABLE_HOST_DISASSEMBLY.");
+  ERROR_LOG("Not compiled with ENABLE_HOST_DISASSEMBLY.");
 }
 
 u32 CPU::CodeCache::GetHostInstructionCount(const void* start, u32 size)
 {
-  Log_ErrorPrint("Not compiled with ENABLE_HOST_DISASSEMBLY.");
+  ERROR_LOG("Not compiled with ENABLE_HOST_DISASSEMBLY.");
   return 0;
 }
 
@@ -2518,7 +2518,7 @@ void CodeGenerator::EmitUpdateFastmemBase()
 
 void CodeGenerator::BackpatchLoadStore(void* host_pc, const CodeCache::LoadstoreBackpatchInfo& lbi)
 {
-  Log_ProfileFmt("Backpatching {} (guest PC 0x{:08X}) to slowmem", host_pc, lbi.guest_pc);
+  DEV_LOG("Backpatching {} (guest PC 0x{:08X}) to slowmem", host_pc, lbi.guest_pc);
 
   // turn it into a jump to the slowmem handler
   Xbyak::CodeGenerator cg(lbi.code_size, host_pc);
diff --git a/src/core/cpu_recompiler_register_cache.cpp b/src/core/cpu_recompiler_register_cache.cpp
index ec3c1ed6a..5b1737cf7 100644
--- a/src/core/cpu_recompiler_register_cache.cpp
+++ b/src/core/cpu_recompiler_register_cache.cpp
@@ -231,21 +231,21 @@ bool RegisterCache::AllocateHostReg(HostReg reg, HostRegState state /*= HostRegS
 void RegisterCache::DiscardHostReg(HostReg reg)
 {
   DebugAssert(IsHostRegInUse(reg));
-  Log_DebugFmt("Discarding host register {}", m_code_generator.GetHostRegName(reg));
+  DEBUG_LOG("Discarding host register {}", m_code_generator.GetHostRegName(reg));
   m_state.host_reg_state[reg] |= HostRegState::Discarded;
 }
 
 void RegisterCache::UndiscardHostReg(HostReg reg)
 {
   DebugAssert(IsHostRegInUse(reg));
-  Log_DebugFmt("Undiscarding host register {}", m_code_generator.GetHostRegName(reg));
+  DEBUG_LOG("Undiscarding host register {}", m_code_generator.GetHostRegName(reg));
   m_state.host_reg_state[reg] &= ~HostRegState::Discarded;
 }
 
 void RegisterCache::FreeHostReg(HostReg reg)
 {
   DebugAssert(IsHostRegInUse(reg));
-  Log_DebugFmt("Freeing host register {}", m_code_generator.GetHostRegName(reg));
+  DEBUG_LOG("Freeing host register {}", m_code_generator.GetHostRegName(reg));
   m_state.host_reg_state[reg] &= ~HostRegState::InUse;
 }
 
@@ -279,7 +279,7 @@ Value RegisterCache::AllocateScratch(RegSize size, HostReg reg /* = HostReg_Inva
       Panic("Failed to allocate specific host register");
   }
 
-  Log_DebugFmt("Allocating host register {} as scratch", m_code_generator.GetHostRegName(reg));
+  DEBUG_LOG("Allocating host register {} as scratch", m_code_generator.GetHostRegName(reg));
   return Value::FromScratch(this, reg, size);
 }
 
@@ -542,8 +542,8 @@ Value RegisterCache::ReadGuestRegister(Reg guest_reg, bool cache /* = true */, b
         host_reg = forced_host_reg;
       }
 
-      Log_DebugFmt("Allocated host register {} for constant guest register {} (0x{:X})",
-                   m_code_generator.GetHostRegName(host_reg), GetRegName(guest_reg), cache_value.constant_value);
+      DEBUG_LOG("Allocated host register {} for constant guest register {} (0x{:X})",
+                m_code_generator.GetHostRegName(host_reg), GetRegName(guest_reg), cache_value.constant_value);
 
       m_code_generator.EmitCopyValue(host_reg, cache_value);
       cache_value.AddHostReg(this, host_reg);
@@ -576,8 +576,8 @@ Value RegisterCache::ReadGuestRegister(Reg guest_reg, bool cache /* = true */, b
 
   m_code_generator.EmitLoadGuestRegister(host_reg, guest_reg);
 
-  Log_DebugFmt("Loading guest register {} to host register {}{}", GetRegName(guest_reg),
-               m_code_generator.GetHostRegName(host_reg, RegSize_32), cache ? " (cached)" : "");
+  DEBUG_LOG("Loading guest register {} to host register {}{}", GetRegName(guest_reg),
+            m_code_generator.GetHostRegName(host_reg, RegSize_32), cache ? " (cached)" : "");
 
   if (cache)
   {
@@ -604,23 +604,22 @@ Value RegisterCache::ReadGuestRegisterToScratch(Reg guest_reg)
 
     if (cache_value.IsConstant())
     {
-      Log_DebugFmt("Copying guest register {} from constant 0x{:08X} to scratch host register {}",
-                   GetRegName(guest_reg), static_cast<u32>(cache_value.constant_value),
-                   m_code_generator.GetHostRegName(host_reg, RegSize_32));
+      DEBUG_LOG("Copying guest register {} from constant 0x{:08X} to scratch host register {}", GetRegName(guest_reg),
+                static_cast<u32>(cache_value.constant_value), m_code_generator.GetHostRegName(host_reg, RegSize_32));
     }
     else
     {
-      Log_DebugFmt("Copying guest register {} from {} to scratch host register {}", GetRegName(guest_reg),
-                   m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32),
-                   m_code_generator.GetHostRegName(host_reg, RegSize_32));
+      DEBUG_LOG("Copying guest register {} from {} to scratch host register {}", GetRegName(guest_reg),
+                m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32),
+                m_code_generator.GetHostRegName(host_reg, RegSize_32));
     }
   }
   else
   {
     m_code_generator.EmitLoadGuestRegister(host_reg, guest_reg);
 
-    Log_DebugFmt("Loading guest register {} to scratch host register {}", GetRegName(guest_reg),
-                 m_code_generator.GetHostRegName(host_reg, RegSize_32));
+    DEBUG_LOG("Loading guest register {} to scratch host register {}", GetRegName(guest_reg),
+              m_code_generator.GetHostRegName(host_reg, RegSize_32));
   }
 
   return Value::FromScratch(this, host_reg, RegSize_32);
@@ -636,7 +635,7 @@ Value RegisterCache::WriteGuestRegister(Reg guest_reg, Value&& value)
   // cancel any load delay delay
   if (m_state.load_delay_register == guest_reg)
   {
-    Log_DebugFmt("Cancelling load delay of register {} because of non-delayed write", GetRegName(guest_reg));
+    DEBUG_LOG("Cancelling load delay of register {} because of non-delayed write", GetRegName(guest_reg));
     m_state.load_delay_register = Reg::count;
     m_state.load_delay_value.ReleaseAndClear();
   }
@@ -645,8 +644,8 @@ Value RegisterCache::WriteGuestRegister(Reg guest_reg, Value&& value)
   if (cache_value.IsInHostRegister() && value.IsInHostRegister() && cache_value.host_reg == value.host_reg)
   {
     // updating the register value.
-    Log_DebugFmt("Updating guest register {} (in host register {})", GetRegName(guest_reg),
-                 m_code_generator.GetHostRegName(value.host_reg, RegSize_32));
+    DEBUG_LOG("Updating guest register {} (in host register {})", GetRegName(guest_reg),
+              m_code_generator.GetHostRegName(value.host_reg, RegSize_32));
     cache_value = std::move(value);
     cache_value.SetDirty();
     return cache_value;
@@ -668,8 +667,8 @@ Value RegisterCache::WriteGuestRegister(Reg guest_reg, Value&& value)
   // If it's a temporary, we can bind that to the guest register.
   if (value.IsScratch())
   {
-    Log_DebugFmt("Binding scratch register {} to guest register {}",
-                 m_code_generator.GetHostRegName(value.host_reg, RegSize_32), GetRegName(guest_reg));
+    DEBUG_LOG("Binding scratch register {} to guest register {}",
+              m_code_generator.GetHostRegName(value.host_reg, RegSize_32), GetRegName(guest_reg));
 
     cache_value = std::move(value);
     cache_value.flags &= ~ValueFlags::Scratch;
@@ -683,9 +682,9 @@ Value RegisterCache::WriteGuestRegister(Reg guest_reg, Value&& value)
   cache_value.SetHostReg(this, host_reg, RegSize_32);
   cache_value.SetDirty();
 
-  Log_DebugFmt("Copying non-scratch register {} to {} to guest register {}",
-               m_code_generator.GetHostRegName(value.host_reg, RegSize_32),
-               m_code_generator.GetHostRegName(host_reg, RegSize_32), GetRegName(guest_reg));
+  DEBUG_LOG("Copying non-scratch register {} to {} to guest register {}",
+            m_code_generator.GetHostRegName(value.host_reg, RegSize_32),
+            m_code_generator.GetHostRegName(host_reg, RegSize_32), GetRegName(guest_reg));
 
   return Value::FromHostReg(this, cache_value.host_reg, RegSize_32);
 }
@@ -700,7 +699,7 @@ void RegisterCache::WriteGuestRegisterDelayed(Reg guest_reg, Value&& value)
   // two load delays in a row? cancel the first one.
   if (guest_reg == m_state.load_delay_register)
   {
-    Log_DebugFmt("Cancelling load delay of register {} due to new load delay", GetRegName(guest_reg));
+    DEBUG_LOG("Cancelling load delay of register {} due to new load delay", GetRegName(guest_reg));
     m_state.load_delay_register = Reg::count;
     m_state.load_delay_value.ReleaseAndClear();
   }
@@ -716,8 +715,8 @@ void RegisterCache::WriteGuestRegisterDelayed(Reg guest_reg, Value&& value)
   // If it's a temporary, we can bind that to the guest register.
   if (value.IsScratch())
   {
-    Log_DebugFmt("Binding scratch register {} to load-delayed guest register {}",
-                 m_code_generator.GetHostRegName(value.host_reg, RegSize_32), GetRegName(guest_reg));
+    DEBUG_LOG("Binding scratch register {} to load-delayed guest register {}",
+              m_code_generator.GetHostRegName(value.host_reg, RegSize_32), GetRegName(guest_reg));
 
     cache_value = std::move(value);
     return;
@@ -727,9 +726,9 @@ void RegisterCache::WriteGuestRegisterDelayed(Reg guest_reg, Value&& value)
   cache_value = AllocateScratch(RegSize_32);
   m_code_generator.EmitCopyValue(cache_value.host_reg, value);
 
-  Log_DebugFmt("Copying non-scratch register {} to {} to load-delayed guest register {}",
-               m_code_generator.GetHostRegName(value.host_reg, RegSize_32),
-               m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32), GetRegName(guest_reg));
+  DEBUG_LOG("Copying non-scratch register {} to {} to load-delayed guest register {}",
+            m_code_generator.GetHostRegName(value.host_reg, RegSize_32),
+            m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32), GetRegName(guest_reg));
 }
 
 void RegisterCache::UpdateLoadDelay()
@@ -758,7 +757,7 @@ void RegisterCache::CancelLoadDelay()
   if (m_state.load_delay_register == Reg::count)
     return;
 
-  Log_DebugFmt("Cancelling load delay of register {}", GetRegName(m_state.load_delay_register));
+  DEBUG_LOG("Cancelling load delay of register {}", GetRegName(m_state.load_delay_register));
   m_state.load_delay_register = Reg::count;
   m_state.load_delay_value.ReleaseAndClear();
 }
@@ -769,7 +768,7 @@ void RegisterCache::WriteLoadDelayToCPU(bool clear)
   Assert(m_state.next_load_delay_register == Reg::count);
   if (m_state.load_delay_register != Reg::count)
   {
-    Log_DebugFmt("Flushing pending load delay of {}", GetRegName(m_state.load_delay_register));
+    DEBUG_LOG("Flushing pending load delay of {}", GetRegName(m_state.load_delay_register));
     m_code_generator.EmitStoreInterpreterLoadDelay(m_state.load_delay_register, m_state.load_delay_value);
     if (clear)
     {
@@ -804,13 +803,12 @@ void RegisterCache::FlushGuestRegister(Reg guest_reg, bool invalidate, bool clea
   {
     if (cache_value.IsInHostRegister())
     {
-      Log_DebugFmt("Flushing guest register {} from host register {}", GetRegName(guest_reg),
-                   m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32));
+      DEBUG_LOG("Flushing guest register {} from host register {}", GetRegName(guest_reg),
+                m_code_generator.GetHostRegName(cache_value.host_reg, RegSize_32));
     }
     else if (cache_value.IsConstant())
     {
-      Log_DebugFmt("Flushing guest register {} from constant 0x{:X}", GetRegName(guest_reg),
-                   cache_value.constant_value);
+      DEBUG_LOG("Flushing guest register {} from constant 0x{:X}", GetRegName(guest_reg), cache_value.constant_value);
     }
     m_code_generator.EmitStoreGuestRegister(guest_reg, cache_value);
     if (clear_dirty)
@@ -833,7 +831,7 @@ void RegisterCache::InvalidateGuestRegister(Reg guest_reg)
     ClearRegisterFromOrder(guest_reg);
   }
 
-  Log_DebugFmt("Invalidating guest register {}", GetRegName(guest_reg));
+  DEBUG_LOG("Invalidating guest register {}", GetRegName(guest_reg));
   cache_value.Clear();
 }
 
@@ -875,7 +873,7 @@ bool RegisterCache::EvictOneGuestRegister()
 
   // evict the register used the longest time ago
   Reg evict_reg = m_state.guest_reg_order[m_state.guest_reg_order_count - 1];
-  Log_DebugFmt("Evicting guest register {}", GetRegName(evict_reg));
+  DEBUG_LOG("Evicting guest register {}", GetRegName(evict_reg));
   FlushGuestRegister(evict_reg, true, true);
 
   return HasFreeHostRegister();
diff --git a/src/core/dma.cpp b/src/core/dma.cpp
index 838922997..93ca38419 100644
--- a/src/core/dma.cpp
+++ b/src/core/dma.cpp
@@ -307,20 +307,20 @@ u32 DMA::ReadRegister(u32 offset)
     {
       case 0x00:
       {
-        Log_TraceFmt("DMA[{}] base address -> 0x{:08X}", static_cast<Channel>(channel_index),
-                     s_state[channel_index].base_address);
+        TRACE_LOG("DMA[{}] base address -> 0x{:08X}", static_cast<Channel>(channel_index),
+                  s_state[channel_index].base_address);
         return s_state[channel_index].base_address;
       }
       case 0x04:
       {
-        Log_TraceFmt("DMA[{}] block control -> 0x{:08X}", static_cast<Channel>(channel_index),
-                     s_state[channel_index].block_control.bits);
+        TRACE_LOG("DMA[{}] block control -> 0x{:08X}", static_cast<Channel>(channel_index),
+                  s_state[channel_index].block_control.bits);
         return s_state[channel_index].block_control.bits;
       }
       case 0x08:
       {
-        Log_TraceFmt("DMA[{}] channel control -> 0x{:08X}", static_cast<Channel>(channel_index),
-                     s_state[channel_index].channel_control.bits);
+        TRACE_LOG("DMA[{}] channel control -> 0x{:08X}", static_cast<Channel>(channel_index),
+                  s_state[channel_index].channel_control.bits);
         return s_state[channel_index].channel_control.bits;
       }
       default:
@@ -331,17 +331,17 @@ u32 DMA::ReadRegister(u32 offset)
   {
     if (offset == 0x70)
     {
-      Log_TraceFmt("DPCR -> 0x{:08X}", s_DPCR.bits);
+      TRACE_LOG("DPCR -> 0x{:08X}", s_DPCR.bits);
       return s_DPCR.bits;
     }
     else if (offset == 0x74)
     {
-      Log_TraceFmt("DICR -> 0x{:08X}", s_DICR.bits);
+      TRACE_LOG("DICR -> 0x{:08X}", s_DICR.bits);
       return s_DICR.bits;
     }
   }
 
-  Log_ErrorFmt("Unhandled register read: {:02X}", offset);
+  ERROR_LOG("Unhandled register read: {:02X}", offset);
   return UINT32_C(0xFFFFFFFF);
 }
 
@@ -356,13 +356,12 @@ void DMA::WriteRegister(u32 offset, u32 value)
       case 0x00:
       {
         state.base_address = value & BASE_ADDRESS_MASK;
-        Log_TraceFmt("DMA channel {} base address <- 0x{:08X}", static_cast<Channel>(channel_index),
-                     state.base_address);
+        TRACE_LOG("DMA channel {} base address <- 0x{:08X}", static_cast<Channel>(channel_index), state.base_address);
         return;
       }
       case 0x04:
       {
-        Log_TraceFmt("DMA channel {} block control <- 0x{:08X}", static_cast<Channel>(channel_index), value);
+        TRACE_LOG("DMA channel {} block control <- 0x{:08X}", static_cast<Channel>(channel_index), value);
         state.block_control.bits = value;
         return;
       }
@@ -377,8 +376,8 @@ void DMA::WriteRegister(u32 offset, u32 value)
 
         state.channel_control.bits = (state.channel_control.bits & ~ChannelState::ChannelControl::WRITE_MASK) |
                                      (value & ChannelState::ChannelControl::WRITE_MASK);
-        Log_TraceFmt("DMA channel {} channel control <- 0x{:08X}", static_cast<Channel>(channel_index),
-                     state.channel_control.bits);
+        TRACE_LOG("DMA channel {} channel control <- 0x{:08X}", static_cast<Channel>(channel_index),
+                  state.channel_control.bits);
 
         // start/trigger bit must be enabled for OTC
         if (static_cast<Channel>(channel_index) == Channel::OTC)
@@ -399,8 +398,8 @@ void DMA::WriteRegister(u32 offset, u32 value)
             const TickCount delay_cycles = std::min(static_cast<TickCount>(cpu_cycles_per_block * blocks), 500);
             if (delay_cycles > 1 && true)
             {
-              Log_DevFmt("Delaying {} transfer by {} cycles due to chopping", static_cast<Channel>(channel_index),
-                         delay_cycles);
+              DEV_LOG("Delaying {} transfer by {} cycles due to chopping", static_cast<Channel>(channel_index),
+                      delay_cycles);
               HaltTransfer(delay_cycles);
             }
             else
@@ -426,7 +425,7 @@ void DMA::WriteRegister(u32 offset, u32 value)
     {
       case 0x70:
       {
-        Log_TraceFmt("DPCR <- 0x{:08X}", value);
+        TRACE_LOG("DPCR <- 0x{:08X}", value);
         s_DPCR.bits = value;
 
         for (u32 i = 0; i < NUM_CHANNELS; i++)
@@ -443,7 +442,7 @@ void DMA::WriteRegister(u32 offset, u32 value)
 
       case 0x74:
       {
-        Log_TraceFmt("DICR <- 0x{:08X}", value);
+        TRACE_LOG("DICR <- 0x{:08X}", value);
         s_DICR.bits = (s_DICR.bits & ~DICR_WRITE_MASK) | (value & DICR_WRITE_MASK);
         s_DICR.bits = s_DICR.bits & ~(value & DICR_RESET_MASK);
         UpdateIRQ();
@@ -455,7 +454,7 @@ void DMA::WriteRegister(u32 offset, u32 value)
     }
   }
 
-  Log_ErrorFmt("Unhandled register write: {:02X} <- {:08X}", offset, value);
+  ERROR_LOG("Unhandled register write: {:02X} <- {:08X}", offset, value);
 }
 
 void DMA::SetRequest(Channel channel, bool request)
@@ -504,7 +503,7 @@ void DMA::UpdateIRQ()
   [[maybe_unused]] const auto old_dicr = s_DICR;
   s_DICR.UpdateMasterFlag();
   if (!old_dicr.master_flag && s_DICR.master_flag)
-    Log_TracePrint("Firing DMA master interrupt");
+    TRACE_LOG("Firing DMA master interrupt");
   InterruptController::SetLineState(InterruptController::IRQ::DMA, s_DICR.master_flag);
 }
 
@@ -519,7 +518,7 @@ ALWAYS_INLINE_RELEASE bool DMA::CheckForBusError(Channel channel, ChannelState&
   // Relying on a transfer partially happening at the end of RAM, then hitting a bus error would be pretty silly.
   if ((address + size) > Bus::RAM_8MB_SIZE) [[unlikely]]
   {
-    Log_DebugFmt("DMA bus error on channel {} at address 0x{:08X} size {}", channel, address, size);
+    DEBUG_LOG("DMA bus error on channel {} at address 0x{:08X} size {}", channel, address, size);
     cs.channel_control.enable_busy = false;
     s_DICR.bus_error = true;
     s_DICR.SetIRQFlag(channel);
@@ -533,11 +532,11 @@ ALWAYS_INLINE_RELEASE bool DMA::CheckForBusError(Channel channel, ChannelState&
 ALWAYS_INLINE_RELEASE void DMA::CompleteTransfer(Channel channel, ChannelState& cs)
 {
   // start/busy bit is cleared on end of transfer
-  Log_DebugFmt("DMA transfer for channel {} complete", channel);
+  DEBUG_LOG("DMA transfer for channel {} complete", channel);
   cs.channel_control.enable_busy = false;
   if (s_DICR.ShouldSetIRQFlag(channel))
   {
-    Log_DebugFmt("Setting DMA interrupt for channel {}", channel);
+    DEBUG_LOG("Setting DMA interrupt for channel {}", channel);
     s_DICR.SetIRQFlag(channel);
     UpdateIRQ();
   }
@@ -571,8 +570,8 @@ bool DMA::TransferChannel()
     case SyncMode::Manual:
     {
       const u32 word_count = cs.block_control.manual.GetWordCount();
-      Log_DebugFmt("DMA[{}]: Copying {} words {} 0x{:08X}", channel, word_count, copy_to_device ? "from" : "to",
-                   current_address);
+      DEBUG_LOG("DMA[{}]: Copying {} words {} 0x{:08X}", channel, word_count, copy_to_device ? "from" : "to",
+                current_address);
 
       const PhysicalMemoryAddress transfer_addr = current_address & TRANSFER_ADDRESS_MASK;
       if (CheckForBusError(channel, cs, transfer_addr, word_count * sizeof(u32))) [[unlikely]]
@@ -597,7 +596,7 @@ bool DMA::TransferChannel()
         return true;
       }
 
-      Log_DebugFmt("DMA[{}]: Copying linked list starting at 0x{:08X} to device", channel, current_address);
+      DEBUG_LOG("DMA[{}]: Copying linked list starting at 0x{:08X} to device", channel, current_address);
 
       // Prove to the compiler that nothing's going to modify these.
       const u8* const ram_ptr = Bus::g_ram;
@@ -618,8 +617,8 @@ bool DMA::TransferChannel()
         std::memcpy(&header, &ram_ptr[transfer_addr & mask], sizeof(header));
         const u32 word_count = header >> 24;
         const u32 next_address = header & 0x00FFFFFFu;
-        Log_TraceFmt(" .. linked list entry at 0x{:08X} size={}({} words) next=0x{:08X}", current_address,
-                     word_count * 4, word_count, next_address);
+        TRACE_LOG(" .. linked list entry at 0x{:08X} size={}({} words) next=0x{:08X}", current_address, word_count * 4,
+                  word_count, next_address);
 
         const TickCount setup_ticks = (word_count > 0) ?
                                         (LINKED_LIST_HEADER_READ_TICKS + LINKED_LIST_BLOCK_SETUP_TICKS) :
@@ -660,10 +659,10 @@ bool DMA::TransferChannel()
 
     case SyncMode::Request:
     {
-      Log_DebugFmt("DMA[{}]: Copying {} blocks of size {} ({} total words) {} 0x{:08X}", channel,
-                   cs.block_control.request.GetBlockCount(), cs.block_control.request.GetBlockSize(),
-                   cs.block_control.request.GetBlockCount() * cs.block_control.request.GetBlockSize(),
-                   copy_to_device ? "from" : "to", current_address);
+      DEBUG_LOG("DMA[{}]: Copying {} blocks of size {} ({} total words) {} 0x{:08X}", channel,
+                cs.block_control.request.GetBlockCount(), cs.block_control.request.GetBlockSize(),
+                cs.block_control.request.GetBlockCount() * cs.block_control.request.GetBlockSize(),
+                copy_to_device ? "from" : "to", current_address);
 
       const u32 block_size = cs.block_control.request.GetBlockSize();
       u32 blocks_remaining = cs.block_control.request.GetBlockCount();
@@ -744,7 +743,7 @@ bool DMA::TransferChannel()
 void DMA::HaltTransfer(TickCount duration)
 {
   s_halt_ticks_remaining += duration;
-  Log_DebugFmt("Halting DMA for {} ticks", s_halt_ticks_remaining);
+  DEBUG_LOG("Halting DMA for {} ticks", s_halt_ticks_remaining);
   if (s_unhalt_event->IsActive())
     return;
 
@@ -754,7 +753,7 @@ void DMA::HaltTransfer(TickCount duration)
 
 void DMA::UnhaltTransfer(void*, TickCount ticks, TickCount ticks_late)
 {
-  Log_DebugFmt("Resuming DMA after {} ticks, {} ticks late", ticks, -(s_halt_ticks_remaining - ticks));
+  DEBUG_LOG("Resuming DMA after {} ticks, {} ticks late", ticks, -(s_halt_ticks_remaining - ticks));
   s_halt_ticks_remaining -= ticks;
   s_unhalt_event->Deactivate();
 
@@ -779,7 +778,7 @@ TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count
   const u32 mask = Bus::g_ram_mask;
 #ifdef _DEBUG
   if ((address & mask) != address)
-    Log_DebugFmt("DMA TO {} from masked RAM address 0x{:08X} => 0x{:08X}", channel, address, (address & mask));
+    DEBUG_LOG("DMA TO {} from masked RAM address 0x{:08X} => 0x{:08X}", channel, address, (address & mask));
 #endif
 
   address &= mask;
@@ -834,7 +833,7 @@ TickCount DMA::TransferMemoryToDevice(u32 address, u32 increment, u32 word_count
     case Channel::MDECout:
     case Channel::PIO:
     default:
-      Log_ErrorFmt("Unhandled DMA channel {} for device write", static_cast<u32>(channel));
+      ERROR_LOG("Unhandled DMA channel {} for device write", static_cast<u32>(channel));
       break;
   }
 
@@ -847,7 +846,7 @@ TickCount DMA::TransferDeviceToMemory(u32 address, u32 increment, u32 word_count
   const u32 mask = Bus::g_ram_mask;
 #ifdef _DEBUG
   if ((address & mask) != address)
-    Log_DebugFmt("DMA FROM {} to masked RAM address 0x{:08X} => 0x{:08X}", channel, address, (address & mask));
+    DEBUG_LOG("DMA FROM {} to masked RAM address 0x{:08X} => 0x{:08X}", channel, address, (address & mask));
 #endif
 
   // TODO: This might not be correct for OTC.
@@ -899,7 +898,7 @@ TickCount DMA::TransferDeviceToMemory(u32 address, u32 increment, u32 word_count
       break;
 
     default:
-      Log_ErrorFmt("Unhandled DMA channel {} for device read", static_cast<u32>(channel));
+      ERROR_LOG("Unhandled DMA channel {} for device read", static_cast<u32>(channel));
       std::fill_n(dest_pointer, word_count, UINT32_C(0xFFFFFFFF));
       break;
   }
diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp
index 6de0e10e3..87f323e28 100644
--- a/src/core/fullscreen_ui.cpp
+++ b/src/core/fullscreen_ui.cpp
@@ -5491,7 +5491,7 @@ void FullscreenUI::PopulateSaveStateScreenshot(SaveStateListEntry* li, const Ext
   }
 
   if (!li->preview_texture)
-    Log_ErrorPrint("Failed to upload save state image to GPU");
+    ERROR_LOG("Failed to upload save state image to GPU");
 }
 
 void FullscreenUI::ClearSaveStateEntryList()
diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp
index dd0e7b8be..342c14d27 100644
--- a/src/core/game_database.cpp
+++ b/src/core/game_database.cpp
@@ -165,14 +165,14 @@ static bool GetUIntFromObject(const ryml::ConstNodeRef& object, std::string_view
   const c4::csubstr val = member.val();
   if (val.empty())
   {
-    Log_ErrorFmt("Unexpected empty value in {}", key);
+    ERROR_LOG("Unexpected empty value in {}", key);
     return false;
   }
 
   const std::optional<T> opt_value = StringUtil::FromChars<T>(to_stringview(val));
   if (!opt_value.has_value())
   {
-    Log_ErrorFmt("Unexpected non-uint value in {}", key);
+    ERROR_LOG("Unexpected non-uint value in {}", key);
     return false;
   }
 
@@ -195,14 +195,14 @@ static std::optional<T> GetOptionalTFromObject(const ryml::ConstNodeRef& object,
       if (!ret.has_value())
       {
         if constexpr (std::is_floating_point_v<T>)
-          Log_ErrorFmt("Unexpected non-float value in {}", key);
+          ERROR_LOG("Unexpected non-float value in {}", key);
         else if constexpr (std::is_integral_v<T>)
-          Log_ErrorFmt("Unexpected non-int value in {}", key);
+          ERROR_LOG("Unexpected non-int value in {}", key);
       }
     }
     else
     {
-      Log_ErrorFmt("Unexpected empty value in {}", key);
+      ERROR_LOG("Unexpected empty value in {}", key);
     }
   }
 
@@ -223,11 +223,11 @@ static std::optional<T> ParseOptionalTFromObject(const ryml::ConstNodeRef& objec
     {
       ret = from_string_function(TinyString(to_stringview(val)));
       if (!ret.has_value())
-        Log_ErrorFmt("Unknown value for {}: {}", key, to_stringview(val));
+        ERROR_LOG("Unknown value for {}: {}", key, to_stringview(val));
     }
     else
     {
-      Log_ErrorFmt("Unexpected empty value in {}", key);
+      ERROR_LOG("Unexpected empty value in {}", key);
     }
   }
 
@@ -252,7 +252,7 @@ void GameDatabase::EnsureLoaded()
     SaveToCache();
   }
 
-  Log_InfoFmt("Database load of {} entries took {:.0f}ms.", s_entries.size(), timer.GetTimeMilliseconds());
+  INFO_LOG("Database load of {} entries took {:.0f}ms.", s_entries.size(), timer.GetTimeMilliseconds());
 }
 
 void GameDatabase::Unload()
@@ -307,7 +307,7 @@ const GameDatabase::Entry* GameDatabase::GetEntryForDisc(CDImage* image)
   if (entry)
     return entry;
 
-  Log_WarningFmt("No entry found for disc '{}'", id);
+  WARNING_LOG("No entry found for disc '{}'", id);
   return nullptr;
 }
 
@@ -636,19 +636,19 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
 
   if (HasTrait(Trait::ForceRecompilerMemoryExceptions))
   {
-    Log_WarningPrint("Memory exceptions for recompiler forced by compatibility settings.");
+    WARNING_LOG("Memory exceptions for recompiler forced by compatibility settings.");
     settings.cpu_recompiler_memory_exceptions = true;
   }
 
   if (HasTrait(Trait::ForceRecompilerICache))
   {
-    Log_WarningPrint("ICache for recompiler forced by compatibility settings.");
+    WARNING_LOG("ICache for recompiler forced by compatibility settings.");
     settings.cpu_recompiler_icache = true;
   }
 
   if (settings.cpu_fastmem_mode == CPUFastmemMode::MMap && HasTrait(Trait::ForceRecompilerLUTFastmem))
   {
-    Log_WarningPrint("LUT fastmem for recompiler forced by compatibility settings.");
+    WARNING_LOG("LUT fastmem for recompiler forced by compatibility settings.");
     settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
   }
 
@@ -861,7 +861,7 @@ bool GameDatabase::LoadFromCache()
     ByteStream::OpenFile(GetCacheFile().c_str(), BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED));
   if (!stream)
   {
-    Log_DevPrint("Cache does not exist, loading full database.");
+    DEV_LOG("Cache does not exist, loading full database.");
     return false;
   }
 
@@ -873,13 +873,13 @@ bool GameDatabase::LoadFromCache()
       !stream->ReadU32(&num_entries) || !stream->ReadU32(&num_codes) || signature != GAME_DATABASE_CACHE_SIGNATURE ||
       version != GAME_DATABASE_CACHE_VERSION)
   {
-    Log_DevPrint("Cache header is corrupted or version mismatch.");
+    DEV_LOG("Cache header is corrupted or version mismatch.");
     return false;
   }
 
   if (gamedb_ts != file_gamedb_ts)
   {
-    Log_DevPrint("Cache is out of date, recreating.");
+    DEV_LOG("Cache is out of date, recreating.");
     return false;
   }
 
@@ -917,7 +917,7 @@ bool GameDatabase::LoadFromCache()
         !ReadOptionalFromStream(stream.get(), &entry.gpu_line_detect_mode) ||
         !stream->ReadSizePrefixedString(&entry.disc_set_name) || !stream->ReadU32(&num_disc_set_serials))
     {
-      Log_DevPrint("Cache entry is corrupted.");
+      DEV_LOG("Cache entry is corrupted.");
       return false;
     }
 
@@ -928,7 +928,7 @@ bool GameDatabase::LoadFromCache()
       {
         if (!stream->ReadSizePrefixedString(&entry.disc_set_serials.emplace_back()))
         {
-          Log_DevPrint("Cache entry is corrupted.");
+          DEV_LOG("Cache entry is corrupted.");
           return false;
         }
       }
@@ -950,7 +950,7 @@ bool GameDatabase::LoadFromCache()
     if (!stream->ReadSizePrefixedString(&code) || !stream->ReadU32(&index) ||
         index >= static_cast<u32>(s_entries.size()))
     {
-      Log_DevPrint("Cache code entry is corrupted.");
+      DEV_LOG("Cache code entry is corrupted.");
       return false;
     }
 
@@ -1037,11 +1037,11 @@ void GameDatabase::SetRymlCallbacks()
 {
   ryml::Callbacks callbacks = ryml::get_callbacks();
   callbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void* userdata) {
-    Log_ErrorFmt("Parse error at {}:{} (bufpos={}): {}", loc.line, loc.col, loc.offset, std::string_view(msg, msg_len));
+    ERROR_LOG("Parse error at {}:{} (bufpos={}): {}", loc.line, loc.col, loc.offset, std::string_view(msg, msg_len));
   };
   ryml::set_callbacks(callbacks);
   c4::set_error_callback(
-    [](const char* msg, size_t msg_size) { Log_ErrorFmt("C4 error: {}", std::string_view(msg, msg_size)); });
+    [](const char* msg, size_t msg_size) { ERROR_LOG("C4 error: {}", std::string_view(msg, msg_size)); });
 }
 
 bool GameDatabase::LoadGameDBYaml()
@@ -1049,7 +1049,7 @@ bool GameDatabase::LoadGameDBYaml()
   const std::optional<std::string> gamedb_data = Host::ReadResourceFileToString(GAMEDB_YAML_FILENAME, false);
   if (!gamedb_data.has_value())
   {
-    Log_ErrorPrint("Failed to read game database");
+    ERROR_LOG("Failed to read game database");
     return false;
   }
 
@@ -1082,7 +1082,7 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
   entry->serial = to_stringview(value.key());
   if (entry->serial.empty())
   {
-    Log_ErrorPrint("Missing serial for entry.");
+    ERROR_LOG("Missing serial for entry.");
     return false;
   }
 
@@ -1131,14 +1131,14 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
       const std::string_view controller_str = to_stringview(controller.val());
       if (controller_str.empty())
       {
-        Log_WarningFmt("controller is not a string in {}", entry->serial);
+        WARNING_LOG("controller is not a string in {}", entry->serial);
         return false;
       }
 
       const Controller::ControllerInfo* cinfo = Controller::GetControllerInfo(controller_str);
       if (!cinfo)
       {
-        Log_WarningFmt("Invalid controller type {} in {}", controller_str, entry->serial);
+        WARNING_LOG("Invalid controller type {} in {}", controller_str, entry->serial);
         continue;
       }
 
@@ -1169,7 +1169,7 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
       }
       else
       {
-        Log_WarningFmt("Unknown compatibility rating {} in {}", rating_str, entry->serial);
+        WARNING_LOG("Unknown compatibility rating {} in {}", rating_str, entry->serial);
       }
     }
 
@@ -1184,14 +1184,14 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
       const std::string_view trait_str = to_stringview(trait.val());
       if (trait_str.empty())
       {
-        Log_WarningFmt("Empty trait in {}", entry->serial);
+        WARNING_LOG("Empty trait in {}", entry->serial);
         continue;
       }
 
       const auto iter = std::find(s_trait_names.begin(), s_trait_names.end(), trait_str);
       if (iter == s_trait_names.end())
       {
-        Log_WarningFmt("Unknown trait {} in {}", trait_str, entry->serial);
+        WARNING_LOG("Unknown trait {} in {}", trait_str, entry->serial);
         continue;
       }
 
@@ -1210,7 +1210,7 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
     }
     else
     {
-      Log_WarningFmt("Invalid libcrypt value in {}", entry->serial);
+      WARNING_LOG("Invalid libcrypt value in {}", entry->serial);
     }
   }
 
@@ -1244,14 +1244,14 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
         const std::string_view serial_str = to_stringview(serial.val());
         if (serial_str.empty())
         {
-          Log_WarningFmt("Empty disc set serial in {}", entry->serial);
+          WARNING_LOG("Empty disc set serial in {}", entry->serial);
           continue;
         }
 
         if (std::find(entry->disc_set_serials.begin(), entry->disc_set_serials.end(), serial_str) !=
             entry->disc_set_serials.end())
         {
-          Log_WarningFmt("Duplicate serial {} in disc set serials for {}", serial_str, entry->serial);
+          WARNING_LOG("Duplicate serial {} in disc set serials for {}", serial_str, entry->serial);
           continue;
         }
 
@@ -1272,7 +1272,7 @@ bool GameDatabase::ParseYamlCodes(u32 index, const ryml::ConstNodeRef& value, st
     auto iter = s_code_lookup.find(serial);
     if (iter != s_code_lookup.end())
     {
-      Log_WarningFmt("Duplicate code '{}'", serial);
+      WARNING_LOG("Duplicate code '{}'", serial);
       return false;
     }
 
@@ -1286,14 +1286,14 @@ bool GameDatabase::ParseYamlCodes(u32 index, const ryml::ConstNodeRef& value, st
     const std::string_view current_code_str = to_stringview(current_code.val());
     if (current_code_str.empty())
     {
-      Log_WarningFmt("code is not a string in {}", serial);
+      WARNING_LOG("code is not a string in {}", serial);
       continue;
     }
 
     auto iter = s_code_lookup.find(current_code_str);
     if (iter != s_code_lookup.end())
     {
-      Log_WarningFmt("Duplicate code '{}' in {}", current_code_str, serial);
+      WARNING_LOG("Duplicate code '{}' in {}", current_code_str, serial);
       continue;
     }
 
@@ -1320,7 +1320,7 @@ bool GameDatabase::LoadTrackHashes()
   std::optional<std::string> gamedb_data(Host::ReadResourceFileToString(DISCDB_YAML_FILENAME, false));
   if (!gamedb_data.has_value())
   {
-    Log_ErrorPrint("Failed to read game database");
+    ERROR_LOG("Failed to read game database");
     return false;
   }
 
@@ -1338,14 +1338,14 @@ bool GameDatabase::LoadTrackHashes()
     const std::string_view serial = to_stringview(current.key());
     if (serial.empty() || !current.has_children())
     {
-      Log_WarningPrint("entry is not an object");
+      WARNING_LOG("entry is not an object");
       continue;
     }
 
     const ryml::ConstNodeRef track_data = current.find_child(to_csubstr("trackData"));
     if (!track_data.valid() || !track_data.has_children())
     {
-      Log_WarningFmt("trackData is missing in {}", serial);
+      WARNING_LOG("trackData is missing in {}", serial);
       continue;
     }
 
@@ -1355,7 +1355,7 @@ bool GameDatabase::LoadTrackHashes()
       const ryml::ConstNodeRef tracks = track_revisions.find_child(to_csubstr("tracks"));
       if (!tracks.valid() || !tracks.has_children())
       {
-        Log_WarningFmt("tracks member is missing in {}", serial);
+        WARNING_LOG("tracks member is missing in {}", serial);
         continue;
       }
 
@@ -1368,7 +1368,7 @@ bool GameDatabase::LoadTrackHashes()
         std::string_view md5_str;
         if (!md5.valid() || (md5_str = to_stringview(md5.val())).empty())
         {
-          Log_WarningFmt("md5 is missing in track in {}", serial);
+          WARNING_LOG("md5 is missing in track in {}", serial);
           continue;
         }
 
@@ -1380,7 +1380,7 @@ bool GameDatabase::LoadTrackHashes()
         }
         else
         {
-          Log_WarningFmt("invalid md5 in {}", serial);
+          WARNING_LOG("invalid md5 in {}", serial);
         }
       }
       revision++;
@@ -1390,8 +1390,8 @@ bool GameDatabase::LoadTrackHashes()
   }
 
   ryml::reset_callbacks();
-  Log_InfoFmt("Loaded {} track hashes from {} serials in {:.0f}ms.", s_track_hashes_map.size(), serials,
-              load_timer.GetTimeMilliseconds());
+  INFO_LOG("Loaded {} track hashes from {} serials in {:.0f}ms.", s_track_hashes_map.size(), serials,
+           load_timer.GetTimeMilliseconds());
   return !s_track_hashes_map.empty();
 }
 
diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp
index 5e11a35de..ce87f415f 100644
--- a/src/core/game_list.cpp
+++ b/src/core/game_list.cpp
@@ -151,7 +151,7 @@ bool GameList::GetExeListEntry(const std::string& path, GameList::Entry* entry)
 
   if (!BIOS::IsValidPSExeHeader(header, file_size))
   {
-    Log_WarningFmt("{} is not a valid PS-EXE", path);
+    WARNING_LOG("{} is not a valid PS-EXE", path);
     return false;
   }
 
@@ -286,7 +286,7 @@ bool GameList::GetDiscListEntry(const std::string& path, Entry* entry)
     {
       if (!cdi->SwitchSubImage(i, nullptr))
       {
-        Log_ErrorFmt("Failed to switch to subimage {} in '{}'", i, entry->path);
+        ERROR_LOG("Failed to switch to subimage {} in '{}'", i, entry->path);
         continue;
       }
 
@@ -323,7 +323,7 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
   if (!stream->ReadU32(&file_signature) || !stream->ReadU32(&file_version) ||
       file_signature != GAME_LIST_CACHE_SIGNATURE || file_version != GAME_LIST_CACHE_VERSION)
   {
-    Log_WarningPrint("Game list cache is corrupted");
+    WARNING_LOG("Game list cache is corrupted");
     return false;
   }
 
@@ -348,7 +348,7 @@ bool GameList::LoadEntriesFromCache(ByteStream* stream)
         region >= static_cast<u8>(DiscRegion::Count) || type >= static_cast<u8>(EntryType::Count) ||
         compatibility_rating >= static_cast<u8>(GameDatabase::CompatibilityRating::Count))
     {
-      Log_WarningPrint("Game list cache entry is corrupted");
+      WARNING_LOG("Game list cache entry is corrupted");
       return false;
     }
 
@@ -409,7 +409,7 @@ void GameList::LoadCache()
 
   if (!LoadEntriesFromCache(stream.get()))
   {
-    Log_WarningFmt("Deleting corrupted cache file '{}'", Path::GetFileName(filename));
+    WARNING_LOG("Deleting corrupted cache file '{}'", Path::GetFileName(filename));
     stream.reset();
     s_cache_map.clear();
     DeleteCacheFile();
@@ -438,7 +438,7 @@ bool GameList::OpenCacheForWriting()
     s_cache_write_stream.reset();
   }
 
-  Log_InfoFmt("Creating new game list cache file: '{}'", Path::GetFileName(cache_filename));
+  INFO_LOG("Creating new game list cache file: '{}'", Path::GetFileName(cache_filename));
 
   s_cache_write_stream = ByteStream::OpenFile(
     cache_filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_TRUNCATE | BYTESTREAM_OPEN_WRITE);
@@ -449,7 +449,7 @@ bool GameList::OpenCacheForWriting()
   if (!s_cache_write_stream->WriteU32(GAME_LIST_CACHE_SIGNATURE) ||
       !s_cache_write_stream->WriteU32(GAME_LIST_CACHE_VERSION))
   {
-    Log_ErrorPrint("Failed to write game list cache header");
+    ERROR_LOG("Failed to write game list cache header");
     s_cache_write_stream.reset();
     FileSystem::DeleteFile(cache_filename.c_str());
     return false;
@@ -477,9 +477,9 @@ void GameList::DeleteCacheFile()
 
   Error error;
   if (FileSystem::DeleteFile(filename.c_str(), &error))
-    Log_InfoFmt("Deleted game list cache '{}'", Path::GetFileName(filename));
+    INFO_LOG("Deleted game list cache '{}'", Path::GetFileName(filename));
   else
-    Log_WarningFmt("Failed to delete game list cache '{}': {}", Path::GetFileName(filename), error.GetDescription());
+    WARNING_LOG("Failed to delete game list cache '{}': {}", Path::GetFileName(filename), error.GetDescription());
 }
 
 static bool IsPathExcluded(const std::vector<std::string>& excluded_paths, const std::string& path)
@@ -492,7 +492,7 @@ void GameList::ScanDirectory(const char* path, bool recursive, bool only_cache,
                              const std::vector<std::string>& excluded_paths, const PlayedTimeMap& played_time_map,
                              ProgressCallback* progress)
 {
-  Log_InfoFmt("Scanning {}{}", path, recursive ? " (recursively)" : "");
+  INFO_LOG("Scanning {}{}", path, recursive ? " (recursively)" : "");
 
   progress->SetStatusText(SmallString::from_format(TRANSLATE_FS("GameList", "Scanning directory '{}'..."), path));
 
@@ -559,7 +559,7 @@ bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_loc
   // don't block UI while scanning
   lock.unlock();
 
-  Log_DevFmt("Scanning '{}'...", path);
+  DEV_LOG("Scanning '{}'...", path);
 
   Entry entry;
   if (!PopulateEntryFromPath(path, &entry))
@@ -571,7 +571,7 @@ bool GameList::ScanFile(std::string path, std::time_t timestamp, std::unique_loc
   if (s_cache_write_stream || OpenCacheForWriting())
   {
     if (!WriteEntryToCache(&entry)) [[unlikely]]
-      Log_WarningFmt("Failed to write entry '{}' to cache", entry.path);
+      WARNING_LOG("Failed to write entry '{}' to cache", entry.path);
   }
 
   auto iter = played_time_map.find(entry.serial);
@@ -781,7 +781,7 @@ void GameList::CreateDiscSetEntries(const PlayedTimeMap& played_time_map)
     }
     if (!found_another_disc)
     {
-      Log_DevFmt("Not creating disc set {}, only one disc found", disc_set_name);
+      DEV_LOG("Not creating disc set {}, only one disc found", disc_set_name);
       continue;
     }
 
@@ -834,7 +834,7 @@ void GameList::CreateDiscSetEntries(const PlayedTimeMap& played_time_map)
         continue;
       }
 
-      Log_DevFmt("Adding {} to disc set {}", Path::GetFileName(other_entry.path), disc_set_name);
+      DEV_LOG("Adding {} to disc set {}", Path::GetFileName(other_entry.path), disc_set_name);
       other_entry.disc_set_member = true;
       set_entry.last_modified_time = std::min(set_entry.last_modified_time, other_entry.last_modified_time);
       set_entry.file_size += other_entry.file_size;
@@ -842,7 +842,7 @@ void GameList::CreateDiscSetEntries(const PlayedTimeMap& played_time_map)
       num_parts++;
     }
 
-    Log_DevFmt("Created disc set {} from {} entries", disc_set_name, num_parts);
+    DEV_LOG("Created disc set {} from {} entries", disc_set_name, num_parts);
 
     // entry is done :)
     s_entries.push_back(std::move(set_entry));
@@ -947,7 +947,7 @@ bool GameList::ParsePlayedTimeLine(char* line, std::string& serial, PlayedTimeEn
   size_t len = std::strlen(line);
   if (len != (PLAYED_TIME_LINE_LENGTH + 1)) // \n
   {
-    Log_WarningFmt("Malformed line: '{}'", line);
+    WARNING_LOG("Malformed line: '{}'", line);
     return false;
   }
 
@@ -961,7 +961,7 @@ bool GameList::ParsePlayedTimeLine(char* line, std::string& serial, PlayedTimeEn
   const std::optional<u64> last_played_time(StringUtil::FromChars<u64>(last_played_time_tok));
   if (serial_tok.empty() || !last_played_time.has_value() || !total_played_time.has_value())
   {
-    Log_WarningFmt("Malformed line: '{}'", line);
+    WARNING_LOG("Malformed line: '{}'", line);
     return false;
   }
 
@@ -1010,7 +1010,7 @@ GameList::PlayedTimeMap GameList::LoadPlayedTimeMap(const std::string& path)
 
       if (ret.find(serial) != ret.end())
       {
-        Log_WarningFmt("Duplicate entry: '%s'", serial);
+        WARNING_LOG("Duplicate entry: '%s'", serial);
         continue;
       }
 
@@ -1043,7 +1043,7 @@ GameList::PlayedTimeEntry GameList::UpdatePlayedTimeFile(const std::string& path
 
   if (!fp)
   {
-    Log_ErrorFmt("Failed to open '{}' for update.", path);
+    ERROR_LOG("Failed to open '{}' for update.", path);
     return new_entry;
   }
 
@@ -1074,7 +1074,7 @@ GameList::PlayedTimeEntry GameList::UpdatePlayedTimeFile(const std::string& path
     if (FileSystem::FSeek64(fp.get(), line_pos, SEEK_SET) != 0 ||
         std::fwrite(new_line.data(), new_line.length(), 1, fp.get()) != 1)
     {
-      Log_ErrorFmt("Failed to update '%s'.", path);
+      ERROR_LOG("Failed to update '%s'.", path);
     }
 
     return line_entry;
@@ -1087,7 +1087,7 @@ GameList::PlayedTimeEntry GameList::UpdatePlayedTimeFile(const std::string& path
     if (FileSystem::FSeek64(fp.get(), 0, SEEK_END) != 0 ||
         std::fwrite(new_line.data(), new_line.length(), 1, fp.get()) != 1)
     {
-      Log_ErrorFmt("Failed to write '%s'.", path);
+      ERROR_LOG("Failed to write '%s'.", path);
     }
   }
 
@@ -1100,8 +1100,8 @@ void GameList::AddPlayedTimeForSerial(const std::string& serial, std::time_t las
     return;
 
   const PlayedTimeEntry pt(UpdatePlayedTimeFile(GetPlayedTimeFile(), serial, last_time, add_time));
-  Log_VerboseFmt("Add {} seconds play time to {} -> now {}", static_cast<unsigned>(add_time), serial.c_str(),
-                 static_cast<unsigned>(pt.total_played_time));
+  VERBOSE_LOG("Add {} seconds play time to {} -> now {}", static_cast<unsigned>(add_time), serial.c_str(),
+              static_cast<unsigned>(pt.total_played_time));
 
   std::unique_lock<std::recursive_mutex> lock(s_mutex);
   for (GameList::Entry& entry : s_entries)
diff --git a/src/core/gdb_protocol.cpp b/src/core/gdb_protocol.cpp
index 2fbba630d..8e79bfcd9 100644
--- a/src/core/gdb_protocol.cpp
+++ b/src/core/gdb_protocol.cpp
@@ -167,7 +167,7 @@ static std::optional<std::string> Cmd$G(std::string_view data)
   }
   else
   {
-    Log_ErrorFmt("Wrong payload size for 'G' command, expected {} got {}", NUM_GDB_REGISTERS * 8, data.size());
+    ERROR_LOG("Wrong payload size for 'G' command, expected {} got {}", NUM_GDB_REGISTERS * 8, data.size());
   }
 
   return {""};
@@ -308,7 +308,7 @@ std::string ProcessPacket(std::string_view data)
   {
     if (trimmedData[0] == '-')
     {
-      Log_ErrorPrint("Received negative ack");
+      ERROR_LOG("Received negative ack");
     }
     trimmedData = trimmedData.substr(1);
   }
@@ -317,7 +317,7 @@ std::string ProcessPacket(std::string_view data)
   auto packet = DeserializePacket(trimmedData);
   if (!packet)
   {
-    Log_ErrorFmt("Malformed packet '{}'", trimmedData);
+    ERROR_LOG("Malformed packet '{}'", trimmedData);
     return "-";
   }
 
@@ -329,7 +329,7 @@ std::string ProcessPacket(std::string_view data)
   {
     if (packet->starts_with(command.first))
     {
-      Log_DebugFmt("Processing command '{}'", command.first);
+      DEBUG_LOG("Processing command '{}'", command.first);
 
       // Invoke command, remove command name from payload.
       reply = command.second(packet->substr(strlen(command.first)));
@@ -339,7 +339,7 @@ std::string ProcessPacket(std::string_view data)
   }
 
   if (!processed)
-    Log_WarningFmt("Failed to process packet '{}'", trimmedData);
+    WARNING_LOG("Failed to process packet '{}'", trimmedData);
 
   return reply ? "+" + SerializePacket(*reply) : "+";
 }
diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp
index 6fe8231ba..c411bb3bb 100644
--- a/src/core/gpu.cpp
+++ b/src/core/gpu.cpp
@@ -486,7 +486,7 @@ u32 GPU::ReadRegister(u32 offset)
     }
 
     default:
-      Log_ErrorFmt("Unhandled register read: {:02X}", offset);
+      ERROR_LOG("Unhandled register read: {:02X}", offset);
       return UINT32_C(0xFFFFFFFF);
   }
 }
@@ -505,7 +505,7 @@ void GPU::WriteRegister(u32 offset, u32 value)
       return;
 
     default:
-      Log_ErrorFmt("Unhandled register write: {:02X} <- {:08X}", offset, value);
+      ERROR_LOG("Unhandled register write: {:02X} <- {:08X}", offset, value);
       return;
   }
 }
@@ -514,7 +514,7 @@ void GPU::DMARead(u32* words, u32 word_count)
 {
   if (m_GPUSTAT.dma_direction != DMADirection::GPUREADtoCPU)
   {
-    Log_ErrorPrint("Invalid DMA direction from GPU DMA read");
+    ERROR_LOG("Invalid DMA direction from GPU DMA read");
     std::fill_n(words, word_count, UINT32_C(0xFFFFFFFF));
     return;
   }
@@ -1017,7 +1017,7 @@ void GPU::CRTCTickEvent(TickCount ticks)
     {
       if (new_vblank)
       {
-        Log_DebugPrint("Now in v-blank");
+        DEBUG_LOG("Now in v-blank");
 
         // flush any pending draws and "scan out" the image
         // TODO: move present in here I guess
@@ -1038,8 +1038,8 @@ void GPU::CRTCTickEvent(TickCount ticks)
             static_cast<double>(s_active_gpu_cycles) /
             static_cast<double>(SystemTicksToGPUTicks(System::ScaleTicksToOverclock(System::MASTER_CLOCK)) *
                                 (ComputeVerticalFrequency() / 60.0f));
-          Log_DevFmt("PSX GPU Usage: {:.2f}% [{:.0f} cycles avg per frame]", busy_frac * 100,
-                     static_cast<double>(s_active_gpu_cycles) / static_cast<double>(s_active_gpu_cycles_frames));
+          DEV_LOG("PSX GPU Usage: {:.2f}% [{:.0f} cycles avg per frame]", busy_frac * 100,
+                  static_cast<double>(s_active_gpu_cycles) / static_cast<double>(s_active_gpu_cycles_frames));
           s_active_gpu_cycles = 0;
           s_active_gpu_cycles_frames = 0;
         }
@@ -1123,10 +1123,10 @@ void GPU::ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float win
   *display_x = scaled_display_x * static_cast<float>(m_crtc_state.display_width);
   *display_y = scaled_display_y * static_cast<float>(m_crtc_state.display_height);
 
-  Log_DevFmt("win {:.0f},{:.0f} -> local {:.0f},{:.0f}, disp {:.2f},{:.2f} (size {},{} frac {},{})", window_x, window_y,
-             window_x - draw_rc.left, window_y - draw_rc.top, *display_x, *display_y, m_crtc_state.display_width,
-             m_crtc_state.display_height, *display_x / static_cast<float>(m_crtc_state.display_width),
-             *display_y / static_cast<float>(m_crtc_state.display_height));
+  DEV_LOG("win {:.0f},{:.0f} -> local {:.0f},{:.0f}, disp {:.2f},{:.2f} (size {},{} frac {},{})", window_x, window_y,
+          window_x - draw_rc.left, window_y - draw_rc.top, *display_x, *display_y, m_crtc_state.display_width,
+          m_crtc_state.display_height, *display_x / static_cast<float>(m_crtc_state.display_width),
+          *display_y / static_cast<float>(m_crtc_state.display_height));
 }
 
 bool GPU::ConvertDisplayCoordinatesToBeamTicksAndLines(float display_x, float display_y, float x_scale, u32* out_tick,
@@ -1207,7 +1207,7 @@ u32 GPU::ReadGPUREAD()
 
       if (++m_vram_transfer.row == m_vram_transfer.height)
       {
-        Log_DebugPrint("End of VRAM->CPU transfer");
+        DEBUG_LOG("End of VRAM->CPU transfer");
         m_vram_transfer = {};
         m_blitter_state = BlitterState::Idle;
 
@@ -1230,7 +1230,7 @@ void GPU::WriteGP1(u32 value)
   {
     case 0x00: // Reset GPU
     {
-      Log_DebugPrint("GP1 reset GPU");
+      DEBUG_LOG("GP1 reset GPU");
       m_command_tick_event->InvokeEarly();
       SynchronizeCRTC();
       SoftReset();
@@ -1239,7 +1239,7 @@ void GPU::WriteGP1(u32 value)
 
     case 0x01: // Clear FIFO
     {
-      Log_DebugPrint("GP1 clear FIFO");
+      DEBUG_LOG("GP1 clear FIFO");
       m_command_tick_event->InvokeEarly();
       SynchronizeCRTC();
 
@@ -1262,7 +1262,7 @@ void GPU::WriteGP1(u32 value)
 
     case 0x02: // Acknowledge Interrupt
     {
-      Log_DebugPrint("Acknowledge interrupt");
+      DEBUG_LOG("Acknowledge interrupt");
       m_GPUSTAT.interrupt_request = false;
     }
     break;
@@ -1270,7 +1270,7 @@ void GPU::WriteGP1(u32 value)
     case 0x03: // Display on/off
     {
       const bool disable = ConvertToBoolUnchecked(value & 0x01);
-      Log_DebugFmt("Display {}", disable ? "disabled" : "enabled");
+      DEBUG_LOG("Display {}", disable ? "disabled" : "enabled");
       SynchronizeCRTC();
 
       if (!m_GPUSTAT.display_disable && disable && m_GPUSTAT.vertical_interlace && !m_force_progressive_scan)
@@ -1282,7 +1282,7 @@ void GPU::WriteGP1(u32 value)
 
     case 0x04: // DMA Direction
     {
-      Log_DebugFmt("DMA direction <- 0x{:02X}", static_cast<u32>(param));
+      DEBUG_LOG("DMA direction <- 0x{:02X}", static_cast<u32>(param));
       if (m_GPUSTAT.dma_direction != static_cast<DMADirection>(param))
       {
         m_GPUSTAT.dma_direction = static_cast<DMADirection>(param);
@@ -1294,7 +1294,7 @@ void GPU::WriteGP1(u32 value)
     case 0x05: // Set display start address
     {
       const u32 new_value = param & CRTCState::Regs::DISPLAY_ADDRESS_START_MASK;
-      Log_DebugFmt("Display address start <- 0x{:08X}", new_value);
+      DEBUG_LOG("Display address start <- 0x{:08X}", new_value);
 
       System::IncrementInternalFrameNumber();
       if (m_crtc_state.regs.display_address_start != new_value)
@@ -1309,7 +1309,7 @@ void GPU::WriteGP1(u32 value)
     case 0x06: // Set horizontal display range
     {
       const u32 new_value = param & CRTCState::Regs::HORIZONTAL_DISPLAY_RANGE_MASK;
-      Log_DebugFmt("Horizontal display range <- 0x{:08X}", new_value);
+      DEBUG_LOG("Horizontal display range <- 0x{:08X}", new_value);
 
       if (m_crtc_state.regs.horizontal_display_range != new_value)
       {
@@ -1323,7 +1323,7 @@ void GPU::WriteGP1(u32 value)
     case 0x07: // Set vertical display range
     {
       const u32 new_value = param & CRTCState::Regs::VERTICAL_DISPLAY_RANGE_MASK;
-      Log_DebugFmt("Vertical display range <- 0x{:08X}", new_value);
+      DEBUG_LOG("Vertical display range <- 0x{:08X}", new_value);
 
       if (m_crtc_state.regs.vertical_display_range != new_value)
       {
@@ -1358,7 +1358,7 @@ void GPU::WriteGP1(u32 value)
       new_GPUSTAT.vertical_interlace = dm.vertical_interlace;
       new_GPUSTAT.horizontal_resolution_2 = dm.horizontal_resolution_2;
       new_GPUSTAT.reverse_flag = dm.reverse_flag;
-      Log_DebugFmt("Set display mode <- 0x{:08X}", dm.bits);
+      DEBUG_LOG("Set display mode <- 0x{:08X}", dm.bits);
 
       if (!m_GPUSTAT.vertical_interlace && dm.vertical_interlace && !m_force_progressive_scan)
       {
@@ -1381,7 +1381,7 @@ void GPU::WriteGP1(u32 value)
     case 0x09: // Allow texture disable
     {
       m_set_texture_disable_mask = ConvertToBoolUnchecked(param & 0x01);
-      Log_DebugFmt("Set texture disable mask <- {}", m_set_texture_disable_mask ? "allowed" : "ignored");
+      DEBUG_LOG("Set texture disable mask <- {}", m_set_texture_disable_mask ? "allowed" : "ignored");
     }
     break;
 
@@ -1406,7 +1406,7 @@ void GPU::WriteGP1(u32 value)
     }
     break;
 
-      [[unlikely]] default : Log_ErrorFmt("Unimplemented GP1 command 0x{:02X}", command);
+      [[unlikely]] default : ERROR_LOG("Unimplemented GP1 command 0x{:02X}", command);
       break;
   }
 }
@@ -1425,14 +1425,14 @@ void GPU::HandleGetGPUInfoCommand(u32 value)
 
     case 0x02: // Get Texture Window
     {
-      Log_DebugPrint("Get texture window");
+      DEBUG_LOG("Get texture window");
       m_GPUREAD_latch = m_draw_mode.texture_window_value;
     }
     break;
 
     case 0x03: // Get Draw Area Top Left
     {
-      Log_DebugPrint("Get drawing area top left");
+      DEBUG_LOG("Get drawing area top left");
       m_GPUREAD_latch =
         ((m_drawing_area.left & UINT32_C(0b1111111111)) | ((m_drawing_area.top & UINT32_C(0b1111111111)) << 10));
     }
@@ -1440,7 +1440,7 @@ void GPU::HandleGetGPUInfoCommand(u32 value)
 
     case 0x04: // Get Draw Area Bottom Right
     {
-      Log_DebugPrint("Get drawing area bottom right");
+      DEBUG_LOG("Get drawing area bottom right");
       m_GPUREAD_latch =
         ((m_drawing_area.right & UINT32_C(0b1111111111)) | ((m_drawing_area.bottom & UINT32_C(0b1111111111)) << 10));
     }
@@ -1448,13 +1448,13 @@ void GPU::HandleGetGPUInfoCommand(u32 value)
 
     case 0x05: // Get Drawing Offset
     {
-      Log_DebugPrint("Get drawing offset");
+      DEBUG_LOG("Get drawing offset");
       m_GPUREAD_latch =
         ((m_drawing_offset.x & INT32_C(0b11111111111)) | ((m_drawing_offset.y & INT32_C(0b11111111111)) << 11));
     }
     break;
 
-      [[unlikely]] default : Log_WarningFmt("Unhandled GetGPUInfo(0x{:02X})", subcommand);
+      [[unlikely]] default : WARNING_LOG("Unhandled GetGPUInfo(0x{:02X})", subcommand);
       break;
   }
 }
@@ -1467,7 +1467,7 @@ void GPU::UpdateCLUTIfNeeded(GPUTextureMode texmode, GPUTexturePaletteReg clut)
   const bool needs_8bit = (texmode == GPUTextureMode::Palette8Bit);
   if ((clut.bits != m_current_clut_reg_bits) || BoolToUInt8(needs_8bit) > BoolToUInt8(m_current_clut_is_8bit))
   {
-    Log_DebugFmt("Reloading CLUT from {},{}, {}", clut.GetXBase(), clut.GetYBase(), needs_8bit ? "8-bit" : "4-bit");
+    DEBUG_LOG("Reloading CLUT from {},{}, {}", clut.GetXBase(), clut.GetYBase(), needs_8bit ? "8-bit" : "4-bit");
     UpdateCLUT(clut, needs_8bit);
     m_current_clut_reg_bits = clut.bits;
     m_current_clut_is_8bit = needs_8bit;
@@ -1697,7 +1697,7 @@ void GPU::SetTextureWindow(u32 value)
   const u8 mask_y = Truncate8((value >> 5) & UINT32_C(0x1F));
   const u8 offset_x = Truncate8((value >> 10) & UINT32_C(0x1F));
   const u8 offset_y = Truncate8((value >> 15) & UINT32_C(0x1F));
-  Log_DebugFmt("Set texture window {:02X} {:02X} {:02X} {:02X}", mask_x, mask_y, offset_x, offset_y);
+  DEBUG_LOG("Set texture window {:02X} {:02X} {:02X} {:02X}", mask_x, mask_y, offset_x, offset_y);
 
   m_draw_mode.texture_window.and_x = ~(mask_x * 8);
   m_draw_mode.texture_window.and_y = ~(mask_y * 8);
@@ -2462,7 +2462,7 @@ bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename,
         }
         else
         {
-          Log_ErrorFmt("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
+          ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
           result = false;
         }
       }
@@ -2473,7 +2473,7 @@ bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename,
     }
     else
     {
-      Log_ErrorFmt("Unable to determine file extension for '{}'", filename);
+      ERROR_LOG("Unable to determine file extension for '{}'", filename);
       result = false;
     }
 
@@ -2556,8 +2556,8 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool compress_on_threa
   {
     if (!(dltex = g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat())))
     {
-      Log_ErrorFmt("Failed to create {}x{} {} download texture", read_width, read_height,
-                   GPUTexture::GetFormatName(m_display_texture->GetFormat()));
+      ERROR_LOG("Failed to create {}x{} {} download texture", read_width, read_height,
+                GPUTexture::GetFormatName(m_display_texture->GetFormat()));
       return false;
     }
   }
@@ -2575,7 +2575,7 @@ bool GPU::WriteDisplayTextureToFile(std::string filename, bool compress_on_threa
   auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb", &error);
   if (!fp)
   {
-    Log_ErrorFmt("Can't open file '{}': {}", Path::GetFileName(filename), error.GetDescription());
+    ERROR_LOG("Can't open file '{}': {}", Path::GetFileName(filename), error.GetDescription());
     return false;
   }
 
@@ -2616,7 +2616,7 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const Common::Rectangl
   {
     if (!(dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat)))
     {
-      Log_ErrorFmt("Failed to create {}x{} download texture", width, height);
+      ERROR_LOG("Failed to create {}x{} download texture", width, height);
       return false;
     }
   }
@@ -2702,7 +2702,7 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
   if (!RenderScreenshotToBuffer(width, height, draw_rect, !internal_resolution, &pixels, &pixels_stride,
                                 &pixels_format))
   {
-    Log_ErrorFmt("Failed to render {}x{} screenshot", width, height);
+    ERROR_LOG("Failed to render {}x{} screenshot", width, height);
     return false;
   }
 
@@ -2710,7 +2710,7 @@ bool GPU::RenderScreenshotToFile(std::string filename, DisplayScreenshotMode mod
   auto fp = FileSystem::OpenManagedCFile(filename.c_str(), "wb", &error);
   if (!fp)
   {
-    Log_ErrorFmt("Can't open file '{}': {}", Path::GetFileName(filename), error.GetDescription());
+    ERROR_LOG("Can't open file '{}': {}", Path::GetFileName(filename), error.GetDescription());
     return false;
   }
 
@@ -2734,7 +2734,7 @@ bool GPU::DumpVRAMToFile(const char* filename)
   }
   else
   {
-    Log_ErrorFmt("Unknown extension: '{}'", filename);
+    ERROR_LOG("Unknown extension: '{}'", filename);
     return false;
   }
 }
diff --git a/src/core/gpu_backend.cpp b/src/core/gpu_backend.cpp
index 23bc5e7bf..367236e05 100644
--- a/src/core/gpu_backend.cpp
+++ b/src/core/gpu_backend.cpp
@@ -183,7 +183,7 @@ void GPUBackend::StartGPUThread()
   m_gpu_loop_done.store(false);
   m_use_gpu_thread = true;
   m_gpu_thread.Start([this]() { RunGPULoop(); });
-  Log_InfoPrint("GPU thread started.");
+  INFO_LOG("GPU thread started.");
 }
 
 void GPUBackend::StopGPUThread()
@@ -195,7 +195,7 @@ void GPUBackend::StopGPUThread()
   WakeGPUThread();
   m_gpu_thread.Join();
   m_use_gpu_thread = false;
-  Log_InfoPrint("GPU thread stopped.");
+  INFO_LOG("GPU thread stopped.");
 }
 
 void GPUBackend::Sync(bool allow_sleep)
diff --git a/src/core/gpu_commands.cpp b/src/core/gpu_commands.cpp
index 09c1eae03..f6173c60b 100644
--- a/src/core/gpu_commands.cpp
+++ b/src/core/gpu_commands.cpp
@@ -49,7 +49,7 @@ void GPU::TryExecuteCommands()
           m_blit_buffer.push_back(FifoPop());
         m_blit_remaining_words -= words_to_copy;
 
-        Log_DebugFmt("VRAM write burst of {} words, {} words remaining", words_to_copy, m_blit_remaining_words);
+        DEBUG_LOG("VRAM write burst of {} words, {} words remaining", words_to_copy, m_blit_remaining_words);
         if (m_blit_remaining_words == 0)
           FinishVRAMWrite();
 
@@ -84,12 +84,12 @@ void GPU::TryExecuteCommands()
             m_blit_buffer.push_back(FifoPop());
         }
 
-        Log_DebugFmt("Added {} words to polyline", words_to_copy);
+        DEBUG_LOG("Added {} words to polyline", words_to_copy);
         if (found_terminator)
         {
           // drop terminator
           m_fifo.RemoveOne();
-          Log_DebugFmt("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
+          DEBUG_LOG("Drawing poly-line with {} vertices", GetPolyLineVertexCount());
           DispatchRenderCommand();
           m_blit_buffer.clear();
           EndCommand();
@@ -175,12 +175,12 @@ GPU::GP0CommandHandlerTable GPU::GenerateGP0CommandHandlerTable()
 bool GPU::HandleUnknownGP0Command()
 {
   const u32 command = FifoPeek() >> 24;
-  Log_ErrorFmt("Unimplemented GP0 command 0x{:02X}", command);
+  ERROR_LOG("Unimplemented GP0 command 0x{:02X}", command);
 
   SmallString dump;
   for (u32 i = 0; i < m_fifo.GetSize(); i++)
     dump.append_format("{}{:08X}", (i > 0) ? " " : "", FifoPeek(i));
-  Log_ErrorFmt("FIFO: {}", dump);
+  ERROR_LOG("FIFO: {}", dump);
 
   m_fifo.RemoveOne();
   EndCommand();
@@ -196,7 +196,7 @@ bool GPU::HandleNOPCommand()
 
 bool GPU::HandleClearCacheCommand()
 {
-  Log_DebugPrint("GP0 clear cache");
+  DEBUG_LOG("GP0 clear cache");
   m_draw_mode.SetTexturePageChanged();
   InvalidateCLUT();
   m_fifo.RemoveOne();
@@ -207,7 +207,7 @@ bool GPU::HandleClearCacheCommand()
 
 bool GPU::HandleInterruptRequestCommand()
 {
-  Log_DebugPrint("GP0 interrupt request");
+  DEBUG_LOG("GP0 interrupt request");
 
   m_GPUSTAT.interrupt_request = true;
   InterruptController::SetLineState(InterruptController::IRQ::GPU, m_GPUSTAT.interrupt_request);
@@ -221,7 +221,7 @@ bool GPU::HandleInterruptRequestCommand()
 bool GPU::HandleSetDrawModeCommand()
 {
   const u32 param = FifoPop() & 0x00FFFFFFu;
-  Log_DebugFmt("Set draw mode {:08X}", param);
+  DEBUG_LOG("Set draw mode {:08X}", param);
   SetDrawMode(Truncate16(param));
   AddCommandTicks(1);
   EndCommand();
@@ -242,7 +242,7 @@ bool GPU::HandleSetDrawingAreaTopLeftCommand()
   const u32 param = FifoPop() & 0x00FFFFFFu;
   const u32 left = param & DRAWING_AREA_COORD_MASK;
   const u32 top = (param >> 10) & DRAWING_AREA_COORD_MASK;
-  Log_DebugFmt("Set drawing area top-left: ({}, {})", left, top);
+  DEBUG_LOG("Set drawing area top-left: ({}, {})", left, top);
   if (m_drawing_area.left != left || m_drawing_area.top != top)
   {
     FlushRender();
@@ -263,7 +263,7 @@ bool GPU::HandleSetDrawingAreaBottomRightCommand()
 
   const u32 right = param & DRAWING_AREA_COORD_MASK;
   const u32 bottom = (param >> 10) & DRAWING_AREA_COORD_MASK;
-  Log_DebugFmt("Set drawing area bottom-right: ({}, {})", m_drawing_area.right, m_drawing_area.bottom);
+  DEBUG_LOG("Set drawing area bottom-right: ({}, {})", m_drawing_area.right, m_drawing_area.bottom);
   if (m_drawing_area.right != right || m_drawing_area.bottom != bottom)
   {
     FlushRender();
@@ -283,7 +283,7 @@ bool GPU::HandleSetDrawingOffsetCommand()
   const u32 param = FifoPop() & 0x00FFFFFFu;
   const s32 x = SignExtendN<11, s32>(param & 0x7FFu);
   const s32 y = SignExtendN<11, s32>((param >> 11) & 0x7FFu);
-  Log_DebugFmt("Set drawing offset ({}, {})", m_drawing_offset.x, m_drawing_offset.y);
+  DEBUG_LOG("Set drawing offset ({}, {})", m_drawing_offset.x, m_drawing_offset.y);
   if (m_drawing_offset.x != x || m_drawing_offset.y != y)
   {
     FlushRender();
@@ -308,8 +308,8 @@ bool GPU::HandleSetMaskBitCommand()
     FlushRender();
     m_GPUSTAT.bits = (m_GPUSTAT.bits & ~gpustat_mask) | gpustat_bits;
   }
-  Log_DebugFmt("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
-               BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
+  DEBUG_LOG("Set mask bit {} {}", BoolToUInt32(m_GPUSTAT.set_mask_while_drawing),
+            BoolToUInt32(m_GPUSTAT.check_mask_before_draw));
 
   AddCommandTicks(1);
   EndCommand();
@@ -335,10 +335,10 @@ bool GPU::HandleRenderPolygonCommand()
     s_setup_time[BoolToUInt8(rc.quad_polygon)][BoolToUInt8(rc.shading_enable)][BoolToUInt8(rc.texture_enable)]));
   AddCommandTicks(setup_ticks);
 
-  Log_TraceFmt("Render {} {} {} {} polygon ({} verts, {} words per vert), {} setup ticks",
-               rc.quad_polygon ? "four-point" : "three-point", rc.transparency_enable ? "semi-transparent" : "opaque",
-               rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome",
-               num_vertices, words_per_vertex, setup_ticks);
+  TRACE_LOG("Render {} {} {} {} polygon ({} verts, {} words per vert), {} setup ticks",
+            rc.quad_polygon ? "four-point" : "three-point", rc.transparency_enable ? "semi-transparent" : "opaque",
+            rc.texture_enable ? "textured" : "non-textured", rc.shading_enable ? "shaded" : "monochrome", num_vertices,
+            words_per_vertex, setup_ticks);
 
   // set draw state up
   if (rc.texture_enable)
@@ -380,9 +380,9 @@ bool GPU::HandleRenderRectangleCommand()
   const TickCount setup_ticks = 16;
   AddCommandTicks(setup_ticks);
 
-  Log_TraceFmt("Render {} {} {} rectangle ({} words), {} setup ticks",
-               rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
-               rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
+  TRACE_LOG("Render {} {} {} rectangle ({} words), {} setup ticks",
+            rc.transparency_enable ? "semi-transparent" : "opaque", rc.texture_enable ? "textured" : "non-textured",
+            rc.shading_enable ? "shaded" : "monochrome", total_words, setup_ticks);
 
   m_counters.num_vertices++;
   m_counters.num_primitives++;
@@ -403,8 +403,8 @@ bool GPU::HandleRenderLineCommand()
   if (IsInterlacedRenderingEnabled() && IsCRTCScanlinePending())
     SynchronizeCRTC();
 
-  Log_TraceFmt("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
-               rc.shading_enable ? "shaded" : "monochrome", total_words);
+  TRACE_LOG("Render {} {} line ({} total words)", rc.transparency_enable ? "semi-transparent" : "opaque",
+            rc.shading_enable ? "shaded" : "monochrome", total_words);
 
   m_counters.num_vertices += 2;
   m_counters.num_primitives++;
@@ -429,8 +429,8 @@ bool GPU::HandleRenderPolyLineCommand()
   const TickCount setup_ticks = 16;
   AddCommandTicks(setup_ticks);
 
-  Log_TraceFmt("Render {} {} poly-line, {} setup ticks", rc.transparency_enable ? "semi-transparent" : "opaque",
-               rc.shading_enable ? "shaded" : "monochrome", setup_ticks);
+  TRACE_LOG("Render {} {} poly-line, {} setup ticks", rc.transparency_enable ? "semi-transparent" : "opaque",
+            rc.shading_enable ? "shaded" : "monochrome", setup_ticks);
 
   m_render_command.bits = rc.bits;
   m_fifo.RemoveOne();
@@ -463,7 +463,7 @@ bool GPU::HandleFillRectangleCommand()
   const u32 width = ((FifoPeek() & VRAM_WIDTH_MASK) + 0xF) & ~0xF;
   const u32 height = (FifoPop() >> 16) & VRAM_HEIGHT_MASK;
 
-  Log_DebugFmt("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
+  DEBUG_LOG("Fill VRAM rectangle offset=({},{}), size=({},{})", dst_x, dst_y, width, height);
 
   if (width > 0 && height > 0)
     FillVRAM(dst_x, dst_y, width, height, color);
@@ -486,7 +486,7 @@ bool GPU::HandleCopyRectangleCPUToVRAMCommand()
   const u32 num_pixels = copy_width * copy_height;
   const u32 num_words = ((num_pixels + 1) / 2);
 
-  Log_DebugFmt("Copy rectangle from CPU to VRAM offset=({},{}), size=({},{})", dst_x, dst_y, copy_width, copy_height);
+  DEBUG_LOG("Copy rectangle from CPU to VRAM offset=({},{}), size=({},{})", dst_x, dst_y, copy_width, copy_height);
 
   EndCommand();
 
@@ -533,8 +533,8 @@ void GPU::FinishVRAMWrite()
     const u32 transferred_full_rows = transferred_pixels / m_vram_transfer.width;
     const u32 transferred_width_last_row = transferred_pixels % m_vram_transfer.width;
 
-    Log_WarningFmt("Partial VRAM write - transfer finished with {} of {} words remaining ({} full rows, {} last row)",
-                   m_blit_remaining_words, num_words, transferred_full_rows, transferred_width_last_row);
+    WARNING_LOG("Partial VRAM write - transfer finished with {} of {} words remaining ({} full rows, {} last row)",
+                m_blit_remaining_words, num_words, transferred_full_rows, transferred_width_last_row);
 
     const u8* blit_ptr = reinterpret_cast<const u8*>(m_blit_buffer.data());
     if (transferred_full_rows > 0)
@@ -566,8 +566,8 @@ bool GPU::HandleCopyRectangleVRAMToCPUCommand()
   m_vram_transfer.width = ((Truncate16(FifoPeek()) - 1) & VRAM_WIDTH_MASK) + 1;
   m_vram_transfer.height = ((Truncate16(FifoPop() >> 16) - 1) & VRAM_HEIGHT_MASK) + 1;
 
-  Log_DebugFmt("Copy rectangle from VRAM to CPU offset=({},{}), size=({},{})", m_vram_transfer.x, m_vram_transfer.y,
-               m_vram_transfer.width, m_vram_transfer.height);
+  DEBUG_LOG("Copy rectangle from VRAM to CPU offset=({},{}), size=({},{})", m_vram_transfer.x, m_vram_transfer.y,
+            m_vram_transfer.width, m_vram_transfer.height);
   DebugAssert(m_vram_transfer.col == 0 && m_vram_transfer.row == 0);
 
   // all rendering should be done first...
@@ -602,8 +602,8 @@ bool GPU::HandleCopyRectangleVRAMToVRAMCommand()
   const u32 width = ReplaceZero(FifoPeek() & VRAM_WIDTH_MASK, 0x400);
   const u32 height = ReplaceZero((FifoPop() >> 16) & VRAM_HEIGHT_MASK, 0x200);
 
-  Log_DebugFmt("Copy rectangle from VRAM to VRAM src=({},{}), dst=({},{}), size=({},{})", src_x, src_y, dst_x, dst_y,
-               width, height);
+  DEBUG_LOG("Copy rectangle from VRAM to VRAM src=({},{}), dst=({},{}), size=({},{})", src_x, src_y, dst_x, dst_y,
+            width, height);
 
   // Some VRAM copies aren't going to do anything. Most games seem to send a 2x2 VRAM copy at the end of a frame.
   const bool skip_copy =
diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp
index df528cb7e..72d4152fb 100644
--- a/src/core/gpu_hw.cpp
+++ b/src/core/gpu_hw.cpp
@@ -216,13 +216,13 @@ bool GPU_HW::Initialize()
 
   if (!CompilePipelines())
   {
-    Log_ErrorPrint("Failed to compile pipelines");
+    ERROR_LOG("Failed to compile pipelines");
     return false;
   }
 
   if (!CreateBuffers())
   {
-    Log_ErrorPrint("Failed to create framebuffer");
+    ERROR_LOG("Failed to create framebuffer");
     return false;
   }
 
@@ -467,7 +467,7 @@ void GPU_HW::CheckSettings()
   }
 
   if (!features.noperspective_interpolation && !ShouldDisableColorPerspective())
-    Log_WarningPrint("Disable color perspective not supported, but should be used.");
+    WARNING_LOG("Disable color perspective not supported, but should be used.");
 
   if (!features.geometry_shaders && m_wireframe_mode != GPUWireframeMode::Disabled)
   {
@@ -546,7 +546,7 @@ u32 GPU_HW::CalculateResolutionScale() const
 
     const s32 preferred_scale =
       static_cast<s32>(std::ceil(static_cast<float>(g_gpu_device->GetWindowHeight() * widescreen_multiplier) / height));
-    Log_VerboseFmt("Height = {}, preferred scale = {}", height, preferred_scale);
+    VERBOSE_LOG("Height = {}, preferred scale = {}", height, preferred_scale);
 
     scale = static_cast<u32>(std::clamp<s32>(preferred_scale, 1, max_resolution_scale));
   }
@@ -554,7 +554,7 @@ u32 GPU_HW::CalculateResolutionScale() const
   if (g_settings.gpu_downsample_mode == GPUDownsampleMode::Adaptive && scale > 1 && !Common::IsPow2(scale))
   {
     const u32 new_scale = Common::PreviousPow2(scale);
-    Log_WarningFmt("Resolution scale {}x not supported for adaptive downsampling, using {}x", scale, new_scale);
+    WARNING_LOG("Resolution scale {}x not supported for adaptive downsampling, using {}x", scale, new_scale);
 
     if (g_settings.gpu_resolution_scale != 0)
     {
@@ -641,19 +641,18 @@ std::tuple<u32, u32> GPU_HW::GetFullDisplayResolution(bool scaled /* = true */)
 
 void GPU_HW::PrintSettingsToLog()
 {
-  Log_InfoFmt("Resolution Scale: {} ({}x{}), maximum {}", m_resolution_scale, VRAM_WIDTH * m_resolution_scale,
-              VRAM_HEIGHT * m_resolution_scale, GetMaxResolutionScale());
-  Log_InfoFmt("Multisampling: {}x{}", m_multisamples, m_per_sample_shading ? " (per sample shading)" : "");
-  Log_InfoFmt("Dithering: {}{}", m_true_color ? "Disabled" : "Enabled",
-              (!m_true_color && m_scaled_dithering) ? " (Scaled)" :
-                                                      ((m_true_color && m_debanding) ? " (Debanding)" : ""));
-  Log_InfoFmt("Texture Filtering: {}", Settings::GetTextureFilterDisplayName(m_texture_filtering));
-  Log_InfoFmt("Dual-source blending: {}", m_supports_dual_source_blend ? "Supported" : "Not supported");
-  Log_InfoFmt("Clamping UVs: {}", m_clamp_uvs ? "YES" : "NO");
-  Log_InfoFmt("Depth buffer: {}", m_pgxp_depth_buffer ? "YES" : "NO");
-  Log_InfoFmt("Downsampling: {}", Settings::GetDownsampleModeDisplayName(m_downsample_mode));
-  Log_InfoFmt("Wireframe rendering: {}", Settings::GetGPUWireframeModeDisplayName(m_wireframe_mode));
-  Log_InfoFmt("Using software renderer for readbacks: {}", m_sw_renderer ? "YES" : "NO");
+  INFO_LOG("Resolution Scale: {} ({}x{}), maximum {}", m_resolution_scale, VRAM_WIDTH * m_resolution_scale,
+           VRAM_HEIGHT * m_resolution_scale, GetMaxResolutionScale());
+  INFO_LOG("Multisampling: {}x{}", m_multisamples, m_per_sample_shading ? " (per sample shading)" : "");
+  INFO_LOG("Dithering: {}{}", m_true_color ? "Disabled" : "Enabled",
+           (!m_true_color && m_scaled_dithering) ? " (Scaled)" : ((m_true_color && m_debanding) ? " (Debanding)" : ""));
+  INFO_LOG("Texture Filtering: {}", Settings::GetTextureFilterDisplayName(m_texture_filtering));
+  INFO_LOG("Dual-source blending: {}", m_supports_dual_source_blend ? "Supported" : "Not supported");
+  INFO_LOG("Clamping UVs: {}", m_clamp_uvs ? "YES" : "NO");
+  INFO_LOG("Depth buffer: {}", m_pgxp_depth_buffer ? "YES" : "NO");
+  INFO_LOG("Downsampling: {}", Settings::GetDownsampleModeDisplayName(m_downsample_mode));
+  INFO_LOG("Wireframe rendering: {}", Settings::GetGPUWireframeModeDisplayName(m_wireframe_mode));
+  INFO_LOG("Using software renderer for readbacks: {}", m_sw_renderer ? "YES" : "NO");
 }
 
 bool GPU_HW::NeedsDepthBuffer() const
@@ -671,7 +670,7 @@ bool GPU_HW::CreateBuffers()
   const u32 texture_height = VRAM_HEIGHT * m_resolution_scale;
   const u8 samples = static_cast<u8>(m_multisamples);
   const bool needs_depth_buffer = NeedsDepthBuffer();
-  Log_DevFmt("Depth buffer is {}needed", needs_depth_buffer ? "" : "NOT ");
+  DEV_LOG("Depth buffer is {}needed", needs_depth_buffer ? "" : "NOT ");
 
   // Needed for Metal resolve.
   const GPUTexture::Type read_texture_type = (g_gpu_device->GetRenderAPI() == RenderAPI::Metal && m_multisamples > 1) ?
@@ -699,12 +698,12 @@ bool GPU_HW::CreateBuffers()
 
   if (g_gpu_device->GetFeatures().memory_import)
   {
-    Log_DevPrint("Trying to import guest VRAM buffer for downloads...");
+    DEV_LOG("Trying to import guest VRAM buffer for downloads...");
     m_vram_readback_download_texture = g_gpu_device->CreateDownloadTexture(
       m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(), m_vram_readback_texture->GetFormat(),
       g_vram, sizeof(g_vram), VRAM_WIDTH * sizeof(u16));
     if (!m_vram_readback_download_texture)
-      Log_ErrorPrint("Failed to create imported readback buffer");
+      ERROR_LOG("Failed to create imported readback buffer");
   }
   if (!m_vram_readback_download_texture)
   {
@@ -712,7 +711,7 @@ bool GPU_HW::CreateBuffers()
       m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(), m_vram_readback_texture->GetFormat());
     if (!m_vram_readback_download_texture)
     {
-      Log_ErrorPrint("Failed to create readback download texture");
+      ERROR_LOG("Failed to create readback download texture");
       return false;
     }
   }
@@ -728,7 +727,7 @@ bool GPU_HW::CreateBuffers()
     GL_OBJECT_NAME(m_vram_upload_buffer, "VRAM Upload Buffer");
   }
 
-  Log_InfoFmt("Created HW framebuffer of {}x{}", texture_width, texture_height);
+  INFO_LOG("Created HW framebuffer of {}x{}", texture_width, texture_height);
 
   if (m_downsample_mode == GPUDownsampleMode::Adaptive)
     m_downsample_scale_or_levels = GetAdaptiveDownsamplingMipLevels();
@@ -2033,9 +2032,9 @@ void GPU_HW::LoadVertices()
 
       if (first_tri_culled)
       {
-        Log_DebugFmt("Culling too-large polygon: {},{} {},{} {},{}", native_vertex_positions[0][0],
-                     native_vertex_positions[0][1], native_vertex_positions[1][0], native_vertex_positions[1][1],
-                     native_vertex_positions[2][0], native_vertex_positions[2][1]);
+        DEBUG_LOG("Culling too-large polygon: {},{} {},{} {},{}", native_vertex_positions[0][0],
+                  native_vertex_positions[0][1], native_vertex_positions[1][0], native_vertex_positions[1][1],
+                  native_vertex_positions[2][0], native_vertex_positions[2][1]);
       }
       else
       {
@@ -2066,9 +2065,9 @@ void GPU_HW::LoadVertices()
         // Cull polygons which are too large.
         if ((max_x_123 - min_x_123) >= MAX_PRIMITIVE_WIDTH || (max_y_123 - min_y_123) >= MAX_PRIMITIVE_HEIGHT)
         {
-          Log_DebugFmt("Culling too-large polygon (quad second half): {},{} {},{} {},{}", native_vertex_positions[2][0],
-                       native_vertex_positions[2][1], native_vertex_positions[1][0], native_vertex_positions[1][1],
-                       native_vertex_positions[0][0], native_vertex_positions[0][1]);
+          DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", native_vertex_positions[2][0],
+                    native_vertex_positions[2][1], native_vertex_positions[1][0], native_vertex_positions[1][1],
+                    native_vertex_positions[0][0], native_vertex_positions[0][1]);
         }
         else
         {
@@ -2148,7 +2147,7 @@ void GPU_HW::LoadVertices()
 
           if (rectangle_width >= MAX_PRIMITIVE_WIDTH || rectangle_height >= MAX_PRIMITIVE_HEIGHT)
           {
-            Log_DebugFmt("Culling too-large rectangle: {},{} {}x{}", pos_x, pos_y, rectangle_width, rectangle_height);
+            DEBUG_LOG("Culling too-large rectangle: {},{} {}x{}", pos_x, pos_y, rectangle_width, rectangle_height);
             return;
           }
         }
@@ -2265,7 +2264,7 @@ void GPU_HW::LoadVertices()
         const auto [min_y, max_y] = MinMax(start_y, end_y);
         if ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT)
         {
-          Log_DebugFmt("Culling too-large line: {},{} - {},{}", start_x, start_y, end_x, end_y);
+          DEBUG_LOG("Culling too-large line: {},{} - {},{}", start_x, start_y, end_x, end_y);
           return;
         }
 
@@ -2325,7 +2324,7 @@ void GPU_HW::LoadVertices()
           const auto [min_y, max_y] = MinMax(start_y, end_y);
           if ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT)
           {
-            Log_DebugFmt("Culling too-large line: {},{} - {},{}", start_x, start_y, end_x, end_y);
+            DEBUG_LOG("Culling too-large line: {},{} - {},{}", start_x, start_y, end_x, end_y);
           }
           else
           {
@@ -2376,7 +2375,7 @@ bool GPU_HW::BlitVRAMReplacementTexture(const TextureReplacementTexture* tex, u3
   {
     if (!m_vram_replacement_texture->Update(0, 0, tex->GetWidth(), tex->GetHeight(), tex->GetPixels(), tex->GetPitch()))
     {
-      Log_ErrorFmt("Update {}x{} texture failed.", width, height);
+      ERROR_LOG("Update {}x{} texture failed.", width, height);
       return false;
     }
   }
@@ -2556,7 +2555,7 @@ void GPU_HW::EnsureVertexBufferSpaceForCurrentCommand()
 
 void GPU_HW::ResetBatchVertexDepth()
 {
-  Log_DevPrint("Resetting batch vertex depth");
+  DEV_LOG("Resetting batch vertex depth");
 
   if (m_vram_depth_texture && !m_pgxp_depth_buffer)
     UpdateDepthBufferFromMaskBit();
@@ -2796,7 +2795,7 @@ void GPU_HW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, b
                                                 GPUTexture::Format::R16U, data, width * sizeof(u16));
     if (!upload_texture)
     {
-      Log_ErrorFmt("Failed to get {}x{} upload texture. Things are gonna break.", width, height);
+      ERROR_LOG("Failed to get {}x{} upload texture. Things are gonna break.", width, height);
       return;
     }
   }
@@ -3343,7 +3342,7 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top
                                           GPUTexture::Type::RenderTarget, GPUTexture::Format::R8);
   if (!m_downsample_texture || !level_texture || !weight_texture)
   {
-    Log_ErrorFmt("Failed to create {}x{} RTs for adaptive downsampling", width, height);
+    ERROR_LOG("Failed to create {}x{} RTs for adaptive downsampling", width, height);
     SetDisplayTexture(source, left, top, width, height);
     return;
   }
@@ -3453,7 +3452,7 @@ void GPU_HW::DownsampleFramebufferBoxFilter(GPUTexture* source, u32 left, u32 to
   }
   if (!m_downsample_texture)
   {
-    Log_ErrorFmt("Failed to create {}x{} RT for box downsampling", width, height);
+    ERROR_LOG("Failed to create {}x{} RT for box downsampling", width, height);
     SetDisplayTexture(source, left, top, width, height);
     return;
   }
diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp
index d3f3f2c8a..93c49da52 100644
--- a/src/core/gpu_sw.cpp
+++ b/src/core/gpu_sw.cpp
@@ -100,7 +100,7 @@ GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format
     m_upload_texture =
       g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::DynamicTexture, format, nullptr, 0);
     if (!m_upload_texture) [[unlikely]]
-      Log_ErrorFmt("Failed to create {}x{} {} texture", width, height, static_cast<u32>(format));
+      ERROR_LOG("Failed to create {}x{} {} texture", width, height, static_cast<u32>(format));
   }
 
   return m_upload_texture.get();
@@ -586,8 +586,8 @@ void GPU_SW::DispatchRenderCommand()
 
       if ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT)
       {
-        Log_DebugFmt("Culling too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
-                     cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
+        DEBUG_LOG("Culling too-large polygon: {},{} {},{} {},{}", cmd->vertices[0].x, cmd->vertices[0].y,
+                  cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[2].x, cmd->vertices[2].y);
       }
       else
       {
@@ -607,9 +607,8 @@ void GPU_SW::DispatchRenderCommand()
         // Cull polygons which are too large.
         if ((max_x_123 - min_x_123) >= MAX_PRIMITIVE_WIDTH || (max_y_123 - min_y_123) >= MAX_PRIMITIVE_HEIGHT)
         {
-          Log_DebugFmt("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
-                       cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x,
-                       cmd->vertices[0].y);
+          DEBUG_LOG("Culling too-large polygon (quad second half): {},{} {},{} {},{}", cmd->vertices[2].x,
+                    cmd->vertices[2].y, cmd->vertices[1].x, cmd->vertices[1].y, cmd->vertices[0].x, cmd->vertices[0].y);
         }
         else
         {
@@ -667,7 +666,7 @@ void GPU_SW::DispatchRenderCommand()
 
           if (cmd->width >= MAX_PRIMITIVE_WIDTH || cmd->height >= MAX_PRIMITIVE_HEIGHT)
           {
-            Log_DebugFmt("Culling too-large rectangle: {},{} {}x{}", cmd->x, cmd->y, cmd->width, cmd->height);
+            DEBUG_LOG("Culling too-large rectangle: {},{} {}x{}", cmd->x, cmd->y, cmd->width, cmd->height);
             return;
           }
         }
@@ -724,8 +723,8 @@ void GPU_SW::DispatchRenderCommand()
         const auto [min_y, max_y] = MinMax(cmd->vertices[0].y, cmd->vertices[1].y);
         if ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT)
         {
-          Log_DebugFmt("Culling too-large line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y,
-                       cmd->vertices[1].x, cmd->vertices[1].y);
+          DEBUG_LOG("Culling too-large line: {},{} - {},{}", cmd->vertices[0].y, cmd->vertices[0].y, cmd->vertices[1].x,
+                    cmd->vertices[1].y);
           return;
         }
 
@@ -759,8 +758,8 @@ void GPU_SW::DispatchRenderCommand()
           const auto [min_y, max_y] = MinMax(cmd->vertices[i - 1].y, cmd->vertices[i].y);
           if ((max_x - min_x) >= MAX_PRIMITIVE_WIDTH || (max_y - min_y) >= MAX_PRIMITIVE_HEIGHT)
           {
-            Log_DebugFmt("Culling too-large line: {},{} - {},{}", cmd->vertices[i - 1].x, cmd->vertices[i - 1].y,
-                         cmd->vertices[i].x, cmd->vertices[i].y);
+            DEBUG_LOG("Culling too-large line: {},{} - {},{}", cmd->vertices[i - 1].x, cmd->vertices[i - 1].y,
+                      cmd->vertices[i].x, cmd->vertices[i].y);
           }
           else
           {
diff --git a/src/core/guncon.cpp b/src/core/guncon.cpp
index 804e673d3..8f3702eda 100644
--- a/src/core/guncon.cpp
+++ b/src/core/guncon.cpp
@@ -217,7 +217,7 @@ void GunCon::UpdatePosition()
       !g_gpu->ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) ||
       m_shoot_offscreen)
   {
-    Log_DebugFmt("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y);
+    DEBUG_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y);
     m_position_x = 0x01;
     m_position_y = 0x0A;
     return;
@@ -227,8 +227,8 @@ void GunCon::UpdatePosition()
   const double divider = static_cast<double>(g_gpu->GetCRTCFrequency()) / 8000000.0;
   m_position_x = static_cast<u16>(static_cast<float>(tick) / static_cast<float>(divider));
   m_position_y = static_cast<u16>(line);
-  Log_DebugFmt("Lightgun window coordinates {:.0f},{:.0f} -> tick {} line {} 8mhz ticks {}", display_x, display_y, tick,
-               line, m_position_x);
+  DEBUG_LOG("Lightgun window coordinates {:.0f},{:.0f} -> tick {} line {} 8mhz ticks {}", display_x, display_y, tick,
+            line, m_position_x);
 }
 
 std::pair<float, float> GunCon::GetAbsolutePositionFromRelativeAxes() const
diff --git a/src/core/host.cpp b/src/core/host.cpp
index e2669290e..51ab3974b 100644
--- a/src/core/host.cpp
+++ b/src/core/host.cpp
@@ -264,7 +264,7 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
 {
   DebugAssert(!g_gpu_device);
 
-  Log_InfoFmt("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
+  INFO_LOG("Trying to create a {} GPU device...", GPUDevice::RenderAPIToString(api));
   g_gpu_device = GPUDevice::CreateDeviceForAPI(api);
 
   std::optional<bool> exclusive_fullscreen_control;
@@ -293,7 +293,7 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
                          g_settings.gpu_threaded_presentation, exclusive_fullscreen_control,
                          static_cast<GPUDevice::FeatureMask>(disabled_features), &create_error))
   {
-    Log_ErrorFmt("Failed to create GPU device: {}", create_error.GetDescription());
+    ERROR_LOG("Failed to create GPU device: {}", create_error.GetDescription());
     if (g_gpu_device)
       g_gpu_device->Destroy();
     g_gpu_device.reset();
@@ -309,7 +309,7 @@ bool Host::CreateGPUDevice(RenderAPI api, Error* error)
   if (!ImGuiManager::Initialize(g_settings.display_osd_scale / 100.0f, g_settings.display_show_osd_messages,
                                 &create_error))
   {
-    Log_ErrorFmt("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
+    ERROR_LOG("Failed to initialize ImGuiManager: {}", create_error.GetDescription());
     Error::SetStringFmt(error, "Failed to initialize ImGuiManager: {}", create_error.GetDescription());
     g_gpu_device->Destroy();
     g_gpu_device.reset();
@@ -348,7 +348,7 @@ void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
   if (!g_gpu_device)
     return;
 
-  Log_DevFmt("Display window resized to {}x{}", width, height);
+  DEV_LOG("Display window resized to {}x{}", width, height);
 
   g_gpu_device->ResizeWindow(width, height, scale);
   ImGuiManager::WindowResized();
@@ -377,7 +377,7 @@ void Host::ReleaseGPUDevice()
   FullscreenUI::Shutdown();
   ImGuiManager::Shutdown();
 
-  Log_InfoFmt("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
+  INFO_LOG("Destroying {} GPU device...", GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
   g_gpu_device->Destroy();
   g_gpu_device.reset();
 }
diff --git a/src/core/host_interface_progress_callback.cpp b/src/core/host_interface_progress_callback.cpp
index 6bb0295f9..cb1c314f4 100644
--- a/src/core/host_interface_progress_callback.cpp
+++ b/src/core/host_interface_progress_callback.cpp
@@ -75,37 +75,37 @@ void HostInterfaceProgressCallback::Redraw(bool force)
 
 void HostInterfaceProgressCallback::DisplayError(const char* message)
 {
-  Log_ErrorPrint(message);
+  ERROR_LOG(message);
 }
 
 void HostInterfaceProgressCallback::DisplayWarning(const char* message)
 {
-  Log_WarningPrint(message);
+  WARNING_LOG(message);
 }
 
 void HostInterfaceProgressCallback::DisplayInformation(const char* message)
 {
-  Log_InfoPrint(message);
+  INFO_LOG(message);
 }
 
 void HostInterfaceProgressCallback::DisplayDebugMessage(const char* message)
 {
-  Log_DevPrint(message);
+  DEV_LOG(message);
 }
 
 void HostInterfaceProgressCallback::ModalError(const char* message)
 {
-  Log_ErrorPrint(message);
+  ERROR_LOG(message);
   Host::ReportErrorAsync("Error", message);
 }
 
 bool HostInterfaceProgressCallback::ModalConfirmation(const char* message)
 {
-  Log_InfoPrint(message);
+  INFO_LOG(message);
   return Host::ConfirmMessage("Confirm", message);
 }
 
 void HostInterfaceProgressCallback::ModalInformation(const char* message)
 {
-  Log_InfoPrint(message);
+  INFO_LOG(message);
 }
diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp
index 5fcb9d79a..8a47f2761 100644
--- a/src/core/imgui_overlays.cpp
+++ b/src/core/imgui_overlays.cpp
@@ -130,7 +130,7 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
 {
   if (!g_gpu_device)
   {
-    Log_InfoFmt("{}: {}/{}", message, progress_value, progress_max);
+    INFO_LOG("{}: {}/{}", message, progress_value, progress_max);
     return;
   }
 
@@ -187,14 +187,14 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
 
       ImGui::ProgressBar(static_cast<float>(progress_value) / static_cast<float>(progress_max - progress_min),
                          ImVec2(-1.0f, 0.0f), "");
-      Log_InfoFmt("{}: {}", message, buf);
+      INFO_LOG("{}: {}", message, buf);
     }
     else
     {
       const ImVec2 text_size(ImGui::CalcTextSize(message));
       ImGui::SetCursorPosX((width - text_size.x) / 2.0f);
       ImGui::TextUnformatted(message);
-      Log_InfoPrint(message);
+      INFO_LOG(message);
     }
   }
   ImGui::End();
@@ -927,7 +927,7 @@ void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateIn
         ssi->screenshot_width, ssi->screenshot_height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
         ssi->screenshot_data.data(), sizeof(u32) * ssi->screenshot_width);
       if (!li->preview_texture) [[unlikely]]
-        Log_ErrorPrint("Failed to upload save state image to GPU");
+        ERROR_LOG("Failed to upload save state image to GPU");
     }
   }
 }
diff --git a/src/core/interrupt_controller.cpp b/src/core/interrupt_controller.cpp
index f3746797e..aabea79ec 100644
--- a/src/core/interrupt_controller.cpp
+++ b/src/core/interrupt_controller.cpp
@@ -53,9 +53,9 @@ void InterruptController::SetLineState(IRQ irq, bool state)
 
 #ifdef _DEBUG
   if (!(prev_state & bit) && state)
-    Log_DebugFmt("{} IRQ triggered", s_irq_names[static_cast<size_t>(irq)]);
+    DEBUG_LOG("{} IRQ triggered", s_irq_names[static_cast<size_t>(irq)]);
   else if ((prev_state & bit) && !state)
-    Log_DebugFmt("{} IRQ line inactive", s_irq_names[static_cast<size_t>(irq)]);
+    DEBUG_LOG("{} IRQ line inactive", s_irq_names[static_cast<size_t>(irq)]);
 #endif
 
   s_interrupt_status_register |= (state ? (prev_state ^ s_interrupt_line_state) : 0u) & s_interrupt_line_state;
@@ -72,8 +72,8 @@ u32 InterruptController::ReadRegister(u32 offset)
     case 0x04: // I_MASK
       return s_interrupt_mask_register;
 
-    default: [[unlikely]]
-      Log_ErrorFmt("Invalid read at offset 0x{:08X}", offset);
+    [[unlikely]] default:
+      ERROR_LOG("Invalid read at offset 0x{:08X}", offset);
       return UINT32_C(0xFFFFFFFF);
   }
 }
@@ -89,7 +89,7 @@ void InterruptController::WriteRegister(u32 offset, u32 value)
       for (u32 i = 0; i < static_cast<u32>(IRQ::MaxCount); i++)
       {
         if (cleared_bits & (1u << i))
-          Log_DebugFmt("{} IRQ cleared", s_irq_names[i]);
+          DEBUG_LOG("{} IRQ cleared", s_irq_names[i]);
       }
 #endif
 
@@ -100,14 +100,14 @@ void InterruptController::WriteRegister(u32 offset, u32 value)
 
     case 0x04: // I_MASK
     {
-      Log_DebugFmt("Interrupt mask <- 0x{:08X}", value);
+      DEBUG_LOG("Interrupt mask <- 0x{:08X}", value);
       s_interrupt_mask_register = value & REGISTER_WRITE_MASK;
       UpdateCPUInterruptRequest();
     }
     break;
 
-    default: [[unlikely]]
-      Log_ErrorFmt("Invalid write at offset 0x{:08X}", offset);
+    default:
+      [[unlikely]] ERROR_LOG("Invalid write at offset 0x{:08X}", offset);
       break;
   }
 }
diff --git a/src/core/justifier.cpp b/src/core/justifier.cpp
index 9a51c61d8..72df27b00 100644
--- a/src/core/justifier.cpp
+++ b/src/core/justifier.cpp
@@ -219,7 +219,7 @@ void Justifier::UpdatePosition()
       !g_gpu->ConvertDisplayCoordinatesToBeamTicksAndLines(display_x, display_y, m_x_scale, &tick, &line) ||
       m_shoot_offscreen)
   {
-    Log_DevFmt("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y);
+    DEV_LOG("Lightgun out of range for window coordinates {:.0f},{:.0f}", window_x, window_y);
     m_position_valid = false;
     UpdateIRQEvent();
     return;
@@ -236,8 +236,8 @@ void Justifier::UpdatePosition()
                                                      static_cast<s32>(g_gpu->GetCRTCActiveStartLine()),
                                                      static_cast<s32>(g_gpu->GetCRTCActiveEndLine())));
 
-  Log_DevFmt("Lightgun window coordinates {},{} -> dpy {},{} -> tick {} line {} [{}-{}]", window_x, window_y, display_x,
-             display_y, tick, line, m_irq_first_line, m_irq_last_line);
+  DEV_LOG("Lightgun window coordinates {},{} -> dpy {},{} -> tick {} line {} [{}-{}]", window_x, window_y, display_x,
+          display_y, tick, line, m_irq_first_line, m_irq_last_line);
 
   UpdateIRQEvent();
 }
@@ -260,7 +260,7 @@ void Justifier::UpdateIRQEvent()
     target_line = current_line + 1;
 
   const TickCount ticks_until_pos = g_gpu->GetSystemTicksUntilTicksAndLine(m_irq_tick, target_line);
-  Log_DebugFmt("Triggering IRQ in {} ticks @ tick {} line {}", ticks_until_pos, m_irq_tick, target_line);
+  DEBUG_LOG("Triggering IRQ in {} ticks @ tick {} line {}", ticks_until_pos, m_irq_tick, target_line);
   m_irq_event->Schedule(ticks_until_pos);
 }
 
@@ -272,13 +272,13 @@ void Justifier::IRQEvent()
 
   const u32 expected_line = (s_irq_current_line == m_irq_last_line) ? m_irq_first_line : (s_irq_current_line + 1);
   if (line < expected_line)
-    Log_WarningFmt("IRQ event fired {} lines too early", expected_line - line);
+    WARNING_LOG("IRQ event fired {} lines too early", expected_line - line);
   else if (line > expected_line)
-    Log_WarningFmt("IRQ event fired {} lines too late", line - expected_line);
+    WARNING_LOG("IRQ event fired {} lines too late", line - expected_line);
   if (ticks < m_irq_tick)
-    Log_WarningFmt("IRQ event fired {} ticks too early", m_irq_tick - ticks);
+    WARNING_LOG("IRQ event fired {} ticks too early", m_irq_tick - ticks);
   else if (ticks > m_irq_tick)
-    Log_WarningFmt("IRQ event fired {} ticks too late", ticks - m_irq_tick);
+    WARNING_LOG("IRQ event fired {} ticks too late", ticks - m_irq_tick);
   s_irq_current_line = line;
 #endif
 
diff --git a/src/core/mdec.cpp b/src/core/mdec.cpp
index 01323ac13..45f2d5af6 100644
--- a/src/core/mdec.cpp
+++ b/src/core/mdec.cpp
@@ -202,13 +202,13 @@ u32 MDEC::ReadRegister(u32 offset)
 
     case 4:
     {
-      Log_TraceFmt("MDEC status register -> 0x{:08X}", s_status.bits);
+      TRACE_LOG("MDEC status register -> 0x{:08X}", s_status.bits);
       return s_status.bits;
     }
 
       [[unlikely]] default:
       {
-        Log_ErrorFmt("Unknown MDEC register read: 0x{:08X}", offset);
+        ERROR_LOG("Unknown MDEC register read: 0x{:08X}", offset);
         return UINT32_C(0xFFFFFFFF);
       }
   }
@@ -226,7 +226,7 @@ void MDEC::WriteRegister(u32 offset, u32 value)
 
     case 4:
     {
-      Log_DebugFmt("MDEC control register <- 0x{:08X}", value);
+      DEBUG_LOG("MDEC control register <- 0x{:08X}", value);
 
       const ControlRegister cr{value};
       if (cr.reset)
@@ -240,7 +240,7 @@ void MDEC::WriteRegister(u32 offset, u32 value)
 
       [[unlikely]] default:
       {
-        Log_ErrorFmt("Unknown MDEC register write: 0x{:08X} <- 0x{:08X}", offset, value);
+        ERROR_LOG("Unknown MDEC register write: 0x{:08X} <- 0x{:08X}", offset, value);
         return;
       }
   }
@@ -250,7 +250,7 @@ void MDEC::DMARead(u32* words, u32 word_count)
 {
   if (s_data_out_fifo.GetSize() < word_count) [[unlikely]]
   {
-    Log_WarningFmt("Insufficient data in output FIFO (requested {}, have {})", word_count, s_data_out_fifo.GetSize());
+    WARNING_LOG("Insufficient data in output FIFO (requested {}, have {})", word_count, s_data_out_fifo.GetSize());
   }
 
   const u32 words_to_read = std::min(word_count, s_data_out_fifo.GetSize());
@@ -261,7 +261,7 @@ void MDEC::DMARead(u32* words, u32 word_count)
     word_count -= words_to_read;
   }
 
-  Log_DebugFmt("DMA read complete, {} bytes left", s_data_out_fifo.GetSize() * sizeof(u32));
+  DEBUG_LOG("DMA read complete, {} bytes left", s_data_out_fifo.GetSize() * sizeof(u32));
   if (s_data_out_fifo.IsEmpty())
     Execute();
 }
@@ -270,7 +270,7 @@ void MDEC::DMAWrite(const u32* words, u32 word_count)
 {
   if (s_data_in_fifo.GetSpace() < (word_count * 2)) [[unlikely]]
   {
-    Log_WarningFmt("Input FIFO overflow (writing {}, space {})", word_count * 2, s_data_in_fifo.GetSpace());
+    WARNING_LOG("Input FIFO overflow (writing {}, space {})", word_count * 2, s_data_in_fifo.GetSpace());
   }
 
   const u32 halfwords_to_write = std::min(word_count * 2, s_data_in_fifo.GetSpace() & ~u32(2));
@@ -333,12 +333,12 @@ u32 MDEC::ReadDataRegister()
     // Stall the CPU until we're done processing.
     if (HasPendingBlockCopyOut())
     {
-      Log_DevPrint("MDEC data out FIFO empty on read - stalling CPU");
+      DEV_LOG("MDEC data out FIFO empty on read - stalling CPU");
       CPU::AddPendingTicks(s_block_copy_out_event->GetTicksUntilNextExecution());
     }
     else
     {
-      Log_WarningPrint("MDEC data out FIFO empty on read and no data processing");
+      WARNING_LOG("MDEC data out FIFO empty on read and no data processing");
       return UINT32_C(0xFFFFFFFF);
     }
   }
@@ -354,7 +354,7 @@ u32 MDEC::ReadDataRegister()
 
 void MDEC::WriteCommandRegister(u32 value)
 {
-  Log_TraceFmt("MDEC command/data register <- 0x{:08X}", value);
+  TRACE_LOG("MDEC command/data register <- 0x{:08X}", value);
 
   s_data_in_fifo.Push(Truncate16(value));
   s_data_in_fifo.Push(Truncate16(value >> 16));
@@ -401,14 +401,14 @@ void MDEC::Execute()
             break;
 
           default:
-            [[unlikely]] Log_DevFmt("Invalid MDEC command 0x{:08X}", cw.bits);
+            [[unlikely]] DEV_LOG("Invalid MDEC command 0x{:08X}", cw.bits);
             num_words = cw.parameter_word_count.GetValue();
             new_state = State::NoCommand;
             break;
         }
 
-        Log_DebugFmt("MDEC command: 0x{:08X} ({}, {} words in parameter, {} expected)", cw.bits,
-                     static_cast<u8>(cw.command.GetValue()), cw.parameter_word_count.GetValue(), num_words);
+        DEBUG_LOG("MDEC command: 0x{:08X} ({}, {} words in parameter, {} expected)", cw.bits,
+                  static_cast<u8>(cw.command.GetValue()), cw.parameter_word_count.GetValue(), num_words);
 
         s_remaining_halfwords = num_words * 2;
         s_state = new_state;
@@ -508,7 +508,7 @@ bool MDEC::DecodeMonoMacroblock()
 
   IDCT(s_blocks[0].data());
 
-  Log_DebugFmt("Decoded mono macroblock, {} words remaining", s_remaining_halfwords / 2);
+  DEBUG_LOG("Decoded mono macroblock, {} words remaining", s_remaining_halfwords / 2);
   ResetDecoder();
   s_state = State::WritingMacroblock;
 
@@ -534,7 +534,7 @@ bool MDEC::DecodeColoredMacroblock()
     return false;
 
   // done decoding
-  Log_DebugFmt("Decoded colored macroblock, {} words remaining", s_remaining_halfwords / 2);
+  DEBUG_LOG("Decoded colored macroblock, {} words remaining", s_remaining_halfwords / 2);
   ResetDecoder();
   s_state = State::WritingMacroblock;
 
@@ -551,7 +551,7 @@ bool MDEC::DecodeColoredMacroblock()
 void MDEC::ScheduleBlockCopyOut(TickCount ticks)
 {
   DebugAssert(!HasPendingBlockCopyOut());
-  Log_DebugFmt("Scheduling block copy out in {} ticks", ticks);
+  DEBUG_LOG("Scheduling block copy out in {} ticks", ticks);
 
   s_block_copy_out_event->SetIntervalAndSchedule(ticks);
 }
@@ -685,8 +685,8 @@ void MDEC::CopyOutBlock(void* param, TickCount ticks, TickCount ticks_late)
       break;
   }
 
-  Log_DebugFmt("Block copied out, fifo size = {} ({} bytes)", s_data_out_fifo.GetSize(),
-               s_data_out_fifo.GetSize() * sizeof(u32));
+  DEBUG_LOG("Block copied out, fifo size = {} ({} bytes)", s_data_out_fifo.GetSize(),
+            s_data_out_fifo.GetSize() * sizeof(u32));
 
   // if we've copied out all blocks, command is complete
   s_state = (s_remaining_halfwords == 0) ? State::Idle : State::DecodingMacroblock;
diff --git a/src/core/memory_card.cpp b/src/core/memory_card.cpp
index 83542c626..4bd68724c 100644
--- a/src/core/memory_card.cpp
+++ b/src/core/memory_card.cpp
@@ -143,7 +143,7 @@ bool MemoryCard::Transfer(const u8 data_in, u8* data_out)
       const u8 bits = m_data[ZeroExtend32(m_address) * MemoryCardImage::FRAME_SIZE + m_sector_offset];
       if (m_sector_offset == 0)
       {
-        Log_DevFmt("Reading memory card sector {}", m_address);
+        DEV_LOG("Reading memory card sector {}", m_address);
         m_checksum = Truncate8(m_address >> 8) ^ Truncate8(m_address) ^ bits;
       }
       else
@@ -177,7 +177,7 @@ bool MemoryCard::Transfer(const u8 data_in, u8* data_out)
     {
       if (m_sector_offset == 0)
       {
-        Log_InfoFmt("Writing memory card sector {}", m_address);
+        INFO_LOG("Writing memory card sector {}", m_address);
         m_checksum = Truncate8(m_address >> 8) ^ Truncate8(m_address) ^ data_in;
         m_FLAG.no_write_yet = false;
       }
@@ -251,7 +251,7 @@ bool MemoryCard::Transfer(const u8 data_in, u8* data_out)
         default:
           [[unlikely]]
           {
-            Log_ErrorFmt("Invalid command 0x{:02X}", data_in);
+            ERROR_LOG("Invalid command 0x{:02X}", data_in);
             *data_out = m_FLAG.bits;
             ack = false;
             m_state = State::Idle;
@@ -265,8 +265,8 @@ bool MemoryCard::Transfer(const u8 data_in, u8* data_out)
       break;
   }
 
-  Log_DebugFmt("Transfer, old_state={}, new_state={}, data_in=0x{:02X}, data_out=0x{:02X}, ack={}",
-               static_cast<u32>(old_state), static_cast<u32>(m_state), data_in, *data_out, ack ? "true" : "false");
+  DEBUG_LOG("Transfer, old_state={}, new_state={}, data_in=0x{:02X}, data_out=0x{:02X}, ack={}",
+            static_cast<u32>(old_state), static_cast<u32>(m_state), data_in, *data_out, ack ? "true" : "false");
   m_last_byte = data_in;
   return ack;
 }
@@ -289,7 +289,7 @@ std::unique_ptr<MemoryCard> MemoryCard::Open(std::string_view filename)
   mc->m_filename = filename;
   if (!mc->LoadFromFile())
   {
-    Log_InfoFmt("Memory card at '{}' could not be read, formatting.", mc->m_filename);
+    INFO_LOG("Memory card at '{}' could not be read, formatting.", mc->m_filename);
     Host::AddIconOSDMessage(fmt::format("memory_card_{}", filename), ICON_FA_SD_CARD,
                             fmt::format(TRANSLATE_FS("OSDMessage", "Memory card '{}' could not be read, formatting."),
                                         Path::GetFileName(filename), Host::OSD_INFO_DURATION));
diff --git a/src/core/memory_card_image.cpp b/src/core/memory_card_image.cpp
index 1ee3f52c7..57167452c 100644
--- a/src/core/memory_card_image.cpp
+++ b/src/core/memory_card_image.cpp
@@ -110,11 +110,11 @@ bool MemoryCardImage::LoadFromFile(DataArray* data, const char* filename)
   const size_t num_read = stream->Read(data->data(), DATA_SIZE);
   if (num_read != DATA_SIZE)
   {
-    Log_ErrorFmt("Only read {} of {} sectors from '{}'", num_read / FRAME_SIZE, static_cast<u32>(NUM_FRAMES), filename);
+    ERROR_LOG("Only read {} of {} sectors from '{}'", num_read / FRAME_SIZE, static_cast<u32>(NUM_FRAMES), filename);
     return false;
   }
 
-  Log_VerboseFmt("Loaded memory card from {}", filename);
+  VERBOSE_LOG("Loaded memory card from {}", filename);
   return true;
 }
 
@@ -125,18 +125,18 @@ bool MemoryCardImage::SaveToFile(const DataArray& data, const char* filename)
                                      BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED);
   if (!stream)
   {
-    Log_ErrorFmt("Failed to open '{}' for writing.", filename);
+    ERROR_LOG("Failed to open '{}' for writing.", filename);
     return false;
   }
 
   if (!stream->Write2(data.data(), DATA_SIZE) || !stream->Commit())
   {
-    Log_ErrorFmt("Failed to write sectors to '{}'", filename);
+    ERROR_LOG("Failed to write sectors to '{}'", filename);
     stream->Discard();
     return false;
   }
 
-  Log_VerboseFmt("Saved memory card to '{}'", filename);
+  VERBOSE_LOG("Saved memory card to '{}'", filename);
   return true;
 }
 
@@ -267,7 +267,7 @@ std::vector<MemoryCardImage::FileInfo> MemoryCardImage::EnumerateFiles(const Dat
     if (fi.num_blocks == FRAMES_PER_BLOCK)
     {
       // invalid
-      Log_WarningFmt("Invalid block chain in block {}", dir_frame);
+      WARNING_LOG("Invalid block chain in block {}", dir_frame);
       continue;
     }
 
@@ -281,7 +281,7 @@ std::vector<MemoryCardImage::FileInfo> MemoryCardImage::EnumerateFiles(const Dat
       num_icon_frames = 3;
     else
     {
-      Log_WarningFmt("Unknown icon flag 0x{:02X}", tf->icon_flag);
+      WARNING_LOG("Unknown icon flag 0x{:02X}", tf->icon_flag);
       continue;
     }
 
@@ -385,13 +385,13 @@ bool MemoryCardImage::WriteFile(DataArray* data, std::string_view filename, cons
       std::memset(data_block + size_to_copy, 0, size_to_zero);
   }
 
-  Log_InfoFmt("Wrote {} byte ({} block) file to memory card", buffer.size(), num_blocks);
+  INFO_LOG("Wrote {} byte ({} block) file to memory card", buffer.size(), num_blocks);
   return true;
 }
 
 bool MemoryCardImage::DeleteFile(DataArray* data, const FileInfo& fi, bool clear_sectors)
 {
-  Log_InfoFmt("Deleting '{}' from memory card ({} blocks)", fi.filename, fi.num_blocks);
+  INFO_LOG("Deleting '{}' from memory card ({} blocks)", fi.filename, fi.num_blocks);
 
   u32 block_number = fi.first_block;
   for (u32 i = 0; i < fi.num_blocks && (block_number > 0 && block_number < NUM_BLOCKS); i++)
@@ -424,11 +424,11 @@ bool MemoryCardImage::UndeleteFile(DataArray* data, const FileInfo& fi)
 {
   if (!fi.deleted)
   {
-    Log_ErrorFmt("File '{}' is not deleted", fi.filename);
+    ERROR_LOG("File '{}' is not deleted", fi.filename);
     return false;
   }
 
-  Log_InfoFmt("Undeleting '{}' from memory card ({} blocks)", fi.filename, fi.num_blocks);
+  INFO_LOG("Undeleting '{}' from memory card ({} blocks)", fi.filename, fi.num_blocks);
 
   // check that all blocks are present first
   u32 block_number = fi.first_block;
@@ -442,8 +442,8 @@ bool MemoryCardImage::UndeleteFile(DataArray* data, const FileInfo& fi)
     {
       if (df->block_allocation_state != 0xA1)
       {
-        Log_ErrorFmt("Incorrect block state for {}, expected 0xA1 got 0x{:02X}", this_block_number,
-                     df->block_allocation_state);
+        ERROR_LOG("Incorrect block state for {}, expected 0xA1 got 0x{:02X}", this_block_number,
+                  df->block_allocation_state);
         return false;
       }
     }
@@ -451,8 +451,8 @@ bool MemoryCardImage::UndeleteFile(DataArray* data, const FileInfo& fi)
     {
       if (df->block_allocation_state != 0xA3)
       {
-        Log_ErrorFmt("Incorrect block state for %u, expected 0xA3 got 0x{:02X}", this_block_number,
-                     df->block_allocation_state);
+        ERROR_LOG("Incorrect block state for %u, expected 0xA3 got 0x{:02X}", this_block_number,
+                  df->block_allocation_state);
         return false;
       }
     }
@@ -460,8 +460,8 @@ bool MemoryCardImage::UndeleteFile(DataArray* data, const FileInfo& fi)
     {
       if (df->block_allocation_state != 0xA2)
       {
-        Log_ErrorFmt("Incorrect block state for {}, expected 0xA2 got 0x{:02X}", this_block_number,
-                     df->block_allocation_state);
+        ERROR_LOG("Incorrect block state for {}, expected 0xA2 got 0x{:02X}", this_block_number,
+                  df->block_allocation_state);
         return false;
       }
     }
@@ -532,8 +532,8 @@ bool MemoryCardImage::ImportCardGME(DataArray* data, const char* filename, std::
   const u32 expected_size = sizeof(GMEHeader) + DATA_SIZE;
   if (file_data.size() < expected_size)
   {
-    Log_WarningFmt("GME memory card '{}' is too small (got {} expected {}), padding with zeroes", filename,
-                   file_data.size(), expected_size);
+    WARNING_LOG("GME memory card '{}' is too small (got {} expected {}), padding with zeroes", filename,
+                file_data.size(), expected_size);
     file_data.resize(expected_size);
   }
 
diff --git a/src/core/multitap.cpp b/src/core/multitap.cpp
index ca3edad79..18a61ef84 100644
--- a/src/core/multitap.cpp
+++ b/src/core/multitap.cpp
@@ -148,7 +148,7 @@ bool Multitap::Transfer(const u8 data_in, u8* data_out)
 
       if (!ack)
       {
-        Log_DevPrint("Memory card transfer ended");
+        DEV_LOG("Memory card transfer ended");
         m_transfer_state = TransferState::Idle;
       }
     }
@@ -206,7 +206,7 @@ bool Multitap::Transfer(const u8 data_in, u8* data_out)
 
       if (!ack)
       {
-        Log_DevPrint("Controller transfer ended");
+        DEV_LOG("Controller transfer ended");
         m_transfer_state = TransferState::Idle;
       }
     }
diff --git a/src/core/negcon_rumble.cpp b/src/core/negcon_rumble.cpp
index d9d6b26d3..3fd0e51ed 100644
--- a/src/core/negcon_rumble.cpp
+++ b/src/core/negcon_rumble.cpp
@@ -239,7 +239,7 @@ void NeGconRumble::SetAnalogMode(bool enabled, bool show_message)
   if (m_analog_mode == enabled)
     return;
 
-  Log_InfoFmt("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
+  INFO_LOG("Controller {} switched to {} mode.", m_index + 1u, m_analog_mode ? "analog" : "digital");
   if (show_message)
   {
     Host::AddIconOSDMessage(
@@ -361,12 +361,12 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
 
       if (data_in == 0x01)
       {
-        Log_DebugPrint("ACK controller access");
+        DEBUG_LOG("ACK controller access");
         m_command = Command::Ready;
         return true;
       }
 
-      Log_DevFmt("Unknown data_in = 0x{:02X}", data_in);
+      DEV_LOG("Unknown data_in = 0x{:02X}", data_in);
       return false;
     }
     break;
@@ -437,7 +437,7 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
       else
       {
         if (m_configuration_mode)
-          Log_ErrorFmt("Unimplemented config mode command 0x{:02X}", data_in);
+          ERROR_LOG("Unimplemented config mode command 0x{:02X}", data_in);
 
         *data_out = 0xFF;
         return false;
@@ -587,7 +587,7 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
           m_status_byte = 0x5A;
         }
 
-        Log_DevFmt("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave");
+        DEV_LOG("0x{:02x}({}) config mode", m_rx_buffer[2], m_configuration_mode ? "enter" : "leave");
       }
     }
     break;
@@ -596,14 +596,14 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
     {
       if (m_command_step == 2)
       {
-        Log_DevFmt("analog mode val 0x{:02x}", data_in);
+        DEV_LOG("analog mode val 0x{:02x}", data_in);
 
         if (data_in == 0x00 || data_in == 0x01)
           SetAnalogMode((data_in == 0x01), true);
       }
       else if (m_command_step == 3)
       {
-        Log_DevFmt("analog mode lock 0x{:02x}", data_in);
+        DEV_LOG("analog mode lock 0x{:02x}", data_in);
 
         if (data_in == 0x02 || data_in == 0x03)
           m_analog_locked = (data_in == 0x03);
@@ -700,10 +700,10 @@ bool NeGconRumble::Transfer(const u8 data_in, u8* data_out)
   {
     m_command = Command::Idle;
 
-    Log_DebugFmt("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1],
-                 m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]);
-    Log_DebugFmt("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1],
-                 m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]);
+    DEBUG_LOG("Rx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_rx_buffer[0], m_rx_buffer[1],
+              m_rx_buffer[2], m_rx_buffer[3], m_rx_buffer[4], m_rx_buffer[5], m_rx_buffer[6], m_rx_buffer[7]);
+    DEBUG_LOG("Tx: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x}", m_tx_buffer[0], m_tx_buffer[1],
+              m_tx_buffer[2], m_tx_buffer[3], m_tx_buffer[4], m_tx_buffer[5], m_tx_buffer[6], m_tx_buffer[7]);
 
     m_rx_buffer.fill(0x00);
     m_tx_buffer.fill(0x00);
diff --git a/src/core/pad.cpp b/src/core/pad.cpp
index aeddae137..19cea375a 100644
--- a/src/core/pad.cpp
+++ b/src/core/pad.cpp
@@ -214,10 +214,10 @@ bool Pad::DoStateController(StateWrapper& sw, u32 i)
                                    state_cinfo ? state_cinfo->GetDisplayName() : "", i + 1u);
     }
 
-    Log_DevFmt("Controller type mismatch in slot {}: state={}({}) ui={}({}) load_from_state={}", i + 1u,
-               state_cinfo ? state_cinfo->name : "", static_cast<unsigned>(state_controller_type),
-               Controller::GetControllerInfo(controller_type)->name, static_cast<unsigned>(controller_type),
-               g_settings.load_devices_from_save_states ? "yes" : "no");
+    DEV_LOG("Controller type mismatch in slot {}: state={}({}) ui={}({}) load_from_state={}", i + 1u,
+            state_cinfo ? state_cinfo->name : "", static_cast<unsigned>(state_controller_type),
+            Controller::GetControllerInfo(controller_type)->name, static_cast<unsigned>(controller_type),
+            g_settings.load_devices_from_save_states ? "yes" : "no");
 
     if (g_settings.load_devices_from_save_states)
     {
@@ -295,7 +295,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
     {
       if (s_memory_cards[i]->GetData() == card_ptr->GetData())
       {
-        Log_DevFmt("Card {} data matches, copying state", i + 1u);
+        DEV_LOG("Card {} data matches, copying state", i + 1u);
         s_memory_cards[i]->CopyState(card_ptr);
       }
       else
@@ -314,7 +314,7 @@ bool Pad::DoStateMemcard(StateWrapper& sw, u32 i, bool is_memory_state)
         // for the game to decide it was removed and purge its cache. Once implemented, this could be
         // described as deferred re-plugging in the log.
 
-        Log_WarningFmt("Memory card {} data mismatch. Using current data via instant-replugging.", i + 1u);
+        WARNING_LOG("Memory card {} data mismatch. Using current data via instant-replugging.", i + 1u);
         s_memory_cards[i]->Reset();
       }
     }
@@ -365,7 +365,7 @@ MemoryCard* Pad::GetDummyMemcard()
 
 void Pad::BackupMemoryCardState()
 {
-  Log_DevPrint("Backing up memory card state.");
+  DEV_LOG("Backing up memory card state.");
 
   if (!s_memory_card_backup)
   {
@@ -388,7 +388,7 @@ void Pad::RestoreMemoryCardState()
 {
   DebugAssert(s_memory_card_backup);
 
-  Log_VerbosePrint("Restoring backed up memory card state.");
+  VERBOSE_LOG("Restoring backed up memory card state.");
 
   s_memory_card_backup->SeekAbsolute(0);
   StateWrapper sw(s_memory_card_backup.get(), StateWrapper::Mode::Read, SAVE_STATE_VERSION);
@@ -558,8 +558,8 @@ MemoryCard* Pad::GetMemoryCard(u32 slot)
 
 void Pad::SetMemoryCard(u32 slot, std::unique_ptr<MemoryCard> dev)
 {
-  Log_InfoFmt("Memory card slot {}: {}", slot,
-              dev ? (dev->GetFilename().empty() ? "<no file configured>" : dev->GetFilename().c_str()) : "<unplugged>");
+  INFO_LOG("Memory card slot {}: {}", slot,
+           dev ? (dev->GetFilename().empty() ? "<no file configured>" : dev->GetFilename().c_str()) : "<unplugged>");
 
   s_memory_cards[slot] = std::move(dev);
 }
@@ -587,7 +587,7 @@ u32 Pad::ReadRegister(u32 offset)
         s_transfer_event->InvokeEarly();
 
       const u8 value = s_receive_buffer_full ? s_receive_buffer : 0xFF;
-      Log_DebugFmt("JOY_DATA (R) -> 0x{:02X}{}", value, s_receive_buffer_full ? "" : "(EMPTY)");
+      DEBUG_LOG("JOY_DATA (R) -> 0x{:02X}{}", value, s_receive_buffer_full ? "" : "(EMPTY)");
       s_receive_buffer_full = false;
       UpdateJoyStat();
 
@@ -615,7 +615,7 @@ u32 Pad::ReadRegister(u32 offset)
       return ZeroExtend32(s_JOY_BAUD);
 
     [[unlikely]] default:
-      Log_ErrorFmt("Unknown register read: 0x{:X}", offset);
+      ERROR_LOG("Unknown register read: 0x{:X}", offset);
       return UINT32_C(0xFFFFFFFF);
   }
 }
@@ -626,10 +626,10 @@ void Pad::WriteRegister(u32 offset, u32 value)
   {
     case 0x00: // JOY_DATA
     {
-      Log_DebugFmt("JOY_DATA (W) <- 0x{:02X}", value);
+      DEBUG_LOG("JOY_DATA (W) <- 0x{:02X}", value);
 
       if (s_transmit_buffer_full)
-        Log_WarningPrint("TX FIFO overrun");
+        WARNING_LOG("TX FIFO overrun");
 
       s_transmit_buffer = Truncate8(value);
       s_transmit_buffer_full = true;
@@ -642,7 +642,7 @@ void Pad::WriteRegister(u32 offset, u32 value)
 
     case 0x0A: // JOY_CTRL
     {
-      Log_DebugFmt("JOY_CTRL <- 0x{:04X}", value);
+      DEBUG_LOG("JOY_CTRL <- 0x{:04X}", value);
 
       s_JOY_CTRL.bits = Truncate16(value);
       if (s_JOY_CTRL.RESET)
@@ -675,21 +675,21 @@ void Pad::WriteRegister(u32 offset, u32 value)
 
     case 0x08: // JOY_MODE
     {
-      Log_DebugFmt("JOY_MODE <- 0x{:08X}", value);
+      DEBUG_LOG("JOY_MODE <- 0x{:08X}", value);
       s_JOY_MODE.bits = Truncate16(value);
       return;
     }
 
     case 0x0E:
     {
-      Log_DebugFmt("JOY_BAUD <- 0x{:08X}", value);
+      DEBUG_LOG("JOY_BAUD <- 0x{:08X}", value);
       s_JOY_BAUD = Truncate16(value);
       return;
     }
 
       [[unlikely]] default:
       {
-        Log_ErrorFmt("Unknown register write: 0x{:X} <- 0x{:08X}", offset, value);
+        ERROR_LOG("Unknown register write: 0x{:X} <- 0x{:08X}", offset, value);
         return;
       }
   }
@@ -744,7 +744,7 @@ void Pad::TransferEvent(void*, TickCount ticks, TickCount ticks_late)
 void Pad::BeginTransfer()
 {
   DebugAssert(s_state == State::Idle && CanTransfer());
-  Log_DebugPrint("Starting transfer");
+  DEBUG_LOG("Starting transfer");
 
   s_JOY_CTRL.RXEN = true;
   s_transmit_value = s_transmit_buffer;
@@ -771,7 +771,7 @@ void Pad::BeginTransfer()
 
 void Pad::DoTransfer(TickCount ticks_late)
 {
-  Log_DebugFmt("Transferring slot {}", s_JOY_CTRL.SLOT.GetValue());
+  DEBUG_LOG("Transferring slot {}", s_JOY_CTRL.SLOT.GetValue());
 
   const u8 device_index = s_multitaps[0].IsEnabled() ? 4u : s_JOY_CTRL.SLOT;
   Controller* const controller = s_controllers[device_index].get();
@@ -793,8 +793,8 @@ void Pad::DoTransfer(TickCount ticks_late)
       {
         if ((ack = s_multitaps[s_JOY_CTRL.SLOT].Transfer(data_out, &data_in)) == true)
         {
-          Log_TraceFmt("Active device set to tap {}, sent 0x{:02X}, received 0x{:02X}",
-                       static_cast<int>(s_JOY_CTRL.SLOT), data_out, data_in);
+          TRACE_LOG("Active device set to tap {}, sent 0x{:02X}, received 0x{:02X}", static_cast<int>(s_JOY_CTRL.SLOT),
+                    data_out, data_in);
           s_active_device = ActiveDevice::Multitap;
         }
       }
@@ -805,12 +805,12 @@ void Pad::DoTransfer(TickCount ticks_late)
           if (!memory_card || (ack = memory_card->Transfer(data_out, &data_in)) == false)
           {
             // nothing connected to this port
-            Log_TracePrint("Nothing connected or ACK'ed");
+            TRACE_LOG("Nothing connected or ACK'ed");
           }
           else
           {
             // memory card responded, make it the active device until non-ack
-            Log_TraceFmt("Transfer to memory card, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
+            TRACE_LOG("Transfer to memory card, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
             s_active_device = ActiveDevice::MemoryCard;
 
             // back up memory card state in case we roll back to before this transfer begun
@@ -827,7 +827,7 @@ void Pad::DoTransfer(TickCount ticks_late)
         else
         {
           // controller responded, make it the active device until non-ack
-          Log_TraceFmt("Transfer to controller, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
+          TRACE_LOG("Transfer to controller, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
           s_active_device = ActiveDevice::Controller;
         }
       }
@@ -839,7 +839,7 @@ void Pad::DoTransfer(TickCount ticks_late)
       if (controller)
       {
         ack = controller->Transfer(data_out, &data_in);
-        Log_TraceFmt("Transfer to controller, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
+        TRACE_LOG("Transfer to controller, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
       }
     }
     break;
@@ -850,7 +850,7 @@ void Pad::DoTransfer(TickCount ticks_late)
       {
         s_last_memory_card_transfer_frame = System::GetFrameNumber();
         ack = memory_card->Transfer(data_out, &data_in);
-        Log_TraceFmt("Transfer to memory card, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
+        TRACE_LOG("Transfer to memory card, data_out=0x{:02X}, data_in=0x{:02X}", data_out, data_in);
       }
     }
     break;
@@ -860,8 +860,8 @@ void Pad::DoTransfer(TickCount ticks_late)
       if (s_multitaps[s_JOY_CTRL.SLOT].IsEnabled())
       {
         ack = s_multitaps[s_JOY_CTRL.SLOT].Transfer(data_out, &data_in);
-        Log_TraceFmt("Transfer tap {}, sent 0x{:02X}, received 0x{:02X}, acked: {}", static_cast<int>(s_JOY_CTRL.SLOT),
-                     data_out, data_in, ack ? "true" : "false");
+        TRACE_LOG("Transfer tap {}, sent 0x{:02X}, received 0x{:02X}, acked: {}", static_cast<int>(s_JOY_CTRL.SLOT),
+                  data_out, data_in, ack ? "true" : "false");
       }
     }
     break;
@@ -883,7 +883,7 @@ void Pad::DoTransfer(TickCount ticks_late)
       (s_active_device == ActiveDevice::Multitap && s_multitaps[s_JOY_CTRL.SLOT].IsReadingMemoryCard());
 
     const TickCount ack_timer = GetACKTicks(memcard_transfer);
-    Log_DebugFmt("Delaying ACK for {} ticks", ack_timer);
+    DEBUG_LOG("Delaying ACK for {} ticks", ack_timer);
     s_state = State::WaitingForACK;
     s_transfer_event->SetPeriodAndSchedule(ack_timer);
   }
@@ -897,7 +897,7 @@ void Pad::DoACK()
 
   if (s_JOY_CTRL.ACKINTEN)
   {
-    Log_DebugPrint("Triggering ACK interrupt");
+    DEBUG_LOG("Triggering ACK interrupt");
     s_JOY_STAT.INTR = true;
     InterruptController::SetLineState(InterruptController::IRQ::PAD, true);
   }
@@ -912,7 +912,7 @@ void Pad::DoACK()
 void Pad::EndTransfer()
 {
   DebugAssert(s_state == State::Transmitting || s_state == State::WaitingForACK);
-  Log_DebugPrint("Ending transfer");
+  DEBUG_LOG("Ending transfer");
 
   s_state = State::Idle;
   s_transfer_event->Deactivate();
diff --git a/src/core/pcdrv.cpp b/src/core/pcdrv.cpp
index ec22794db..2983b14c2 100644
--- a/src/core/pcdrv.cpp
+++ b/src/core/pcdrv.cpp
@@ -33,7 +33,7 @@ static s32 GetFreeFileHandle()
 
   if (s_files.size() >= MAX_FILES)
   {
-    Log_ErrorPrint("Too many open files.");
+    ERROR_LOG("Too many open files.");
     return -1;
   }
 
@@ -45,7 +45,7 @@ static s32 GetFreeFileHandle()
 static void CloseAllFiles()
 {
   if (!s_files.empty())
-    Log_DevFmt("Closing {} open files.", s_files.size());
+    DEV_LOG("Closing {} open files.", s_files.size());
 
   s_files.clear();
 }
@@ -54,7 +54,7 @@ static FILE* GetFileFromHandle(u32 handle)
 {
   if (handle >= static_cast<u32>(s_files.size()) || !s_files[handle])
   {
-    Log_ErrorFmt("Invalid file handle {}", static_cast<s32>(handle));
+    ERROR_LOG("Invalid file handle {}", static_cast<s32>(handle));
     return nullptr;
   }
 
@@ -65,7 +65,7 @@ static bool CloseFileHandle(u32 handle)
 {
   if (handle >= static_cast<u32>(s_files.size()) || !s_files[handle])
   {
-    Log_ErrorFmt("Invalid file handle {}", static_cast<s32>(handle));
+    ERROR_LOG("Invalid file handle {}", static_cast<s32>(handle));
     return false;
   }
 
@@ -85,9 +85,9 @@ static std::string ResolveHostPath(const std::string& path)
       !canonicalized_path.starts_with(root) ||                            // and start with the host root,
       canonicalized_path[root.length()] != FS_OSPATH_SEPARATOR_CHARACTER) // and we can't access a sibling.
   {
-    Log_ErrorFmt("Denying access to path outside of PCDrv directory. Requested path: '{}', "
-                 "Resolved path: '{}', Root directory: '{}'",
-                 path, root, canonicalized_path);
+    ERROR_LOG("Denying access to path outside of PCDrv directory. Requested path: '{}', "
+              "Resolved path: '{}', Root directory: '{}'",
+              path, root, canonicalized_path);
     canonicalized_path.clear();
   }
 
@@ -99,8 +99,8 @@ void PCDrv::Initialize()
   if (!g_settings.pcdrv_enable)
     return;
 
-  Log_WarningFmt("{} PCDrv is enabled at '{}'", g_settings.pcdrv_enable_writes ? "Read/Write" : "Read-Only",
-                 g_settings.pcdrv_root);
+  WARNING_LOG("{} PCDrv is enabled at '{}'", g_settings.pcdrv_enable_writes ? "Read/Write" : "Read-Only",
+              g_settings.pcdrv_root);
 }
 
 void PCDrv::Reset()
@@ -126,7 +126,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
   {
     case 0x101: // PCinit
     {
-      Log_DevPrint("PCinit");
+      DEV_LOG("PCinit");
       CloseAllFiles();
       regs.v0 = 0;
       regs.v1 = 0;
@@ -142,11 +142,11 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
       std::string filename;
       if (!CPU::SafeReadMemoryCString(regs.a1, &filename))
       {
-        Log_ErrorFmt("{}: Invalid string", func);
+        ERROR_LOG("{}: Invalid string", func);
         return false;
       }
 
-      Log_DebugFmt("{}: '{}' mode {}", func, filename, mode);
+      DEBUG_LOG("{}: '{}' mode {}", func, filename, mode);
       if ((filename = ResolveHostPath(filename)).empty())
       {
         RETURN_ERROR();
@@ -155,7 +155,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
 
       if (!is_open && !g_settings.pcdrv_enable_writes)
       {
-        Log_ErrorFmt("{}: Writes are not enabled", func);
+        ERROR_LOG("{}: Writes are not enabled", func);
         RETURN_ERROR();
         return true;
       }
@@ -163,7 +163,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
       // Directories are unsupported for now, ignore other attributes
       if (mode & PCDRV_ATTRIBUTE_DIRECTORY)
       {
-        Log_ErrorFmt("{}: Directories are unsupported", func);
+        ERROR_LOG("{}: Directories are unsupported", func);
         RETURN_ERROR();
         return true;
       }
@@ -180,12 +180,12 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
                                                      is_open ? (g_settings.pcdrv_enable_writes ? "r+b" : "rb") : "w+b");
       if (!s_files[handle])
       {
-        Log_ErrorFmt("{}: Failed to open '{}'", func, filename);
+        ERROR_LOG("{}: Failed to open '{}'", func, filename);
         RETURN_ERROR();
         return true;
       }
 
-      Log_ErrorFmt("PCDrv: Opened '{}' => {}", filename, handle);
+      ERROR_LOG("PCDrv: Opened '{}' => {}", filename, handle);
       regs.v0 = 0;
       regs.v1 = static_cast<u32>(handle);
       return true;
@@ -193,7 +193,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
 
     case 0x104: // PCclose
     {
-      Log_DebugFmt("PCclose({})", regs.a1);
+      DEBUG_LOG("PCclose({})", regs.a1);
 
       if (!CloseFileHandle(regs.a1))
       {
@@ -208,7 +208,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
 
     case 0x105: // PCread
     {
-      Log_DebugFmt("PCread({}, {}, 0x{:08X})", regs.a1, regs.a2, regs.a3);
+      DEBUG_LOG("PCread({}, {}, 0x{:08X})", regs.a1, regs.a2, regs.a3);
 
       std::FILE* fp = GetFileFromHandle(regs.a1);
       if (!fp)
@@ -246,7 +246,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
 
     case 0x106: // PCwrite
     {
-      Log_DebugFmt("PCwrite({}, {}, 0x{:08x})", regs.a1, regs.a2, regs.a3);
+      DEBUG_LOG("PCwrite({}, {}, 0x{:08x})", regs.a1, regs.a2, regs.a3);
 
       std::FILE* fp = GetFileFromHandle(regs.a1);
       if (!fp)
@@ -281,7 +281,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
 
     case 0x107: // PClseek
     {
-      Log_DebugFmt("PClseek({}, {}, {})", regs.a1, regs.a2, regs.a3);
+      DEBUG_LOG("PClseek({}, {}, {})", regs.a1, regs.a2, regs.a3);
 
       std::FILE* fp = GetFileFromHandle(regs.a1);
       if (!fp)
@@ -311,7 +311,7 @@ bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs)
 
       if (FileSystem::FSeek64(fp, offset, hmode) != 0)
       {
-        Log_ErrorFmt("FSeek for PCDrv failed: {} {}", offset, hmode);
+        ERROR_LOG("FSeek for PCDrv failed: {} {}", offset, hmode);
         RETURN_ERROR();
         return true;
       }
diff --git a/src/core/psf_loader.cpp b/src/core/psf_loader.cpp
index 14079d067..73e9e0056 100644
--- a/src/core/psf_loader.cpp
+++ b/src/core/psf_loader.cpp
@@ -66,7 +66,7 @@ bool File::Load(const char* path)
   std::optional<std::vector<u8>> file_data(FileSystem::ReadBinaryFile(path));
   if (!file_data.has_value() || file_data->empty())
   {
-    Log_ErrorFmt("Failed to open/read PSF file '{}'", path);
+    ERROR_LOG("Failed to open/read PSF file '{}'", path);
     return false;
   }
 
@@ -81,7 +81,7 @@ bool File::Load(const char* path)
       header.compressed_program_size == 0 ||
       (sizeof(header) + header.reserved_area_size + header.compressed_program_size) > file_size)
   {
-    Log_ErrorFmt("Invalid or incompatible header in PSF '{}'", path);
+    ERROR_LOG("Invalid or incompatible header in PSF '{}'", path);
     return false;
   }
 
@@ -98,7 +98,7 @@ bool File::Load(const char* path)
   int err = inflateInit(&strm);
   if (err != Z_OK)
   {
-    Log_ErrorFmt("inflateInit() failed: {}", err);
+    ERROR_LOG("inflateInit() failed: {}", err);
     return false;
   }
 
@@ -106,14 +106,14 @@ bool File::Load(const char* path)
   err = inflate(&strm, Z_NO_FLUSH);
   if (err != Z_STREAM_END)
   {
-    Log_ErrorFmt("inflate() failed: {}", err);
+    ERROR_LOG("inflate() failed: {}", err);
     inflateEnd(&strm);
     return false;
   }
   else if (strm.total_in != header.compressed_program_size)
   {
-    Log_WarningFmt("Mismatch between compressed size in header and stream {}/{}", header.compressed_program_size,
-                      static_cast<u32>(strm.total_in));
+    WARNING_LOG("Mismatch between compressed size in header and stream {}/{}", header.compressed_program_size,
+                static_cast<u32>(strm.total_in));
   }
 
   m_program_data.resize(strm.total_out);
@@ -147,7 +147,7 @@ bool File::Load(const char* path)
 
       if (!tag_key.empty())
       {
-        Log_DevFmt("PSF Tag: '{}' = '{}'", tag_key, tag_value);
+        DEV_LOG("PSF Tag: '{}' = '{}'", tag_key, tag_value);
         m_tags.emplace(std::move(tag_key), std::move(tag_value));
       }
     }
@@ -171,14 +171,14 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
   // don't recurse past 10 levels just in case of broken files
   if (depth >= 10)
   {
-    Log_ErrorFmt("Recursion depth exceeded when loading PSF '{}'", path);
+    ERROR_LOG("Recursion depth exceeded when loading PSF '{}'", path);
     return false;
   }
 
   File file;
   if (!file.Load(path))
   {
-    Log_ErrorFmt("Failed to load main PSF '{}'", path);
+    ERROR_LOG("Failed to load main PSF '{}'", path);
     return false;
   }
 
@@ -187,13 +187,13 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
   if (lib_name.has_value())
   {
     const std::string lib_path(Path::BuildRelativePath(path, lib_name.value()));
-    Log_InfoFmt("Loading main parent PSF '{}'", lib_path);
+    INFO_LOG("Loading main parent PSF '{}'", lib_path);
 
     // We should use the initial SP/PC from the **first** parent lib.
     const bool lib_use_pc_sp = (depth == 0);
     if (!LoadLibraryPSF(lib_path.c_str(), lib_use_pc_sp, depth + 1))
     {
-      Log_ErrorFmt("Failed to load main parent PSF '{}'", lib_path);
+      ERROR_LOG("Failed to load main parent PSF '{}'", lib_path);
       return false;
     }
 
@@ -206,7 +206,7 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
   if (!System::InjectEXEFromBuffer(file.GetProgramData().data(), static_cast<u32>(file.GetProgramData().size()),
                                    use_pc_sp))
   {
-    Log_ErrorFmt("Failed to parse EXE from PSF '{}'", path);
+    ERROR_LOG("Failed to parse EXE from PSF '{}'", path);
     return false;
   }
 
@@ -219,10 +219,10 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
       break;
 
     const std::string lib_path(Path::BuildRelativePath(path, lib_name.value()));
-    Log_InfoFmt("Loading parent PSF '{}'", lib_path);
+    INFO_LOG("Loading parent PSF '{}'", lib_path);
     if (!LoadLibraryPSF(lib_path.c_str(), false, depth + 1))
     {
-      Log_ErrorFmt("Failed to load parent PSF '{}'", lib_path);
+      ERROR_LOG("Failed to load parent PSF '{}'", lib_path);
       return false;
     }
   }
@@ -232,7 +232,7 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0)
 
 bool Load(const char* path)
 {
-  Log_InfoFmt("Loading PSF file from '{}'", path);
+  INFO_LOG("Loading PSF file from '{}'", path);
   return LoadLibraryPSF(path, true);
 }
 
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 38440d85a..153a849a1 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -735,7 +735,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
 #ifndef ENABLE_MMAP_FASTMEM
   if (g_settings.cpu_fastmem_mode == CPUFastmemMode::MMap)
   {
-    Log_WarningPrint("mmap fastmem is not available on this platform, using LUT instead.");
+    WARNING_LOG("mmap fastmem is not available on this platform, using LUT instead.");
     g_settings.cpu_fastmem_mode = CPUFastmemMode::LUT;
   }
 #endif
@@ -770,7 +770,7 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
     // be unlinked. Which would be thousands of blocks.
     if (g_settings.cpu_recompiler_block_linking)
     {
-      Log_WarningPrint("Disabling block linking due to runahead.");
+      WARNING_LOG("Disabling block linking due to runahead.");
       g_settings.cpu_recompiler_block_linking = false;
     }
   }
@@ -844,14 +844,15 @@ void Settings::SetDefaultControllerConfig(SettingsInterface& si)
 #endif
 }
 
-static constexpr const std::array s_log_level_names = {"None",    "Error", "Warning", "Perf",  "Info",
-                                                       "Verbose", "Dev",   "Profile", "Debug", "Trace"};
+static constexpr const std::array s_log_level_names = {
+  "None", "Error", "Warning", "Info", "Verbose", "Dev", "Debug", "Trace",
+};
 static constexpr const std::array s_log_level_display_names = {
-  TRANSLATE_NOOP("LogLevel", "None"),        TRANSLATE_NOOP("LogLevel", "Error"),
-  TRANSLATE_NOOP("LogLevel", "Warning"),     TRANSLATE_NOOP("LogLevel", "Performance"),
-  TRANSLATE_NOOP("LogLevel", "Information"), TRANSLATE_NOOP("LogLevel", "Verbose"),
-  TRANSLATE_NOOP("LogLevel", "Developer"),   TRANSLATE_NOOP("LogLevel", "Profile"),
-  TRANSLATE_NOOP("LogLevel", "Debug"),       TRANSLATE_NOOP("LogLevel", "Trace")};
+  TRANSLATE_NOOP("LogLevel", "None"),    TRANSLATE_NOOP("LogLevel", "Error"),
+  TRANSLATE_NOOP("LogLevel", "Warning"), TRANSLATE_NOOP("LogLevel", "Information"),
+  TRANSLATE_NOOP("LogLevel", "Verbose"), TRANSLATE_NOOP("LogLevel", "Developer"),
+  TRANSLATE_NOOP("LogLevel", "Debug"),   TRANSLATE_NOOP("LogLevel", "Trace"),
+};
 
 std::optional<LOGLEVEL> Settings::ParseLogLevelName(const char* str)
 {
@@ -1418,11 +1419,7 @@ const char* Settings::GetDisplayAlignmentDisplayName(DisplayAlignment alignment)
 }
 
 static constexpr const std::array s_display_scaling_names = {
-  "Nearest",
-  "NearestInteger",
-  "BilinearSmooth",
-  "BilinearSharp",
-  "BilinearInteger",
+  "Nearest", "NearestInteger", "BilinearSmooth", "BilinearSharp", "BilinearInteger",
 };
 static constexpr const std::array s_display_scaling_display_names = {
   TRANSLATE_NOOP("DisplayScalingMode", "Nearest-Neighbor"),
@@ -1752,20 +1749,20 @@ void EmuFolders::LoadConfig(SettingsInterface& si)
   Textures = LoadPathFromSettings(si, DataRoot, "Folders", "Textures", "textures");
   UserResources = LoadPathFromSettings(si, DataRoot, "Folders", "UserResources", "resources");
 
-  Log_DevFmt("BIOS Directory: {}", Bios);
-  Log_DevFmt("Cache Directory: {}", Cache);
-  Log_DevFmt("Cheats Directory: {}", Cheats);
-  Log_DevFmt("Covers Directory: {}", Covers);
-  Log_DevFmt("Dumps Directory: {}", Dumps);
-  Log_DevFmt("Game Settings Directory: {}", GameSettings);
-  Log_DevFmt("Input Profile Directory: {}", InputProfiles);
-  Log_DevFmt("MemoryCards Directory: {}", MemoryCards);
-  Log_DevFmt("Resources Directory: {}", Resources);
-  Log_DevFmt("SaveStates Directory: {}", SaveStates);
-  Log_DevFmt("Screenshots Directory: {}", Screenshots);
-  Log_DevFmt("Shaders Directory: {}", Shaders);
-  Log_DevFmt("Textures Directory: {}", Textures);
-  Log_DevFmt("User Resources Directory: {}", UserResources);
+  DEV_LOG("BIOS Directory: {}", Bios);
+  DEV_LOG("Cache Directory: {}", Cache);
+  DEV_LOG("Cheats Directory: {}", Cheats);
+  DEV_LOG("Covers Directory: {}", Covers);
+  DEV_LOG("Dumps Directory: {}", Dumps);
+  DEV_LOG("Game Settings Directory: {}", GameSettings);
+  DEV_LOG("Input Profile Directory: {}", InputProfiles);
+  DEV_LOG("MemoryCards Directory: {}", MemoryCards);
+  DEV_LOG("Resources Directory: {}", Resources);
+  DEV_LOG("SaveStates Directory: {}", SaveStates);
+  DEV_LOG("Screenshots Directory: {}", Screenshots);
+  DEV_LOG("Shaders Directory: {}", Shaders);
+  DEV_LOG("Textures Directory: {}", Textures);
+  DEV_LOG("User Resources Directory: {}", UserResources);
 }
 
 void EmuFolders::Save(SettingsInterface& si)
@@ -1841,7 +1838,7 @@ std::string EmuFolders::GetOverridableResourcePath(std::string_view name)
   if (FileSystem::FileExists(upath.c_str()))
   {
     if (UserResources != Resources)
-      Log_WarningFmt("Using user-provided resource file {}", name);
+      WARNING_LOG("Using user-provided resource file {}", name);
   }
   else
   {
diff --git a/src/core/sio.cpp b/src/core/sio.cpp
index ba2629784..037aa86f7 100644
--- a/src/core/sio.cpp
+++ b/src/core/sio.cpp
@@ -106,7 +106,7 @@ u32 SIO::ReadRegister(u32 offset)
   {
     case 0x00: // SIO_DATA
     {
-      Log_ErrorPrint("Read SIO_DATA");
+      ERROR_LOG("Read SIO_DATA");
 
       const u8 value = 0xFF;
       return (ZeroExtend32(value) | (ZeroExtend32(value) << 8) | (ZeroExtend32(value) << 16) |
@@ -129,7 +129,7 @@ u32 SIO::ReadRegister(u32 offset)
       return ZeroExtend32(s_SIO_BAUD);
 
     [[unlikely]] default:
-      Log_ErrorFmt("Unknown register read: 0x{:X}", offset);
+      ERROR_LOG("Unknown register read: 0x{:X}", offset);
       return UINT32_C(0xFFFFFFFF);
   }
 }
@@ -140,13 +140,13 @@ void SIO::WriteRegister(u32 offset, u32 value)
   {
     case 0x00: // SIO_DATA
     {
-      Log_WarningFmt("SIO_DATA (W) <- 0x{:02X}", value);
+      WARNING_LOG("SIO_DATA (W) <- 0x{:02X}", value);
       return;
     }
 
     case 0x0A: // SIO_CTRL
     {
-      Log_DebugFmt("SIO_CTRL <- 0x{:04X}", value);
+      DEBUG_LOG("SIO_CTRL <- 0x{:04X}", value);
 
       s_SIO_CTRL.bits = Truncate16(value);
       if (s_SIO_CTRL.RESET)
@@ -157,20 +157,20 @@ void SIO::WriteRegister(u32 offset, u32 value)
 
     case 0x08: // SIO_MODE
     {
-      Log_DebugFmt("SIO_MODE <- 0x{:08X}", value);
+      DEBUG_LOG("SIO_MODE <- 0x{:08X}", value);
       s_SIO_MODE.bits = Truncate16(value);
       return;
     }
 
     case 0x0E:
     {
-      Log_DebugFmt("SIO_BAUD <- 0x{:08X}", value);
+      DEBUG_LOG("SIO_BAUD <- 0x{:08X}", value);
       s_SIO_BAUD = Truncate16(value);
       return;
     }
 
     default:
-      Log_ErrorFmt("Unknown register write: 0x{:X} <- 0x{:08X}", offset, value);
+      ERROR_LOG("Unknown register write: 0x{:X} <- 0x{:08X}", offset, value);
       return;
   }
 }
diff --git a/src/core/spu.cpp b/src/core/spu.cpp
index 675b0ffef..bc6f06366 100644
--- a/src/core/spu.cpp
+++ b/src/core/spu.cpp
@@ -430,13 +430,12 @@ void SPU::Initialize()
 
 void SPU::CreateOutputStream()
 {
-  Log_InfoFmt(
-    "Creating '{}' audio stream, sample rate = {}, expansion = {}, buffer = {}, latency = {}{}, stretching = {}",
-    AudioStream::GetBackendName(g_settings.audio_backend), static_cast<u32>(SAMPLE_RATE),
-    AudioStream::GetExpansionModeName(g_settings.audio_stream_parameters.expansion_mode),
-    g_settings.audio_stream_parameters.buffer_ms, g_settings.audio_stream_parameters.output_latency_ms,
-    g_settings.audio_stream_parameters.output_latency_minimal ? " (or minimal)" : "",
-    AudioStream::GetStretchModeName(g_settings.audio_stream_parameters.stretch_mode));
+  INFO_LOG("Creating '{}' audio stream, sample rate = {}, expansion = {}, buffer = {}, latency = {}{}, stretching = {}",
+           AudioStream::GetBackendName(g_settings.audio_backend), static_cast<u32>(SAMPLE_RATE),
+           AudioStream::GetExpansionModeName(g_settings.audio_stream_parameters.expansion_mode),
+           g_settings.audio_stream_parameters.buffer_ms, g_settings.audio_stream_parameters.output_latency_ms,
+           g_settings.audio_stream_parameters.output_latency_minimal ? " (or minimal)" : "",
+           AudioStream::GetStretchModeName(g_settings.audio_stream_parameters.stretch_mode));
 
   Error error;
   s_audio_stream =
@@ -665,28 +664,28 @@ u16 SPU::ReadRegister(u32 offset)
       return s_reverb_registers.mBASE;
 
     case 0x1F801DA4 - SPU_BASE:
-      Log_TraceFmt("SPU IRQ address -> 0x{:04X}", s_irq_address);
+      TRACE_LOG("SPU IRQ address -> 0x{:04X}", s_irq_address);
       return s_irq_address;
 
     case 0x1F801DA6 - SPU_BASE:
-      Log_TraceFmt("SPU transfer address register -> 0x{:04X}", s_transfer_address_reg);
+      TRACE_LOG("SPU transfer address register -> 0x{:04X}", s_transfer_address_reg);
       return s_transfer_address_reg;
 
     case 0x1F801DA8 - SPU_BASE:
-      Log_TraceFmt("SPU transfer data register read");
+      TRACE_LOG("SPU transfer data register read");
       return UINT16_C(0xFFFF);
 
     case 0x1F801DAA - SPU_BASE:
-      Log_TraceFmt("SPU control register -> 0x{:04X}", s_SPUCNT.bits);
+      TRACE_LOG("SPU control register -> 0x{:04X}", s_SPUCNT.bits);
       return s_SPUCNT.bits;
 
     case 0x1F801DAC - SPU_BASE:
-      Log_TraceFmt("SPU transfer control register -> 0x{:04X}", s_transfer_control.bits);
+      TRACE_LOG("SPU transfer control register -> 0x{:04X}", s_transfer_control.bits);
       return s_transfer_control.bits;
 
     case 0x1F801DAE - SPU_BASE:
       GeneratePendingSamples();
-      Log_TraceFmt("SPU status register -> 0x{:04X}", s_SPUCNT.bits);
+      TRACE_LOG("SPU status register -> 0x{:04X}", s_SPUCNT.bits);
       return s_SPUSTAT.bits;
 
     case 0x1F801DB0 - SPU_BASE:
@@ -727,7 +726,7 @@ u16 SPU::ReadRegister(u32 offset)
           return s_voices[voice_index].right_volume.current_level;
       }
 
-      Log_DevFmt("Unknown SPU register read: offset 0x{:X} (address 0x{:08X})", offset, offset | SPU_BASE);
+      DEV_LOG("Unknown SPU register read: offset 0x{:X} (address 0x{:08X})", offset, offset | SPU_BASE);
       return UINT16_C(0xFFFF);
     }
   }
@@ -739,7 +738,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
   {
     case 0x1F801D80 - SPU_BASE:
     {
-      Log_DebugFmt("SPU main volume left <- 0x{:04X}", value);
+      DEBUG_LOG("SPU main volume left <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_main_volume_left_reg.bits = value;
       s_main_volume_left.Reset(s_main_volume_left_reg);
@@ -748,7 +747,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D82 - SPU_BASE:
     {
-      Log_DebugFmt("SPU main volume right <- 0x{:04X}", value);
+      DEBUG_LOG("SPU main volume right <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_main_volume_right_reg.bits = value;
       s_main_volume_right.Reset(s_main_volume_right_reg);
@@ -757,7 +756,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D84 - SPU_BASE:
     {
-      Log_DebugFmt("SPU reverb output volume left <- 0x{:04X}", value);
+      DEBUG_LOG("SPU reverb output volume left <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_reverb_registers.vLOUT = value;
       return;
@@ -765,7 +764,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D86 - SPU_BASE:
     {
-      Log_DebugFmt("SPU reverb output volume right <- 0x{:04X}", value);
+      DEBUG_LOG("SPU reverb output volume right <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_reverb_registers.vROUT = value;
       return;
@@ -773,7 +772,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D88 - SPU_BASE:
     {
-      Log_DebugFmt("SPU key on low <- 0x{:04X}", value);
+      DEBUG_LOG("SPU key on low <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_key_on_register = (s_key_on_register & 0xFFFF0000) | ZeroExtend32(value);
     }
@@ -781,7 +780,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D8A - SPU_BASE:
     {
-      Log_DebugFmt("SPU key on high <- 0x{:04X}", value);
+      DEBUG_LOG("SPU key on high <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_key_on_register = (s_key_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
     }
@@ -789,7 +788,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D8C - SPU_BASE:
     {
-      Log_DebugFmt("SPU key off low <- 0x{:04X}", value);
+      DEBUG_LOG("SPU key off low <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_key_off_register = (s_key_off_register & 0xFFFF0000) | ZeroExtend32(value);
     }
@@ -797,7 +796,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D8E - SPU_BASE:
     {
-      Log_DebugFmt("SPU key off high <- 0x{:04X}", value);
+      DEBUG_LOG("SPU key off high <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_key_off_register = (s_key_off_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
     }
@@ -807,7 +806,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
     {
       GeneratePendingSamples();
       s_pitch_modulation_enable_register = (s_pitch_modulation_enable_register & 0xFFFF0000) | ZeroExtend32(value);
-      Log_DebugFmt("SPU pitch modulation enable register <- 0x{:08X}", s_pitch_modulation_enable_register);
+      DEBUG_LOG("SPU pitch modulation enable register <- 0x{:08X}", s_pitch_modulation_enable_register);
     }
     break;
 
@@ -816,13 +815,13 @@ void SPU::WriteRegister(u32 offset, u16 value)
       GeneratePendingSamples();
       s_pitch_modulation_enable_register =
         (s_pitch_modulation_enable_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
-      Log_DebugFmt("SPU pitch modulation enable register <- 0x{:08X}", s_pitch_modulation_enable_register);
+      DEBUG_LOG("SPU pitch modulation enable register <- 0x{:08X}", s_pitch_modulation_enable_register);
     }
     break;
 
     case 0x1F801D94 - SPU_BASE:
     {
-      Log_DebugFmt("SPU noise mode register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU noise mode register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_noise_mode_register = (s_noise_mode_register & 0xFFFF0000) | ZeroExtend32(value);
     }
@@ -830,7 +829,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D96 - SPU_BASE:
     {
-      Log_DebugFmt("SPU noise mode register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU noise mode register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_noise_mode_register = (s_noise_mode_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
     }
@@ -838,7 +837,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D98 - SPU_BASE:
     {
-      Log_DebugFmt("SPU reverb on register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU reverb on register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_reverb_on_register = (s_reverb_on_register & 0xFFFF0000) | ZeroExtend32(value);
     }
@@ -846,7 +845,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801D9A - SPU_BASE:
     {
-      Log_DebugFmt("SPU reverb on register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU reverb on register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_reverb_on_register = (s_reverb_on_register & 0x0000FFFF) | (ZeroExtend32(value) << 16);
     }
@@ -854,7 +853,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DA2 - SPU_BASE:
     {
-      Log_DebugFmt("SPU reverb base address < 0x{:04X}", value);
+      DEBUG_LOG("SPU reverb base address < 0x{:04X}", value);
       GeneratePendingSamples();
       s_reverb_registers.mBASE = value;
       s_reverb_base_address = ZeroExtend32(value << 2) & 0x3FFFFu;
@@ -864,7 +863,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DA4 - SPU_BASE:
     {
-      Log_DebugFmt("SPU IRQ address register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU IRQ address register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_irq_address = value;
 
@@ -876,14 +875,14 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DA6 - SPU_BASE:
     {
-      Log_DebugFmt("SPU transfer address register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU transfer address register <- 0x{:04X}", value);
       s_transfer_event->InvokeEarly();
       s_transfer_address_reg = value;
       s_transfer_address = ZeroExtend32(value) * 8;
       if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address))
       {
-        Log_DebugFmt("Trigger IRQ @ {:08X} {:04X} from transfer address reg set", s_transfer_address,
-                     s_transfer_address / 8);
+        DEBUG_LOG("Trigger IRQ @ {:08X} {:04X} from transfer address reg set", s_transfer_address,
+                  s_transfer_address / 8);
         TriggerRAMIRQ();
       }
       return;
@@ -891,8 +890,8 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DA8 - SPU_BASE:
     {
-      Log_TraceFmt("SPU transfer data register <- 0x{:04X} (RAM offset 0x{:08X})", ZeroExtend32(value),
-                   s_transfer_address);
+      TRACE_LOG("SPU transfer data register <- 0x{:04X} (RAM offset 0x{:08X})", ZeroExtend32(value),
+                s_transfer_address);
 
       ManualTransferWrite(value);
       return;
@@ -900,7 +899,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DAA - SPU_BASE:
     {
-      Log_DebugFmt("SPU control register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU control register <- 0x{:04X}", value);
       GeneratePendingSamples();
 
       const SPUCNT new_value{value};
@@ -914,14 +913,14 @@ void SPU::WriteRegister(u32 offset, u16 value)
           {
             // I would guess on the console it would gradually write the FIFO out. Hopefully nothing relies on this
             // level of timing granularity if we force it all out here.
-            Log_WarningFmt("Draining write SPU transfer FIFO with {} bytes left", s_transfer_fifo.GetSize());
+            WARNING_LOG("Draining write SPU transfer FIFO with {} bytes left", s_transfer_fifo.GetSize());
             TickCount ticks = std::numeric_limits<TickCount>::max();
             ExecuteFIFOWriteToRAM(ticks);
             DebugAssert(s_transfer_fifo.IsEmpty());
           }
           else
           {
-            Log_DebugFmt("Clearing read SPU transfer FIFO with {} bytes left", s_transfer_fifo.GetSize());
+            DEBUG_LOG("Clearing read SPU transfer FIFO with {} bytes left", s_transfer_fifo.GetSize());
             s_transfer_fifo.Clear();
           }
         }
@@ -956,14 +955,14 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DAC - SPU_BASE:
     {
-      Log_DebugFmt("SPU transfer control register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU transfer control register <- 0x{:04X}", value);
       s_transfer_control.bits = value;
       return;
     }
 
     case 0x1F801DB0 - SPU_BASE:
     {
-      Log_DebugFmt("SPU left cd audio register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU left cd audio register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_cd_audio_volume_left = value;
     }
@@ -971,7 +970,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
 
     case 0x1F801DB2 - SPU_BASE:
     {
-      Log_DebugFmt("SPU right cd audio register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU right cd audio register <- 0x{:04X}", value);
       GeneratePendingSamples();
       s_cd_audio_volume_right = value;
     }
@@ -980,7 +979,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
     case 0x1F801DB4 - SPU_BASE:
     {
       // External volumes aren't used, so don't bother syncing.
-      Log_DebugFmt("SPU left external volume register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU left external volume register <- 0x{:04X}", value);
       s_external_volume_left = value;
     }
     break;
@@ -988,7 +987,7 @@ void SPU::WriteRegister(u32 offset, u16 value)
     case 0x1F801DB6 - SPU_BASE:
     {
       // External volumes aren't used, so don't bother syncing.
-      Log_DebugFmt("SPU right external volume register <- 0x{:04X}", value);
+      DEBUG_LOG("SPU right external volume register <- 0x{:04X}", value);
       s_external_volume_right = value;
     }
     break;
@@ -1010,14 +1009,14 @@ void SPU::WriteRegister(u32 offset, u16 value)
       if (offset >= (0x1F801DC0 - SPU_BASE) && offset < (0x1F801E00 - SPU_BASE))
       {
         const u32 reg = (offset - (0x1F801DC0 - SPU_BASE)) / 2;
-        Log_DebugFmt("SPU reverb register {} <- 0x{:04X}", reg, value);
+        DEBUG_LOG("SPU reverb register {} <- 0x{:04X}", reg, value);
         GeneratePendingSamples();
         s_reverb_registers.rev[reg] = value;
         return;
       }
 
-      Log_DevFmt("Unknown SPU register write: offset 0x{:X} (address 0x{:08X}) value 0x{:04X}", offset,
-                 offset | SPU_BASE, value);
+      DEV_LOG("Unknown SPU register write: offset 0x{:X} (address 0x{:08X}) value 0x{:04X}", offset, offset | SPU_BASE,
+              value);
       return;
     }
   }
@@ -1034,7 +1033,7 @@ u16 SPU::ReadVoiceRegister(u32 offset)
   if (reg_index >= 6 && (voice.IsOn() || s_key_on_register & (1u << voice_index)))
     GeneratePendingSamples();
 
-  Log_TraceFmt("Read voice {} register {} -> 0x{:02X}", voice_index, reg_index, voice.regs.index[reg_index]);
+  TRACE_LOG("Read voice {} register {} -> 0x{:02X}", voice_index, reg_index, voice.regs.index[reg_index]);
   return voice.regs.index[reg_index];
 }
 
@@ -1053,7 +1052,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
   {
     case 0x00: // volume left
     {
-      Log_DebugFmt("SPU voice {} volume left <- 0x{:04X}", voice_index, value);
+      DEBUG_LOG("SPU voice {} volume left <- 0x{:04X}", voice_index, value);
       voice.regs.volume_left.bits = value;
       voice.left_volume.Reset(voice.regs.volume_left);
     }
@@ -1061,7 +1060,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
 
     case 0x02: // volume right
     {
-      Log_DebugFmt("SPU voice {} volume right <- 0x{:04X}", voice_index, value);
+      DEBUG_LOG("SPU voice {} volume right <- 0x{:04X}", voice_index, value);
       voice.regs.volume_right.bits = value;
       voice.right_volume.Reset(voice.regs.volume_right);
     }
@@ -1069,21 +1068,21 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
 
     case 0x04: // sample rate
     {
-      Log_DebugFmt("SPU voice {} ADPCM sample rate <- 0x{:04X}", voice_index, value);
+      DEBUG_LOG("SPU voice {} ADPCM sample rate <- 0x{:04X}", voice_index, value);
       voice.regs.adpcm_sample_rate = value;
     }
     break;
 
     case 0x06: // start address
     {
-      Log_DebugFmt("SPU voice {} ADPCM start address <- 0x{:04X}", voice_index, value);
+      DEBUG_LOG("SPU voice {} ADPCM start address <- 0x{:04X}", voice_index, value);
       voice.regs.adpcm_start_address = value;
     }
     break;
 
     case 0x08: // adsr low
     {
-      Log_DebugFmt("SPU voice {} ADSR low <- 0x{:04X} (was 0x{:04X})", voice_index, value, voice.regs.adsr.bits_low);
+      DEBUG_LOG("SPU voice {} ADSR low <- 0x{:04X} (was 0x{:04X})", voice_index, value, voice.regs.adsr.bits_low);
       voice.regs.adsr.bits_low = value;
       if (voice.IsOn())
         voice.UpdateADSREnvelope();
@@ -1092,7 +1091,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
 
     case 0x0A: // adsr high
     {
-      Log_DebugFmt("SPU voice {} ADSR high <- 0x{:04X} (was 0x{:04X})", voice_index, value, voice.regs.adsr.bits_low);
+      DEBUG_LOG("SPU voice {} ADSR high <- 0x{:04X} (was 0x{:04X})", voice_index, value, voice.regs.adsr.bits_low);
       voice.regs.adsr.bits_high = value;
       if (voice.IsOn())
         voice.UpdateADSREnvelope();
@@ -1101,7 +1100,7 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
 
     case 0x0C: // adsr volume
     {
-      Log_DebugFmt("SPU voice {} ADSR volume <- 0x{:04X} (was 0x{:04X})", voice_index, value, voice.regs.adsr_volume);
+      DEBUG_LOG("SPU voice {} ADSR volume <- 0x{:04X} (was 0x{:04X})", voice_index, value, voice.regs.adsr_volume);
       voice.regs.adsr_volume = value;
     }
     break;
@@ -1115,22 +1114,22 @@ void SPU::WriteVoiceRegister(u32 offset, u16 value)
       //  - Valkyrie Profile
 
       const bool ignore_loop_address = voice.IsOn() && !voice.is_first_block;
-      Log_DebugFmt("SPU voice {} ADPCM repeat address <- 0x{:04X}", voice_index, value);
+      DEBUG_LOG("SPU voice {} ADPCM repeat address <- 0x{:04X}", voice_index, value);
       voice.regs.adpcm_repeat_address = value;
       voice.ignore_loop_address |= ignore_loop_address;
 
       if (!ignore_loop_address)
       {
-        Log_DevFmt("Not ignoring loop address, the ADPCM repeat address of 0x{:04X} for voice {} will be overwritten",
-                   value, voice_index);
+        DEV_LOG("Not ignoring loop address, the ADPCM repeat address of 0x{:04X} for voice {} will be overwritten",
+                value, voice_index);
       }
     }
     break;
 
     default:
     {
-      Log_ErrorFmt("Unknown SPU voice {} register write: offset 0x%X (address 0x{:08X}) value 0x{:04X}", offset,
-                   voice_index, offset | SPU_BASE, ZeroExtend32(value));
+      ERROR_LOG("Unknown SPU voice {} register write: offset 0x%X (address 0x{:08X}) value 0x{:04X}", offset,
+                voice_index, offset | SPU_BASE, ZeroExtend32(value));
     }
     break;
   }
@@ -1177,7 +1176,7 @@ void SPU::CheckForLateRAMIRQs()
 {
   if (CheckRAMIRQ(s_transfer_address))
   {
-    Log_DebugFmt("Trigger IRQ @ {:08X} {:04X} from late transfer", s_transfer_address, s_transfer_address / 8);
+    DEBUG_LOG("Trigger IRQ @ {:08X} {:04X} from late transfer", s_transfer_address, s_transfer_address / 8);
     TriggerRAMIRQ();
     return;
   }
@@ -1193,7 +1192,7 @@ void SPU::CheckForLateRAMIRQs()
     const u32 address = v.current_address * 8;
     if (CheckRAMIRQ(address) || CheckRAMIRQ((address + 8) & RAM_MASK))
     {
-      Log_DebugFmt("Trigger IRQ @ {:08X} ({:04X}) from late", address, address / 8);
+      DEBUG_LOG("Trigger IRQ @ {:08X} ({:04X}) from late", address, address / 8);
       TriggerRAMIRQ();
       return;
     }
@@ -1207,7 +1206,7 @@ void SPU::WriteToCaptureBuffer(u32 index, s16 value)
   std::memcpy(&s_ram[ram_address], &value, sizeof(value));
   if (IsRAMIRQTriggerable() && CheckRAMIRQ(ram_address))
   {
-    Log_DebugFmt("Trigger IRQ @ {:08X} ({:04X}) from capture buffer", ram_address, ram_address / 8);
+    DEBUG_LOG("Trigger IRQ @ {:08X} ({:04X}) from capture buffer", ram_address, ram_address / 8);
     TriggerRAMIRQ();
   }
 }
@@ -1231,7 +1230,7 @@ ALWAYS_INLINE_RELEASE void SPU::ExecuteFIFOReadFromRAM(TickCount& ticks)
 
     if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address))
     {
-      Log_DebugFmt("Trigger IRQ @ {:08X} ({:04X}) from transfer read", s_transfer_address, s_transfer_address / 8);
+      DEBUG_LOG("Trigger IRQ @ {:08X} ({:04X}) from transfer read", s_transfer_address, s_transfer_address / 8);
       TriggerRAMIRQ();
     }
   }
@@ -1248,7 +1247,7 @@ ALWAYS_INLINE_RELEASE void SPU::ExecuteFIFOWriteToRAM(TickCount& ticks)
 
     if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address))
     {
-      Log_DebugFmt("Trigger IRQ @ {:08X} ({:04X}) from transfer write", s_transfer_address, s_transfer_address / 8);
+      DEBUG_LOG("Trigger IRQ @ {:08X} ({:04X}) from transfer write", s_transfer_address, s_transfer_address / 8);
       TriggerRAMIRQ();
     }
   }
@@ -1312,7 +1311,7 @@ void SPU::ManualTransferWrite(u16 value)
 {
   if (!s_transfer_fifo.IsEmpty() && s_SPUCNT.ram_transfer_mode != RAMTransferMode::DMARead)
   {
-    Log_WarningPrint("FIFO not empty on manual SPU write, draining to hopefully avoid corruption. Game is silly.");
+    WARNING_LOG("FIFO not empty on manual SPU write, draining to hopefully avoid corruption. Game is silly.");
     if (s_SPUCNT.ram_transfer_mode != RAMTransferMode::Stopped)
       ExecuteTransfer(nullptr, std::numeric_limits<s32>::max(), 0);
   }
@@ -1322,7 +1321,7 @@ void SPU::ManualTransferWrite(u16 value)
 
   if (IsRAMIRQTriggerable() && CheckRAMIRQ(s_transfer_address))
   {
-    Log_DebugFmt("Trigger IRQ @ {:08X} ({:04X}) from manual write", s_transfer_address, s_transfer_address / 8);
+    DEBUG_LOG("Trigger IRQ @ {:08X} ({:04X}) from manual write", s_transfer_address, s_transfer_address / 8);
     TriggerRAMIRQ();
   }
 }
@@ -1440,7 +1439,7 @@ void SPU::DMARead(u32* words, u32 word_count)
       fill_value = halfwords[size - 1];
     }
 
-    Log_WarningFmt("Transfer FIFO underflow, filling with 0x{:04X}", fill_value);
+    WARNING_LOG("Transfer FIFO underflow, filling with 0x{:04X}", fill_value);
     std::fill_n(&halfwords[size], halfword_count - size, fill_value);
   }
   else
@@ -1461,7 +1460,7 @@ void SPU::DMAWrite(const u32* words, u32 word_count)
   s_transfer_fifo.PushRange(halfwords, words_to_transfer);
 
   if (words_to_transfer != halfword_count) [[unlikely]]
-    Log_WarningFmt("Transfer FIFO overflow, dropping {} halfwords", halfword_count - words_to_transfer);
+    WARNING_LOG("Transfer FIFO overflow, dropping {} halfwords", halfword_count - words_to_transfer);
 
   UpdateDMARequest();
   UpdateTransferEvent();
@@ -1500,7 +1499,7 @@ bool SPU::StartDumpingAudio(const char* filename)
   s_dump_writer = std::make_unique<WAVWriter>();
   if (!s_dump_writer->Open(filename, SAMPLE_RATE, 2))
   {
-    Log_ErrorFmt("Failed to open '{}'", filename);
+    ERROR_LOG("Failed to open '{}'", filename);
     s_dump_writer.reset();
     return false;
   }
@@ -1520,7 +1519,7 @@ bool SPU::StartDumpingAudio(const char* filename)
     const std::string voice_filename = Path::ReplaceExtension(filename, new_suffix);
     if (!s_voice_dump_writers[i]->Open(voice_filename.c_str(), SAMPLE_RATE, 2))
     {
-      Log_ErrorFmt("Failed to open voice dump filename '{}'", voice_filename.c_str());
+      ERROR_LOG("Failed to open voice dump filename '{}'", voice_filename.c_str());
       s_voice_dump_writers[i].reset();
     }
   }
@@ -1925,7 +1924,7 @@ void SPU::ReadADPCMBlock(u16 address, ADPCMBlock* block)
   u32 ram_address = (ZeroExtend32(address) * 8) & RAM_MASK;
   if (IsRAMIRQTriggerable() && (CheckRAMIRQ(ram_address) || CheckRAMIRQ((ram_address + 8) & RAM_MASK)))
   {
-    Log_DebugFmt("Trigger IRQ @ {:08X} ({:04X}) from ADPCM reader", ram_address, ram_address / 8);
+    DEBUG_LOG("Trigger IRQ @ {:08X} ({:04X}) from ADPCM reader", ram_address, ram_address / 8);
     TriggerRAMIRQ();
   }
 
@@ -1974,7 +1973,7 @@ ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
 
     if (voice.current_block_flags.loop_start && !voice.ignore_loop_address)
     {
-      Log_TraceFmt("Voice {} loop start @ 0x{:08X}", voice_index, voice.current_address);
+      TRACE_LOG("Voice {} loop start @ 0x{:08X}", voice_index, voice.current_address);
       voice.regs.adpcm_repeat_address = voice.current_address;
     }
   }
@@ -2036,17 +2035,17 @@ ALWAYS_INLINE_RELEASE std::tuple<s32, s32> SPU::SampleVoice(u32 voice_index)
         // End+Mute flags are ignored when noise is enabled. ADPCM data is still decoded.
         if (!IsVoiceNoiseEnabled(voice_index))
         {
-          Log_TraceFmt("Voice {} loop end+mute @ 0x{:04X}", voice_index, voice.current_address);
+          TRACE_LOG("Voice {} loop end+mute @ 0x{:04X}", voice_index, voice.current_address);
           voice.ForceOff();
         }
         else
         {
-          Log_TraceFmt("IGNORING voice {} loop end+mute @ 0x{:04X}", voice_index, voice.current_address);
+          TRACE_LOG("IGNORING voice {} loop end+mute @ 0x{:04X}", voice_index, voice.current_address);
         }
       }
       else
       {
-        Log_TraceFmt("Voice {} loop end+repeat @ 0x{:04X}", voice_index, voice.current_address);
+        TRACE_LOG("Voice {} loop end+repeat @ 0x{:04X}", voice_index, voice.current_address);
       }
     }
   }
diff --git a/src/core/system.cpp b/src/core/system.cpp
index d0e26eb5e..f3328bd0a 100644
--- a/src/core/system.cpp
+++ b/src/core/system.cpp
@@ -288,13 +288,13 @@ void System::CheckCacheLineSize()
   const size_t runtime_cache_line_size = PlatformMisc::GetRuntimeCacheLineSize();
   if (runtime_cache_line_size == 0)
   {
-    Log_ErrorFmt("Cannot determine size of cache line. Continuing with expectation of {} byte lines.",
-                 runtime_cache_line_size);
+    ERROR_LOG("Cannot determine size of cache line. Continuing with expectation of {} byte lines.",
+              runtime_cache_line_size);
   }
   else if (HOST_CACHE_LINE_SIZE != runtime_cache_line_size)
   {
     // Not fatal, but does have performance implications.
-    Log_WarningFmt(
+    WARNING_LOG(
       "Cache line size mismatch. This build was compiled with {} byte lines, but the system has {} byte lines.",
       HOST_CACHE_LINE_SIZE, runtime_cache_line_size);
   }
@@ -680,7 +680,7 @@ bool System::GetGameDetailsFromImage(CDImage* cdi, std::string* out_id, GameHash
   XXH64_update(state, &track_1_length, sizeof(track_1_length));
   const GameHash hash = XXH64_digest(state);
   XXH64_freeState(state);
-  Log_DevFmt("Hash for '{}' - {:016X}", exe_name, hash);
+  DEV_LOG("Hash for '{}' - {:016X}", exe_name, hash);
 
   if (exe_name != FALLBACK_EXE_NAME)
   {
@@ -798,7 +798,7 @@ std::string System::GetExecutableNameForImage(IsoReader& iso, bool strip_subdire
     if (code.compare(0, 6, "cdrom:") == 0)
       code.erase(0, 6);
     else
-      Log_WarningFmt("Unknown prefix in executable path: '{}'", code);
+      WARNING_LOG("Unknown prefix in executable path: '{}'", code);
 
     // remove leading slashes
     while (code[0] == '/' || code[0] == '\\')
@@ -836,12 +836,12 @@ bool System::ReadExecutableFromImage(IsoReader& iso, std::string* out_executable
                                      std::vector<u8>* out_executable_data)
 {
   const std::string executable_path = GetExecutableNameForImage(iso, false);
-  Log_DevFmt("Executable path: '{}'", executable_path);
+  DEV_LOG("Executable path: '{}'", executable_path);
   if (!executable_path.empty() && out_executable_data)
   {
     if (!iso.ReadFile(executable_path.c_str(), out_executable_data))
     {
-      Log_ErrorFmt("Failed to read executable '{}' from disc", executable_path);
+      ERROR_LOG("Failed to read executable '{}' from disc", executable_path);
       return false;
     }
   }
@@ -979,7 +979,7 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
   StateWrapper sw(state_stream.get(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
   const bool state_valid = g_gpu->DoState(sw, nullptr, false) && TimingEvents::DoState(sw);
   if (!state_valid)
-    Log_ErrorPrint("Failed to save old GPU state when switching renderers");
+    ERROR_LOG("Failed to save old GPU state when switching renderers");
 
   // create new renderer
   g_gpu.reset();
@@ -1119,7 +1119,7 @@ void System::SetDefaultSettings(SettingsInterface& si)
 
 void System::ApplySettings(bool display_osd_messages)
 {
-  Log_DevPrint("Applying settings...");
+  DEV_LOG("Applying settings...");
 
   const Settings old_config(std::move(g_settings));
   g_settings = Settings();
@@ -1160,17 +1160,17 @@ bool System::UpdateGameSettingsLayer()
     std::string filename(GetGameSettingsPath(s_running_game_serial));
     if (FileSystem::FileExists(filename.c_str()))
     {
-      Log_InfoFmt("Loading game settings from '%s'...", Path::GetFileName(filename));
+      INFO_LOG("Loading game settings from '%s'...", Path::GetFileName(filename));
       new_interface = std::make_unique<INISettingsInterface>(std::move(filename));
       if (!new_interface->Load())
       {
-        Log_ErrorFmt("Failed to parse game settings ini '%s'", new_interface->GetFileName());
+        ERROR_LOG("Failed to parse game settings ini '%s'", new_interface->GetFileName());
         new_interface.reset();
       }
     }
     else
     {
-      Log_InfoFmt("No game settings found (tried '%s')", Path::GetFileName(filename));
+      INFO_LOG("No game settings found (tried '%s')", Path::GetFileName(filename));
     }
   }
 
@@ -1194,18 +1194,18 @@ bool System::UpdateGameSettingsLayer()
     const std::string filename(GetInputProfilePath(input_profile_name));
     if (FileSystem::FileExists(filename.c_str()))
     {
-      Log_InfoFmt("Loading input profile from '{}'...", Path::GetFileName(filename));
+      INFO_LOG("Loading input profile from '{}'...", Path::GetFileName(filename));
       input_interface = std::make_unique<INISettingsInterface>(std::move(filename));
       if (!input_interface->Load())
       {
-        Log_ErrorFmt("Failed to parse input profile ini '{}'", Path::GetFileName(input_interface->GetFileName()));
+        ERROR_LOG("Failed to parse input profile ini '{}'", Path::GetFileName(input_interface->GetFileName()));
         input_interface.reset();
         input_profile_name = {};
       }
     }
     else
     {
-      Log_WarningFmt("No input profile found (tried '{}')", Path::GetFileName(filename));
+      WARNING_LOG("No input profile found (tried '{}')", Path::GetFileName(filename));
       input_profile_name = {};
     }
   }
@@ -1310,7 +1310,7 @@ bool System::LoadState(const char* filename, Error* error)
     return false;
   }
 
-  Log_InfoFmt("Loading state from '{}'...", filename);
+  INFO_LOG("Loading state from '{}'...", filename);
 
   {
     const std::string display_name(FileSystem::GetDisplayNameFromPath(filename));
@@ -1335,7 +1335,7 @@ bool System::LoadState(const char* filename, Error* error)
   if (IsPaused())
     InvalidateDisplay();
 
-  Log_VerboseFmt("Loading state took {:.2f} msec", load_timer.GetTimeMilliseconds());
+  VERBOSE_LOG("Loading state took {:.2f} msec", load_timer.GetTimeMilliseconds());
   return true;
 }
 
@@ -1347,8 +1347,8 @@ bool System::SaveState(const char* filename, Error* error, bool backup_existing_
     const std::string backup_filename = Path::ReplaceExtension(filename, "bak");
     if (!FileSystem::RenamePath(filename, backup_filename.c_str(), &backup_error))
     {
-      Log_ErrorFmt("Failed to rename save state backup '{}': {}", Path::GetFileName(backup_filename),
-                   backup_error.GetDescription());
+      ERROR_LOG("Failed to rename save state backup '{}': {}", Path::GetFileName(backup_filename),
+                backup_error.GetDescription());
     }
   }
 
@@ -1365,7 +1365,7 @@ bool System::SaveState(const char* filename, Error* error, bool backup_existing_
     return false;
   }
 
-  Log_InfoFmt("Saving state to '{}'...", filename);
+  INFO_LOG("Saving state to '{}'...", filename);
 
   const u32 screenshot_size = 256;
   const bool result = SaveStateToStream(stream.get(), error, screenshot_size,
@@ -1384,7 +1384,7 @@ bool System::SaveState(const char* filename, Error* error, bool backup_existing_
     stream->Commit();
   }
 
-  Log_VerboseFmt("Saving state took {:.2f} msec", save_timer.GetTimeMilliseconds());
+  VERBOSE_LOG("Saving state took {:.2f} msec", save_timer.GetTimeMilliseconds());
   return result;
 }
 
@@ -1411,9 +1411,9 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
   }
 
   if (parameters.filename.empty())
-    Log_InfoPrint("Boot Filename: <BIOS/Shell>");
+    INFO_LOG("Boot Filename: <BIOS/Shell>");
   else
-    Log_InfoFmt("Boot Filename: {}", parameters.filename);
+    INFO_LOG("Boot Filename: {}", parameters.filename);
 
   Assert(s_state == State::Shutdown);
   s_state = State::Starting;
@@ -1437,7 +1437,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
       {
         const DiscRegion file_region =
           (do_exe_boot ? GetRegionForExe(parameters.filename.c_str()) : GetRegionForPsf(parameters.filename.c_str()));
-        Log_InfoFmt("EXE/PSF Region: {}", Settings::GetDiscRegionDisplayName(file_region));
+        INFO_LOG("EXE/PSF Region: {}", Settings::GetDiscRegionDisplayName(file_region));
         s_region = GetConsoleRegionForDiscRegion(file_region);
       }
       if (do_psf_boot)
@@ -1447,7 +1447,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
     }
     else
     {
-      Log_InfoFmt("Loading CD image '{}'...", Path::GetFileName(parameters.filename));
+      INFO_LOG("Loading CD image '{}'...", Path::GetFileName(parameters.filename));
       disc = CDImage::Open(parameters.filename.c_str(), g_settings.cdrom_load_image_patches, error);
       if (!disc)
       {
@@ -1464,14 +1464,14 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
         if (disc_region != DiscRegion::Other)
         {
           s_region = GetConsoleRegionForDiscRegion(disc_region);
-          Log_InfoFmt("Auto-detected console {} region for '{}' (region {})", Settings::GetConsoleRegionName(s_region),
-                      parameters.filename, Settings::GetDiscRegionName(disc_region));
+          INFO_LOG("Auto-detected console {} region for '{}' (region {})", Settings::GetConsoleRegionName(s_region),
+                   parameters.filename, Settings::GetDiscRegionName(disc_region));
         }
         else
         {
           s_region = ConsoleRegion::NTSC_U;
-          Log_WarningFmt("Could not determine console region for disc region {}. Defaulting to {}.",
-                         Settings::GetDiscRegionName(disc_region), Settings::GetConsoleRegionName(s_region));
+          WARNING_LOG("Could not determine console region for disc region {}. Defaulting to {}.",
+                      Settings::GetDiscRegionName(disc_region), Settings::GetConsoleRegionName(s_region));
         }
       }
     }
@@ -1483,7 +1483,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
       s_region = ConsoleRegion::NTSC_U;
   }
 
-  Log_InfoFmt("Console Region: {}", Settings::GetConsoleRegionDisplayName(s_region));
+  INFO_LOG("Console Region: {}", Settings::GetConsoleRegionDisplayName(s_region));
 
   // Switch subimage.
   if (disc && parameters.media_playlist_index != 0 && !disc->SwitchSubImage(parameters.media_playlist_index, error))
@@ -1511,7 +1511,7 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
       return false;
     }
 
-    Log_InfoFmt("Overriding boot executable: '{}'", parameters.override_exe);
+    INFO_LOG("Overriding boot executable: '{}'", parameters.override_exe);
     exe_boot = std::move(parameters.override_exe);
   }
 
@@ -1607,11 +1607,11 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
   {
     if (!CDROM::IsMediaPS1Disc())
     {
-      Log_ErrorPrint("Not fast booting non-PS1 disc.");
+      ERROR_LOG("Not fast booting non-PS1 disc.");
     }
     else if (!s_bios_image_info || !s_bios_image_info->patch_compatible)
     {
-      Log_ErrorPrint("Not patching fast boot, as BIOS is not patch compatible.");
+      ERROR_LOG("Not patching fast boot, as BIOS is not patch compatible.");
     }
     else
     {
@@ -2006,7 +2006,7 @@ void System::FrameDone()
   }
   else
   {
-    Log_DebugPrint("Skipping displaying frame");
+    DEBUG_LOG("Skipping displaying frame");
     s_skipped_frame_count++;
     if (s_throttler_enabled)
       Throttle(current_time);
@@ -2171,8 +2171,8 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
   {
     if (g_gpu_device)
     {
-      Log_WarningFmt("Recreating GPU device, expecting {} got {}", GPUDevice::RenderAPIToString(api),
-                     GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
+      WARNING_LOG("Recreating GPU device, expecting {} got {}", GPUDevice::RenderAPIToString(api),
+                  GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI()));
       PostProcessing::Shutdown();
     }
 
@@ -2194,8 +2194,8 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
 
   if (!g_gpu)
   {
-    Log_ErrorFmt("Failed to initialize {} renderer, falling back to software renderer",
-                 Settings::GetRendererName(renderer));
+    ERROR_LOG("Failed to initialize {} renderer, falling back to software renderer",
+              Settings::GetRendererName(renderer));
     Host::AddFormattedOSDMessage(
       30.0f, TRANSLATE("OSDMessage", "Failed to initialize %s renderer, falling back to software renderer."),
       Settings::GetRendererName(renderer));
@@ -2203,7 +2203,7 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, Error* error)
     g_gpu = GPU::CreateSoftwareRenderer();
     if (!g_gpu)
     {
-      Log_ErrorPrint("Failed to create fallback software renderer.");
+      ERROR_LOG("Failed to create fallback software renderer.");
       if (!s_keep_gpu_device_on_shutdown)
       {
         PostProcessing::Shutdown();
@@ -2233,7 +2233,7 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
     sw.DoBytesEx(bios_hash.bytes, sizeof(bios_hash.bytes), 58, s_bios_hash.bytes);
     if (bios_hash != s_bios_hash)
     {
-      Log_WarningFmt("BIOS hash mismatch: System: {} | State: {}", s_bios_hash.ToString(), bios_hash.ToString());
+      WARNING_LOG("BIOS hash mismatch: System: {} | State: {}", s_bios_hash.ToString(), bios_hash.ToString());
       Host::AddKeyedOSDMessage("StateBIOSMismatch",
                                TRANSLATE_STR("OSDMessage",
                                              "This save state was created with a different BIOS version or patch "
@@ -2344,9 +2344,9 @@ bool System::LoadBIOS(Error* error)
   s_bios_hash = BIOS::GetImageHash(bios_image.value());
   s_bios_image_info = BIOS::GetInfoForImage(bios_image.value(), s_bios_hash);
   if (s_bios_image_info)
-    Log_InfoFmt("Using BIOS: {}", s_bios_image_info->description);
+    INFO_LOG("Using BIOS: {}", s_bios_image_info->description);
   else
-    Log_WarningFmt("Using an unknown BIOS: {}", s_bios_hash.ToString());
+    WARNING_LOG("Using an unknown BIOS: {}", s_bios_hash.ToString());
 
   std::memcpy(Bus::g_bios, bios_image->data(), Bus::BIOS_SIZE);
   return true;
@@ -2451,7 +2451,7 @@ bool System::LoadStateFromStream(ByteStream* state, Error* error, bool update_di
       std::unique_ptr<CDImage> old_media = CDROM::RemoveMedia(false);
       if (old_media && old_media->GetFileName() == media_filename)
       {
-        Log_InfoFmt("Re-using same media '{}'", media_filename);
+        INFO_LOG("Re-using same media '{}'", media_filename);
         media = std::move(old_media);
       }
       else
@@ -2498,7 +2498,7 @@ bool System::LoadStateFromStream(ByteStream* state, Error* error, bool update_di
       }
       else
       {
-        Log_InfoFmt("Switched to subimage {} in '{}'", header.media_subimage_index, media_filename.c_str());
+        INFO_LOG("Switched to subimage {} in '{}'", header.media_subimage_index, media_filename.c_str());
       }
     }
 
@@ -2600,7 +2600,7 @@ bool System::SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_s
     const u32 screenshot_height =
       std::max(1u, static_cast<u32>(static_cast<float>(screenshot_width) /
                                     ((display_aspect_ratio > 0.0f) ? display_aspect_ratio : 1.0f)));
-    Log_VerboseFmt("Saving {}x{} screenshot for state", screenshot_width, screenshot_height);
+    VERBOSE_LOG("Saving {}x{} screenshot for state", screenshot_width, screenshot_height);
 
     std::vector<u32> screenshot_buffer;
     u32 screenshot_stride;
@@ -2613,8 +2613,8 @@ bool System::SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_s
     {
       if (screenshot_stride != (screenshot_width * sizeof(u32)))
       {
-        Log_WarningFmt("Failed to save {}x{} screenshot for save state due to incorrect stride(%u)", screenshot_width,
-                       screenshot_height, screenshot_stride);
+        WARNING_LOG("Failed to save {}x{} screenshot for save state due to incorrect stride(%u)", screenshot_width,
+                    screenshot_height, screenshot_stride);
       }
       else
       {
@@ -2634,8 +2634,8 @@ bool System::SaveStateToStream(ByteStream* state, Error* error, u32 screenshot_s
     }
     else
     {
-      Log_WarningFmt("Failed to save {}x{} screenshot for save state due to render/conversion failure",
-                     screenshot_width, screenshot_height);
+      WARNING_LOG("Failed to save {}x{} screenshot for save state due to render/conversion failure", screenshot_width,
+                  screenshot_height);
     }
   }
 
@@ -2758,9 +2758,8 @@ void System::UpdatePerformanceCounters()
   if (s_pre_frame_sleep)
     UpdatePreFrameSleepTime();
 
-  Log_VerboseFmt("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Average: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_fps,
-                 s_vps, s_cpu_thread_usage, s_gpu_usage, s_average_frame_time, s_minimum_frame_time,
-                 s_maximum_frame_time);
+  VERBOSE_LOG("FPS: {:.2f} VPS: {:.2f} CPU: {:.2f} GPU: {:.2f} Average: {:.2f}ms Min: {:.2f}ms Max: {:.2f}ms", s_fps,
+              s_vps, s_cpu_thread_usage, s_gpu_usage, s_average_frame_time, s_minimum_frame_time, s_maximum_frame_time);
 
   Host::OnPerformanceCountersUpdated();
 }
@@ -2797,9 +2796,9 @@ void System::AccumulatePreFrameSleepTime()
   {
     s_pre_frame_sleep_time = Common::AlignDown(max_sleep_time_for_this_frame,
                                                static_cast<unsigned int>(Common::Timer::ConvertMillisecondsToValue(1)));
-    Log_DevFmt("Adjust pre-frame time to {} ms due to overrun of {} ms",
-               Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time),
-               Common::Timer::ConvertValueToMilliseconds(s_last_active_frame_time));
+    DEV_LOG("Adjust pre-frame time to {} ms due to overrun of {} ms",
+            Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time),
+            Common::Timer::ConvertValueToMilliseconds(s_last_active_frame_time));
   }
 }
 
@@ -2811,9 +2810,9 @@ void System::UpdatePreFrameSleepTime()
     s_max_active_frame_time + Common::Timer::ConvertMillisecondsToValue(g_settings.display_pre_frame_sleep_buffer);
   s_pre_frame_sleep_time = Common::AlignDown(s_frame_period - std::min(expected_frame_time, s_frame_period),
                                              static_cast<unsigned int>(Common::Timer::ConvertMillisecondsToValue(1)));
-  Log_DevFmt("Set pre-frame time to {} ms (expected frame time of {} ms)",
-             Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time),
-             Common::Timer::ConvertValueToMilliseconds(expected_frame_time));
+  DEV_LOG("Set pre-frame time to {} ms (expected frame time of {} ms)",
+          Common::Timer::ConvertValueToMilliseconds(s_pre_frame_sleep_time),
+          Common::Timer::ConvertValueToMilliseconds(expected_frame_time));
 
   s_max_active_frame_time = 0;
 }
@@ -2856,8 +2855,8 @@ void System::UpdateSpeedLimiterState()
     {
       const float ratio = host_refresh_rate.value() / System::GetThrottleFrequency();
       s_syncing_to_host = (ratio >= 0.95f && ratio <= 1.05f);
-      Log_InfoFmt("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate.value(),
-                  System::GetThrottleFrequency(), ratio, s_syncing_to_host ? "can sync" : "can't sync");
+      INFO_LOG("Refresh rate: Host={}hz Guest={}hz Ratio={} - {}", host_refresh_rate.value(),
+               System::GetThrottleFrequency(), ratio, s_syncing_to_host ? "can sync" : "can't sync");
       if (s_syncing_to_host)
         s_target_speed *= ratio;
     }
@@ -2867,11 +2866,11 @@ void System::UpdateSpeedLimiterState()
   s_syncing_to_host_with_vsync = (s_syncing_to_host && IsHostVSyncEffectivelyEnabled());
   if (s_syncing_to_host_with_vsync)
   {
-    Log_InfoPrint("Using host vsync for throttling.");
+    INFO_LOG("Using host vsync for throttling.");
     s_throttler_enabled = false;
   }
 
-  Log_VerboseFmt("Target speed: {}%", s_target_speed * 100.0f);
+  VERBOSE_LOG("Target speed: {}%", s_target_speed * 100.0f);
 
   // Update audio output.
   AudioStream* stream = SPU::GetOutputStream();
@@ -2890,10 +2889,10 @@ void System::UpdateDisplaySync()
 {
   const bool vsync_enabled = IsHostVSyncEffectivelyEnabled();
   const float max_display_fps = (s_throttler_enabled || s_syncing_to_host) ? 0.0f : g_settings.display_max_fps;
-  Log_VerboseFmt("VSync: {}{}", vsync_enabled ? "Enabled" : "Disabled",
-                 s_syncing_to_host_with_vsync ? " (for throttling)" : "");
-  Log_VerboseFmt("Max display fps: {}", max_display_fps);
-  Log_VerboseFmt("Preset timing: {}", s_optimal_frame_pacing ? "consistent" : "immediate");
+  VERBOSE_LOG("VSync: {}{}", vsync_enabled ? "Enabled" : "Disabled",
+              s_syncing_to_host_with_vsync ? " (for throttling)" : "");
+  VERBOSE_LOG("Max display fps: {}", max_display_fps);
+  VERBOSE_LOG("Preset timing: {}", s_optimal_frame_pacing ? "consistent" : "immediate");
 
   g_gpu_device->SetDisplayMaxFPS(max_display_fps);
   g_gpu_device->SetVSyncEnabled(vsync_enabled, vsync_enabled && !IsHostVSyncUsedForTiming());
@@ -3015,7 +3014,7 @@ static bool LoadEXEToRAM(const char* filename, bool patch_bios)
   std::FILE* fp = FileSystem::OpenCFile(filename, "rb");
   if (!fp)
   {
-    Log_ErrorFmt("Failed to open exe file '{}'", filename);
+    ERROR_LOG("Failed to open exe file '{}'", filename);
     return false;
   }
 
@@ -3026,7 +3025,7 @@ static bool LoadEXEToRAM(const char* filename, bool patch_bios)
   BIOS::PSEXEHeader header;
   if (std::fread(&header, sizeof(header), 1, fp) != 1 || !BIOS::IsValidPSExeHeader(header, file_size))
   {
-    Log_ErrorFmt("'{}' is not a valid PS-EXE", filename);
+    ERROR_LOG("'{}' is not a valid PS-EXE", filename);
     std::fclose(fp);
     return false;
   }
@@ -3076,7 +3075,7 @@ bool System::LoadEXE(const char* filename)
   const std::string libps_path(Path::BuildRelativePath(filename, "libps.exe"));
   if (!libps_path.empty() && FileSystem::FileExists(libps_path.c_str()) && !LoadEXEToRAM(libps_path.c_str(), false))
   {
-    Log_ErrorFmt("Failed to load libps.exe from '{}'", libps_path.c_str());
+    ERROR_LOG("Failed to load libps.exe from '{}'", libps_path.c_str());
     return false;
   }
 
@@ -3359,7 +3358,7 @@ void System::UpdateMemoryCardTypes()
     if (card)
     {
       if (const std::string& filename = card->GetFilename(); !filename.empty())
-        Log_InfoFmt("Memory Card Slot {}: {}", i + 1, filename);
+        INFO_LOG("Memory Card Slot {}: {}", i + 1, filename);
 
       Pad::SetMemoryCard(i, std::move(card));
     }
@@ -3380,7 +3379,7 @@ void System::UpdatePerGameMemoryCards()
     if (card)
     {
       if (const std::string& filename = card->GetFilename(); !filename.empty())
-        Log_InfoFmt("Memory Card Slot {}: {}", i + 1, filename);
+        INFO_LOG("Memory Card Slot {}: {}", i + 1, filename);
 
       Pad::SetMemoryCard(i, std::move(card));
     }
@@ -3528,7 +3527,7 @@ bool System::InsertMedia(const char* path)
   const DiscRegion region = GetRegionForImage(image.get());
   UpdateRunningGame(path, image.get(), false);
   CDROM::InsertMedia(std::move(image), region);
-  Log_InfoFmt("Inserted media from {} ({}, {})", s_running_game_path, s_running_game_serial, s_running_game_title);
+  INFO_LOG("Inserted media from {} ({}, {})", s_running_game_path, s_running_game_serial, s_running_game_title);
   if (g_settings.cdrom_load_image_to_ram)
     CDROM::PrecacheMedia();
 
@@ -3641,7 +3640,7 @@ bool System::CheckForSBIFile(CDImage* image, Error* error)
     return true;
   }
 
-  Log_WarningFmt("SBI file missing but required for {} ({})", s_running_game_serial, s_running_game_title);
+  WARNING_LOG("SBI file missing but required for {} ({})", s_running_game_serial, s_running_game_title);
 
   if (Host::GetBoolSettingValue("CDROM", "AllowBootingWithoutSBIFile", false))
   {
@@ -4133,7 +4132,7 @@ void System::LogUnsafeSettingsToConsole(const SmallStringBase& messages)
       break;
     }
   }
-  Log_WarningPrint(console_messages);
+  WARNING_LOG(console_messages);
 }
 
 void System::CalculateRewindMemoryUsage(u32 num_saves, u32 resolution_scale, u64* ram_usage, u64* vram_usage)
@@ -4163,9 +4162,9 @@ void System::UpdateMemorySaveStateSettings()
 
     u64 ram_usage, vram_usage;
     CalculateRewindMemoryUsage(g_settings.rewind_save_slots, g_settings.gpu_resolution_scale, &ram_usage, &vram_usage);
-    Log_InfoFmt("Rewind is enabled, saving every {} frames, with {} slots and {}MB RAM and {}MB VRAM usage",
-                std::max(s_rewind_save_frequency, 1), g_settings.rewind_save_slots, ram_usage / 1048576,
-                vram_usage / 1048576);
+    INFO_LOG("Rewind is enabled, saving every {} frames, with {} slots and {}MB RAM and {}MB VRAM usage",
+             std::max(s_rewind_save_frequency, 1), g_settings.rewind_save_slots, ram_usage / 1048576,
+             vram_usage / 1048576);
   }
   else
   {
@@ -4179,7 +4178,7 @@ void System::UpdateMemorySaveStateSettings()
   s_runahead_frames = g_settings.runahead_frames;
   s_runahead_replay_pending = false;
   if (s_runahead_frames > 0)
-    Log_InfoFmt("Runahead is active with {} frames", s_runahead_frames);
+    INFO_LOG("Runahead is active with {} frames", s_runahead_frames);
 }
 
 bool System::LoadMemoryState(const MemorySaveState& mss)
@@ -4209,7 +4208,7 @@ bool System::SaveMemoryState(MemorySaveState* mss)
   StateWrapper sw(mss->state_stream.get(), StateWrapper::Mode::Write, SAVE_STATE_VERSION);
   if (!DoState(sw, &host_texture, false, true))
   {
-    Log_ErrorPrint("Failed to create rewind state.");
+    ERROR_LOG("Failed to create rewind state.");
     delete host_texture;
     return false;
   }
@@ -4239,8 +4238,8 @@ bool System::SaveRewindState()
   s_rewind_states.push_back(std::move(mss));
 
 #ifdef PROFILE_MEMORY_SAVE_STATES
-  Log_DevFmt("Saved rewind state ({} bytes, took {:.4f} ms)", s_rewind_states.back().state_stream->GetSize(),
-             save_timer.GetTimeMilliseconds());
+  DEV_LOG("Saved rewind state ({} bytes, took {:.4f} ms)", s_rewind_states.back().state_stream->GetSize(),
+          save_timer.GetTimeMilliseconds());
 #endif
 
   return true;
@@ -4269,7 +4268,7 @@ bool System::LoadRewindState(u32 skip_saves /*= 0*/, bool consume_state /*=true
     s_rewind_states.pop_back();
 
 #ifdef PROFILE_MEMORY_SAVE_STATES
-  Log_DevFmt("Rewind load took {:.4f} ms", load_timer.GetTimeMilliseconds());
+  DEV_LOG("Rewind load took {:.4f} ms", load_timer.GetTimeMilliseconds());
 #endif
 
   return true;
@@ -4336,7 +4335,7 @@ void System::SaveRunaheadState()
 
   if (!SaveMemoryState(&mss))
   {
-    Log_ErrorPrint("Failed to save runahead state.");
+    ERROR_LOG("Failed to save runahead state.");
     return;
   }
 
@@ -4352,7 +4351,7 @@ bool System::DoRunahead()
   if (s_runahead_replay_pending)
   {
 #ifdef PROFILE_MEMORY_SAVE_STATES
-    Log_DevFmt("runahead starting at frame {}", s_frame_number);
+    DEV_LOG("runahead starting at frame {}", s_frame_number);
     replay_timer.Reset();
 #endif
 
@@ -4374,7 +4373,7 @@ bool System::DoRunahead()
     SPU::SetAudioOutputMuted(true);
 
 #ifdef PROFILE_MEMORY_SAVE_STATES
-    Log_VerboseFmt("Rewound to frame {}, took {:.2f} ms", s_frame_number, replay_timer.GetTimeMilliseconds());
+    VERBOSE_LOG("Rewound to frame {}, took {:.2f} ms", s_frame_number, replay_timer.GetTimeMilliseconds());
 #endif
 
     // we don't want to save the frame we just loaded. but we are "one frame ahead", because the frame we just tossed
@@ -4395,14 +4394,14 @@ bool System::DoRunahead()
   }
 
 #ifdef PROFILE_MEMORY_SAVE_STATES
-  Log_VerboseFmt("Running {} frames to catch up took {:.2f} ms", s_runahead_frames, replay_timer.GetTimeMilliseconds());
+  VERBOSE_LOG("Running {} frames to catch up took {:.2f} ms", s_runahead_frames, replay_timer.GetTimeMilliseconds());
 #endif
 
   // we're all caught up. this frame gets saved in DoMemoryStates().
   SPU::SetAudioOutputMuted(false);
 
 #ifdef PROFILE_MEMORY_SAVE_STATES
-  Log_DevFmt("runahead ending at frame {}, took {:.2f} ms", s_frame_number, replay_timer.GetTimeMilliseconds());
+  DEV_LOG("runahead ending at frame {}, took {:.2f} ms", s_frame_number, replay_timer.GetTimeMilliseconds());
 #endif
 
   return false;
@@ -4414,7 +4413,7 @@ void System::SetRunaheadReplayFlag()
     return;
 
 #ifdef PROFILE_MEMORY_SAVE_STATES
-  Log_DevPrint("Runahead rewind pending...");
+  DEV_LOG("Runahead rewind pending...");
 #endif
 
   s_runahead_replay_pending = true;
@@ -4480,7 +4479,7 @@ bool System::UndoLoadState()
     return false;
   }
 
-  Log_InfoPrint("Loaded undo save state.");
+  INFO_LOG("Loaded undo save state.");
   m_undo_load_state.reset();
   return true;
 }
@@ -4501,7 +4500,7 @@ bool System::SaveUndoLoadState()
     return false;
   }
 
-  Log_InfoFmt("Saved undo load state: {} bytes", m_undo_load_state->GetSize());
+  INFO_LOG("Saved undo load state: {} bytes", m_undo_load_state->GetSize());
   return true;
 }
 
@@ -4737,11 +4736,11 @@ void System::DeleteSaveStates(const char* serial, bool resume)
     if (si.global || (!resume && si.slot < 0))
       continue;
 
-    Log_InfoFmt("Removing save state '{}'", Path::GetFileName(si.path));
+    INFO_LOG("Removing save state '{}'", Path::GetFileName(si.path));
 
     Error error;
     if (!FileSystem::DeleteFile(si.path.c_str(), &error)) [[unlikely]]
-      Log_ErrorFmt("Failed to delete save state file '{}': {}", Path::GetFileName(si.path), error.GetDescription());
+      ERROR_LOG("Failed to delete save state file '{}': {}", Path::GetFileName(si.path), error.GetDescription());
   }
 }
 
@@ -4903,7 +4902,7 @@ bool System::LoadCheatListFromDatabase()
   if (!cl->LoadFromPackage(s_running_game_serial))
     return false;
 
-  Log_InfoFmt("Loaded {} cheats from database.", cl->GetCodeCount());
+  INFO_LOG("Loaded {} cheats from database.", cl->GetCodeCount());
   SetCheatList(std::move(cl));
   return true;
 }
diff --git a/src/core/texture_replacements.cpp b/src/core/texture_replacements.cpp
index 49bc1dc41..a4edae46e 100644
--- a/src/core/texture_replacements.cpp
+++ b/src/core/texture_replacements.cpp
@@ -113,9 +113,9 @@ void TextureReplacements::DumpVRAMWrite(u32 width, u32 height, const void* pixel
     }
   }
 
-  Log_InfoFmt("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename));
+  INFO_LOG("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename));
   if (!image.SaveToFile(filename.c_str())) [[unlikely]]
-    Log_ErrorFmt("Failed to dump {}x{} VRAM write to '{}'", width, height, filename);
+    ERROR_LOG("Failed to dump {}x{} VRAM write to '{}'", width, height, filename);
 }
 
 void TextureReplacements::Shutdown()
@@ -255,7 +255,7 @@ void TextureReplacements::FindTextures(const std::string& dir)
         auto it = m_vram_write_replacements.find(hash);
         if (it != m_vram_write_replacements.end())
         {
-          Log_WarningFmt("Duplicate VRAM write replacement: '{}' and '{}'", it->second, fd.FileName);
+          WARNING_LOG("Duplicate VRAM write replacement: '{}' and '{}'", it->second, fd.FileName);
           continue;
         }
 
@@ -265,7 +265,7 @@ void TextureReplacements::FindTextures(const std::string& dir)
     }
   }
 
-  Log_InfoFmt("Found {} replacement VRAM writes for '{}'", m_vram_write_replacements.size(), m_game_id);
+  INFO_LOG("Found {} replacement VRAM writes for '{}'", m_vram_write_replacements.size(), m_game_id);
 }
 
 const TextureReplacementTexture* TextureReplacements::LoadTexture(const std::string& filename)
@@ -277,11 +277,11 @@ const TextureReplacementTexture* TextureReplacements::LoadTexture(const std::str
   RGBA8Image image;
   if (!image.LoadFromFile(filename.c_str()))
   {
-    Log_ErrorFmt("Failed to load '%s'", Path::GetFileName(filename));
+    ERROR_LOG("Failed to load '%s'", Path::GetFileName(filename));
     return nullptr;
   }
 
-  Log_InfoFmt("Loaded '{}': {}x{}", Path::GetFileName(filename), image.GetWidth(), image.GetHeight());
+  INFO_LOG("Loaded '{}': {}x{}", Path::GetFileName(filename), image.GetWidth(), image.GetHeight());
   it = m_texture_cache.emplace(filename, std::move(image)).first;
   return &it->second;
 }
diff --git a/src/core/timers.cpp b/src/core/timers.cpp
index c2c6b8838..6c2aeb377 100644
--- a/src/core/timers.cpp
+++ b/src/core/timers.cpp
@@ -249,7 +249,7 @@ void Timers::CheckForIRQ(u32 timer, u32 old_counter)
       if (!cs.irq_done || cs.mode.irq_repeat)
       {
         // this is actually low for a few cycles
-        Log_DebugFmt("Raising timer {} pulse IRQ", timer);
+        DEBUG_LOG("Raising timer {} pulse IRQ", timer);
         InterruptController::SetLineState(irqnum, false);
         InterruptController::SetLineState(irqnum, true);
       }
@@ -262,7 +262,7 @@ void Timers::CheckForIRQ(u32 timer, u32 old_counter)
       // TODO: How does the non-repeat mode work here?
       cs.mode.interrupt_request_n ^= true;
       if (!cs.mode.interrupt_request_n)
-        Log_DebugFmt("Raising timer {} alternate IRQ", timer);
+        DEBUG_LOG("Raising timer {} alternate IRQ", timer);
 
       InterruptController::SetLineState(irqnum, !cs.mode.interrupt_request_n);
     }
@@ -297,7 +297,7 @@ u32 Timers::ReadRegister(u32 offset)
   const u32 port_offset = offset & u32(0x0F);
   if (timer_index >= 3) [[unlikely]]
   {
-    Log_ErrorFmt("Timer read out of range: offset 0x{:02X}", offset);
+    ERROR_LOG("Timer read out of range: offset 0x{:02X}", offset);
     return UINT32_C(0xFFFFFFFF);
   }
 
@@ -340,7 +340,7 @@ u32 Timers::ReadRegister(u32 offset)
       return cs.target;
 
     default:
-      [[unlikely]] Log_ErrorFmt("Read unknown register in timer {} (offset 0x{:02X})", timer_index, offset);
+      [[unlikely]] ERROR_LOG("Read unknown register in timer {} (offset 0x{:02X})", timer_index, offset);
       return UINT32_C(0xFFFFFFFF);
   }
 }
@@ -351,7 +351,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
   const u32 port_offset = offset & u32(0x0F);
   if (timer_index >= 3) [[unlikely]]
   {
-    Log_ErrorFmt("Timer write out of range: offset 0{:02X} value 0x{:08X}", offset, value);
+    ERROR_LOG("Timer write out of range: offset 0{:02X} value 0x{:08X}", offset, value);
     return;
   }
 
@@ -372,7 +372,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
     case 0x00:
     {
       const u32 old_counter = cs.counter;
-      Log_DebugFmt("Timer {} write counter {}", timer_index, value);
+      DEBUG_LOG("Timer {} write counter {}", timer_index, value);
       cs.counter = value & u32(0xFFFF);
       CheckForIRQ(timer_index, old_counter);
       if (timer_index == 2 || !cs.external_counting_enabled)
@@ -384,7 +384,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
     {
       static constexpr u32 WRITE_MASK = 0b1110001111111111;
 
-      Log_DebugFmt("Timer {} write mode register 0x{:04X}", timer_index, value);
+      DEBUG_LOG("Timer {} write mode register 0x{:04X}", timer_index, value);
       cs.mode.bits = (value & WRITE_MASK) | (cs.mode.bits & ~WRITE_MASK);
       cs.use_external_clock = (cs.mode.clock_source & (timer_index == 2 ? 2 : 1)) != 0;
       cs.counter = 0;
@@ -400,7 +400,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
 
     case 0x08:
     {
-      Log_DebugFmt("Timer %u write target 0x{:04X}", timer_index, ZeroExtend32(Truncate16(value)));
+      DEBUG_LOG("Timer %u write target 0x{:04X}", timer_index, ZeroExtend32(Truncate16(value)));
       cs.target = value & u32(0xFFFF);
       CheckForIRQ(timer_index, cs.counter);
       if (timer_index == 2 || !cs.external_counting_enabled)
@@ -409,7 +409,7 @@ void Timers::WriteRegister(u32 offset, u32 value)
     break;
 
     default:
-      Log_ErrorFmt("Write unknown register in timer {} (offset 0x{:02X}, value 0x{:X})", timer_index, offset, value);
+      ERROR_LOG("Write unknown register in timer {} (offset 0x{:02X}, value 0x{:X})", timer_index, offset, value);
       break;
   }
 }
diff --git a/src/core/timing_event.cpp b/src/core/timing_event.cpp
index c537537a5..1ff4705bc 100644
--- a/src/core/timing_event.cpp
+++ b/src/core/timing_event.cpp
@@ -374,7 +374,7 @@ bool DoState(StateWrapper& sw)
       TimingEvent* event = FindActiveEvent(event_name.c_str());
       if (!event)
       {
-        Log_WarningFmt("Save state has event '{}', but couldn't find this event when loading.", event_name);
+        WARNING_LOG("Save state has event '{}', but couldn't find this event when loading.", event_name);
         continue;
       }
 
@@ -391,7 +391,7 @@ bool DoState(StateWrapper& sw)
       sw.Do(&last_event_run_time);
     }
 
-    Log_DebugFmt("Loaded {} events from save state.", event_count);
+    DEBUG_LOG("Loaded {} events from save state.", event_count);
     SortEvents();
   }
   else
@@ -408,7 +408,7 @@ bool DoState(StateWrapper& sw)
       sw.Do(&event->m_interval);
     }
 
-    Log_DebugFmt("Wrote {} events to save state.", s_active_event_count);
+    DEBUG_LOG("Wrote {} events to save state.", s_active_event_count);
   }
 
   return !sw.HasError();
diff --git a/src/duckstation-qt/autoupdaterdialog.cpp b/src/duckstation-qt/autoupdaterdialog.cpp
index f90e9e560..47cdde1d4 100644
--- a/src/duckstation-qt/autoupdaterdialog.cpp
+++ b/src/duckstation-qt/autoupdaterdialog.cpp
@@ -74,7 +74,7 @@ AutoUpdaterDialog::AutoUpdaterDialog(QWidget* parent /* = nullptr */) : QDialog(
 
   m_http = HTTPDownloader::Create(Host::GetHTTPUserAgent());
   if (!m_http)
-    Log_ErrorPrint("Failed to create HTTP downloader, auto updater will not be available.");
+    ERROR_LOG("Failed to create HTTP downloader, auto updater will not be available.");
 }
 
 AutoUpdaterDialog::~AutoUpdaterDialog() = default;
@@ -86,7 +86,7 @@ bool AutoUpdaterDialog::isSupported()
   // For Linux, we need to check whether we're running from the appimage.
   if (!std::getenv("APPIMAGE"))
   {
-    Log_InfoPrint("We're a CI release, but not running from an AppImage. Disabling automatic updater.");
+    INFO_LOG("We're a CI release, but not running from an AppImage. Disabling automatic updater.");
     return false;
   }
 
@@ -165,7 +165,7 @@ void AutoUpdaterDialog::httpPollTimerPoll()
 
   if (!m_http->HasAnyRequests())
   {
-    Log_VerbosePrint("All HTTP requests done.");
+    VERBOSE_LOG("All HTTP requests done.");
     m_http_poll_timer->stop();
   }
 }
@@ -465,16 +465,16 @@ bool AutoUpdaterDialog::updateNeeded() const
 {
   QString last_checked_sha = QString::fromStdString(Host::GetBaseStringSettingValue("AutoUpdater", "LastVersion"));
 
-  Log_InfoFmt("Current SHA: {}", g_scm_hash_str);
-  Log_InfoFmt("Latest SHA: {}", m_latest_sha.toUtf8().constData());
-  Log_InfoFmt("Last Checked SHA: {}", last_checked_sha.toUtf8().constData());
+  INFO_LOG("Current SHA: {}", g_scm_hash_str);
+  INFO_LOG("Latest SHA: {}", m_latest_sha.toUtf8().constData());
+  INFO_LOG("Last Checked SHA: {}", last_checked_sha.toUtf8().constData());
   if (m_latest_sha == g_scm_hash_str || m_latest_sha == last_checked_sha)
   {
-    Log_InfoPrint("No update needed.");
+    INFO_LOG("No update needed.");
     return false;
   }
 
-  Log_InfoPrint("Update needed.");
+  INFO_LOG("Update needed.");
   return true;
 }
 
@@ -697,8 +697,8 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
     zip_file.close();
   }
 
-  Log_InfoFmt("Beginning update:\nUpdater path: {}\nZip path: {}\nStaging directory: {}\nOutput directory: {}",
-              updater_app, zip_path, staging_directory, bundle_path.value());
+  INFO_LOG("Beginning update:\nUpdater path: {}\nZip path: {}\nStaging directory: {}\nOutput directory: {}",
+           updater_app, zip_path, staging_directory, bundle_path.value());
 
   const std::string_view args[] = {
     zip_path,
@@ -735,9 +735,9 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
 
   const QString new_appimage_path(qappimage_path + QStringLiteral(".new"));
   const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup"));
-  Log_InfoFmt("APPIMAGE = {}", appimage_path);
-  Log_InfoFmt("Backup AppImage path = {}", backup_appimage_path.toUtf8().constData());
-  Log_InfoFmt("New AppImage path = {}", new_appimage_path.toUtf8().constData());
+  INFO_LOG("APPIMAGE = {}", appimage_path);
+  INFO_LOG("Backup AppImage path = {}", backup_appimage_path.toUtf8().constData());
+  INFO_LOG("New AppImage path = {}", new_appimage_path.toUtf8().constData());
 
   // Remove old "new" appimage and existing backup appimage.
   if (QFile::exists(new_appimage_path) && !QFile::remove(new_appimage_path))
@@ -809,11 +809,10 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
   if (!QFile::exists(backup_appimage_path))
     return;
 
-  Log_InfoPrint(QStringLiteral("Removing backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
+  INFO_LOG(QStringLiteral("Removing backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
   if (!QFile::remove(backup_appimage_path))
   {
-    Log_ErrorPrint(
-      QStringLiteral("Failed to remove backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
+    ERROR_LOG(QStringLiteral("Failed to remove backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
   }
 }
 
diff --git a/src/duckstation-qt/controllerbindingwidgets.cpp b/src/duckstation-qt/controllerbindingwidgets.cpp
index c536e7ec7..03a0e0d82 100644
--- a/src/duckstation-qt/controllerbindingwidgets.cpp
+++ b/src/duckstation-qt/controllerbindingwidgets.cpp
@@ -481,7 +481,7 @@ void ControllerBindingWidget::bindBindingWidgets(QWidget* parent)
       InputBindingWidget* widget = parent->findChild<InputBindingWidget*>(QString::fromUtf8(bi.name));
       if (!widget)
       {
-        Log_ErrorFmt("No widget found for '{}' ({})", bi.name, m_controller_info->name);
+        ERROR_LOG("No widget found for '{}' ({})", bi.name, m_controller_info->name);
         continue;
       }
 
diff --git a/src/duckstation-qt/displaywidget.cpp b/src/duckstation-qt/displaywidget.cpp
index 5d0b13955..0a1f665a2 100644
--- a/src/duckstation-qt/displaywidget.cpp
+++ b/src/duckstation-qt/displaywidget.cpp
@@ -84,7 +84,7 @@ void DisplayWidget::updateRelativeMode(bool enabled)
   if (m_relative_mouse_enabled == enabled && m_clip_mouse_enabled == clip_cursor)
     return;
 
-  Log_InfoFmt("updateRelativeMode(): relative={}, clip={}", enabled ? "yes" : "no", clip_cursor ? "yes" : "no");
+  INFO_LOG("updateRelativeMode(): relative={}, clip={}", enabled ? "yes" : "no", clip_cursor ? "yes" : "no");
 
   if (!clip_cursor && m_clip_mouse_enabled)
   {
@@ -95,7 +95,7 @@ void DisplayWidget::updateRelativeMode(bool enabled)
   if (m_relative_mouse_enabled == enabled)
     return;
 
-  Log_InfoFmt("updateRelativeMode(): relative={}", enabled ? "yes" : "no");
+  INFO_LOG("updateRelativeMode(): relative={}", enabled ? "yes" : "no");
 #endif
 
   if (enabled)
@@ -126,12 +126,12 @@ void DisplayWidget::updateCursor(bool hidden)
   m_cursor_hidden = hidden;
   if (hidden)
   {
-    Log_DevPrint("updateCursor(): Cursor is now hidden");
+    DEV_LOG("updateCursor(): Cursor is now hidden");
     setCursor(Qt::BlankCursor);
   }
   else
   {
-    Log_DevPrint("updateCursor(): Cursor is now shown");
+    DEV_LOG("updateCursor(): Cursor is now shown");
     unsetCursor();
   }
 }
diff --git a/src/duckstation-qt/gdbconnection.cpp b/src/duckstation-qt/gdbconnection.cpp
index 2a5745f19..e00f5d6a5 100644
--- a/src/duckstation-qt/gdbconnection.cpp
+++ b/src/duckstation-qt/gdbconnection.cpp
@@ -11,7 +11,7 @@ GDBConnection::GDBConnection(GDBServer* parent, intptr_t descriptor) : QTcpSocke
 {
   if (!setSocketDescriptor(descriptor))
   {
-    Log_ErrorFmt("{} failed to set socket descriptor: {}", descriptor, errorString().toStdString());
+    ERROR_LOG("{} failed to set socket descriptor: {}", descriptor, errorString().toStdString());
     deleteLater();
     return;
   }
@@ -21,7 +21,7 @@ GDBConnection::GDBConnection(GDBServer* parent, intptr_t descriptor) : QTcpSocke
   connect(this, &QTcpSocket::readyRead, this, &GDBConnection::receivedData);
   connect(this, &QTcpSocket::disconnected, this, &GDBConnection::gotDisconnected);
 
-  Log_InfoFmt("{} client connected", m_descriptor);
+  INFO_LOG("{} client connected", m_descriptor);
 
   m_seen_resume = System::IsPaused();
   g_emu_thread->setSystemPaused(true);
@@ -29,7 +29,7 @@ GDBConnection::GDBConnection(GDBServer* parent, intptr_t descriptor) : QTcpSocke
 
 void GDBConnection::gotDisconnected()
 {
-  Log_InfoFmt("{} client disconnected", m_descriptor);
+  INFO_LOG("{} client disconnected", m_descriptor);
   deleteLater();
 }
 
@@ -46,19 +46,19 @@ void GDBConnection::receivedData()
 
       if (GDBProtocol::IsPacketInterrupt(m_readBuffer))
       {
-        Log_DebugFmt("{} > Interrupt request", m_descriptor);
+        DEBUG_LOG("{} > Interrupt request", m_descriptor);
         g_emu_thread->setSystemPaused(true);
         m_readBuffer.erase();
       }
       else if (GDBProtocol::IsPacketContinue(m_readBuffer))
       {
-        Log_DebugFmt("{} > Continue request", m_descriptor);
+        DEBUG_LOG("{} > Continue request", m_descriptor);
         g_emu_thread->setSystemPaused(false);
         m_readBuffer.erase();
       }
       else if (GDBProtocol::IsPacketComplete(m_readBuffer))
       {
-        Log_DebugFmt("{} > %s", m_descriptor, m_readBuffer.c_str());
+        DEBUG_LOG("{} > %s", m_descriptor, m_readBuffer.c_str());
         writePacket(GDBProtocol::ProcessPacket(m_readBuffer));
         m_readBuffer.erase();
       }
@@ -66,7 +66,7 @@ void GDBConnection::receivedData()
   }
   if (bytesRead == -1)
   {
-    Log_ErrorFmt("{} failed to read from socket: %s", m_descriptor, errorString().toStdString());
+    ERROR_LOG("{} failed to read from socket: %s", m_descriptor, errorString().toStdString());
   }
 }
 
@@ -89,7 +89,7 @@ void GDBConnection::onEmulationResumed()
 
 void GDBConnection::writePacket(std::string_view packet)
 {
-  Log_DebugFmt("{} < {}", m_descriptor, packet);
+  DEBUG_LOG("{} < {}", m_descriptor, packet);
   if (write(packet.data(), packet.length()) == -1)
-    Log_ErrorFmt("{} failed to write to socket: {}", m_descriptor, errorString().toStdString());
+    ERROR_LOG("{} failed to write to socket: {}", m_descriptor, errorString().toStdString());
 }
diff --git a/src/duckstation-qt/gdbserver.cpp b/src/duckstation-qt/gdbserver.cpp
index bcb5eee55..504cc0733 100644
--- a/src/duckstation-qt/gdbserver.cpp
+++ b/src/duckstation-qt/gdbserver.cpp
@@ -25,11 +25,11 @@ void GDBServer::start(quint16 port)
 
   if (!listen(QHostAddress::LocalHost, port))
   {
-    Log_ErrorFmt("Failed to listen on TCP port {} for GDB server: {}", port, errorString().toUtf8().constData());
+    ERROR_LOG("Failed to listen on TCP port {} for GDB server: {}", port, errorString().toUtf8().constData());
     return;
   }
 
-  Log_InfoFmt("GDB server listening on TCP port {}", port);
+  INFO_LOG("GDB server listening on TCP port {}", port);
 }
 
 void GDBServer::stop()
@@ -37,7 +37,7 @@ void GDBServer::stop()
   if (isListening())
   {
     close();
-    Log_InfoPrint("GDB server stopped");
+    INFO_LOG("GDB server stopped");
   }
 
   for (QObject* connection : children())
diff --git a/src/duckstation-qt/logwindow.cpp b/src/duckstation-qt/logwindow.cpp
index 7b7831e9c..e4f946f8a 100644
--- a/src/duckstation-qt/logwindow.cpp
+++ b/src/duckstation-qt/logwindow.cpp
@@ -292,7 +292,7 @@ void LogWindow::logCallback(void* pUserParam, const char* channelName, const cha
   qmessage.append(QUtf8StringView(message.data(), message.length()));
   qmessage.append(QChar('\n'));
 
-  const QLatin1StringView qchannel((level <= LOGLEVEL_PERF) ? functionName : channelName);
+  const QLatin1StringView qchannel((level <= LOGLEVEL_WARNING) ? functionName : channelName);
 
   if (g_emu_thread->isOnUIThread())
   {
@@ -331,16 +331,14 @@ void LogWindow::appendMessage(const QLatin1StringView& channel, quint32 level, c
   temp_cursor.movePosition(QTextCursor::End);
 
   {
-    static constexpr const QChar level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'P', 'I', 'V', 'D', 'R', 'B', 'T'};
+    static constexpr const QChar level_characters[LOGLEVEL_COUNT] = {'X', 'E', 'W', 'I', 'V', 'D', 'B', 'T'};
     static constexpr const QColor level_colors[LOGLEVEL_COUNT] = {
       QColor(255, 255, 255),    // NONE
       QColor(0xE7, 0x48, 0x56), // ERROR, Red Intensity
       QColor(0xF9, 0xF1, 0xA5), // WARNING, Yellow Intensity
-      QColor(0xB4, 0x00, 0x9E), // PERF, Purple Intensity
       QColor(0xF2, 0xF2, 0xF2), // INFO, White Intensity
       QColor(0x16, 0xC6, 0x0C), // VERBOSE, Green Intensity
       QColor(0xCC, 0xCC, 0xCC), // DEV, White
-      QColor(0x61, 0xD6, 0xD6), // PROFILE, Cyan Intensity
       QColor(0x13, 0xA1, 0x0E), // DEBUG, Green
       QColor(0x00, 0x37, 0xDA), // TRACE, Blue
     };
@@ -358,7 +356,7 @@ void LogWindow::appendMessage(const QLatin1StringView& channel, quint32 level, c
       temp_cursor.insertText(qtimestamp);
     }
 
-    const QString qchannel = (level <= LOGLEVEL_PERF) ?
+    const QString qchannel = (level <= LOGLEVEL_WARNING) ?
                                QStringLiteral("%1(%2): ").arg(level_characters[level]).arg(channel) :
                                QStringLiteral("%1/%2: ").arg(level_characters[level]).arg(channel);
     format.setForeground(QBrush(channel_color));
diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp
index ef1c3c28e..f8bfa679d 100644
--- a/src/duckstation-qt/mainwindow.cpp
+++ b/src/duckstation-qt/mainwindow.cpp
@@ -246,10 +246,9 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr
 std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window, bool fullscreen, bool render_to_main,
                                                           bool surfaceless, bool use_main_window_pos)
 {
-  Log_DevFmt(
-    "acquireRenderWindow() recreate={} fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
-    recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false",
-    surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false");
+  DEV_LOG("acquireRenderWindow() recreate={} fullscreen={} render_to_main={} surfaceless={} use_main_window_pos={}",
+          recreate_window ? "true" : "false", fullscreen ? "true" : "false", render_to_main ? "true" : "false",
+          surfaceless ? "true" : "false", use_main_window_pos ? "true" : "false");
 
   QWidget* container =
     m_display_container ? static_cast<QWidget*>(m_display_container) : static_cast<QWidget*>(m_display_widget);
@@ -269,7 +268,7 @@ std::optional<WindowInfo> MainWindow::acquireRenderWindow(bool recreate_window,
   if (m_display_created && !recreate_window && !is_rendering_to_main && !render_to_main &&
       has_container == needs_container && !needs_container && !changing_surfaceless)
   {
-    Log_DevFmt("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
+    DEV_LOG("Toggling to {} without recreating surface", (fullscreen ? "fullscreen" : "windowed"));
 
     // since we don't destroy the display widget, we need to save it here
     if (!is_fullscreen && !is_rendering_to_main)
diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp
index 1be0d1137..f84bb52e0 100644
--- a/src/duckstation-qt/qthost.cpp
+++ b/src/duckstation-qt/qthost.cpp
@@ -202,7 +202,7 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
   // if there's no keys, just toss the whole thing out
   if (delete_if_empty && ini->IsEmpty())
   {
-    Log_InfoFmt("Removing empty gamesettings ini {}", Path::GetFileName(ini->GetFileName()));
+    INFO_LOG("Removing empty gamesettings ini {}", Path::GetFileName(ini->GetFileName()));
     if (FileSystem::FileExists(ini->GetFileName().c_str()) &&
         !FileSystem::DeleteFile(ini->GetFileName().c_str(), &error))
     {
@@ -297,7 +297,7 @@ std::optional<bool> QtHost::DownloadFile(QWidget* parent, const QString& title,
 
 bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url, const char* path)
 {
-  Log_InfoFmt("Download from {}, saving to {}.", url, path);
+  INFO_LOG("Download from {}, saving to {}.", url, path);
 
   std::vector<u8> data;
   if (!DownloadFile(parent, title, std::move(url), &data).value_or(false) || data.empty())
@@ -320,7 +320,7 @@ bool QtHost::DownloadFile(QWidget* parent, const QString& title, std::string url
 bool QtHost::DownloadFileFromZip(QWidget* parent, const QString& title, std::string url, const char* zip_filename,
                                  const char* output_path)
 {
-  Log_InfoFmt("Download {} from {}, saving to {}.", zip_filename, url, output_path);
+  INFO_LOG("Download {} from {}, saving to {}.", zip_filename, url, output_path);
 
   std::vector<u8> data;
   if (!DownloadFile(parent, title, std::move(url), &data).value_or(false) || data.empty())
@@ -401,7 +401,7 @@ bool QtHost::InitializeConfig(std::string settings_filename)
     settings_filename = Path::Combine(EmuFolders::DataRoot, "settings.ini");
 
   const bool settings_exists = FileSystem::FileExists(settings_filename.c_str());
-  Log_InfoFmt("Loading config from {}.", settings_filename);
+  INFO_LOG("Loading config from {}.", settings_filename);
   s_base_settings_interface = std::make_unique<INISettingsInterface>(std::move(settings_filename));
   Host::Internal::SetBaseSettingsLayer(s_base_settings_interface.get());
 
@@ -465,9 +465,9 @@ bool QtHost::SetCriticalFolders()
     return false;
 
   // logging of directories in case something goes wrong super early
-  Log_DevFmt("AppRoot Directory: {}", EmuFolders::AppRoot);
-  Log_DevFmt("DataRoot Directory: {}", EmuFolders::DataRoot);
-  Log_DevFmt("Resources Directory: {}", EmuFolders::Resources);
+  DEV_LOG("AppRoot Directory: {}", EmuFolders::AppRoot);
+  DEV_LOG("DataRoot Directory: {}", EmuFolders::DataRoot);
+  DEV_LOG("Resources Directory: {}", EmuFolders::Resources);
 
   // Write crash dumps to the data directory, since that'll be accessible for certain.
   CrashHandler::SetWriteDirectory(EmuFolders::DataRoot);
@@ -493,7 +493,7 @@ bool QtHost::ShouldUsePortableMode()
 void QtHost::SetAppRoot()
 {
   const std::string program_path = FileSystem::GetProgramPath();
-  Log_InfoFmt("Program Path: {}", program_path.c_str());
+  INFO_LOG("Program Path: {}", program_path.c_str());
 
   EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path));
 }
@@ -1813,9 +1813,9 @@ void Host::ReportFatalError(std::string_view title, std::string_view message)
 void Host::ReportErrorAsync(std::string_view title, std::string_view message)
 {
   if (!title.empty() && !message.empty())
-    Log_ErrorFmt("ReportErrorAsync: {}: {}", title, message);
+    ERROR_LOG("ReportErrorAsync: {}: {}", title, message);
   else if (!message.empty())
-    Log_ErrorFmt("ReportErrorAsync: {}", message);
+    ERROR_LOG("ReportErrorAsync: {}", message);
 
   QMetaObject::invokeMethod(
     g_main_window, "reportError", Qt::QueuedConnection,
@@ -1913,7 +1913,7 @@ std::optional<std::vector<u8>> Host::ReadResourceFile(std::string_view filename,
   const std::string path = QtHost::GetResourcePath(filename, allow_override);
   std::optional<std::vector<u8>> ret(FileSystem::ReadBinaryFile(path.c_str()));
   if (!ret.has_value())
-    Log_ErrorFmt("Failed to read resource file '{}'", filename);
+    ERROR_LOG("Failed to read resource file '{}'", filename);
   return ret;
 }
 
@@ -1922,7 +1922,7 @@ std::optional<std::string> Host::ReadResourceFileToString(std::string_view filen
   const std::string path = QtHost::GetResourcePath(filename, allow_override);
   std::optional<std::string> ret(FileSystem::ReadFileToString(path.c_str()));
   if (!ret.has_value())
-    Log_ErrorFmt("Failed to read resource file to string '{}'", filename);
+    ERROR_LOG("Failed to read resource file to string '{}'", filename);
   return ret;
 }
 
@@ -1933,7 +1933,7 @@ std::optional<std::time_t> Host::GetResourceFileTimestamp(std::string_view filen
   FILESYSTEM_STAT_DATA sd;
   if (!FileSystem::StatFile(path.c_str(), &sd))
   {
-    Log_ErrorFmt("Failed to stat resource file '{}'", filename);
+    ERROR_LOG("Failed to stat resource file '{}'", filename);
     return std::nullopt;
   }
 
@@ -2055,7 +2055,7 @@ void QtHost::SaveSettings()
     Error error;
     auto lock = Host::GetSettingsLock();
     if (!s_base_settings_interface->Save(&error))
-      Log_ErrorFmt("Failed to save settings: {}", error.GetDescription());
+      ERROR_LOG("Failed to save settings: {}", error.GetDescription());
   }
 
   if (s_settings_save_timer)
@@ -2287,88 +2287,88 @@ bool QtHost::ParseCommandLineParametersAndInitializeConfig(QApplication& app,
       }
       else if (CHECK_ARG("-batch"))
       {
-        Log_InfoPrint("Command Line: Using batch mode.");
+        INFO_LOG("Command Line: Using batch mode.");
         s_batch_mode = true;
         continue;
       }
       else if (CHECK_ARG("-nogui"))
       {
-        Log_InfoPrint("Command Line: Using NoGUI mode.");
+        INFO_LOG("Command Line: Using NoGUI mode.");
         s_nogui_mode = true;
         s_batch_mode = true;
         continue;
       }
       else if (CHECK_ARG("-bios"))
       {
-        Log_InfoPrint("Command Line: Starting BIOS.");
+        INFO_LOG("Command Line: Starting BIOS.");
         AutoBoot(autoboot);
         starting_bios = true;
         continue;
       }
       else if (CHECK_ARG("-fastboot"))
       {
-        Log_InfoPrint("Command Line: Forcing fast boot.");
+        INFO_LOG("Command Line: Forcing fast boot.");
         AutoBoot(autoboot)->override_fast_boot = true;
         continue;
       }
       else if (CHECK_ARG("-slowboot"))
       {
-        Log_InfoPrint("Command Line: Forcing slow boot.");
+        INFO_LOG("Command Line: Forcing slow boot.");
         AutoBoot(autoboot)->override_fast_boot = false;
         continue;
       }
       else if (CHECK_ARG("-resume"))
       {
         state_index = -1;
-        Log_InfoPrint("Command Line: Loading resume state.");
+        INFO_LOG("Command Line: Loading resume state.");
         continue;
       }
       else if (CHECK_ARG_PARAM("-state"))
       {
         state_index = args[++i].toInt();
-        Log_InfoFmt("Command Line: Loading state index: {}", state_index.value());
+        INFO_LOG("Command Line: Loading state index: {}", state_index.value());
         continue;
       }
       else if (CHECK_ARG_PARAM("-statefile"))
       {
         AutoBoot(autoboot)->save_state = args[++i].toStdString();
-        Log_InfoFmt("Command Line: Loading state file: '{}'", autoboot->save_state);
+        INFO_LOG("Command Line: Loading state file: '{}'", autoboot->save_state);
         continue;
       }
       else if (CHECK_ARG_PARAM("-exe"))
       {
         AutoBoot(autoboot)->override_exe = args[++i].toStdString();
-        Log_InfoFmt("Command Line: Overriding EXE file: '{}'", autoboot->override_exe);
+        INFO_LOG("Command Line: Overriding EXE file: '{}'", autoboot->override_exe);
         continue;
       }
       else if (CHECK_ARG("-fullscreen"))
       {
-        Log_InfoPrint("Command Line: Using fullscreen.");
+        INFO_LOG("Command Line: Using fullscreen.");
         AutoBoot(autoboot)->override_fullscreen = true;
         s_start_fullscreen_ui_fullscreen = true;
         continue;
       }
       else if (CHECK_ARG("-nofullscreen"))
       {
-        Log_InfoPrint("Command Line: Not using fullscreen.");
+        INFO_LOG("Command Line: Not using fullscreen.");
         AutoBoot(autoboot)->override_fullscreen = false;
         continue;
       }
       else if (CHECK_ARG("-portable"))
       {
-        Log_InfoPrint("Command Line: Using portable mode.");
+        INFO_LOG("Command Line: Using portable mode.");
         EmuFolders::DataRoot = EmuFolders::AppRoot;
         continue;
       }
       else if (CHECK_ARG_PARAM("-settings"))
       {
         settings_filename = args[++i].toStdString();
-        Log_InfoFmt("Command Line: Overriding settings filename: {}", settings_filename);
+        INFO_LOG("Command Line: Overriding settings filename: {}", settings_filename);
         continue;
       }
       else if (CHECK_ARG("-bigpicture"))
       {
-        Log_InfoPrint("Command Line: Starting big picture mode.");
+        INFO_LOG("Command Line: Starting big picture mode.");
         s_start_fullscreen_ui = true;
         continue;
       }
diff --git a/src/duckstation-qt/qttranslations.cpp b/src/duckstation-qt/qttranslations.cpp
index 257ecbee1..77e3b4efd 100644
--- a/src/duckstation-qt/qttranslations.cpp
+++ b/src/duckstation-qt/qttranslations.cpp
@@ -136,7 +136,7 @@ void QtHost::InstallTranslator(QWidget* dialog_parent)
     return;
   }
 
-  Log_InfoFmt("Loaded translation file for language {}", language.toUtf8().constData());
+  INFO_LOG("Loaded translation file for language {}", language.toUtf8().constData());
   qApp->installTranslator(translator);
   s_translators.push_back(translator);
 
diff --git a/src/duckstation-qt/settingswindow.cpp b/src/duckstation-qt/settingswindow.cpp
index db78f1950..5e293dc76 100644
--- a/src/duckstation-qt/settingswindow.cpp
+++ b/src/duckstation-qt/settingswindow.cpp
@@ -622,7 +622,7 @@ void SettingsWindow::openGamePropertiesDialog(const std::string& path, const std
     if (image)
       dentry = GameDatabase::GetEntryForDisc(image.get());
     else
-      Log_ErrorFmt("Failed to open '{}' for game properties: {}", path, error.GetDescription());
+      ERROR_LOG("Failed to open '{}' for game properties: {}", path, error.GetDescription());
 
     if (!dentry)
     {
diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp
index 405320a1c..2adf5d784 100644
--- a/src/duckstation-regtest/regtest_host.cpp
+++ b/src/duckstation-regtest/regtest_host.cpp
@@ -52,7 +52,7 @@ static std::string s_dump_game_directory;
 bool RegTestHost::SetFolders()
 {
   std::string program_path(FileSystem::GetProgramPath());
-  Log_InfoFmt("Program Path: {}", program_path);
+  INFO_LOG("Program Path: {}", program_path);
 
   EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path));
   EmuFolders::DataRoot = EmuFolders::AppRoot;
@@ -67,9 +67,9 @@ bool RegTestHost::SetFolders()
   // On Windows/Linux, these are in the binary directory.
   EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources");
 
-  Log_DevFmt("AppRoot Directory: {}", EmuFolders::AppRoot);
-  Log_DevFmt("DataRoot Directory: {}", EmuFolders::DataRoot);
-  Log_DevFmt("Resources Directory: {}", EmuFolders::Resources);
+  DEV_LOG("AppRoot Directory: {}", EmuFolders::AppRoot);
+  DEV_LOG("DataRoot Directory: {}", EmuFolders::DataRoot);
+  DEV_LOG("Resources Directory: {}", EmuFolders::Resources);
 
   // Write crash dumps to the data directory, since that'll be accessible for certain.
   CrashHandler::SetWriteDirectory(EmuFolders::DataRoot);
@@ -77,7 +77,7 @@ bool RegTestHost::SetFolders()
   // the resources directory should exist, bail out if not
   if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str()))
   {
-    Log_ErrorPrint("Resources directory is missing, your installation is incomplete.");
+    ERROR_LOG("Resources directory is missing, your installation is incomplete.");
     return false;
   }
 
@@ -119,31 +119,31 @@ bool RegTestHost::InitializeConfig()
 
 void Host::ReportFatalError(std::string_view title, std::string_view message)
 {
-  Log_ErrorFmt("ReportFatalError: {}", message);
+  ERROR_LOG("ReportFatalError: {}", message);
   abort();
 }
 
 void Host::ReportErrorAsync(std::string_view title, std::string_view message)
 {
   if (!title.empty() && !message.empty())
-    Log_ErrorFmt("ReportErrorAsync: {}: {}", title, message);
+    ERROR_LOG("ReportErrorAsync: {}: {}", title, message);
   else if (!message.empty())
-    Log_ErrorFmt("ReportErrorAsync: {}", message);
+    ERROR_LOG("ReportErrorAsync: {}", message);
 }
 
 bool Host::ConfirmMessage(std::string_view title, std::string_view message)
 {
   if (!title.empty() && !message.empty())
-    Log_ErrorFmt("ConfirmMessage: {}: {}", title, message);
+    ERROR_LOG("ConfirmMessage: {}: {}", title, message);
   else if (!message.empty())
-    Log_ErrorFmt("ConfirmMessage: {}", message);
+    ERROR_LOG("ConfirmMessage: {}", message);
 
   return true;
 }
 
 void Host::ReportDebuggerMessage(std::string_view message)
 {
-  Log_ErrorFmt("ReportDebuggerMessage: {}", message);
+  ERROR_LOG("ReportDebuggerMessage: {}", message);
 }
 
 std::span<const std::pair<const char*, const char*>> Host::GetAvailableLanguageList()
@@ -209,7 +209,7 @@ std::optional<std::vector<u8>> Host::ReadResourceFile(std::string_view filename,
   const std::string path(Path::Combine(EmuFolders::Resources, filename));
   std::optional<std::vector<u8>> ret(FileSystem::ReadBinaryFile(path.c_str()));
   if (!ret.has_value())
-    Log_ErrorFmt("Failed to read resource file '{}'", filename);
+    ERROR_LOG("Failed to read resource file '{}'", filename);
   return ret;
 }
 
@@ -218,7 +218,7 @@ std::optional<std::string> Host::ReadResourceFileToString(std::string_view filen
   const std::string path(Path::Combine(EmuFolders::Resources, filename));
   std::optional<std::string> ret(FileSystem::ReadFileToString(path.c_str()));
   if (!ret.has_value())
-    Log_ErrorFmt("Failed to read resource file to string '{}'", filename);
+    ERROR_LOG("Failed to read resource file to string '{}'", filename);
   return ret;
 }
 
@@ -228,7 +228,7 @@ std::optional<std::time_t> Host::GetResourceFileTimestamp(std::string_view filen
   FILESYSTEM_STAT_DATA sd;
   if (!FileSystem::StatFile(path.c_str(), &sd))
   {
-    Log_ErrorFmt("Failed to stat resource file '{}'", filename);
+    ERROR_LOG("Failed to stat resource file '{}'", filename);
     return std::nullopt;
   }
 
@@ -272,21 +272,21 @@ void Host::OnPerformanceCountersUpdated()
 
 void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name)
 {
-  Log_InfoFmt("Disc Path: {}", disc_path);
-  Log_InfoFmt("Game Serial: {}", game_serial);
-  Log_InfoFmt("Game Name: {}", game_name);
+  INFO_LOG("Disc Path: {}", disc_path);
+  INFO_LOG("Game Serial: {}", game_serial);
+  INFO_LOG("Game Name: {}", game_name);
 
   if (!s_dump_base_directory.empty())
   {
     s_dump_game_directory = Path::Combine(s_dump_base_directory, game_name);
     if (!FileSystem::DirectoryExists(s_dump_game_directory.c_str()))
     {
-      Log_InfoFmt("Creating directory '{}'...", s_dump_game_directory);
+      INFO_LOG("Creating directory '{}'...", s_dump_game_directory);
       if (!FileSystem::CreateDirectory(s_dump_game_directory.c_str(), false))
         Panic("Failed to create dump directory.");
     }
 
-    Log_InfoFmt("Dumping frames to '{}'...", s_dump_game_directory);
+    INFO_LOG("Dumping frames to '{}'...", s_dump_game_directory);
   }
 }
 
@@ -540,7 +540,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         s_dump_base_directory = argv[++i];
         if (s_dump_base_directory.empty())
         {
-          Log_ErrorPrint("Invalid dump directory specified.");
+          ERROR_LOG("Invalid dump directory specified.");
           return false;
         }
 
@@ -551,7 +551,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         s_frame_dump_interval = StringUtil::FromChars<u32>(argv[++i]).value_or(0);
         if (s_frames_to_run <= 0)
         {
-          Log_ErrorFmt("Invalid dump interval specified: {}", argv[i]);
+          ERROR_LOG("Invalid dump interval specified: {}", argv[i]);
           return false;
         }
 
@@ -562,7 +562,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         s_frames_to_run = StringUtil::FromChars<u32>(argv[++i]).value_or(0);
         if (s_frames_to_run == 0)
         {
-          Log_ErrorFmt("Invalid frame count specified: {}", argv[i]);
+          ERROR_LOG("Invalid frame count specified: {}", argv[i]);
           return false;
         }
 
@@ -573,7 +573,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         std::optional<LOGLEVEL> level = Settings::ParseLogLevelName(argv[++i]);
         if (!level.has_value())
         {
-          Log_ErrorPrint("Invalid log level specified.");
+          ERROR_LOG("Invalid log level specified.");
           return false;
         }
 
@@ -586,7 +586,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         std::optional<GPURenderer> renderer = Settings::ParseRendererName(argv[++i]);
         if (!renderer.has_value())
         {
-          Log_ErrorPrint("Invalid renderer specified.");
+          ERROR_LOG("Invalid renderer specified.");
           return false;
         }
 
@@ -598,11 +598,11 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         const u32 upscale = StringUtil::FromChars<u32>(argv[++i]).value_or(0);
         if (upscale == 0)
         {
-          Log_ErrorPrint("Invalid upscale value.");
+          ERROR_LOG("Invalid upscale value.");
           return false;
         }
 
-        Log_InfoFmt("Setting upscale to {}.", upscale);
+        INFO_LOG("Setting upscale to {}.", upscale);
         s_base_settings_interface->SetIntValue("GPU", "ResolutionScale", static_cast<s32>(upscale));
         continue;
       }
@@ -611,24 +611,24 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
         const std::optional<CPUExecutionMode> cpu = Settings::ParseCPUExecutionMode(argv[++i]);
         if (!cpu.has_value())
         {
-          Log_ErrorPrint("Invalid CPU execution mode.");
+          ERROR_LOG("Invalid CPU execution mode.");
           return false;
         }
 
-        Log_InfoFmt("Setting CPU execution mode to {}.", Settings::GetCPUExecutionModeName(cpu.value()));
+        INFO_LOG("Setting CPU execution mode to {}.", Settings::GetCPUExecutionModeName(cpu.value()));
         s_base_settings_interface->SetStringValue("CPU", "ExecutionMode",
                                                   Settings::GetCPUExecutionModeName(cpu.value()));
         continue;
       }
       else if (CHECK_ARG("-pgxp"))
       {
-        Log_InfoPrint("Enabling PGXP.");
+        INFO_LOG("Enabling PGXP.");
         s_base_settings_interface->SetBoolValue("GPU", "PGXPEnable", true);
         continue;
       }
       else if (CHECK_ARG("-pgxp-cpu"))
       {
-        Log_InfoPrint("Enabling PGXP CPU mode.");
+        INFO_LOG("Enabling PGXP CPU mode.");
         s_base_settings_interface->SetBoolValue("GPU", "PGXPEnable", true);
         s_base_settings_interface->SetBoolValue("GPU", "PGXPCPU", true);
         continue;
@@ -640,7 +640,7 @@ bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::option
       }
       else if (argv[i][0] == '-')
       {
-        Log_ErrorFmt("Unknown parameter: '{}'", argv[i]);
+        ERROR_LOG("Unknown parameter: '{}'", argv[i]);
         return false;
       }
 
@@ -674,7 +674,7 @@ int main(int argc, char* argv[])
 
   if (!autoboot || autoboot->filename.empty())
   {
-    Log_ErrorPrint("No boot path specified.");
+    ERROR_LOG("No boot path specified.");
     return EXIT_FAILURE;
   }
 
@@ -683,7 +683,7 @@ int main(int argc, char* argv[])
     if (!System::Internal::PerformEarlyHardwareChecks(&startup_error) ||
         !System::Internal::CPUThreadInitialize(&startup_error))
     {
-      Log_ErrorFmt("CPUThreadInitialize() failed: {}", startup_error.GetDescription());
+      ERROR_LOG("CPUThreadInitialize() failed: {}", startup_error.GetDescription());
       return EXIT_FAILURE;
     }
   }
@@ -692,10 +692,10 @@ int main(int argc, char* argv[])
 
   Error error;
   int result = -1;
-  Log_InfoFmt("Trying to boot '{}'...", autoboot->filename);
+  INFO_LOG("Trying to boot '{}'...", autoboot->filename);
   if (!System::BootSystem(std::move(autoboot.value()), &error))
   {
-    Log_ErrorFmt("Failed to boot system: {}", error.GetDescription());
+    ERROR_LOG("Failed to boot system: {}", error.GetDescription());
     goto cleanup;
   }
 
@@ -703,17 +703,17 @@ int main(int argc, char* argv[])
   {
     if (s_dump_base_directory.empty())
     {
-      Log_ErrorPrint("Dump directory not specified.");
+      ERROR_LOG("Dump directory not specified.");
       goto cleanup;
     }
 
-    Log_InfoFmt("Dumping every {}th frame to '{}'.", s_frame_dump_interval, s_dump_base_directory);
+    INFO_LOG("Dumping every {}th frame to '{}'.", s_frame_dump_interval, s_dump_base_directory);
   }
 
-  Log_InfoFmt("Running for %d frames...", s_frames_to_run);
+  INFO_LOG("Running for %d frames...", s_frames_to_run);
   System::Execute();
 
-  Log_InfoPrint("Exiting with success.");
+  INFO_LOG("Exiting with success.");
   result = 0;
 
 cleanup:
diff --git a/src/updater/cocoa_progress_callback.mm b/src/updater/cocoa_progress_callback.mm
index a64a9ed96..ade99927a 100644
--- a/src/updater/cocoa_progress_callback.mm
+++ b/src/updater/cocoa_progress_callback.mm
@@ -150,19 +150,19 @@ void CocoaProgressCallback::UpdateProgress()
 
 void CocoaProgressCallback::DisplayError(const char* message)
 {
-  Log_ErrorPrint(message);
+  ERROR_LOG(message);
   AppendMessage(message);
 }
 
 void CocoaProgressCallback::DisplayWarning(const char* message)
 {
-  Log_WarningPrint(message);
+  WARNING_LOG(message);
   AppendMessage(message);
 }
 
 void CocoaProgressCallback::DisplayInformation(const char* message)
 {
-  Log_InfoPrint(message);
+  INFO_LOG(message);
   AppendMessage(message);
 }
 
@@ -185,7 +185,7 @@ void CocoaProgressCallback::AppendMessage(const char* message)
 
 void CocoaProgressCallback::DisplayDebugMessage(const char* message)
 {
-  Log_DevPrint(message);
+  DEV_LOG(message);
 }
 
 void CocoaProgressCallback::ModalError(const char* message)
diff --git a/src/updater/win32_progress_callback.cpp b/src/updater/win32_progress_callback.cpp
index 5943462c5..79ad2beee 100644
--- a/src/updater/win32_progress_callback.cpp
+++ b/src/updater/win32_progress_callback.cpp
@@ -71,7 +71,7 @@ bool Win32ProgressCallback::Create()
     wc.lpszClassName = CLASS_NAME;
     if (!RegisterClassExA(&wc))
     {
-      Log_ErrorPrint("Failed to register window class");
+      ERROR_LOG("Failed to register window class");
       return false;
     }
 
@@ -83,7 +83,7 @@ bool Win32ProgressCallback::Create()
                     CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, nullptr, nullptr, GetModuleHandle(nullptr), this);
   if (!m_window_hwnd)
   {
-    Log_ErrorPrint("Failed to create window");
+    ERROR_LOG("Failed to create window");
     return false;
   }
 
@@ -187,7 +187,7 @@ LRESULT CALLBACK Win32ProgressCallback::WndProc(HWND hwnd, UINT msg, WPARAM wpar
 
 void Win32ProgressCallback::DisplayError(const char* message)
 {
-  Log_ErrorPrint(message);
+  ERROR_LOG(message);
   SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(message));
   SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
   PumpMessages();
@@ -195,7 +195,7 @@ void Win32ProgressCallback::DisplayError(const char* message)
 
 void Win32ProgressCallback::DisplayWarning(const char* message)
 {
-  Log_WarningPrint(message);
+  WARNING_LOG(message);
   SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(message));
   SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
   PumpMessages();
@@ -203,7 +203,7 @@ void Win32ProgressCallback::DisplayWarning(const char* message)
 
 void Win32ProgressCallback::DisplayInformation(const char* message)
 {
-  Log_InfoPrint(message);
+  INFO_LOG(message);
   SendMessageA(m_list_box_hwnd, LB_ADDSTRING, 0, reinterpret_cast<LPARAM>(message));
   SendMessageA(m_list_box_hwnd, WM_VSCROLL, SB_BOTTOM, 0);
   PumpMessages();
@@ -211,7 +211,7 @@ void Win32ProgressCallback::DisplayInformation(const char* message)
 
 void Win32ProgressCallback::DisplayDebugMessage(const char* message)
 {
-  Log_DevPrint(message);
+  DEV_LOG(message);
 }
 
 void Win32ProgressCallback::ModalError(const char* message)
diff --git a/src/util/audio_stream.cpp b/src/util/audio_stream.cpp
index b94ba1128..7d29cedbf 100644
--- a/src/util/audio_stream.cpp
+++ b/src/util/audio_stream.cpp
@@ -290,7 +290,7 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
     else
     {
       m_filling = false;
-      Log_VerboseFmt("Underrun compensation done ({} frames buffered)", toFill);
+      VERBOSE_LOG("Underrun compensation done ({} frames buffered)", toFill);
     }
   }
 
@@ -357,7 +357,7 @@ void AudioStream::ReadFrames(SampleType* samples, u32 num_frames)
         resample_subpos %= 65536u;
       }
 
-      Log_VerboseFmt("Audio buffer underflow, resampled {} frames to {}", frames_to_read, num_frames);
+      VERBOSE_LOG("Audio buffer underflow, resampled {} frames to {}", frames_to_read, num_frames);
     }
     else
     {
@@ -441,7 +441,7 @@ void AudioStream::InternalWriteFrames(s16* data, u32 num_frames)
     }
     else
     {
-      Log_DebugPrint("Buffer overrun, chunk dropped");
+      DEBUG_LOG("Buffer overrun, chunk dropped");
       return;
     }
   }
@@ -498,7 +498,7 @@ void AudioStream::AllocateBuffer()
   if (IsExpansionEnabled())
     m_expand_buffer = std::make_unique<float[]>(m_parameters.expand_block_size * NUM_INPUT_CHANNELS);
 
-  Log_DevFmt(
+  DEV_LOG(
     "Allocated buffer of {} frames for buffer of {} ms [expansion {} (block size {}), stretch {}, target size {}].",
     m_buffer_size, m_parameters.buffer_ms, GetExpansionModeName(m_parameters.expansion_mode),
     m_parameters.expand_block_size, GetStretchModeName(m_parameters.stretch_mode), m_target_buffer_size);
@@ -891,7 +891,7 @@ void AudioStream::UpdateStretchTempo()
   // state vars
   if (m_stretch_reset >= STRETCH_RESET_THRESHOLD)
   {
-    Log_VerbosePrint("___ Stretcher is being reset.");
+    VERBOSE_LOG("___ Stretcher is being reset.");
     m_stretch_inactive = false;
     m_stretch_ok_count = 0;
     m_dynamic_target_usage = base_target_usage;
@@ -928,13 +928,13 @@ void AudioStream::UpdateStretchTempo()
 
     if (m_stretch_ok_count >= INACTIVE_MIN_OK_COUNT)
     {
-      Log_VerbosePrint("=== Stretcher is now inactive.");
+      VERBOSE_LOG("=== Stretcher is now inactive.");
       m_stretch_inactive = true;
     }
   }
   else if (!IsInRange(tempo, 1.0f / INACTIVE_BAD_FACTOR, INACTIVE_BAD_FACTOR))
   {
-    Log_VerboseFmt("~~~ Stretcher is now active @ tempo {}.", tempo);
+    VERBOSE_LOG("~~~ Stretcher is now active @ tempo {}.", tempo);
     m_stretch_inactive = false;
     m_stretch_ok_count = 0;
   }
@@ -951,9 +951,9 @@ void AudioStream::UpdateStretchTempo()
 
     if (Common::Timer::ConvertValueToSeconds(now - last_log_time) > 1.0f)
     {
-      Log_VerboseFmt("buffers: {:4d} ms ({:3.0f}%), tempo: {}, comp: {:2.3f}, iters: {}, reset:{}",
-                     (ibuffer_usage * 1000u) / m_sample_rate, 100.0f * buffer_usage / base_target_usage, tempo,
-                     m_dynamic_target_usage / base_target_usage, iterations, m_stretch_reset);
+      VERBOSE_LOG("buffers: {:4d} ms ({:3.0f}%), tempo: {}, comp: {:2.3f}, iters: {}, reset:{}",
+                  (ibuffer_usage * 1000u) / m_sample_rate, 100.0f * buffer_usage / base_target_usage, tempo,
+                  m_dynamic_target_usage / base_target_usage, iterations, m_stretch_reset);
 
       last_log_time = now;
       iterations = 0;
diff --git a/src/util/cd_image.cpp b/src/util/cd_image.cpp
index 29cbb0195..82c66a946 100644
--- a/src/util/cd_image.cpp
+++ b/src/util/cd_image.cpp
@@ -302,7 +302,7 @@ bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq)
       // TODO: This is where we'd reconstruct the header for other mode tracks.
       if (!ReadSectorFromIndex(buffer, *m_current_index, m_position_in_index))
       {
-        Log_ErrorFmt("Read of LBA {} failed", m_position_on_disc);
+        ERROR_LOG("Read of LBA {} failed", m_position_on_disc);
         Seek(m_position_on_disc);
         return false;
       }
@@ -324,7 +324,7 @@ bool CDImage::ReadRawSector(void* buffer, SubChannelQ* subq)
 
   if (subq && !ReadSubChannelQ(subq, *m_current_index, m_position_in_index))
   {
-    Log_ErrorFmt("Subchannel read of LBA {} failed", m_position_on_disc);
+    ERROR_LOG("Subchannel read of LBA {} failed", m_position_on_disc);
     Seek(m_position_on_disc);
     return false;
   }
diff --git a/src/util/cd_image_chd.cpp b/src/util/cd_image_chd.cpp
index c715f51f5..49976b912 100644
--- a/src/util/cd_image_chd.cpp
+++ b/src/util/cd_image_chd.cpp
@@ -116,14 +116,14 @@ chd_file* CDImageCHD::OpenCHD(std::string_view filename, FileSystem::ManagedCFil
   }
   else if (err != CHDERR_REQUIRES_PARENT)
   {
-    Log_ErrorFmt("Failed to open CHD '{}': {}", filename, chd_error_string(err));
+    ERROR_LOG("Failed to open CHD '{}': {}", filename, chd_error_string(err));
     Error::SetString(error, chd_error_string(err));
     return nullptr;
   }
 
   if (recursion_level >= MAX_PARENTS)
   {
-    Log_ErrorFmt("Failed to open CHD '{}': Too many parent files", filename);
+    ERROR_LOG("Failed to open CHD '{}': Too many parent files", filename);
     Error::SetString(error, "Too many parent files");
     return nullptr;
   }
@@ -133,7 +133,7 @@ chd_file* CDImageCHD::OpenCHD(std::string_view filename, FileSystem::ManagedCFil
   err = chd_read_header_file(fp.get(), &header);
   if (err != CHDERR_NONE)
   {
-    Log_ErrorFmt("Failed to read CHD header '{}': {}", filename, chd_error_string(err));
+    ERROR_LOG("Failed to read CHD header '{}': {}", filename, chd_error_string(err));
     Error::SetString(error, chd_error_string(err));
     return nullptr;
   }
@@ -166,8 +166,8 @@ chd_file* CDImageCHD::OpenCHD(std::string_view filename, FileSystem::ManagedCFil
       parent_chd = OpenCHD(filename_to_open, std::move(parent_fp), error, recursion_level + 1);
       if (parent_chd)
       {
-        Log_VerboseFmt("Using parent CHD '{}' from cache for '{}'.", Path::GetFileName(filename_to_open),
-                       Path::GetFileName(filename));
+        VERBOSE_LOG("Using parent CHD '{}' from cache for '{}'.", Path::GetFileName(filename_to_open),
+                    Path::GetFileName(filename));
       }
     }
 
@@ -208,14 +208,14 @@ chd_file* CDImageCHD::OpenCHD(std::string_view filename, FileSystem::ManagedCFil
       parent_chd = OpenCHD(fd.FileName, std::move(parent_fp), error, recursion_level + 1);
       if (parent_chd)
       {
-        Log_VerboseFmt("Using parent CHD '{}' for '{}'.", Path::GetFileName(fd.FileName), Path::GetFileName(filename));
+        VERBOSE_LOG("Using parent CHD '{}' for '{}'.", Path::GetFileName(fd.FileName), Path::GetFileName(filename));
         break;
       }
     }
   }
   if (!parent_chd)
   {
-    Log_ErrorFmt("Failed to open CHD '{}': Failed to find parent CHD, it must be in the same directory.", filename);
+    ERROR_LOG("Failed to open CHD '{}': Failed to find parent CHD, it must be in the same directory.", filename);
     Error::SetString(error, "Failed to find parent CHD, it must be in the same directory.");
     return nullptr;
   }
@@ -224,7 +224,7 @@ chd_file* CDImageCHD::OpenCHD(std::string_view filename, FileSystem::ManagedCFil
   err = chd_open_file(fp.get(), CHD_OPEN_READ | CHD_OPEN_TRANSFER_FILE, parent_chd, &chd);
   if (err != CHDERR_NONE)
   {
-    Log_ErrorFmt("Failed to open CHD '{}': {}", filename, chd_error_string(err));
+    ERROR_LOG("Failed to open CHD '{}': {}", filename, chd_error_string(err));
     Error::SetString(error, chd_error_string(err));
     return nullptr;
   }
@@ -239,7 +239,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
   auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite);
   if (!fp)
   {
-    Log_ErrorFmt("Failed to open CHD '{}': errno {}", filename, errno);
+    ERROR_LOG("Failed to open CHD '{}': errno {}", filename, errno);
     if (error)
       error->SetErrno(errno);
 
@@ -254,7 +254,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
   m_hunk_size = header->hunkbytes;
   if ((m_hunk_size % CHD_CD_SECTOR_DATA_SIZE) != 0)
   {
-    Log_ErrorFmt("Hunk size ({}) is not a multiple of {}", m_hunk_size, CHD_CD_SECTOR_DATA_SIZE);
+    ERROR_LOG("Hunk size ({}) is not a multiple of {}", m_hunk_size, CHD_CD_SECTOR_DATA_SIZE);
     Error::SetString(error, fmt::format("Hunk size ({}) is not a multiple of {}", m_hunk_size,
                                         static_cast<u32>(CHD_CD_SECTOR_DATA_SIZE)));
     return false;
@@ -286,7 +286,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
       if (std::sscanf(metadata_str, CDROM_TRACK_METADATA2_FORMAT, &track_num, type_str, subtype_str, &frames,
                       &pregap_frames, pgtype_str, pgsub_str, &postgap_frames) != 8)
       {
-        Log_ErrorFmt("Invalid track v2 metadata: '{}'", metadata_str);
+        ERROR_LOG("Invalid track v2 metadata: '{}'", metadata_str);
         Error::SetString(error, fmt::format("Invalid track v2 metadata: '{}'", metadata_str));
         return false;
       }
@@ -304,7 +304,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
 
       if (std::sscanf(metadata_str, CDROM_TRACK_METADATA_FORMAT, &track_num, type_str, subtype_str, &frames) != 4)
       {
-        Log_ErrorFmt("Invalid track metadata: '{}'", metadata_str);
+        ERROR_LOG("Invalid track metadata: '{}'", metadata_str);
         Error::SetString(error, fmt::format("Invalid track v2 metadata: '{}'", metadata_str));
         return false;
       }
@@ -319,7 +319,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
 
     if (track_num != (num_tracks + 1))
     {
-      Log_ErrorFmt("Incorrect track number at index {}, expected {} got {}", num_tracks, (num_tracks + 1), track_num);
+      ERROR_LOG("Incorrect track number at index {}, expected {} got {}", num_tracks, (num_tracks + 1), track_num);
       Error::SetString(error, fmt::format("Incorrect track number at index {}, expected {} got {}", num_tracks,
                                           (num_tracks + 1), track_num));
       return false;
@@ -328,7 +328,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
     std::optional<TrackMode> mode = ParseTrackModeString(type_str);
     if (!mode.has_value())
     {
-      Log_ErrorFmt("Invalid track mode: '{}'", type_str);
+      ERROR_LOG("Invalid track mode: '{}'", type_str);
       Error::SetString(error, fmt::format("Invalid track mode: '{}'", type_str));
       return false;
     }
@@ -360,7 +360,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
       {
         if (pregap_frames > frames)
         {
-          Log_ErrorFmt("Pregap length {} exceeds track length {}", pregap_frames, frames);
+          ERROR_LOG("Pregap length {} exceeds track length {}", pregap_frames, frames);
           Error::SetString(error, fmt::format("Pregap length {} exceeds track length {}", pregap_frames, frames));
           return false;
         }
@@ -407,7 +407,7 @@ bool CDImageCHD::Open(const char* filename, Error* error)
 
   if (m_tracks.empty())
   {
-    Log_ErrorFmt("File '{}' contains no tracks", filename);
+    ERROR_LOG("File '{}' contains no tracks", filename);
     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
     return false;
   }
@@ -561,7 +561,7 @@ ALWAYS_INLINE_RELEASE bool CDImageCHD::UpdateHunkBuffer(const Index& index, LBA
   const chd_error err = chd_read(m_chd, hunk_index, m_hunk_buffer.data());
   if (err != CHDERR_NONE)
   {
-    Log_ErrorFmt("chd_read({}) failed: %s", hunk_index, chd_error_string(err));
+    ERROR_LOG("chd_read({}) failed: %s", hunk_index, chd_error_string(err));
 
     // data might have been partially written
     m_current_hunk_index = static_cast<u32>(-1);
diff --git a/src/util/cd_image_cue.cpp b/src/util/cd_image_cue.cpp
index ff74162e9..17629c9c1 100644
--- a/src/util/cd_image_cue.cpp
+++ b/src/util/cd_image_cue.cpp
@@ -110,15 +110,15 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error)
         track_fp = FileSystem::OpenCFile(alternative_filename.c_str(), "rb");
         if (track_fp)
         {
-          Log_WarningFmt("Your cue sheet references an invalid file '{}', but this was found at '{}' instead.",
-                         track_filename, alternative_filename);
+          WARNING_LOG("Your cue sheet references an invalid file '{}', but this was found at '{}' instead.",
+                      track_filename, alternative_filename);
         }
       }
 
       if (!track_fp)
       {
-        Log_ErrorFmt("Failed to open track filename '{}' (from '{}' and '{}'): {}", track_full_filename, track_filename,
-                     filename, track_error.GetDescription());
+        ERROR_LOG("Failed to open track filename '{}' (from '{}' and '{}'): {}", track_full_filename, track_filename,
+                  filename, track_error.GetDescription());
         Error::SetStringFmt(error, "Failed to open track filename '{}' (from '{}' and '{}'): {}", track_full_filename,
                             track_filename, Path::GetFileName(filename), track_error.GetDescription());
         return false;
@@ -149,8 +149,8 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error)
       file_size /= track_sector_size;
       if (track_start >= file_size)
       {
-        Log_ErrorFmt("Failed to open track {} in '{}': track start is out of range ({} vs {})", track_num, filename,
-                     track_start, file_size);
+        ERROR_LOG("Failed to open track {} in '{}': track start is out of range ({} vs {})", track_num, filename,
+                  track_start, file_size);
         Error::SetStringFmt(error, "Failed to open track {} in '{}': track start is out of range ({} vs {}))",
                             track_num, Path::GetFileName(filename), track_start, file_size);
         return false;
@@ -285,7 +285,7 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Error* error)
 
   if (m_tracks.empty())
   {
-    Log_ErrorFmt("File '{}' contains no tracks", filename);
+    ERROR_LOG("File '{}' contains no tracks", filename);
     Error::SetStringFmt(error, "File '{}' contains no tracks", Path::GetFileName(filename));
     return false;
   }
diff --git a/src/util/cd_image_device.cpp b/src/util/cd_image_device.cpp
index 507cfe193..5ef2bd1b1 100644
--- a/src/util/cd_image_device.cpp
+++ b/src/util/cd_image_device.cpp
@@ -101,7 +101,7 @@ enum class SCSIReadMode : u8
   const u32 expected_size = SCSIReadCommandOutputSize(mode);
   if (buffer.size() != expected_size)
   {
-    Log_ErrorFmt("SCSI returned {} bytes, expected {}", buffer.size(), expected_size);
+    ERROR_LOG("SCSI returned {} bytes, expected {}", buffer.size(), expected_size);
     return false;
   }
 
@@ -115,14 +115,14 @@ enum class SCSIReadMode : u8
     CDImage::DeinterleaveSubcode(buffer.data() + CDImage::RAW_SECTOR_SIZE, deinterleaved_subcode);
     std::memcpy(&subq, &deinterleaved_subcode[CDImage::SUBCHANNEL_BYTES_PER_FRAME], sizeof(subq));
 
-    Log_DevFmt("SCSI full subcode read returned [{}] for {:02d}:{:02d}:{:02d}",
-               StringUtil::EncodeHex(subq.data.data(), static_cast<int>(subq.data.size())), expected_pos.minute,
-               expected_pos.second, expected_pos.frame);
+    DEV_LOG("SCSI full subcode read returned [{}] for {:02d}:{:02d}:{:02d}",
+            StringUtil::EncodeHex(subq.data.data(), static_cast<int>(subq.data.size())), expected_pos.minute,
+            expected_pos.second, expected_pos.frame);
 
     if (!subq.IsCRCValid())
     {
-      Log_WarningFmt("SCSI full subcode read returned invalid SubQ CRC (got {:02X} expected {:02X})", subq.crc,
-                     CDImage::SubChannelQ::ComputeCRC(subq.data));
+      WARNING_LOG("SCSI full subcode read returned invalid SubQ CRC (got {:02X} expected {:02X})", subq.crc,
+                  CDImage::SubChannelQ::ComputeCRC(subq.data));
       return false;
     }
 
@@ -130,7 +130,7 @@ enum class SCSIReadMode : u8
       CDImage::Position::FromBCD(subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd);
     if (expected_pos != got_pos)
     {
-      Log_WarningFmt(
+      WARNING_LOG(
         "SCSI full subcode read returned invalid MSF (got {:02x}:{:02x}:{:02x}, expected {:02d}:{:02d}:{:02d})",
         subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd, expected_pos.minute,
         expected_pos.second, expected_pos.frame);
@@ -143,14 +143,14 @@ enum class SCSIReadMode : u8
   {
     CDImage::SubChannelQ subq;
     std::memcpy(&subq, buffer.data() + CDImage::RAW_SECTOR_SIZE, sizeof(subq));
-    Log_DevFmt("SCSI subq read returned [{}] for {:02d}:{:02d}:{:02d}",
-               StringUtil::EncodeHex(subq.data.data(), static_cast<int>(subq.data.size())), expected_pos.minute,
-               expected_pos.second, expected_pos.frame);
+    DEV_LOG("SCSI subq read returned [{}] for {:02d}:{:02d}:{:02d}",
+            StringUtil::EncodeHex(subq.data.data(), static_cast<int>(subq.data.size())), expected_pos.minute,
+            expected_pos.second, expected_pos.frame);
 
     if (!subq.IsCRCValid())
     {
-      Log_WarningFmt("SCSI subq read returned invalid SubQ CRC (got {:02X} expected {:02X})", subq.crc,
-                     CDImage::SubChannelQ::ComputeCRC(subq.data));
+      WARNING_LOG("SCSI subq read returned invalid SubQ CRC (got {:02X} expected {:02X})", subq.crc,
+                  CDImage::SubChannelQ::ComputeCRC(subq.data));
       return false;
     }
 
@@ -158,9 +158,9 @@ enum class SCSIReadMode : u8
       CDImage::Position::FromBCD(subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd);
     if (expected_pos != got_pos)
     {
-      Log_WarningFmt("SCSI subq read returned invalid MSF (got {:02x}:{:02x}:{:02x}, expected {:02d}:{:02d}:{:02d})",
-                     subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd, expected_pos.minute,
-                     expected_pos.second, expected_pos.frame);
+      WARNING_LOG("SCSI subq read returned invalid MSF (got {:02x}:{:02x}:{:02x}, expected {:02d}:{:02d}:{:02d})",
+                  subq.absolute_minute_bcd, subq.absolute_second_bcd, subq.absolute_frame_bcd, expected_pos.minute,
+                  expected_pos.second, expected_pos.frame);
       return false;
     }
 
@@ -257,12 +257,12 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error)
     m_hDevice = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, NULL);
     if (m_hDevice != INVALID_HANDLE_VALUE)
     {
-      Log_WarningFmt("Could not open '{}' as read/write, can't use SPTD", filename);
+      WARNING_LOG("Could not open '{}' as read/write, can't use SPTD", filename);
       try_sptd = false;
     }
     else
     {
-      Log_ErrorFmt("CreateFile('{}') failed: %08X", filename, GetLastError());
+      ERROR_LOG("CreateFile('{}') failed: %08X", filename, GetLastError());
       if (error)
         error->SetWin32(GetLastError());
 
@@ -275,7 +275,7 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error)
   static constexpr u32 READ_SPEED_KBS = (DATA_SECTOR_SIZE * FRAMES_PER_SECOND * READ_SPEED_MULTIPLIER) / 1024;
   CDROM_SET_SPEED set_speed = {CdromSetSpeed, READ_SPEED_KBS, 0, CdromDefaultRotation};
   if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_SET_SPEED, &set_speed, sizeof(set_speed), nullptr, 0, nullptr, nullptr))
-    Log_WarningFmt("DeviceIoControl(IOCTL_CDROM_SET_SPEED) failed: {:08X}", GetLastError());
+    WARNING_LOG("DeviceIoControl(IOCTL_CDROM_SET_SPEED) failed: {:08X}", GetLastError());
 
   CDROM_READ_TOC_EX read_toc_ex = {};
   read_toc_ex.Format = CDROM_READ_TOC_EX_FORMAT_TOC;
@@ -290,7 +290,7 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error)
                        &bytes_returned, nullptr) ||
       toc.LastTrack < toc.FirstTrack)
   {
-    Log_ErrorFmt("DeviceIoCtl(IOCTL_CDROM_READ_TOC_EX) failed: {:08X}", GetLastError());
+    ERROR_LOG("DeviceIoCtl(IOCTL_CDROM_READ_TOC_EX) failed: {:08X}", GetLastError());
     if (error)
       error->SetWin32(GetLastError());
 
@@ -299,7 +299,7 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error)
 
   DWORD last_track_address = 0;
   LBA disc_lba = 0;
-  Log_DevFmt("FirstTrack={}, LastTrack={}", toc.FirstTrack, toc.LastTrack);
+  DEV_LOG("FirstTrack={}, LastTrack={}", toc.FirstTrack, toc.LastTrack);
 
   const u32 num_tracks_to_check = (toc.LastTrack - toc.FirstTrack) + 1 + 1;
   for (u32 track_index = 0; track_index < num_tracks_to_check; track_index++)
@@ -307,14 +307,14 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error)
     const TRACK_DATA& td = toc.TrackData[track_index];
     const u8 track_num = td.TrackNumber;
     const DWORD track_address = BEToU32(td.Address);
-    Log_DevFmt("  [{}]: Num={:02X}, Address={}", track_index, track_num, track_address);
+    DEV_LOG("  [{}]: Num={:02X}, Address={}", track_index, track_num, track_address);
 
     // fill in the previous track's length
     if (!m_tracks.empty())
     {
       if (track_num < m_tracks.back().track_number)
       {
-        Log_ErrorFmt("Invalid TOC, track {} less than {}", track_num, m_tracks.back().track_number);
+        ERROR_LOG("Invalid TOC, track {} less than {}", track_num, m_tracks.back().track_number);
         return false;
       }
 
@@ -382,29 +382,29 @@ bool CDImageDeviceWin32::Open(const char* filename, Error* error)
 
   if (m_tracks.empty())
   {
-    Log_ErrorFmt("File '{}' contains no tracks", filename);
+    ERROR_LOG("File '{}' contains no tracks", filename);
     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
     return false;
   }
 
   m_lba_count = disc_lba;
 
-  Log_DevFmt("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
+  DEV_LOG("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
   for (u32 i = 0; i < m_tracks.size(); i++)
   {
-    Log_DevFmt(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
-               m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
+    DEV_LOG(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
+            m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
   }
   for (u32 i = 0; i < m_indices.size(); i++)
   {
-    Log_DevFmt(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
-               m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc,
-               m_indices[i].length, m_indices[i].file_sector_size, m_indices[i].file_offset);
+    DEV_LOG(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
+            m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc, m_indices[i].length,
+            m_indices[i].file_sector_size, m_indices[i].file_offset);
   }
 
   if (!DetermineReadMode(try_sptd))
   {
-    Log_ErrorPrint("Could not determine read mode");
+    ERROR_LOG("Could not determine read mode");
     Error::SetString(error, "Could not determine read mode");
     return false;
   }
@@ -479,18 +479,18 @@ std::optional<u32> CDImageDeviceWin32::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], st
   if (!DeviceIoControl(m_hDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd, sizeof(sptd), &sptd, sizeof(sptd),
                        &bytes_returned, nullptr))
   {
-    Log_ErrorFmt("DeviceIoControl() for SCSI 0x{:02X} failed: {}", cmd[0], GetLastError());
+    ERROR_LOG("DeviceIoControl() for SCSI 0x{:02X} failed: {}", cmd[0], GetLastError());
     return std::nullopt;
   }
 
   if (sptd.cmd.ScsiStatus != 0)
   {
-    Log_ErrorFmt("SCSI command 0x{:02X} failed: {}", cmd[0], sptd.cmd.ScsiStatus);
+    ERROR_LOG("SCSI command 0x{:02X} failed: {}", cmd[0], sptd.cmd.ScsiStatus);
     return std::nullopt;
   }
 
   if (sptd.cmd.DataTransferLength != out_buffer.size())
-    Log_WarningFmt("Only read {} of {} bytes", sptd.cmd.DataTransferLength, out_buffer.size());
+    WARNING_LOG("Only read {} of {} bytes", sptd.cmd.DataTransferLength, out_buffer.size());
 
   return sptd.cmd.DataTransferLength;
 }
@@ -524,12 +524,12 @@ bool CDImageDeviceWin32::DoRawRead(LBA lba)
   if (!DeviceIoControl(m_hDevice, IOCTL_CDROM_RAW_READ, &rri, sizeof(rri), m_buffer.data(),
                        static_cast<DWORD>(m_buffer.size()), &bytes_returned, nullptr))
   {
-    Log_ErrorFmt("DeviceIoControl(IOCTL_CDROM_RAW_READ) for LBA {} failed: {:08X}", lba, GetLastError());
+    ERROR_LOG("DeviceIoControl(IOCTL_CDROM_RAW_READ) for LBA {} failed: {:08X}", lba, GetLastError());
     return false;
   }
 
   if (bytes_returned != expected_size)
-    Log_WarningFmt("Only read {} of {} bytes", bytes_returned, expected_size);
+    WARNING_LOG("Only read {} of {} bytes", bytes_returned, expected_size);
 
   return true;
 }
@@ -542,7 +542,7 @@ bool CDImageDeviceWin32::ReadSectorToBuffer(LBA lba)
     const u32 expected_size = SCSIReadCommandOutputSize(m_scsi_read_mode);
     if (size.value_or(0) != expected_size)
     {
-      Log_ErrorFmt("Read of LBA {} failed: only got {} of {} bytes", lba, size.value(), expected_size);
+      ERROR_LOG("Read of LBA {} failed: only got {} of {} bytes", lba, size.value(), expected_size);
       return false;
     }
   }
@@ -568,26 +568,26 @@ bool CDImageDeviceWin32::DetermineReadMode(bool try_sptd)
   {
     std::optional<u32> transfer_size;
 
-    Log_DevPrint("Trying SCSI read with full subcode...");
+    DEV_LOG("Trying SCSI read with full subcode...");
     if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Full)).has_value())
     {
       if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Full,
                              track_1_subq_lba))
       {
-        Log_VerbosePrint("Using SCSI reads with subcode");
+        VERBOSE_LOG("Using SCSI reads with subcode");
         m_scsi_read_mode = SCSIReadMode::Full;
         m_has_valid_subcode = true;
         return true;
       }
     }
 
-    Log_WarningPrint("Full subcode failed, trying SCSI read with only subq...");
+    WARNING_LOG("Full subcode failed, trying SCSI read with only subq...");
     if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::SubQOnly)).has_value())
     {
       if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::SubQOnly,
                              track_1_subq_lba))
       {
-        Log_VerbosePrint("Using SCSI reads with subq only");
+        VERBOSE_LOG("Using SCSI reads with subq only");
         m_scsi_read_mode = SCSIReadMode::SubQOnly;
         m_has_valid_subcode = true;
         return true;
@@ -595,13 +595,13 @@ bool CDImageDeviceWin32::DetermineReadMode(bool try_sptd)
     }
 
     // As a last ditch effort, try SCSI without subcode.
-    Log_WarningPrint("Subq only failed failed, trying SCSI without subcode...");
+    WARNING_LOG("Subq only failed failed, trying SCSI without subcode...");
     if ((transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Raw)).has_value())
     {
       if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Raw,
                              track_1_subq_lba))
       {
-        Log_WarningPrint("Using SCSI raw reads, libcrypt games will not run correctly");
+        WARNING_LOG("Using SCSI raw reads, libcrypt games will not run correctly");
         m_scsi_read_mode = SCSIReadMode::Raw;
         m_has_valid_subcode = false;
         return true;
@@ -609,26 +609,26 @@ bool CDImageDeviceWin32::DetermineReadMode(bool try_sptd)
     }
   }
 
-  Log_WarningPrint("SCSI reads failed, trying raw read...");
+  WARNING_LOG("SCSI reads failed, trying raw read...");
   if (DoRawRead(track_1_lba))
   {
     // verify subcode
     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), SCSIReadCommandOutputSize(SCSIReadMode::Full)),
                            SCSIReadMode::Full, track_1_subq_lba))
     {
-      Log_VerbosePrint("Using raw reads with full subcode");
+      VERBOSE_LOG("Using raw reads with full subcode");
       m_scsi_read_mode = SCSIReadMode::None;
       m_has_valid_subcode = true;
       return true;
     }
 
-    Log_WarningPrint("Using raw reads without subcode, libcrypt games will not run correctly");
+    WARNING_LOG("Using raw reads without subcode, libcrypt games will not run correctly");
     m_scsi_read_mode = SCSIReadMode::None;
     m_has_valid_subcode = false;
     return true;
   }
 
-  Log_ErrorPrint("No read modes were successful, cannot use device.");
+  ERROR_LOG("No read modes were successful, cannot use device.");
   return false;
 }
 
@@ -751,7 +751,7 @@ bool CDImageDeviceLinux::Open(const char* filename, Error* error)
   // Set it to 4x speed. A good balance between readahead and spinning up way too high.
   const int read_speed = 4;
   if (!DoSetSpeed(read_speed) && ioctl(m_fd, CDROM_SELECT_SPEED, &read_speed) != 0)
-    Log_WarningFmt("ioctl(CDROM_SELECT_SPEED) failed: {}", errno);
+    WARNING_LOG("ioctl(CDROM_SELECT_SPEED) failed: {}", errno);
 
   // Read ToC
   cdrom_tochdr toc_hdr = {};
@@ -761,7 +761,7 @@ bool CDImageDeviceLinux::Open(const char* filename, Error* error)
     return false;
   }
 
-  Log_DevFmt("FirstTrack={}, LastTrack={}", toc_hdr.cdth_trk0, toc_hdr.cdth_trk1);
+  DEV_LOG("FirstTrack={}, LastTrack={}", toc_hdr.cdth_trk0, toc_hdr.cdth_trk1);
   if (toc_hdr.cdth_trk1 < toc_hdr.cdth_trk0)
   {
     Error::SetStringFmt(error, "Last track {} is before first track {}", toc_hdr.cdth_trk1, toc_hdr.cdth_trk0);
@@ -785,14 +785,14 @@ bool CDImageDeviceLinux::Open(const char* filename, Error* error)
       return false;
     }
 
-    Log_DevFmt("  [{}]: Num={}, LBA={}", track_index, track_num, toc_ent.cdte_addr.lba);
+    DEV_LOG("  [{}]: Num={}, LBA={}", track_index, track_num, toc_ent.cdte_addr.lba);
 
     // fill in the previous track's length
     if (!m_tracks.empty())
     {
       if (track_num < m_tracks.back().track_number)
       {
-        Log_ErrorFmt("Invalid TOC, track {} less than {}", track_num, m_tracks.back().track_number);
+        ERROR_LOG("Invalid TOC, track {} less than {}", track_num, m_tracks.back().track_number);
         return false;
       }
 
@@ -855,7 +855,7 @@ bool CDImageDeviceLinux::Open(const char* filename, Error* error)
 
   if (m_tracks.empty())
   {
-    Log_ErrorFmt("File '{}' contains no tracks", filename);
+    ERROR_LOG("File '{}' contains no tracks", filename);
     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
     return false;
   }
@@ -884,17 +884,17 @@ bool CDImageDeviceLinux::Open(const char* filename, Error* error)
 
   m_lba_count = disc_lba;
 
-  Log_DevFmt("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
+  DEV_LOG("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
   for (u32 i = 0; i < m_tracks.size(); i++)
   {
-    Log_DevFmt(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
-               m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
+    DEV_LOG(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
+            m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
   }
   for (u32 i = 0; i < m_indices.size(); i++)
   {
-    Log_DevFmt(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
-               m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc,
-               m_indices[i].length, m_indices[i].file_sector_size, m_indices[i].file_offset);
+    DEV_LOG(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
+            m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc, m_indices[i].length,
+            m_indices[i].file_sector_size, m_indices[i].file_offset);
   }
 
   if (!DetermineReadMode(error))
@@ -964,12 +964,12 @@ std::optional<u32> CDImageDeviceLinux::DoSCSICommand(u8 cmd[SCSI_CMD_LENGTH], st
 
   if (ioctl(m_fd, SG_IO, &hdr) != 0)
   {
-    Log_ErrorFmt("ioctl(SG_IO) for command {:02X} failed: {}", cmd[0], errno);
+    ERROR_LOG("ioctl(SG_IO) for command {:02X} failed: {}", cmd[0], errno);
     return std::nullopt;
   }
   else if (hdr.status != 0)
   {
-    Log_ErrorFmt("SCSI command {:02X} failed with status {}", cmd[0], hdr.status);
+    ERROR_LOG("SCSI command {:02X} failed with status {}", cmd[0], hdr.status);
     return std::nullopt;
   }
 
@@ -998,7 +998,7 @@ bool CDImageDeviceLinux::DoRawRead(LBA lba)
   std::memcpy(m_buffer.data(), &msf, sizeof(msf));
   if (ioctl(m_fd, CDROMREADRAW, m_buffer.data()) != 0)
   {
-    Log_ErrorFmt("CDROMREADRAW for LBA {} (MSF {}:{}:{}) failed: {}", lba, msf.minute, msf.second, msf.frame, errno);
+    ERROR_LOG("CDROMREADRAW for LBA {} (MSF {}:{}:{}) failed: {}", lba, msf.minute, msf.second, msf.frame, errno);
     return false;
   }
 
@@ -1013,7 +1013,7 @@ bool CDImageDeviceLinux::ReadSectorToBuffer(LBA lba)
     const u32 expected_size = SCSIReadCommandOutputSize(m_scsi_read_mode);
     if (size.value_or(0) != expected_size)
     {
-      Log_ErrorFmt("Read of LBA {} failed: only got {} of {} bytes", lba, size.value(), expected_size);
+      ERROR_LOG("Read of LBA {} failed: only got {} of {} bytes", lba, size.value(), expected_size);
       return false;
     }
   }
@@ -1034,50 +1034,50 @@ bool CDImageDeviceLinux::DetermineReadMode(Error* error)
   const bool check_subcode = ShouldTryReadingSubcode();
   std::optional<u32> transfer_size;
 
-  Log_DevPrint("Trying SCSI read with full subcode...");
+  DEV_LOG("Trying SCSI read with full subcode...");
   if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Full)).has_value())
   {
     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Full, track_1_subq_lba))
     {
-      Log_VerbosePrint("Using SCSI reads with subcode");
+      VERBOSE_LOG("Using SCSI reads with subcode");
       m_scsi_read_mode = SCSIReadMode::Full;
       return true;
     }
   }
 
-  Log_WarningPrint("Full subcode failed, trying SCSI read with only subq...");
+  WARNING_LOG("Full subcode failed, trying SCSI read with only subq...");
   if (check_subcode && (transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::SubQOnly)).has_value())
   {
     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::SubQOnly,
                            track_1_subq_lba))
     {
-      Log_VerbosePrint("Using SCSI reads with subq only");
+      VERBOSE_LOG("Using SCSI reads with subq only");
       m_scsi_read_mode = SCSIReadMode::SubQOnly;
       return true;
     }
   }
 
-  Log_WarningPrint("SCSI subcode reads failed, trying CDROMREADRAW...");
+  WARNING_LOG("SCSI subcode reads failed, trying CDROMREADRAW...");
   if (DoRawRead(track_1_lba))
   {
-    Log_WarningPrint("Using CDROMREADRAW, libcrypt games will not run correctly");
+    WARNING_LOG("Using CDROMREADRAW, libcrypt games will not run correctly");
     m_scsi_read_mode = SCSIReadMode::None;
     return true;
   }
 
   // As a last ditch effort, try SCSI without subcode.
-  Log_WarningPrint("CDROMREADRAW failed, trying SCSI without subcode...");
+  WARNING_LOG("CDROMREADRAW failed, trying SCSI without subcode...");
   if ((transfer_size = DoSCSIRead(track_1_lba, SCSIReadMode::Raw)).has_value())
   {
     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), transfer_size.value()), SCSIReadMode::Raw, track_1_subq_lba))
     {
-      Log_WarningPrint("Using SCSI raw reads, libcrypt games will not run correctly");
+      WARNING_LOG("Using SCSI raw reads, libcrypt games will not run correctly");
       m_scsi_read_mode = SCSIReadMode::Raw;
       return true;
     }
   }
 
-  Log_ErrorPrint("No read modes were successful, cannot use device.");
+  ERROR_LOG("No read modes were successful, cannot use device.");
   return false;
 }
 
@@ -1200,7 +1200,7 @@ static io_service_t GetDeviceMediaService(std::string_view devname)
   kern_return_t ret = IOServiceGetMatchingServices(0, IOBSDNameMatching(0, 0, rdevname.c_str()), &iterator);
   if (ret != KERN_SUCCESS)
   {
-    Log_ErrorFmt("IOServiceGetMatchingService() returned {}", ret);
+    ERROR_LOG("IOServiceGetMatchingService() returned {}", ret);
     return 0;
   }
 
@@ -1259,7 +1259,7 @@ bool CDImageDeviceMacOS::Open(const char* filename, Error* error)
   }
 
   const u32 desc_count = CDTOCGetDescriptorCount(toc.get());
-  Log_DevFmt("sessionFirst={}, sessionLast={}, count={}", toc->sessionFirst, toc->sessionLast, desc_count);
+  DEV_LOG("sessionFirst={}, sessionLast={}, count={}", toc->sessionFirst, toc->sessionLast, desc_count);
   if (toc->sessionLast < toc->sessionFirst)
   {
     Error::SetStringFmt(error, "Last track {} is before first track {}", toc->sessionLast, toc->sessionFirst);
@@ -1273,8 +1273,8 @@ bool CDImageDeviceMacOS::Open(const char* filename, Error* error)
   for (u32 i = 0; i < desc_count; i++)
   {
     const CDTOCDescriptor& desc = toc->descriptors[i];
-    Log_DevFmt("  [{}]: Num={}, Point=0x{:02X} ADR={} MSF={}:{}:{}", i, desc.tno, desc.point, desc.adr, desc.p.minute,
-               desc.p.second, desc.p.frame);
+    DEV_LOG("  [{}]: Num={}, Point=0x{:02X} ADR={} MSF={}:{}:{}", i, desc.tno, desc.point, desc.adr, desc.p.minute,
+            desc.p.second, desc.p.frame);
 
     // Why does MacOS use 0xA2 instead of 0xAA for leadout??
     if (desc.point == 0xA2)
@@ -1374,7 +1374,7 @@ bool CDImageDeviceMacOS::Open(const char* filename, Error* error)
 
   if (m_tracks.empty())
   {
-    Log_ErrorFmt("File '{}' contains no tracks", filename);
+    ERROR_LOG("File '{}' contains no tracks", filename);
     Error::SetString(error, fmt::format("File '{}' contains no tracks", filename));
     return false;
   }
@@ -1392,17 +1392,17 @@ bool CDImageDeviceMacOS::Open(const char* filename, Error* error)
 
   m_lba_count = disc_lba;
 
-  Log_DevFmt("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
+  DEV_LOG("{} tracks, {} indices, {} lbas", m_tracks.size(), m_indices.size(), m_lba_count);
   for (u32 i = 0; i < m_tracks.size(); i++)
   {
-    Log_DevFmt(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
-               m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
+    DEV_LOG(" Track {}: Start {}, length {}, mode {}, control 0x{:02X}", m_tracks[i].track_number,
+            m_tracks[i].start_lba, m_tracks[i].length, static_cast<u8>(m_tracks[i].mode), m_tracks[i].control.bits);
   }
   for (u32 i = 0; i < m_indices.size(); i++)
   {
-    Log_DevFmt(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
-               m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc,
-               m_indices[i].length, m_indices[i].file_sector_size, m_indices[i].file_offset);
+    DEV_LOG(" Index {}: Track {}, Index [], Start {}, length {}, file sector size {}, file offset {}", i,
+            m_indices[i].track_number, m_indices[i].index_number, m_indices[i].start_lba_on_disc, m_indices[i].length,
+            m_indices[i].file_sector_size, m_indices[i].file_offset);
   }
 
   if (!DetermineReadMode(error))
@@ -1462,7 +1462,7 @@ bool CDImageDeviceMacOS::DoSetSpeed(u32 speed_multiplier)
   const u16 speed = static_cast<u16>((FRAMES_PER_SECOND * RAW_SECTOR_SIZE * speed_multiplier) / 1024);
   if (ioctl(m_fd, DKIOCCDSETSPEED, &speed) != 0)
   {
-    Log_ErrorFmt("DKIOCCDSETSPEED for speed {} failed: {}", speed, errno);
+    ERROR_LOG("DKIOCCDSETSPEED for speed {} failed: {}", speed, errno);
     return false;
   }
 
@@ -1473,7 +1473,7 @@ bool CDImageDeviceMacOS::ReadSectorToBuffer(LBA lba)
 {
   if (lba < RAW_READ_OFFSET)
   {
-    Log_ErrorFmt("Out of bounds LBA {}", lba);
+    ERROR_LOG("Out of bounds LBA {}", lba);
     return false;
   }
 
@@ -1493,7 +1493,7 @@ bool CDImageDeviceMacOS::ReadSectorToBuffer(LBA lba)
   if (ioctl(m_fd, DKIOCCDREAD, &desc) != 0)
   {
     const Position msf = Position::FromLBA(lba);
-    Log_ErrorFmt("DKIOCCDREAD for LBA {} (MSF {}:{}:{}) failed: {}", lba, msf.minute, msf.second, msf.frame, errno);
+    ERROR_LOG("DKIOCCDREAD for LBA {} (MSF {}:{}:{}) failed: {}", lba, msf.minute, msf.second, msf.frame, errno);
     return false;
   }
 
@@ -1506,7 +1506,7 @@ bool CDImageDeviceMacOS::DetermineReadMode(Error* error)
   const LBA track_1_lba = static_cast<LBA>(m_indices[m_tracks[0].first_index].file_offset);
   const bool check_subcode = ShouldTryReadingSubcode();
 
-  Log_DevPrint("Trying read with full subcode...");
+  DEV_LOG("Trying read with full subcode...");
   m_read_mode = SCSIReadMode::Full;
   m_current_lba = m_lba_count;
   if (check_subcode && ReadSectorToBuffer(track_1_lba))
@@ -1514,7 +1514,7 @@ bool CDImageDeviceMacOS::DetermineReadMode(Error* error)
     if (VerifySCSIReadData(std::span<u8>(m_buffer.data(), RAW_SECTOR_SIZE + ALL_SUBCODE_SIZE), SCSIReadMode::Full,
                            track_1_lba))
     {
-      Log_VerbosePrint("Using reads with subcode");
+      VERBOSE_LOG("Using reads with subcode");
       return true;
     }
   }
@@ -1535,16 +1535,16 @@ bool CDImageDeviceMacOS::DetermineReadMode(Error* error)
   }
 #endif
 
-  Log_WarningPrint("SCSI reads failed, trying without subcode...");
+  WARNING_LOG("SCSI reads failed, trying without subcode...");
   m_read_mode = SCSIReadMode::Raw;
   m_current_lba = m_lba_count;
   if (ReadSectorToBuffer(track_1_lba))
   {
-    Log_WarningPrint("Using non-subcode reads, libcrypt games will not run correctly");
+    WARNING_LOG("Using non-subcode reads, libcrypt games will not run correctly");
     return true;
   }
 
-  Log_ErrorPrint("No read modes were successful, cannot use device.");
+  ERROR_LOG("No read modes were successful, cannot use device.");
   return false;
 }
 
diff --git a/src/util/cd_image_ecm.cpp b/src/util/cd_image_ecm.cpp
index 65e43809b..48950f1e9 100644
--- a/src/util/cd_image_ecm.cpp
+++ b/src/util/cd_image_ecm.cpp
@@ -246,7 +246,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
   if (FileSystem::FSeek64(m_fp, 0, SEEK_END) != 0 || (file_size = FileSystem::FTell64(m_fp)) <= 0 ||
       FileSystem::FSeek64(m_fp, 0, SEEK_SET) != 0)
   {
-    Log_ErrorFmt("Get file size failed: errno {}", errno);
+    ERROR_LOG("Get file size failed: errno {}", errno);
     if (error)
       error->SetErrno(errno);
 
@@ -257,7 +257,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
   if (std::fread(header, sizeof(header), 1, m_fp) != 1 || header[0] != 'E' || header[1] != 'C' || header[2] != 'M' ||
       header[3] != 0)
   {
-    Log_ErrorPrint("Failed to read/invalid header");
+    ERROR_LOG("Failed to read/invalid header");
     Error::SetStringView(error, "Failed to read/invalid header");
     return false;
   }
@@ -271,7 +271,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
     int bits = std::fgetc(m_fp);
     if (bits == EOF)
     {
-      Log_ErrorFmt("Unexpected EOF after {} chunks", m_data_map.size());
+      ERROR_LOG("Unexpected EOF after {} chunks", m_data_map.size());
       Error::SetStringFmt(error, "Unexpected EOF after {} chunks", m_data_map.size());
       return false;
     }
@@ -285,7 +285,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
       bits = std::fgetc(m_fp);
       if (bits == EOF)
       {
-        Log_ErrorFmt("Unexpected EOF after {} chunks", m_data_map.size());
+        ERROR_LOG("Unexpected EOF after {} chunks", m_data_map.size());
         Error::SetStringFmt(error, "Unexpected EOF after {} chunks", m_data_map.size());
         return false;
       }
@@ -303,7 +303,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
 
     if (count >= 0x80000000u)
     {
-      Log_ErrorFmt("Corrupted header after {} chunks", m_data_map.size());
+      ERROR_LOG("Corrupted header after {} chunks", m_data_map.size());
       Error::SetStringFmt(error, "Corrupted header after {} chunks", m_data_map.size());
       return false;
     }
@@ -320,7 +320,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
 
         if (static_cast<s64>(file_offset) > file_size)
         {
-          Log_ErrorFmt("Out of file bounds after {} chunks", m_data_map.size());
+          ERROR_LOG("Out of file bounds after {} chunks", m_data_map.size());
           Error::SetStringFmt(error, "Out of file bounds after {} chunks", m_data_map.size());
         }
       }
@@ -337,7 +337,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
 
         if (static_cast<s64>(file_offset) > file_size)
         {
-          Log_ErrorFmt("Out of file bounds after {} chunks", m_data_map.size());
+          ERROR_LOG("Out of file bounds after {} chunks", m_data_map.size());
           Error::SetStringFmt(error, "Out of file bounds after {} chunks", m_data_map.size());
         }
       }
@@ -345,7 +345,7 @@ bool CDImageEcm::Open(const char* filename, Error* error)
 
     if (std::fseek(m_fp, file_offset, SEEK_SET) != 0)
     {
-      Log_ErrorFmt("Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size());
+      ERROR_LOG("Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size());
       Error::SetStringFmt(error, "Failed to seek to offset {} after {} chunks", file_offset, m_data_map.size());
       return false;
     }
@@ -353,14 +353,14 @@ bool CDImageEcm::Open(const char* filename, Error* error)
 
   if (m_data_map.empty())
   {
-    Log_ErrorFmt("No data in image '{}'", filename);
+    ERROR_LOG("No data in image '{}'", filename);
     Error::SetStringFmt(error, "No data in image '{}'", filename);
     return false;
   }
 
   m_lba_count = disc_offset / RAW_SECTOR_SIZE;
   if ((disc_offset % RAW_SECTOR_SIZE) != 0)
-    Log_WarningFmt("ECM image is misaligned with offset {}", disc_offset);
+    WARNING_LOG("ECM image is misaligned with offset {}", disc_offset);
   if (m_lba_count == 0)
     return false;
 
diff --git a/src/util/cd_image_m3u.cpp b/src/util/cd_image_m3u.cpp
index 151f0a338..326bfbcc5 100644
--- a/src/util/cd_image_m3u.cpp
+++ b/src/util/cd_image_m3u.cpp
@@ -107,11 +107,11 @@ bool CDImageM3u::Open(const char* path, bool apply_patches, Error* error)
     else
       entry.filename = std::move(entry_filename);
 
-    Log_DevFmt("Read path from m3u: '{}'", entry.filename);
+    DEV_LOG("Read path from m3u: '{}'", entry.filename);
     m_entries.push_back(std::move(entry));
   }
 
-  Log_InfoFmt("Loaded {} paths from m3u '{}'", m_entries.size(), path);
+  INFO_LOG("Loaded {} paths from m3u '{}'", m_entries.size(), path);
   return !m_entries.empty() && SwitchSubImage(0, error);
 }
 
@@ -146,7 +146,7 @@ bool CDImageM3u::SwitchSubImage(u32 index, Error* error)
   std::unique_ptr<CDImage> new_image = CDImage::Open(entry.filename.c_str(), m_apply_patches, error);
   if (!new_image)
   {
-    Log_ErrorFmt("Failed to load subimage {} ({})", index, entry.filename);
+    ERROR_LOG("Failed to load subimage {} ({})", index, entry.filename);
     return false;
   }
 
diff --git a/src/util/cd_image_mds.cpp b/src/util/cd_image_mds.cpp
index c05dd9f08..3c9d72454 100644
--- a/src/util/cd_image_mds.cpp
+++ b/src/util/cd_image_mds.cpp
@@ -82,7 +82,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
   std::fclose(mds_fp);
   if (!mds_data_opt.has_value() || mds_data_opt->size() < 0x54)
   {
-    Log_ErrorFmt("Failed to read mds file '{}'", Path::GetFileName(filename));
+    ERROR_LOG("Failed to read mds file '{}'", Path::GetFileName(filename));
     Error::SetStringFmt(error, "Failed to read mds file '{}'", filename);
     return false;
   }
@@ -99,7 +99,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
   static constexpr char expected_signature[] = "MEDIA DESCRIPTOR";
   if (std::memcmp(&mds[0], expected_signature, sizeof(expected_signature) - 1) != 0)
   {
-    Log_ErrorFmt("Incorrect signature in '{}'", Path::GetFileName(filename));
+    ERROR_LOG("Incorrect signature in '{}'", Path::GetFileName(filename));
     Error::SetStringFmt(error, "Incorrect signature in '{}'", Path::GetFileName(filename));
     return false;
   }
@@ -108,7 +108,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
   std::memcpy(&session_offset, &mds[0x50], sizeof(session_offset));
   if ((session_offset + 24) > mds.size())
   {
-    Log_ErrorFmt("Invalid session offset in '{}'", Path::GetFileName(filename));
+    ERROR_LOG("Invalid session offset in '{}'", Path::GetFileName(filename));
     Error::SetStringFmt(error, "Invalid session offset in '{}'", Path::GetFileName(filename));
     return false;
   }
@@ -119,7 +119,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
   std::memcpy(&track_offset, &mds[session_offset + 20], sizeof(track_offset));
   if (track_count > 99 || track_offset >= mds.size())
   {
-    Log_ErrorFmt("Invalid track count/block offset {}/{} in '{}'", track_count, track_offset, Path::GetFileName(filename));
+    ERROR_LOG("Invalid track count/block offset {}/{} in '{}'", track_count, track_offset, Path::GetFileName(filename));
     Error::SetStringFmt(error, "Invalid track count/block offset {}/{} in '{}'", track_count, track_offset,
                         Path::GetFileName(filename));
     return false;
@@ -139,7 +139,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
   {
     if ((track_offset + sizeof(TrackEntry)) > mds.size())
     {
-      Log_ErrorFmt("End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
+      ERROR_LOG("End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
       Error::SetStringFmt(error, "End of file in '{}' at track {}", Path::GetFileName(filename), track_number);
       return false;
     }
@@ -150,7 +150,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
 
     if (PackedBCDToBinary(track.track_number) != track_number)
     {
-      Log_ErrorFmt("Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
+      ERROR_LOG("Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
       Error::SetStringFmt(error, "Unexpected track number 0x{:02X} in track {}", track.track_number, track_number);
       return false;
     }
@@ -161,7 +161,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
 
     if ((track.extra_offset + sizeof(u32) + sizeof(u32)) > mds.size())
     {
-      Log_ErrorFmt("Invalid extra offset {} in track {}", track.extra_offset, track_number);
+      ERROR_LOG("Invalid extra offset {} in track {}", track.extra_offset, track_number);
       Error::SetStringFmt(error, "Invalid extra offset {} in track {}", track.extra_offset, track_number);
       return false;
     }
@@ -184,7 +184,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
     {
       if (track_pregap > track_start_lba)
       {
-        Log_ErrorFmt("Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
+        ERROR_LOG("Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
         Error::SetStringFmt(error, "Track pregap {} is too large for start lba {}", track_pregap, track_start_lba);
         return false;
       }
@@ -235,7 +235,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Error* error)
 
   if (m_tracks.empty())
   {
-    Log_ErrorFmt("File '{}' contains no tracks", Path::GetFileName(filename));
+    ERROR_LOG("File '{}' contains no tracks", Path::GetFileName(filename));
     Error::SetStringFmt(error, "File '{}' contains no tracks", Path::GetFileName(filename));
     return false;
   }
diff --git a/src/util/cd_image_memory.cpp b/src/util/cd_image_memory.cpp
index 9b066bede..7c8a827f9 100644
--- a/src/util/cd_image_memory.cpp
+++ b/src/util/cd_image_memory.cpp
@@ -92,7 +92,7 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress)
     {
       if (!image->ReadSectorFromIndex(memory_ptr, index, lba))
       {
-        Log_ErrorFmt("Failed to read LBA {} in index {}", lba, i);
+        ERROR_LOG("Failed to read LBA {} in index {}", lba, i);
         return false;
       }
 
diff --git a/src/util/cd_image_pbp.cpp b/src/util/cd_image_pbp.cpp
index 25a883096..bda985a27 100644
--- a/src/util/cd_image_pbp.cpp
+++ b/src/util/cd_image_pbp.cpp
@@ -225,13 +225,13 @@ bool CDImagePBP::LoadPBPHeader()
 
   if (std::fread(&m_pbp_header, sizeof(PBPHeader), 1, m_file) != 1)
   {
-    Log_ErrorPrint("Unable to read PBP header");
+    ERROR_LOG("Unable to read PBP header");
     return false;
   }
 
   if (std::strncmp((char*)m_pbp_header.magic, "\0PBP", 4) != 0)
   {
-    Log_ErrorPrint("PBP magic number mismatch");
+    ERROR_LOG("PBP magic number mismatch");
     return false;
   }
 
@@ -252,7 +252,7 @@ bool CDImagePBP::LoadSFOHeader()
 
   if (std::strncmp((char*)m_sfo_header.magic, "\0PSF", 4) != 0)
   {
-    Log_ErrorPrint("SFO magic number mismatch");
+    ERROR_LOG("SFO magic number mismatch");
     return false;
   }
 
@@ -298,7 +298,7 @@ bool CDImagePBP::LoadSFOTable()
 
     if (FileSystem::FSeek64(m_file, abs_key_offset, SEEK_SET) != 0)
     {
-      Log_ErrorFmt("Failed seek to key for SFO table entry {}", i);
+      ERROR_LOG("Failed seek to key for SFO table entry {}", i);
       return false;
     }
 
@@ -306,19 +306,19 @@ bool CDImagePBP::LoadSFOTable()
     char key_cstr[20] = {};
     if (std::fgets(key_cstr, sizeof(key_cstr), m_file) == nullptr)
     {
-      Log_ErrorFmt("Failed to read key string for SFO table entry {}", i);
+      ERROR_LOG("Failed to read key string for SFO table entry {}", i);
       return false;
     }
 
     if (FileSystem::FSeek64(m_file, abs_data_offset, SEEK_SET) != 0)
     {
-      Log_ErrorFmt("Failed seek to data for SFO table entry {}", i);
+      ERROR_LOG("Failed seek to data for SFO table entry {}", i);
       return false;
     }
 
     if (m_sfo_index_table[i].data_type == 0x0004) // "special mode" UTF-8 (not null terminated)
     {
-      Log_ErrorFmt("Unhandled special mode UTF-8 type found in SFO table for entry {}", i);
+      ERROR_LOG("Unhandled special mode UTF-8 type found in SFO table for entry {}", i);
       return false;
     }
     else if (m_sfo_index_table[i].data_type == 0x0204) // null-terminated UTF-8 character string
@@ -326,7 +326,7 @@ bool CDImagePBP::LoadSFOTable()
       std::vector<char> data_cstr(m_sfo_index_table[i].data_size);
       if (fgets(data_cstr.data(), static_cast<int>(data_cstr.size() * sizeof(char)), m_file) == nullptr)
       {
-        Log_ErrorFmt("Failed to read data string for SFO table entry {}", i);
+        ERROR_LOG("Failed to read data string for SFO table entry {}", i);
         return false;
       }
 
@@ -337,7 +337,7 @@ bool CDImagePBP::LoadSFOTable()
       u32 val;
       if (std::fread(&val, sizeof(u32), 1, m_file) != 1)
       {
-        Log_ErrorFmt("Failed to read unsigned data value for SFO table entry {}", i);
+        ERROR_LOG("Failed to read unsigned data value for SFO table entry {}", i);
         return false;
       }
 
@@ -345,8 +345,7 @@ bool CDImagePBP::LoadSFOTable()
     }
     else
     {
-      Log_ErrorFmt("Unhandled SFO data type 0x{:04X} found in SFO table for entry {}", m_sfo_index_table[i].data_type,
-                      i);
+      ERROR_LOG("Unhandled SFO data type 0x{:04X} found in SFO table for entry {}", m_sfo_index_table[i].data_type, i);
       return false;
     }
   }
@@ -368,14 +367,14 @@ bool CDImagePBP::IsValidEboot(Error* error)
     SFOTableDataValue data_value = a_it->second;
     if (!std::holds_alternative<u32>(data_value) || std::get<u32>(data_value) != 1)
     {
-      Log_ErrorPrint("Invalid BOOTABLE value");
+      ERROR_LOG("Invalid BOOTABLE value");
       Error::SetString(error, "Invalid BOOTABLE value");
       return false;
     }
   }
   else
   {
-    Log_ErrorPrint("No BOOTABLE value found");
+    ERROR_LOG("No BOOTABLE value found");
     Error::SetString(error, "No BOOTABLE value found");
     return false;
   }
@@ -386,14 +385,14 @@ bool CDImagePBP::IsValidEboot(Error* error)
     SFOTableDataValue data_value = a_it->second;
     if (!std::holds_alternative<std::string>(data_value) || std::get<std::string>(data_value) != "ME")
     {
-      Log_ErrorPrint("Invalid CATEGORY value");
+      ERROR_LOG("Invalid CATEGORY value");
       Error::SetString(error, "Invalid CATEGORY value");
       return false;
     }
   }
   else
   {
-    Log_ErrorPrint("No CATEGORY value found");
+    ERROR_LOG("No CATEGORY value found");
     Error::SetString(error, "No CATEGORY value found");
     return false;
   }
@@ -415,7 +414,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
   // Read in PBP header
   if (!LoadPBPHeader())
   {
-    Log_ErrorPrint("Failed to load PBP header");
+    ERROR_LOG("Failed to load PBP header");
     Error::SetString(error, "Failed to load PBP header");
     return false;
   }
@@ -423,7 +422,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
   // Read in SFO header
   if (!LoadSFOHeader())
   {
-    Log_ErrorPrint("Failed to load SFO header");
+    ERROR_LOG("Failed to load SFO header");
     Error::SetString(error, "Failed to load SFO header");
     return false;
   }
@@ -431,7 +430,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
   // Read in SFO index table
   if (!LoadSFOIndexTable())
   {
-    Log_ErrorPrint("Failed to load SFO index table");
+    ERROR_LOG("Failed to load SFO index table");
     Error::SetString(error, "Failed to load SFO index table");
     return false;
   }
@@ -439,7 +438,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
   // Read in SFO table
   if (!LoadSFOTable())
   {
-    Log_ErrorPrint("Failed to load SFO table");
+    ERROR_LOG("Failed to load SFO table");
     Error::SetString(error, "Failed to load SFO table");
     return false;
   }
@@ -447,7 +446,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
   // Since PBP files can store things that aren't PS1 CD images, make sure we're loading the right kind
   if (!IsValidEboot(error))
   {
-    Log_ErrorPrint("Couldn't validate EBOOT");
+    ERROR_LOG("Couldn't validate EBOOT");
     return false;
   }
 
@@ -476,7 +475,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
     // Ignore encrypted files
     if (disc_table[0] == 0x44475000) // "\0PGD"
     {
-      Log_ErrorFmt("Encrypted PBP images are not supported, skipping %s", m_filename);
+      ERROR_LOG("Encrypted PBP images are not supported, skipping %s", m_filename);
       Error::SetString(error, "Encrypted PBP images are not supported");
       return false;
     }
@@ -492,7 +491,7 @@ bool CDImagePBP::Open(const char* filename, Error* error)
 
     if (m_disc_offsets.size() < 1)
     {
-      Log_ErrorFmt("Invalid number of discs ({}) in multi-disc PBP file", m_disc_offsets.size());
+      ERROR_LOG("Invalid number of discs ({}) in multi-disc PBP file", m_disc_offsets.size());
       return false;
     }
   }
@@ -509,7 +508,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
 {
   if (index >= m_disc_offsets.size())
   {
-    Log_ErrorFmt("File does not contain disc {}", index + 1);
+    ERROR_LOG("File does not contain disc {}", index + 1);
     Error::SetString(error, fmt::format("File does not contain disc {}", index + 1));
     return false;
   }
@@ -531,7 +530,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
 
   if (std::strncmp(iso_header_magic, "PSISOIMG0000", 12) != 0)
   {
-    Log_ErrorPrint("ISO header magic number mismatch");
+    ERROR_LOG("ISO header magic number mismatch");
     return false;
   }
 
@@ -545,7 +544,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
 
   if (pgd_magic == 0x44475000) // "\0PGD"
   {
-    Log_ErrorFmt("Encrypted PBP images are not supported, skipping {}", m_filename);
+    ERROR_LOG("Encrypted PBP images are not supported, skipping {}", m_filename);
     Error::SetString(error, "Encrypted PBP images are not supported");
     return false;
   }
@@ -594,7 +593,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
   // valid. Not sure what m_toc[0].userdata_start.s encodes on homebrew EBOOTs though, so ignore that
   if (m_toc[0].point != 0xA0 || m_toc[1].point != 0xA1 || m_toc[2].point != 0xA2)
   {
-    Log_ErrorPrint("Invalid points on information tracks");
+    ERROR_LOG("Invalid points on information tracks");
     return false;
   }
 
@@ -605,7 +604,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
 
   if (first_track != 1 || last_track < first_track)
   {
-    Log_ErrorPrint("Invalid starting track number or track count");
+    ERROR_LOG("Invalid starting track number or track count");
     return false;
   }
 
@@ -623,7 +622,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
     const TOCEntry& t = m_toc[static_cast<size_t>(curr_track) + 2];
     const u8 track_num = PackedBCDToBinary(t.point);
     if (track_num != curr_track)
-      Log_WarningFmt("Mismatched TOC track number, expected {} but got {}", curr_track, track_num);
+      WARNING_LOG("Mismatched TOC track number, expected {} but got {}", curr_track, track_num);
 
     const bool is_audio_track = t.type == 0x01;
     const bool is_first_track = curr_track == 1;
@@ -643,12 +642,12 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
     {
       if (!is_first_track || is_audio_track)
       {
-        Log_ErrorFmt("Invalid TOC entry at index {}, user data ({}) should not start before pregap ({})", curr_track,
-                     userdata_start, pregap_start);
+        ERROR_LOG("Invalid TOC entry at index {}, user data ({}) should not start before pregap ({})", curr_track,
+                  userdata_start, pregap_start);
         return false;
       }
 
-      Log_WarningFmt(
+      WARNING_LOG(
         "Invalid TOC entry at index {}, user data ({}) should not start before pregap ({}), assuming not in file.",
         curr_track, userdata_start, pregap_start);
       pregap_start = 0;
@@ -701,7 +700,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
     {
       if (userdata_start >= m_lba_count)
       {
-        Log_ErrorFmt("Last user data index on disc for TOC entry {} should not be 0 or less in length", curr_track);
+        ERROR_LOG("Last user data index on disc for TOC entry {} should not be 0 or less in length", curr_track);
         return false;
       }
       userdata_index.length = m_lba_count - userdata_start;
@@ -715,7 +714,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
 
       if (next_track_num != curr_track + 1 || next_track_start < userdata_start)
       {
-        Log_ErrorFmt("Unable to calculate user data index length for TOC entry {}", curr_track);
+        ERROR_LOG("Unable to calculate user data index length for TOC entry {}", curr_track);
         return false;
       }
 
@@ -734,7 +733,7 @@ bool CDImagePBP::OpenDisc(u32 index, Error* error)
   // Initialize zlib stream
   if (!InitDecompressionStream())
   {
-    Log_ErrorPrint("Failed to initialize zlib decompression stream");
+    ERROR_LOG("Failed to initialize zlib decompression stream");
     return false;
   }
 
@@ -810,7 +809,7 @@ bool CDImagePBP::DecompressBlock(const BlockInfo& block_info)
   int err = inflate(&m_inflate_stream, Z_FINISH);
   if (err != Z_STREAM_END) [[unlikely]]
   {
-    Log_ErrorFmt("Inflate error {}", err);
+    ERROR_LOG("Inflate error {}", err);
     return false;
   }
 
@@ -840,13 +839,13 @@ bool CDImagePBP::ReadSectorFromIndex(void* buffer, const Index& index, LBA lba_i
 
   if (bi.size == 0) [[unlikely]]
   {
-    Log_ErrorFmt("Invalid block {} requested", requested_block);
+    ERROR_LOG("Invalid block {} requested", requested_block);
     return false;
   }
 
   if (m_current_block != requested_block && !DecompressBlock(bi)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to decompress block {}", requested_block);
+    ERROR_LOG("Failed to decompress block {}", requested_block);
     return false;
   }
 
diff --git a/src/util/cd_image_ppf.cpp b/src/util/cd_image_ppf.cpp
index 2a6631c9d..4c627b658 100644
--- a/src/util/cd_image_ppf.cpp
+++ b/src/util/cd_image_ppf.cpp
@@ -69,7 +69,7 @@ bool CDImagePPF::Open(const char* filename, std::unique_ptr<CDImage> parent_imag
   auto fp = FileSystem::OpenManagedSharedCFile(filename, "rb", FileSystem::FileShareMode::DenyWrite);
   if (!fp)
   {
-    Log_ErrorFmt("Failed to open '%s'", filename);
+    ERROR_LOG("Failed to open '%s'", filename);
     return false;
   }
 
@@ -78,7 +78,7 @@ bool CDImagePPF::Open(const char* filename, std::unique_ptr<CDImage> parent_imag
   u32 magic;
   if (std::fread(&magic, sizeof(magic), 1, fp.get()) != 1)
   {
-    Log_ErrorFmt("Failed to read magic from '%s'", filename);
+    ERROR_LOG("Failed to read magic from '%s'", filename);
     return false;
   }
 
@@ -100,7 +100,7 @@ bool CDImagePPF::Open(const char* filename, std::unique_ptr<CDImage> parent_imag
   else if (magic == 0x31465050) // PPF1
     return ReadV1Patch(fp.get());
 
-  Log_ErrorFmt("Unknown PPF magic {:08X}", magic);
+  ERROR_LOG("Unknown PPF magic {:08X}", magic);
   return false;
 }
 
@@ -111,7 +111,7 @@ u32 CDImagePPF::ReadFileIDDiz(std::FILE* fp, u32 version)
   u32 magic;
   if (std::fseek(fp, -(lenidx + 4), SEEK_END) != 0 || std::fread(&magic, sizeof(magic), 1, fp) != 1) [[unlikely]]
   {
-    Log_WarningPrint("Failed to read diz magic");
+    WARNING_LOG("Failed to read diz magic");
     return 0;
   }
 
@@ -121,13 +121,13 @@ u32 CDImagePPF::ReadFileIDDiz(std::FILE* fp, u32 version)
   u32 dlen = 0;
   if (std::fseek(fp, -lenidx, SEEK_END) != 0 || std::fread(&dlen, lenidx, 1, fp) != 1) [[unlikely]]
   {
-    Log_WarningPrint("Failed to read diz length");
+    WARNING_LOG("Failed to read diz length");
     return 0;
   }
 
   if (dlen > static_cast<u32>(std::ftell(fp))) [[unlikely]]
   {
-    Log_WarningPrint("diz length out of range");
+    WARNING_LOG("diz length out of range");
     return 0;
   }
 
@@ -136,11 +136,11 @@ u32 CDImagePPF::ReadFileIDDiz(std::FILE* fp, u32 version)
   if (std::fseek(fp, -(lenidx + 16 + static_cast<int>(dlen)), SEEK_END) != 0 ||
       std::fread(fdiz.data(), 1, dlen, fp) != dlen) [[unlikely]]
   {
-    Log_WarningPrint("Failed to read fdiz");
+    WARNING_LOG("Failed to read fdiz");
     return 0;
   }
 
-  Log_InfoFmt("File_Id.diz: %s", fdiz);
+  INFO_LOG("File_Id.diz: %s", fdiz);
   return dlen;
 }
 
@@ -149,7 +149,7 @@ bool CDImagePPF::ReadV1Patch(std::FILE* fp)
   char desc[DESC_SIZE + 1] = {};
   if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to read description");
+    ERROR_LOG("Failed to read description");
     return false;
   }
 
@@ -157,7 +157,7 @@ bool CDImagePPF::ReadV1Patch(std::FILE* fp)
   if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 56)
     [[unlikely]]
   {
-    Log_ErrorPrint("Invalid ppf file");
+    ERROR_LOG("Invalid ppf file");
     return false;
   }
 
@@ -176,14 +176,14 @@ bool CDImagePPF::ReadV1Patch(std::FILE* fp)
     if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
       [[unlikely]]
     {
-      Log_ErrorPrint("Incomplete ppf");
+      ERROR_LOG("Incomplete ppf");
       return false;
     }
 
     temp.resize(chunk_size);
     if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]]
     {
-      Log_ErrorPrint("Failed to read patch data");
+      ERROR_LOG("Failed to read patch data");
       return false;
     }
 
@@ -193,7 +193,7 @@ bool CDImagePPF::ReadV1Patch(std::FILE* fp)
     count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
   }
 
-  Log_InfoFmt("Loaded {} replacement sectors from version 1 PPF", m_replacement_map.size());
+  INFO_LOG("Loaded {} replacement sectors from version 1 PPF", m_replacement_map.size());
   return true;
 }
 
@@ -202,18 +202,18 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp)
   char desc[DESC_SIZE + 1] = {};
   if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to read description");
+    ERROR_LOG("Failed to read description");
     return false;
   }
 
-  Log_InfoFmt("Patch description: %s", desc);
+  INFO_LOG("Patch description: %s", desc);
 
   const u32 idlen = ReadFileIDDiz(fp, 2);
 
   u32 origlen;
   if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&origlen, sizeof(origlen), 1, fp) != 1) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to read size");
+    ERROR_LOG("Failed to read size");
     return false;
   }
 
@@ -221,7 +221,7 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp)
   temp.resize(BLOCKCHECK_SIZE);
   if (std::fread(temp.data(), 1, BLOCKCHECK_SIZE, fp) != BLOCKCHECK_SIZE) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to read blockcheck data");
+    ERROR_LOG("Failed to read blockcheck data");
     return false;
   }
 
@@ -234,11 +234,11 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp)
     if (m_parent_image->Seek(blockcheck_src_sector) && m_parent_image->ReadRawSector(src_sector.data(), nullptr))
     {
       if (std::memcmp(&src_sector[blockcheck_src_offset], temp.data(), BLOCKCHECK_SIZE) != 0)
-        Log_WarningPrint("Blockcheck failed. The patch may not apply correctly.");
+        WARNING_LOG("Blockcheck failed. The patch may not apply correctly.");
     }
     else
     {
-      Log_WarningFmt("Failed to read blockcheck sector {}", blockcheck_src_sector);
+      WARNING_LOG("Failed to read blockcheck sector {}", blockcheck_src_sector);
     }
   }
 
@@ -246,7 +246,7 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp)
   if (std::fseek(fp, 0, SEEK_END) != 0 || (filelen = static_cast<u32>(std::ftell(fp))) == 0 || filelen < 1084)
     [[unlikely]]
   {
-    Log_ErrorPrint("Invalid ppf file");
+    ERROR_LOG("Invalid ppf file");
     return false;
   }
 
@@ -267,14 +267,14 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp)
     if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
       [[unlikely]]
     {
-      Log_ErrorPrint("Incomplete ppf");
+      ERROR_LOG("Incomplete ppf");
       return false;
     }
 
     temp.resize(chunk_size);
     if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size) [[unlikely]]
     {
-      Log_ErrorPrint("Failed to read patch data");
+      ERROR_LOG("Failed to read patch data");
       return false;
     }
 
@@ -284,7 +284,7 @@ bool CDImagePPF::ReadV2Patch(std::FILE* fp)
     count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
   }
 
-  Log_InfoFmt("Loaded {} replacement sectors from version 2 PPF", m_replacement_map.size());
+  INFO_LOG("Loaded {} replacement sectors from version 2 PPF", m_replacement_map.size());
   return true;
 }
 
@@ -293,11 +293,11 @@ bool CDImagePPF::ReadV3Patch(std::FILE* fp)
   char desc[DESC_SIZE + 1] = {};
   if (std::fseek(fp, 6, SEEK_SET) != 0 || std::fread(desc, sizeof(char), DESC_SIZE, fp) != DESC_SIZE)
   {
-    Log_ErrorPrint("Failed to read description");
+    ERROR_LOG("Failed to read description");
     return false;
   }
 
-  Log_InfoFmt("Patch description: {}", desc);
+  INFO_LOG("Patch description: {}", desc);
 
   u32 idlen = ReadFileIDDiz(fp, 3);
 
@@ -307,7 +307,7 @@ bool CDImagePPF::ReadV3Patch(std::FILE* fp)
   if (std::fseek(fp, 56, SEEK_SET) != 0 || std::fread(&image_type, sizeof(image_type), 1, fp) != 1 ||
       std::fread(&block_check, sizeof(block_check), 1, fp) != 1 || std::fread(&undo, sizeof(undo), 1, fp) != 1)
   {
-    Log_ErrorPrint("Failed to read headers");
+    ERROR_LOG("Failed to read headers");
     return false;
   }
 
@@ -319,7 +319,7 @@ bool CDImagePPF::ReadV3Patch(std::FILE* fp)
   u32 seekpos = (block_check) ? 1084 : 60;
   if (seekpos >= count)
   {
-    Log_ErrorPrint("File is too short");
+    ERROR_LOG("File is too short");
     return false;
   }
 
@@ -329,7 +329,7 @@ bool CDImagePPF::ReadV3Patch(std::FILE* fp)
     const u32 extralen = idlen + 18 + 16 + 2;
     if (count < extralen)
     {
-      Log_ErrorPrint("File is too short (diz)");
+      ERROR_LOG("File is too short (diz)");
       return false;
     }
 
@@ -347,14 +347,14 @@ bool CDImagePPF::ReadV3Patch(std::FILE* fp)
     u8 chunk_size;
     if (std::fread(&offset, sizeof(offset), 1, fp) != 1 || std::fread(&chunk_size, sizeof(chunk_size), 1, fp) != 1)
     {
-      Log_ErrorPrint("Incomplete ppf");
+      ERROR_LOG("Incomplete ppf");
       return false;
     }
 
     temp.resize(chunk_size);
     if (std::fread(temp.data(), 1, chunk_size, fp) != chunk_size)
     {
-      Log_ErrorPrint("Failed to read patch data");
+      ERROR_LOG("Failed to read patch data");
       return false;
     }
 
@@ -364,13 +364,13 @@ bool CDImagePPF::ReadV3Patch(std::FILE* fp)
     count -= sizeof(offset) + sizeof(chunk_size) + chunk_size;
   }
 
-  Log_InfoFmt("Loaded {} replacement sectors from version 3 PPF", m_replacement_map.size());
+  INFO_LOG("Loaded {} replacement sectors from version 3 PPF", m_replacement_map.size());
   return true;
 }
 
 bool CDImagePPF::AddPatch(u64 offset, const u8* patch, u32 patch_size)
 {
-  Log_DebugFmt("Starting applying patch of {} bytes at at offset {}", patch_size, offset);
+  DEBUG_LOG("Starting applying patch of {} bytes at at offset {}", patch_size, offset);
 
   while (patch_size > 0)
   {
@@ -378,7 +378,7 @@ bool CDImagePPF::AddPatch(u64 offset, const u8* patch, u32 patch_size)
     const u32 sector_offset = Truncate32(offset % RAW_SECTOR_SIZE);
     if (sector_index >= m_parent_image->GetLBACount())
     {
-      Log_ErrorFmt("Sector {} in patch is out of range", sector_index);
+      ERROR_LOG("Sector {} in patch is out of range", sector_index);
       return false;
     }
 
@@ -392,7 +392,7 @@ bool CDImagePPF::AddPatch(u64 offset, const u8* patch, u32 patch_size)
       if (!m_parent_image->Seek(sector_index) ||
           !m_parent_image->ReadRawSector(&m_replacement_data[replacement_buffer_start], nullptr))
       {
-        Log_ErrorFmt("Failed to read sector {} from parent image", sector_index);
+        ERROR_LOG("Failed to read sector {} from parent image", sector_index);
         return false;
       }
 
@@ -400,7 +400,7 @@ bool CDImagePPF::AddPatch(u64 offset, const u8* patch, u32 patch_size)
     }
 
     // patch it!
-    Log_DebugFmt("  Patching {} bytes at sector {} offset {}", bytes_to_patch, sector_index, sector_offset);
+    DEBUG_LOG("  Patching {} bytes at sector {} offset {}", bytes_to_patch, sector_index, sector_offset);
     std::memcpy(&m_replacement_data[iter->second + sector_offset], patch, bytes_to_patch);
     offset += bytes_to_patch;
     patch += bytes_to_patch;
diff --git a/src/util/cd_subchannel_replacement.cpp b/src/util/cd_subchannel_replacement.cpp
index da5c11c6a..b191d255a 100644
--- a/src/util/cd_subchannel_replacement.cpp
+++ b/src/util/cd_subchannel_replacement.cpp
@@ -50,14 +50,14 @@ bool CDSubChannelReplacement::LoadSBI(const std::string& path)
   char header[4];
   if (std::fread(header, sizeof(header), 1, fp.get()) != 1)
   {
-    Log_ErrorFmt("Failed to read header for '{}'", path);
+    ERROR_LOG("Failed to read header for '{}'", path);
     return true;
   }
 
   static constexpr char expected_header[] = {'S', 'B', 'I', '\0'};
   if (std::memcmp(header, expected_header, sizeof(header)) != 0)
   {
-    Log_ErrorFmt("Invalid header in '{}'", path);
+    ERROR_LOG("Invalid header in '{}'", path);
     return true;
   }
 
@@ -69,14 +69,14 @@ bool CDSubChannelReplacement::LoadSBI(const std::string& path)
     if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) ||
         !IsValidPackedBCD(entry.frame_bcd))
     {
-      Log_ErrorFmt("Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd,
-                   entry.frame_bcd, path);
+      ERROR_LOG("Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd, entry.frame_bcd,
+                path);
       return false;
     }
 
     if (entry.type != 1)
     {
-      Log_ErrorFmt("Invalid type 0x{:02X} in '{}'", entry.type, path);
+      ERROR_LOG("Invalid type 0x{:02X} in '{}'", entry.type, path);
       return false;
     }
 
@@ -93,7 +93,7 @@ bool CDSubChannelReplacement::LoadSBI(const std::string& path)
     m_replacement_subq.emplace(lba, subq);
   }
 
-  Log_InfoFmt("Loaded {} replacement sectors from SBI '{}'", m_replacement_subq.size(), path);
+  INFO_LOG("Loaded {} replacement sectors from SBI '{}'", m_replacement_subq.size(), path);
   return true;
 }
 
@@ -111,8 +111,8 @@ bool CDSubChannelReplacement::LoadLSD(const std::string& path)
     if (!IsValidPackedBCD(entry.minute_bcd) || !IsValidPackedBCD(entry.second_bcd) ||
         !IsValidPackedBCD(entry.frame_bcd))
     {
-      Log_ErrorFmt("Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd,
-                   entry.frame_bcd, path);
+      ERROR_LOG("Invalid position [{:02x}:{:02x}:{:02x}] in '{}'", entry.minute_bcd, entry.second_bcd, entry.frame_bcd,
+                path);
       return false;
     }
 
@@ -121,12 +121,12 @@ bool CDSubChannelReplacement::LoadLSD(const std::string& path)
     CDImage::SubChannelQ subq;
     std::copy_n(entry.data, countof(entry.data), subq.data.data());
 
-    Log_DebugFmt("{:02x}:{:02x}:{:02x}: CRC {}", entry.minute_bcd, entry.second_bcd, entry.frame_bcd,
-                 subq.IsCRCValid() ? "VALID" : "INVALID");
+    DEBUG_LOG("{:02x}:{:02x}:{:02x}: CRC {}", entry.minute_bcd, entry.second_bcd, entry.frame_bcd,
+              subq.IsCRCValid() ? "VALID" : "INVALID");
     m_replacement_subq.emplace(lba, subq);
   }
 
-  Log_InfoFmt("Loaded {} replacement sectors from LSD '{}'", m_replacement_subq.size(), path);
+  INFO_LOG("Loaded {} replacement sectors from LSD '{}'", m_replacement_subq.size(), path);
   return true;
 }
 
diff --git a/src/util/cubeb_audio_stream.cpp b/src/util/cubeb_audio_stream.cpp
index 198b2ff82..eb16c9240 100644
--- a/src/util/cubeb_audio_stream.cpp
+++ b/src/util/cubeb_audio_stream.cpp
@@ -94,7 +94,7 @@ void CubebAudioStream::LogCallback(const char* fmt, ...)
   va_start(ap, fmt);
   str.vsprintf(fmt, ap);
   va_end(ap);
-  Log_DevPrint(str);
+  DEV_LOG(str);
 }
 
 void CubebAudioStream::DestroyContextAndStream()
@@ -172,8 +172,8 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
   rv = cubeb_get_min_latency(m_context, &params, &min_latency_frames);
   if (rv == CUBEB_ERROR_NOT_SUPPORTED)
   {
-    Log_DevFmt("Cubeb backend does not support latency queries, using latency of {} ms ({} frames).",
-               m_parameters.buffer_ms, latency_frames);
+    DEV_LOG("Cubeb backend does not support latency queries, using latency of {} ms ({} frames).",
+            m_parameters.buffer_ms, latency_frames);
   }
   else
   {
@@ -185,7 +185,7 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
     }
 
     const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
-    Log_DevFmt("Minimum latency: {} ms ({} audio frames)", minimum_latency_ms, min_latency_frames);
+    DEV_LOG("Minimum latency: {} ms ({} audio frames)", minimum_latency_ms, min_latency_frames);
     if (m_parameters.output_latency_minimal)
     {
       // use minimum
@@ -193,8 +193,8 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
     }
     else if (minimum_latency_ms > m_parameters.output_latency_ms)
     {
-      Log_WarningFmt("Minimum latency is above requested latency: {} vs {}, adjusting to compensate.",
-                     min_latency_frames, latency_frames);
+      WARNING_LOG("Minimum latency is above requested latency: {} vs {}, adjusting to compensate.", min_latency_frames,
+                  latency_frames);
       latency_frames = min_latency_frames;
     }
   }
@@ -214,8 +214,7 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
         const cubeb_device_info& di = devices.device[i];
         if (di.device_id && selected_device_name == di.device_id)
         {
-          Log_InfoFmt("Using output device '{}' ({}).", di.device_id,
-                      di.friendly_name ? di.friendly_name : di.device_id);
+          INFO_LOG("Using output device '{}' ({}).", di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
           selected_device = di.devid;
           break;
         }
@@ -229,7 +228,7 @@ bool CubebAudioStream::Initialize(const char* driver_name, const char* device_na
     }
     else
     {
-      Log_WarningFmt("cubeb_enumerate_devices() returned {}, using default device.", GetCubebErrorString(rv));
+      WARNING_LOG("cubeb_enumerate_devices() returned {}, using default device.", GetCubebErrorString(rv));
     }
   }
 
@@ -282,7 +281,7 @@ void CubebAudioStream::SetPaused(bool paused)
   const int rv = paused ? cubeb_stream_stop(stream) : cubeb_stream_start(stream);
   if (rv != CUBEB_OK)
   {
-    Log_ErrorFmt("Could not {} stream: {}", paused ? "pause" : "resume", rv);
+    ERROR_LOG("Could not {} stream: {}", paused ? "pause" : "resume", rv);
     return;
   }
 
@@ -320,7 +319,7 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
   int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
   if (rv != CUBEB_OK)
   {
-    Log_ErrorFmt("cubeb_init() failed: {}", GetCubebErrorString(rv));
+    ERROR_LOG("cubeb_init() failed: {}", GetCubebErrorString(rv));
     return ret;
   }
 
@@ -330,7 +329,7 @@ std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const ch
   rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
   if (rv != CUBEB_OK)
   {
-    Log_ErrorFmt("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv));
+    ERROR_LOG("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv));
     return ret;
   }
 
diff --git a/src/util/cue_parser.cpp b/src/util/cue_parser.cpp
index 56866dfad..2f4db5ebf 100644
--- a/src/util/cue_parser.cpp
+++ b/src/util/cue_parser.cpp
@@ -79,7 +79,7 @@ void CueParser::File::SetError(u32 line_number, Error* error, const char* format
   str.vsprintf(format, ap);
   va_end(ap);
 
-  Log_ErrorFmt("Cue parse error at line {}: {}", line_number, str.c_str());
+  ERROR_LOG("Cue parse error at line {}: {}", line_number, str.c_str());
   Error::SetString(error, fmt::format("Cue parse error at line {}: {}", line_number, str));
 }
 
@@ -194,7 +194,7 @@ bool CueParser::File::ParseLine(const char* line, u32 line_number, Error* error)
 
   if (TokenMatch(command, "POSTGAP"))
   {
-    Log_WarningFmt("Ignoring '{}' command", command);
+    WARNING_LOG("Ignoring '{}' command", command);
     return true;
   }
 
@@ -231,7 +231,7 @@ bool CueParser::File::HandleFileCommand(const char* line, u32 line_number, Error
   }
 
   m_current_file = filename;
-  Log_DebugFmt("File '{}'", filename);
+  DEBUG_LOG("File '{}'", filename);
   return true;
 }
 
@@ -392,7 +392,7 @@ bool CueParser::File::HandleFlagCommand(const char* line, u32 line_number, Error
     else if (TokenMatch(token, "SCMS"))
       m_current_track->SetFlag(TrackFlag::SerialCopyManagement);
     else
-      Log_WarningFmt("Unknown track flag '{}'", token);
+      WARNING_LOG("Unknown track flag '{}'", token);
   }
 
   return true;
@@ -428,7 +428,7 @@ bool CueParser::File::CompleteLastTrack(u32 line_number, Error* error)
   const MSF* index0 = m_current_track->GetIndex(0);
   if (index0 && m_current_track->zero_pregap.has_value())
   {
-    Log_WarningFmt("Zero pregap and index 0 specified in track {}, ignoring zero pregap", m_current_track->number);
+    WARNING_LOG("Zero pregap and index 0 specified in track {}, ignoring zero pregap", m_current_track->number);
     m_current_track->zero_pregap.reset();
   }
 
diff --git a/src/util/d3d11_device.cpp b/src/util/d3d11_device.cpp
index 0b12e2d12..b9544bed0 100644
--- a/src/util/d3d11_device.cpp
+++ b/src/util/d3d11_device.cpp
@@ -126,10 +126,10 @@ bool D3D11Device::CreateDevice(std::string_view adapter, bool threaded_presentat
   ComPtr<IDXGIDevice> dxgi_device;
   if (SUCCEEDED(m_device.As(&dxgi_device)) &&
       SUCCEEDED(dxgi_device->GetParent(IID_PPV_ARGS(dxgi_adapter.GetAddressOf()))))
-    Log_InfoFmt("D3D Adapter: %s", D3DCommon::GetAdapterName(dxgi_adapter.Get()));
+    INFO_LOG("D3D Adapter: %s", D3DCommon::GetAdapterName(dxgi_adapter.Get()));
   else
-    Log_ErrorPrint("Failed to obtain D3D adapter name.");
-  Log_InfoFmt("Max device feature level: {}", D3DCommon::GetFeatureLevelString(m_max_feature_level));
+    ERROR_LOG("Failed to obtain D3D adapter name.");
+  INFO_LOG("Max device feature level: {}", D3DCommon::GetFeatureLevelString(m_max_feature_level));
 
   BOOL allow_tearing_supported = false;
   hr = m_dxgi_factory->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allow_tearing_supported,
@@ -263,12 +263,12 @@ bool D3D11Device::CreateSwapChain()
     fs_desc.Scaling = fullscreen_mode.Scaling;
     fs_desc.Windowed = FALSE;
 
-    Log_VerboseFmt("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
+    VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
     hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
                                                 fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
     if (FAILED(hr))
     {
-      Log_WarningPrint("Failed to create fullscreen swap chain, trying windowed.");
+      WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
       m_is_exclusive_fullscreen = false;
       m_using_allow_tearing = m_allow_tearing_supported && m_using_flip_model_swap_chain;
     }
@@ -276,15 +276,15 @@ bool D3D11Device::CreateSwapChain()
 
   if (!m_is_exclusive_fullscreen)
   {
-    Log_VerboseFmt("Creating a {}x{} {} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
-                   m_using_flip_model_swap_chain ? "flip-discard" : "discard");
+    VERBOSE_LOG("Creating a {}x{} {} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height,
+                m_using_flip_model_swap_chain ? "flip-discard" : "discard");
     hr = m_dxgi_factory->CreateSwapChainForHwnd(m_device.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
                                                 m_swap_chain.ReleaseAndGetAddressOf());
   }
 
   if (FAILED(hr) && m_using_flip_model_swap_chain)
   {
-    Log_WarningPrint("Failed to create a flip-discard swap chain, trying discard.");
+    WARNING_LOG("Failed to create a flip-discard swap chain, trying discard.");
     swap_chain_desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
     swap_chain_desc.Flags = 0;
     m_using_flip_model_swap_chain = false;
@@ -294,7 +294,7 @@ bool D3D11Device::CreateSwapChain()
                                                 m_swap_chain.ReleaseAndGetAddressOf());
     if (FAILED(hr)) [[unlikely]]
     {
-      Log_ErrorFmt("CreateSwapChainForHwnd failed: 0x{:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("CreateSwapChainForHwnd failed: 0x{:08X}", static_cast<unsigned>(hr));
       return false;
     }
   }
@@ -304,7 +304,7 @@ bool D3D11Device::CreateSwapChain()
   if (FAILED(m_swap_chain->GetParent(IID_PPV_ARGS(parent_factory.GetAddressOf()))) ||
       FAILED(parent_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES)))
   {
-    Log_WarningPrint("MakeWindowAssociation() to disable ALT+ENTER failed");
+    WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed");
   }
 
   if (!CreateSwapChainRTV())
@@ -325,7 +325,7 @@ bool D3D11Device::CreateSwapChainRTV()
   HRESULT hr = m_swap_chain->GetBuffer(0, IID_PPV_ARGS(backbuffer.GetAddressOf()));
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -337,7 +337,7 @@ bool D3D11Device::CreateSwapChainRTV()
   hr = m_device->CreateRenderTargetView(backbuffer.Get(), &rtv_desc, m_swap_chain_rtv.ReleaseAndGetAddressOf());
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateRenderTargetView for swap chain failed: 0x{:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateRenderTargetView for swap chain failed: 0x{:08X}", static_cast<unsigned>(hr));
     m_swap_chain_rtv.Reset();
     return false;
   }
@@ -345,7 +345,7 @@ bool D3D11Device::CreateSwapChainRTV()
   m_window_info.surface_width = backbuffer_desc.Width;
   m_window_info.surface_height = backbuffer_desc.Height;
   m_window_info.surface_format = s_swap_chain_format;
-  Log_VerboseFmt("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
+  VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
 
   if (m_window_info.type == WindowInfo::Type::Win32)
   {
@@ -391,7 +391,7 @@ bool D3D11Device::UpdateWindow()
 
   if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateSwapChain())
   {
-    Log_ErrorPrint("Failed to create swap chain on updated window");
+    ERROR_LOG("Failed to create swap chain on updated window");
     return false;
   }
 
@@ -421,7 +421,7 @@ void D3D11Device::ResizeWindow(s32 new_window_width, s32 new_window_height, floa
   HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
                                            m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
   if (FAILED(hr)) [[unlikely]]
-    Log_ErrorFmt("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
 
   if (!CreateSwapChainRTV())
     Panic("Failed to recreate swap chain RTV after resize");
@@ -470,7 +470,7 @@ bool D3D11Device::CreateBuffers()
       !m_index_buffer.Create(D3D11_BIND_INDEX_BUFFER, INDEX_BUFFER_SIZE, INDEX_BUFFER_SIZE) ||
       !m_uniform_buffer.Create(D3D11_BIND_CONSTANT_BUFFER, MIN_UNIFORM_BUFFER_SIZE, MAX_UNIFORM_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to create vertex/index/uniform buffers.");
+    ERROR_LOG("Failed to create vertex/index/uniform buffers.");
     return false;
   }
 
@@ -600,7 +600,7 @@ std::optional<float> D3D11Device::GetHostRefreshRate()
     if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
         desc.BufferDesc.RefreshRate.Denominator > 0)
     {
-      Log_DevFmt("using fs rr: {} {}", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator);
+      DEV_LOG("using fs rr: {} {}", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator);
       return static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
              static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
     }
@@ -776,7 +776,7 @@ void D3D11Device::PopTimestampQuery()
 
     if (disjoint.Disjoint)
     {
-      Log_VerbosePrint("GPU timing disjoint, resetting.");
+      VERBOSE_LOG("GPU timing disjoint, resetting.");
       m_read_timestamp_query = 0;
       m_write_timestamp_query = 0;
       m_waiting_timestamp_queries = 0;
@@ -1078,7 +1078,7 @@ void D3D11Device::UnbindTexture(D3D11Texture* tex)
     {
       if (m_current_render_targets[i] == tex)
       {
-        Log_WarningPrint("Unbinding current RT");
+        WARNING_LOG("Unbinding current RT");
         SetRenderTargets(nullptr, 0, m_current_depth_target);
         break;
       }
@@ -1086,7 +1086,7 @@ void D3D11Device::UnbindTexture(D3D11Texture* tex)
   }
   else if (m_current_depth_target == tex)
   {
-    Log_WarningPrint("Unbinding current DS");
+    WARNING_LOG("Unbinding current DS");
     SetRenderTargets(nullptr, 0, nullptr);
   }
 }
diff --git a/src/util/d3d11_pipeline.cpp b/src/util/d3d11_pipeline.cpp
index ffe9af865..447552f81 100644
--- a/src/util/d3d11_pipeline.cpp
+++ b/src/util/d3d11_pipeline.cpp
@@ -153,7 +153,7 @@ D3D11Device::ComPtr<ID3D11RasterizerState> D3D11Device::GetRasterizationState(co
 
   HRESULT hr = m_device->CreateRasterizerState(&desc, drs.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
-    Log_ErrorFmt("Failed to create depth state with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Failed to create depth state with {:08X}", static_cast<unsigned>(hr));
 
   m_rasterization_states.emplace(rs.key, drs);
   return drs;
@@ -188,7 +188,7 @@ D3D11Device::ComPtr<ID3D11DepthStencilState> D3D11Device::GetDepthState(const GP
 
   HRESULT hr = m_device->CreateDepthStencilState(&desc, dds.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
-    Log_ErrorFmt("Failed to create depth state with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Failed to create depth state with {:08X}", static_cast<unsigned>(hr));
 
   m_depth_states.emplace(ds.key, dds);
   return dds;
@@ -246,7 +246,7 @@ D3D11Device::ComPtr<ID3D11BlendState> D3D11Device::GetBlendState(const GPUPipeli
 
   HRESULT hr = m_device->CreateBlendState(&blend_desc, dbs.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
-    Log_ErrorFmt("Failed to create blend state with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Failed to create blend state with {:08X}", static_cast<unsigned>(hr));
 
   m_blend_states.emplace(bs.key, dbs);
   return dbs;
@@ -298,7 +298,7 @@ D3D11Device::ComPtr<ID3D11InputLayout> D3D11Device::GetInputLayout(const GPUPipe
   HRESULT hr = m_device->CreateInputLayout(elems, static_cast<UINT>(il.vertex_attributes.size()),
                                            vs->GetBytecode().data(), vs->GetBytecode().size(), dil.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
-    Log_ErrorFmt("Failed to create input layout with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Failed to create input layout with {:08X}", static_cast<unsigned>(hr));
 
   m_input_layouts.emplace(il, dil);
   return dil;
diff --git a/src/util/d3d11_stream_buffer.cpp b/src/util/d3d11_stream_buffer.cpp
index 1e824a8f7..1d359cfae 100644
--- a/src/util/d3d11_stream_buffer.cpp
+++ b/src/util/d3d11_stream_buffer.cpp
@@ -40,7 +40,7 @@ bool D3D11StreamBuffer::Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max
       m_use_map_no_overwrite = options.MapNoOverwriteOnDynamicConstantBuffer;
       if (m_use_map_no_overwrite && D3D11Device::GetMaxFeatureLevel() < D3D_FEATURE_LEVEL_12_0)
       {
-        Log_WarningPrint("Ignoring MapNoOverwriteOnDynamicConstantBuffer on driver due to feature level.");
+        WARNING_LOG("Ignoring MapNoOverwriteOnDynamicConstantBuffer on driver due to feature level.");
         m_use_map_no_overwrite = false;
       }
 
@@ -55,14 +55,14 @@ bool D3D11StreamBuffer::Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max
 
     if (!m_use_map_no_overwrite)
     {
-      Log_WarningFmt("Unable to use MAP_NO_OVERWRITE on buffer with bind flag {}, this may affect performance. "
-                     "Update your driver/operating system.",
-                     static_cast<unsigned>(bind_flags));
+      WARNING_LOG("Unable to use MAP_NO_OVERWRITE on buffer with bind flag {}, this may affect performance. "
+                  "Update your driver/operating system.",
+                  static_cast<unsigned>(bind_flags));
     }
   }
   else
   {
-    Log_WarningFmt("ID3D11Device::CheckFeatureSupport() failed: {}", Error::CreateHResult(hr).GetDescription());
+    WARNING_LOG("ID3D11Device::CheckFeatureSupport() failed: {}", Error::CreateHResult(hr).GetDescription());
     m_use_map_no_overwrite = false;
   }
 
@@ -72,7 +72,7 @@ bool D3D11StreamBuffer::Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max
   hr = D3D11Device::GetD3DDevice()->CreateBuffer(&desc, nullptr, &buffer);
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("Creating buffer failed: {}", Error::CreateHResult(hr).GetDescription());
+    ERROR_LOG("Creating buffer failed: {}", Error::CreateHResult(hr).GetDescription());
     return false;
   }
 
@@ -106,7 +106,7 @@ D3D11StreamBuffer::MappingResult D3D11StreamBuffer::Map(ID3D11DeviceContext1* co
       Assert(min_size < m_max_size);
 
       const u32 new_size = std::min(m_max_size, Common::AlignUp(std::max(m_size * 2, min_size), alignment));
-      Log_WarningFmt("Growing buffer from {} bytes to {} bytes", m_size, new_size);
+      WARNING_LOG("Growing buffer from {} bytes to {} bytes", m_size, new_size);
 
       D3D11_BUFFER_DESC new_desc;
       m_buffer->GetDesc(&new_desc);
@@ -115,7 +115,7 @@ D3D11StreamBuffer::MappingResult D3D11StreamBuffer::Map(ID3D11DeviceContext1* co
       hr = D3D11Device::GetD3DDevice()->CreateBuffer(&new_desc, nullptr, m_buffer.ReleaseAndGetAddressOf());
       if (FAILED(hr)) [[unlikely]]
       {
-        Log_ErrorFmt("Creating buffer failed: {}", Error::CreateHResult(hr).GetDescription());
+        ERROR_LOG("Creating buffer failed: {}", Error::CreateHResult(hr).GetDescription());
         Panic("Failed to grow buffer");
       }
 
@@ -128,8 +128,8 @@ D3D11StreamBuffer::MappingResult D3D11StreamBuffer::Map(ID3D11DeviceContext1* co
   hr = context->Map(m_buffer.Get(), 0, map_type, 0, &sr);
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("Map failed: 0x{:08X} (alignment {}, minsize {}, size {}, position [], map type {})",
-                 static_cast<unsigned>(hr), alignment, min_size, m_size, m_position, static_cast<u32>(map_type));
+    ERROR_LOG("Map failed: 0x{:08X} (alignment {}, minsize {}, size {}, position [], map type {})",
+              static_cast<unsigned>(hr), alignment, min_size, m_size, m_position, static_cast<u32>(map_type));
     Panic("Map failed");
   }
 
diff --git a/src/util/d3d11_texture.cpp b/src/util/d3d11_texture.cpp
index cdf613156..a5513fc06 100644
--- a/src/util/d3d11_texture.cpp
+++ b/src/util/d3d11_texture.cpp
@@ -86,7 +86,7 @@ std::unique_ptr<GPUSampler> D3D11Device::CreateSampler(const GPUSampler::Config&
   const HRESULT hr = m_device->CreateSamplerState(&desc, ss.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateSamplerState() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateSamplerState() failed: {:08X}", static_cast<unsigned>(hr));
     return {};
   }
 
@@ -189,7 +189,7 @@ bool D3D11Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32
   HRESULT hr = context->Map(m_texture.Get(), srnum, discard ? D3D11_MAP_WRITE_DISCARD : D3D11_MAP_READ_WRITE, 0, &sr);
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("Map pixels texture failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Map pixels texture failed: {:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -267,10 +267,9 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
   const HRESULT tex_hr = device->CreateTexture2D(&desc, initial_data ? &srd : nullptr, texture.GetAddressOf());
   if (FAILED(tex_hr))
   {
-    Log_ErrorFmt(
-      "Create texture failed: 0x{:08X} ({}x{} levels:{} samples:{} format:{} bind_flags:{:X} initial_data:{})",
-      static_cast<unsigned>(tex_hr), width, height, levels, samples, static_cast<unsigned>(format), bind_flags,
-      initial_data);
+    ERROR_LOG("Create texture failed: 0x{:08X} ({}x{} levels:{} samples:{} format:{} bind_flags:{:X} initial_data:{})",
+              static_cast<unsigned>(tex_hr), width, height, levels, samples, static_cast<unsigned>(format), bind_flags,
+              initial_data);
     return nullptr;
   }
 
@@ -291,7 +290,7 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
     const HRESULT hr = device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.GetAddressOf());
     if (FAILED(hr)) [[unlikely]]
     {
-      Log_ErrorFmt("Create SRV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("Create SRV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
       return nullptr;
     }
   }
@@ -306,7 +305,7 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
     const HRESULT hr = device->CreateRenderTargetView(texture.Get(), &rtv_desc, rtv.GetAddressOf());
     if (FAILED(hr)) [[unlikely]]
     {
-      Log_ErrorFmt("Create RTV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("Create RTV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
       return nullptr;
     }
 
@@ -321,7 +320,7 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
     const HRESULT hr = device->CreateDepthStencilView(texture.Get(), &dsv_desc, dsv.GetAddressOf());
     if (FAILED(hr)) [[unlikely]]
     {
-      Log_ErrorFmt("Create DSV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("Create DSV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
       return nullptr;
     }
 
@@ -354,7 +353,7 @@ bool D3D11TextureBuffer::CreateBuffer()
     D3D11Device::GetD3DDevice()->CreateShaderResourceView(m_buffer.GetD3DBuffer(), &srv_desc, m_srv.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateShaderResourceView() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateShaderResourceView() failed: {:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -421,7 +420,7 @@ std::unique_ptr<D3D11DownloadTexture> D3D11DownloadTexture::Create(u32 width, u3
   HRESULT hr = D3D11Device::GetD3DDevice()->CreateTexture2D(&desc, nullptr, tex.GetAddressOf());
   if (FAILED(hr))
   {
-    Log_ErrorFmt("CreateTexture2D() failed: {:08X}", hr);
+    ERROR_LOG("CreateTexture2D() failed: {:08X}", hr);
     return {};
   }
 
@@ -471,7 +470,7 @@ bool D3D11DownloadTexture::Map(u32 x, u32 y, u32 width, u32 height)
   HRESULT hr = D3D11Device::GetD3DContext()->Map(m_texture.Get(), 0, D3D11_MAP_READ, 0, &sr);
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Map() failed: {:08X}", hr);
+    ERROR_LOG("Map() failed: {:08X}", hr);
     return false;
   }
 
@@ -517,6 +516,6 @@ std::unique_ptr<GPUDownloadTexture> D3D11Device::CreateDownloadTexture(u32 width
                                                                        void* memory, size_t memory_size,
                                                                        u32 memory_stride)
 {
-  Log_ErrorPrint("D3D11 cannot import memory for download textures");
+  ERROR_LOG("D3D11 cannot import memory for download textures");
   return {};
 }
diff --git a/src/util/d3d12_builders.cpp b/src/util/d3d12_builders.cpp
index 9edb05ae6..4e5a7329e 100644
--- a/src/util/d3d12_builders.cpp
+++ b/src/util/d3d12_builders.cpp
@@ -34,7 +34,7 @@ Microsoft::WRL::ComPtr<ID3D12PipelineState> D3D12::GraphicsPipelineBuilder::Crea
   HRESULT hr = device->CreateGraphicsPipelineState(&m_desc, IID_PPV_ARGS(ps.GetAddressOf()));
   if (FAILED(hr))
   {
-    Log_ErrorFmt("CreateGraphicsPipelineState() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateGraphicsPipelineState() failed: {:08X}", static_cast<unsigned>(hr));
     return {};
   }
 
@@ -225,7 +225,7 @@ Microsoft::WRL::ComPtr<ID3D12PipelineState> D3D12::ComputePipelineBuilder::Creat
   HRESULT hr = device->CreateComputePipelineState(&m_desc, IID_PPV_ARGS(ps.GetAddressOf()));
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateComputePipelineState() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateComputePipelineState() failed: {:08X}", static_cast<unsigned>(hr));
     return {};
   }
 
diff --git a/src/util/d3d12_descriptor_heap_manager.cpp b/src/util/d3d12_descriptor_heap_manager.cpp
index bd2e54496..b56eab9cc 100644
--- a/src/util/d3d12_descriptor_heap_manager.cpp
+++ b/src/util/d3d12_descriptor_heap_manager.cpp
@@ -14,14 +14,14 @@ D3D12DescriptorHeapManager::~D3D12DescriptorHeapManager() = default;
 bool D3D12DescriptorHeapManager::Create(ID3D12Device* device, D3D12_DESCRIPTOR_HEAP_TYPE type, u32 num_descriptors,
                                         bool shader_visible)
 {
-  D3D12_DESCRIPTOR_HEAP_DESC desc = {type, static_cast<UINT>(num_descriptors),
-                                     shader_visible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE :
-                                                      D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 0u};
+  D3D12_DESCRIPTOR_HEAP_DESC desc = {
+    type, static_cast<UINT>(num_descriptors),
+    shader_visible ? D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE : D3D12_DESCRIPTOR_HEAP_FLAG_NONE, 0u};
 
   HRESULT hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(m_descriptor_heap.ReleaseAndGetAddressOf()));
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateDescriptorHeap() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateDescriptorHeap() failed: {:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -117,7 +117,7 @@ bool D3D12DescriptorAllocator::Create(ID3D12Device* device, D3D12_DESCRIPTOR_HEA
   const HRESULT hr = device->CreateDescriptorHeap(&desc, IID_PPV_ARGS(m_descriptor_heap.ReleaseAndGetAddressOf()));
   if (FAILED(hr))
   {
-    Log_ErrorFmt("CreateDescriptorHeap() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateDescriptorHeap() failed: {:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
diff --git a/src/util/d3d12_device.cpp b/src/util/d3d12_device.cpp
index 46a5455f5..d839dec6a 100644
--- a/src/util/d3d12_device.cpp
+++ b/src/util/d3d12_device.cpp
@@ -89,9 +89,9 @@ D3D12Device::ComPtr<ID3DBlob> D3D12Device::SerializeRootSignature(const D3D12_RO
     D3D12SerializeRootSignature(desc, D3D_ROOT_SIGNATURE_VERSION_1, blob.GetAddressOf(), error_blob.GetAddressOf());
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("D3D12SerializeRootSignature() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("D3D12SerializeRootSignature() failed: {:08X}", static_cast<unsigned>(hr));
     if (error_blob)
-      Log_ErrorPrint(static_cast<const char*>(error_blob->GetBufferPointer()));
+      ERROR_LOG(static_cast<const char*>(error_blob->GetBufferPointer()));
 
     return {};
   }
@@ -110,7 +110,7 @@ D3D12Device::ComPtr<ID3D12RootSignature> D3D12Device::CreateRootSignature(const
     m_device->CreateRootSignature(0, blob->GetBufferPointer(), blob->GetBufferSize(), IID_PPV_ARGS(rs.GetAddressOf()));
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateRootSignature() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateRootSignature() failed: {:08X}", static_cast<unsigned>(hr));
     return {};
   }
 
@@ -142,7 +142,7 @@ bool D3D12Device::CreateDevice(std::string_view adapter, bool threaded_presentat
     }
     else
     {
-      Log_ErrorPrint("Debug layer requested but not available.");
+      ERROR_LOG("Debug layer requested but not available.");
       m_debug_device = false;
     }
   }
@@ -160,7 +160,7 @@ bool D3D12Device::CreateDevice(std::string_view adapter, bool threaded_presentat
   {
     const LUID luid(m_device->GetAdapterLuid());
     if (FAILED(m_dxgi_factory->EnumAdapterByLuid(luid, IID_PPV_ARGS(m_adapter.GetAddressOf()))))
-      Log_ErrorPrint("Failed to get lookup adapter by device LUID");
+      ERROR_LOG("Failed to get lookup adapter by device LUID");
   }
 
   if (m_debug_device)
@@ -303,22 +303,22 @@ bool D3D12Device::ReadPipelineCache(const std::string& filename)
   // Try without the cache data.
   if (data.has_value())
   {
-    Log_WarningFmt("CreatePipelineLibrary() failed, trying without cache data. Error: {}",
-                   Error::CreateHResult(hr).GetDescription());
+    WARNING_LOG("CreatePipelineLibrary() failed, trying without cache data. Error: {}",
+                Error::CreateHResult(hr).GetDescription());
 
     hr = m_device->CreatePipelineLibrary(nullptr, 0, IID_PPV_ARGS(m_pipeline_library.ReleaseAndGetAddressOf()));
     if (SUCCEEDED(hr))
     {
       // Delete cache file, it's no longer relevant.
-      Log_InfoFmt("Deleting pipeline cache file {}", filename);
+      INFO_LOG("Deleting pipeline cache file {}", filename);
       FileSystem::DeleteFile(filename.c_str());
     }
   }
 
   if (FAILED(hr))
   {
-    Log_WarningFmt("CreatePipelineLibrary() failed, pipeline caching will not be available. Error: {}",
-                   Error::CreateHResult(hr).GetDescription());
+    WARNING_LOG("CreatePipelineLibrary() failed, pipeline caching will not be available. Error: {}",
+                Error::CreateHResult(hr).GetDescription());
     return false;
   }
 
@@ -333,7 +333,7 @@ bool D3D12Device::GetPipelineCacheData(DynamicHeapArray<u8>* data)
   const size_t size = m_pipeline_library->GetSerializedSize();
   if (size == 0)
   {
-    Log_WarningPrint("Empty serialized pipeline state returned.");
+    WARNING_LOG("Empty serialized pipeline state returned.");
     return false;
   }
 
@@ -341,7 +341,7 @@ bool D3D12Device::GetPipelineCacheData(DynamicHeapArray<u8>* data)
   const HRESULT hr = m_pipeline_library->Serialize(data->data(), data->size());
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Serialize() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Serialize() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
     data->deallocate();
     return false;
   }
@@ -362,7 +362,7 @@ bool D3D12Device::CreateCommandLists()
                                             IID_PPV_ARGS(res.command_allocators[j].GetAddressOf()));
       if (FAILED(hr))
       {
-        Log_ErrorFmt("CreateCommandAllocator() failed: {:08X}", static_cast<unsigned>(hr));
+        ERROR_LOG("CreateCommandAllocator() failed: {:08X}", static_cast<unsigned>(hr));
         return false;
       }
 
@@ -370,7 +370,7 @@ bool D3D12Device::CreateCommandLists()
                                        IID_PPV_ARGS(res.command_lists[j].GetAddressOf()));
       if (FAILED(hr))
       {
-        Log_ErrorFmt("CreateCommandList() failed: {:08X}", static_cast<unsigned>(hr));
+        ERROR_LOG("CreateCommandList() failed: {:08X}", static_cast<unsigned>(hr));
         return false;
       }
 
@@ -378,7 +378,7 @@ bool D3D12Device::CreateCommandLists()
       hr = res.command_lists[j]->Close();
       if (FAILED(hr))
       {
-        Log_ErrorFmt("Close() failed: {:08X}", static_cast<unsigned>(hr));
+        ERROR_LOG("Close() failed: {:08X}", static_cast<unsigned>(hr));
         return false;
       }
     }
@@ -386,13 +386,13 @@ bool D3D12Device::CreateCommandLists()
     if (!res.descriptor_allocator.Create(m_device.Get(), D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
                                          MAX_DESCRIPTORS_PER_FRAME))
     {
-      Log_ErrorPrint("Failed to create per frame descriptor allocator");
+      ERROR_LOG("Failed to create per frame descriptor allocator");
       return false;
     }
 
     if (!res.sampler_allocator.Create(m_device.Get(), MAX_SAMPLERS_PER_FRAME))
     {
-      Log_ErrorPrint("Failed to create per frame sampler allocator");
+      ERROR_LOG("Failed to create per frame sampler allocator");
       return false;
     }
   }
@@ -439,7 +439,7 @@ void D3D12Device::MoveToNextCommandList()
     }
     else
     {
-      Log_WarningFmt("Map() for timestamp query failed: {:08X}", static_cast<unsigned>(hr));
+      WARNING_LOG("Map() for timestamp query failed: {:08X}", static_cast<unsigned>(hr));
     }
   }
 
@@ -490,7 +490,7 @@ bool D3D12Device::CreateDescriptorHeaps()
 
   if (!m_descriptor_heap_manager.Allocate(&m_null_srv_descriptor))
   {
-    Log_ErrorPrint("Failed to allocate null descriptor");
+    ERROR_LOG("Failed to allocate null descriptor");
     return false;
   }
 
@@ -551,7 +551,7 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
     hr = res.command_lists[0]->Close();
     if (FAILED(hr)) [[unlikely]]
     {
-      Log_ErrorFmt("Closing init command list failed with HRESULT {:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("Closing init command list failed with HRESULT {:08X}", static_cast<unsigned>(hr));
       Panic("TODO cannot continue");
     }
   }
@@ -560,7 +560,7 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
   hr = res.command_lists[1]->Close();
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("Closing main command list failed with HRESULT {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Closing main command list failed with HRESULT {:08X}", static_cast<unsigned>(hr));
     Panic("TODO cannot continue");
   }
 
@@ -592,7 +592,7 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion, const char* reason
   const std::string reason_str(StringUtil::StdStringFromFormatV(reason, ap));
   va_end(ap);
 
-  Log_WarningFmt("Executing command buffer due to '{}'", reason_str);
+  WARNING_LOG("Executing command buffer due to '{}'", reason_str);
   SubmitCommandList(wait_for_completion);
 }
 
@@ -647,7 +647,7 @@ bool D3D12Device::CreateTimestampQuery()
   HRESULT hr = m_device->CreateQueryHeap(&desc, IID_PPV_ARGS(m_timestamp_query_heap.GetAddressOf()));
   if (FAILED(hr))
   {
-    Log_ErrorFmt("CreateQueryHeap() for timestamp failed with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateQueryHeap() for timestamp failed with {:08X}", static_cast<unsigned>(hr));
     m_features.gpu_timing = false;
     return false;
   }
@@ -669,7 +669,7 @@ bool D3D12Device::CreateTimestampQuery()
                                    IID_PPV_ARGS(m_timestamp_query_buffer.GetAddressOf()));
   if (FAILED(hr))
   {
-    Log_ErrorFmt("CreateResource() for timestamp failed with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateResource() for timestamp failed with {:08X}", static_cast<unsigned>(hr));
     m_features.gpu_timing = false;
     return false;
   }
@@ -678,7 +678,7 @@ bool D3D12Device::CreateTimestampQuery()
   hr = m_command_queue->GetTimestampFrequency(&frequency);
   if (FAILED(hr))
   {
-    Log_ErrorFmt("GetTimestampFrequency() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("GetTimestampFrequency() failed: {:08X}", static_cast<unsigned>(hr));
     m_features.gpu_timing = false;
     return false;
   }
@@ -860,12 +860,12 @@ bool D3D12Device::CreateSwapChain()
     fs_desc.Scaling = fullscreen_mode.Scaling;
     fs_desc.Windowed = FALSE;
 
-    Log_VerboseFmt("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
+    VERBOSE_LOG("Creating a {}x{} exclusive fullscreen swap chain", fs_sd_desc.Width, fs_sd_desc.Height);
     hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &fs_sd_desc, &fs_desc,
                                                 fullscreen_output.Get(), m_swap_chain.ReleaseAndGetAddressOf());
     if (FAILED(hr))
     {
-      Log_WarningPrint("Failed to create fullscreen swap chain, trying windowed.");
+      WARNING_LOG("Failed to create fullscreen swap chain, trying windowed.");
       m_is_exclusive_fullscreen = false;
       m_using_allow_tearing = m_allow_tearing_supported;
     }
@@ -873,14 +873,14 @@ bool D3D12Device::CreateSwapChain()
 
   if (!m_is_exclusive_fullscreen)
   {
-    Log_VerboseFmt("Creating a {}x{} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
+    VERBOSE_LOG("Creating a {}x{} windowed swap chain", swap_chain_desc.Width, swap_chain_desc.Height);
     hr = m_dxgi_factory->CreateSwapChainForHwnd(m_command_queue.Get(), window_hwnd, &swap_chain_desc, nullptr, nullptr,
                                                 m_swap_chain.ReleaseAndGetAddressOf());
   }
 
   hr = m_dxgi_factory->MakeWindowAssociation(window_hwnd, DXGI_MWA_NO_WINDOW_CHANGES);
   if (FAILED(hr))
-    Log_WarningPrint("MakeWindowAssociation() to disable ALT+ENTER failed");
+    WARNING_LOG("MakeWindowAssociation() to disable ALT+ENTER failed");
 
   if (!CreateSwapChainRTV())
   {
@@ -908,7 +908,7 @@ bool D3D12Device::CreateSwapChainRTV()
     hr = m_swap_chain->GetBuffer(i, IID_PPV_ARGS(backbuffer.GetAddressOf()));
     if (FAILED(hr))
     {
-      Log_ErrorFmt("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("GetBuffer for RTV failed: 0x{:08X}", static_cast<unsigned>(hr));
       DestroySwapChainRTVs();
       return false;
     }
@@ -918,7 +918,7 @@ bool D3D12Device::CreateSwapChainRTV()
     D3D12DescriptorHandle rtv;
     if (!m_rtv_heap_manager.Allocate(&rtv))
     {
-      Log_ErrorPrint("Failed to allocate RTV handle");
+      ERROR_LOG("Failed to allocate RTV handle");
       DestroySwapChainRTVs();
       return false;
     }
@@ -930,7 +930,7 @@ bool D3D12Device::CreateSwapChainRTV()
   m_window_info.surface_width = swap_chain_desc.BufferDesc.Width;
   m_window_info.surface_height = swap_chain_desc.BufferDesc.Height;
   m_window_info.surface_format = s_swap_chain_format;
-  Log_VerboseFmt("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
+  VERBOSE_LOG("Swap chain buffer size: {}x{}", m_window_info.surface_width, m_window_info.surface_height);
 
   if (m_window_info.type == WindowInfo::Type::Win32)
   {
@@ -1014,7 +1014,7 @@ bool D3D12Device::UpdateWindow()
 
   if (!CreateSwapChain())
   {
-    Log_ErrorPrint("Failed to create swap chain on updated window");
+    ERROR_LOG("Failed to create swap chain on updated window");
     return false;
   }
 
@@ -1040,7 +1040,7 @@ void D3D12Device::ResizeWindow(s32 new_window_width, s32 new_window_height, floa
   HRESULT hr = m_swap_chain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN,
                                            m_using_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0);
   if (FAILED(hr))
-    Log_ErrorFmt("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("ResizeBuffers() failed: 0x{:08X}", static_cast<unsigned>(hr));
 
   if (!CreateSwapChainRTV())
     Panic("Failed to recreate swap chain RTV after resize");
@@ -1096,7 +1096,7 @@ std::optional<float> D3D12Device::GetHostRefreshRate()
     if (SUCCEEDED(m_swap_chain->GetDesc(&desc)) && desc.BufferDesc.RefreshRate.Numerator > 0 &&
         desc.BufferDesc.RefreshRate.Denominator > 0)
     {
-      Log_DevFmt("using fs rr: {} {}", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator);
+      DEV_LOG("using fs rr: {} {}", desc.BufferDesc.RefreshRate.Numerator, desc.BufferDesc.RefreshRate.Denominator);
       return static_cast<float>(desc.BufferDesc.RefreshRate.Numerator) /
              static_cast<float>(desc.BufferDesc.RefreshRate.Denominator);
     }
@@ -1426,25 +1426,25 @@ bool D3D12Device::CreateBuffers()
 {
   if (!m_vertex_buffer.Create(VERTEX_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate vertex buffer");
+    ERROR_LOG("Failed to allocate vertex buffer");
     return false;
   }
 
   if (!m_index_buffer.Create(INDEX_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate index buffer");
+    ERROR_LOG("Failed to allocate index buffer");
     return false;
   }
 
   if (!m_uniform_buffer.Create(VERTEX_UNIFORM_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate uniform buffer");
+    ERROR_LOG("Failed to allocate uniform buffer");
     return false;
   }
 
   if (!m_texture_upload_buffer.Create(TEXTURE_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate texture upload buffer");
+    ERROR_LOG("Failed to allocate texture upload buffer");
     return false;
   }
 
diff --git a/src/util/d3d12_pipeline.cpp b/src/util/d3d12_pipeline.cpp
index 5c7447316..60515189d 100644
--- a/src/util/d3d12_pipeline.cpp
+++ b/src/util/d3d12_pipeline.cpp
@@ -231,7 +231,7 @@ std::unique_ptr<GPUPipeline> D3D12Device::CreatePipeline(const GPUPipeline::Grap
     {
       // E_INVALIDARG = not found.
       if (hr != E_INVALIDARG)
-        Log_ErrorFmt("LoadGraphicsPipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
+        ERROR_LOG("LoadGraphicsPipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
 
       // Need to create it normally.
       pipeline = gpb.Create(m_device.Get(), false);
@@ -241,7 +241,7 @@ std::unique_ptr<GPUPipeline> D3D12Device::CreatePipeline(const GPUPipeline::Grap
       {
         hr = m_pipeline_library->StorePipeline(name.c_str(), pipeline.Get());
         if (FAILED(hr))
-          Log_ErrorFmt("StorePipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
+          ERROR_LOG("StorePipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
       }
     }
   }
diff --git a/src/util/d3d12_stream_buffer.cpp b/src/util/d3d12_stream_buffer.cpp
index 3878a74ac..42b8ac26a 100644
--- a/src/util/d3d12_stream_buffer.cpp
+++ b/src/util/d3d12_stream_buffer.cpp
@@ -38,7 +38,7 @@ bool D3D12StreamBuffer::Create(u32 size)
     IID_PPV_ARGS(buffer.GetAddressOf()));
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateResource() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateResource() failed: {:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -47,7 +47,7 @@ bool D3D12StreamBuffer::Create(u32 size)
   hr = buffer->Map(0, &read_range, reinterpret_cast<void**>(&host_pointer));
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("Map() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Map() failed: {:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -68,8 +68,8 @@ bool D3D12StreamBuffer::ReserveMemory(u32 num_bytes, u32 alignment)
   // Check for sane allocations
   if (num_bytes > m_size) [[unlikely]]
   {
-    Log_ErrorFmt("Attempting to allocate {} bytes from a {} byte stream buffer", static_cast<u32>(num_bytes),
-                 static_cast<u32>(m_size));
+    ERROR_LOG("Attempting to allocate {} bytes from a {} byte stream buffer", static_cast<u32>(num_bytes),
+              static_cast<u32>(m_size));
     Panic("Stream buffer overflow");
   }
 
diff --git a/src/util/d3d12_texture.cpp b/src/util/d3d12_texture.cpp
index fbab84899..28f661015 100644
--- a/src/util/d3d12_texture.cpp
+++ b/src/util/d3d12_texture.cpp
@@ -119,7 +119,7 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
   {
     // OOM isn't fatal.
     if (hr != E_OUTOFMEMORY)
-      Log_ErrorFmt("Create texture failed: 0x{:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("Create texture failed: 0x{:08X}", static_cast<unsigned>(hr));
 
     return {};
   }
@@ -186,7 +186,7 @@ bool D3D12Device::CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32
 {
   if (!m_descriptor_heap_manager.Allocate(dh))
   {
-    Log_ErrorPrint("Failed to allocate SRV descriptor");
+    ERROR_LOG("Failed to allocate SRV descriptor");
     return false;
   }
 
@@ -229,7 +229,7 @@ bool D3D12Device::CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXG
 {
   if (!m_rtv_heap_manager.Allocate(dh))
   {
-    Log_ErrorPrint("Failed to allocate SRV descriptor");
+    ERROR_LOG("Failed to allocate SRV descriptor");
     return false;
   }
 
@@ -244,7 +244,7 @@ bool D3D12Device::CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXG
 {
   if (!m_dsv_heap_manager.Allocate(dh))
   {
-    Log_ErrorPrint("Failed to allocate SRV descriptor");
+    ERROR_LOG("Failed to allocate SRV descriptor");
     return false;
   }
 
@@ -259,7 +259,7 @@ bool D3D12Device::CreateUAVDescriptor(ID3D12Resource* resource, u32 samples, DXG
 {
   if (!m_descriptor_heap_manager.Allocate(dh))
   {
-    Log_ErrorPrint("Failed to allocate UAV descriptor");
+    ERROR_LOG("Failed to allocate UAV descriptor");
     return false;
   }
 
@@ -358,9 +358,9 @@ ID3D12Resource* D3D12Texture::AllocateUploadStagingBuffer(const void* data, u32
   HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource(
     &allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, allocation.GetAddressOf(),
     IID_PPV_ARGS(resource.GetAddressOf()));
-  if (FAILED(hr))[[unlikely]]
+  if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("CreateResource() failed with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("CreateResource() failed with {:08X}", static_cast<unsigned>(hr));
     return nullptr;
   }
 
@@ -368,7 +368,7 @@ ID3D12Resource* D3D12Texture::AllocateUploadStagingBuffer(const void* data, u32
   hr = resource->Map(0, nullptr, &map_ptr);
   if (FAILED(hr)) [[unlikely]]
   {
-    Log_ErrorFmt("Map() failed with {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Map() failed with {:08X}", static_cast<unsigned>(hr));
     return nullptr;
   }
 
@@ -421,7 +421,7 @@ bool D3D12Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
                                                    required_size);
       if (!sbuffer.ReserveMemory(required_size, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT)) [[unlikely]]
       {
-        Log_ErrorFmt("Failed to reserve texture upload memory ({} bytes).", required_size);
+        ERROR_LOG("Failed to reserve texture upload memory ({} bytes).", required_size);
         return false;
       }
     }
@@ -872,7 +872,7 @@ std::unique_ptr<D3D12DownloadTexture> D3D12DownloadTexture::Create(u32 width, u3
     IID_PPV_ARGS(buffer.GetAddressOf()));
   if (FAILED(hr))
   {
-    Log_ErrorFmt("CreateResource() failed with HRESULT {:08X}", hr);
+    ERROR_LOG("CreateResource() failed with HRESULT {:08X}", hr);
     return {};
   }
 
@@ -954,7 +954,7 @@ bool D3D12DownloadTexture::Map(u32 x, u32 y, u32 width, u32 height)
   const HRESULT hr = m_buffer->Map(0, &read_range, reinterpret_cast<void**>(const_cast<u8**>(&m_map_pointer)));
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Map() failed with HRESULT {:08X}", hr);
+    ERROR_LOG("Map() failed with HRESULT {:08X}", hr);
     return false;
   }
 
@@ -1006,6 +1006,6 @@ std::unique_ptr<GPUDownloadTexture> D3D12Device::CreateDownloadTexture(u32 width
                                                                        void* memory, size_t memory_size,
                                                                        u32 memory_stride)
 {
-  Log_ErrorPrint("D3D12 cannot import memory for download textures");
+  ERROR_LOG("D3D12 cannot import memory for download textures");
   return {};
 }
diff --git a/src/util/d3d_common.cpp b/src/util/d3d_common.cpp
index e4385cd89..135e9d36c 100644
--- a/src/util/d3d_common.cpp
+++ b/src/util/d3d_common.cpp
@@ -73,7 +73,7 @@ D3D_FEATURE_LEVEL D3DCommon::GetDeviceMaxFeatureLevel(IDXGIAdapter1* adapter)
                                  requested_feature_levels.data(), static_cast<UINT>(requested_feature_levels.size()),
                                  D3D11_SDK_VERSION, nullptr, &max_supported_level, nullptr);
   if (FAILED(hr))
-    Log_WarningFmt("D3D11CreateDevice() for getting max feature level failed: 0x{:08X}", static_cast<unsigned>(hr));
+    WARNING_LOG("D3D11CreateDevice() for getting max feature level failed: 0x{:08X}", static_cast<unsigned>(hr));
 
   return max_supported_level;
 }
@@ -124,7 +124,7 @@ std::vector<std::string> D3DCommon::GetAdapterNames(IDXGIFactory5* factory)
 
     if (FAILED(hr))
     {
-      Log_ErrorFmt("IDXGIFactory2::EnumAdapters() returned {:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("IDXGIFactory2::EnumAdapters() returned {:08X}", static_cast<unsigned>(hr));
       continue;
     }
 
@@ -146,21 +146,21 @@ std::vector<std::string> D3DCommon::GetFullscreenModes(IDXGIFactory5* factory, s
   Microsoft::WRL::ComPtr<IDXGIOutput> output;
   if (FAILED(hr = adapter->EnumOutputs(0, &output)))
   {
-    Log_ErrorFmt("EnumOutputs() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("EnumOutputs() failed: {:08X}", static_cast<unsigned>(hr));
     return modes;
   }
 
   UINT num_modes = 0;
   if (FAILED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, nullptr)))
   {
-    Log_ErrorFmt("GetDisplayModeList() failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("GetDisplayModeList() failed: {:08X}", static_cast<unsigned>(hr));
     return modes;
   }
 
   std::vector<DXGI_MODE_DESC> dmodes(num_modes);
   if (FAILED(hr = output->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, 0, &num_modes, dmodes.data())))
   {
-    Log_ErrorFmt("GetDisplayModeList() (2) failed: {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("GetDisplayModeList() (2) failed: {:08X}", static_cast<unsigned>(hr));
     return modes;
   }
 
@@ -223,11 +223,11 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
   {
     if (!first_output)
     {
-      Log_ErrorPrint("No DXGI output found. Can't use exclusive fullscreen.");
+      ERROR_LOG("No DXGI output found. Can't use exclusive fullscreen.");
       return false;
     }
 
-    Log_WarningPrint("No DXGI output found for window, using first.");
+    WARNING_LOG("No DXGI output found for window, using first.");
     intersecting_output = std::move(first_output);
   }
 
@@ -241,7 +241,7 @@ bool D3DCommon::GetRequestedExclusiveFullscreenModeDesc(IDXGIFactory5* factory,
   if (FAILED(hr = intersecting_output->FindClosestMatchingMode(&request_mode, fullscreen_mode, nullptr)) ||
       request_mode.Format != format)
   {
-    Log_ErrorFmt("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("Failed to find closest matching mode, hr={:08X}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -268,21 +268,21 @@ Microsoft::WRL::ComPtr<IDXGIAdapter1> D3DCommon::GetAdapterByName(IDXGIFactory5*
 
     if (FAILED(hr))
     {
-      Log_ErrorFmt("IDXGIFactory2::EnumAdapters() returned {:08X}", static_cast<unsigned>(hr));
+      ERROR_LOG("IDXGIFactory2::EnumAdapters() returned {:08X}", static_cast<unsigned>(hr));
       continue;
     }
 
     std::string adapter_name = FixupDuplicateAdapterNames(adapter_names, GetAdapterName(adapter.Get()));
     if (adapter_name == name)
     {
-      Log_VerboseFmt("Found adapter '{}'", adapter_name);
+      VERBOSE_LOG("Found adapter '{}'", adapter_name);
       return adapter;
     }
 
     adapter_names.push_back(std::move(adapter_name));
   }
 
-  Log_ErrorFmt("Adapter '{}' not found.", name);
+  ERROR_LOG("Adapter '{}' not found.", name);
   return {};
 }
 
@@ -291,7 +291,7 @@ Microsoft::WRL::ComPtr<IDXGIAdapter1> D3DCommon::GetFirstAdapter(IDXGIFactory5*
   Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter;
   HRESULT hr = factory->EnumAdapters1(0, adapter.GetAddressOf());
   if (FAILED(hr))
-    Log_ErrorFmt("IDXGIFactory2::EnumAdapters() for first adapter returned {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("IDXGIFactory2::EnumAdapters() for first adapter returned {:08X}", static_cast<unsigned>(hr));
 
   return adapter;
 }
@@ -317,7 +317,7 @@ std::string D3DCommon::GetAdapterName(IDXGIAdapter1* adapter)
   }
   else
   {
-    Log_ErrorFmt("IDXGIAdapter1::GetDesc() returned {:08X}", static_cast<unsigned>(hr));
+    ERROR_LOG("IDXGIAdapter1::GetDesc() returned {:08X}", static_cast<unsigned>(hr));
   }
 
   if (ret.empty())
@@ -429,13 +429,13 @@ std::optional<DynamicHeapArray<u8>> D3DCommon::CompileShader(D3D_FEATURE_LEVEL f
 
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Failed to compile '{}':\n{}", target, error_string);
+    ERROR_LOG("Failed to compile '{}':\n{}", target, error_string);
     GPUDevice::DumpBadShader(source, error_string);
     return {};
   }
 
   if (!error_string.empty())
-    Log_WarningFmt("'{}' compiled with warnings:\n{}", target, error_string);
+    WARNING_LOG("'{}' compiled with warnings:\n{}", target, error_string);
 
   error_blob.Reset();
 
diff --git a/src/util/dinput_source.cpp b/src/util/dinput_source.cpp
index 6504ee5d6..f92a5d851 100644
--- a/src/util/dinput_source.cpp
+++ b/src/util/dinput_source.cpp
@@ -64,7 +64,7 @@ bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
   m_dinput_module = LoadLibraryW(L"dinput8");
   if (!m_dinput_module)
   {
-    Log_ErrorPrint("Failed to load DInput module.");
+    ERROR_LOG("Failed to load DInput module.");
     return false;
   }
 
@@ -74,7 +74,7 @@ bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
     reinterpret_cast<PFNGETDFDIJOYSTICK>(GetProcAddress(m_dinput_module, "GetdfDIJoystick"));
   if (!create || !get_joystick_data_format)
   {
-    Log_ErrorPrint("Failed to get DInput function pointers.");
+    ERROR_LOG("Failed to get DInput function pointers.");
     return false;
   }
 
@@ -83,7 +83,7 @@ bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
   m_joystick_data_format = get_joystick_data_format();
   if (FAILED(hr) || !m_joystick_data_format)
   {
-    Log_ErrorFmt("DirectInput8Create() failed: {}", static_cast<unsigned>(hr));
+    ERROR_LOG("DirectInput8Create() failed: {}", static_cast<unsigned>(hr));
     return false;
   }
 
@@ -94,7 +94,7 @@ bool DInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
 
   if (!toplevel_wi.has_value() || toplevel_wi->type != WindowInfo::Type::Win32)
   {
-    Log_ErrorPrint("Missing top level window, cannot add DInput devices.");
+    ERROR_LOG("Missing top level window, cannot add DInput devices.");
     return false;
   }
 
@@ -123,7 +123,7 @@ bool DInputSource::ReloadDevices()
   std::vector<DIDEVICEINSTANCEW> devices;
   m_dinput->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumCallback, &devices, DIEDFL_ATTACHEDONLY);
 
-  Log_VerboseFmt("Enumerated {} devices", devices.size());
+  VERBOSE_LOG("Enumerated {} devices", devices.size());
 
   bool changed = false;
   for (DIDEVICEINSTANCEW inst : devices)
@@ -141,9 +141,9 @@ bool DInputSource::ReloadDevices()
     HRESULT hr = m_dinput->CreateDevice(inst.guidInstance, cd.device.GetAddressOf(), nullptr);
     if (FAILED(hr)) [[unlikely]]
     {
-      Log_WarningFmt("Failed to create instance of device [{}, {}]",
-                     StringUtil::WideStringToUTF8String(inst.tszProductName),
-                     StringUtil::WideStringToUTF8String(inst.tszInstanceName));
+      WARNING_LOG("Failed to create instance of device [{}, {}]",
+                  StringUtil::WideStringToUTF8String(inst.tszProductName),
+                  StringUtil::WideStringToUTF8String(inst.tszInstanceName));
       continue;
     }
 
@@ -179,24 +179,24 @@ bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
     hr = cd.device->SetCooperativeLevel(m_toplevel_window, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE);
     if (FAILED(hr))
     {
-      Log_ErrorFmt("Failed to set cooperative level for '{}'", name);
+      ERROR_LOG("Failed to set cooperative level for '{}'", name);
       return false;
     }
 
-    Log_WarningFmt("Failed to set exclusive mode for '{}'", name);
+    WARNING_LOG("Failed to set exclusive mode for '{}'", name);
   }
 
   hr = cd.device->SetDataFormat(m_joystick_data_format);
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Failed to set data format for '{}'", name);
+    ERROR_LOG("Failed to set data format for '{}'", name);
     return false;
   }
 
   hr = cd.device->Acquire();
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Failed to acquire device '{}'", name);
+    ERROR_LOG("Failed to acquire device '{}'", name);
     return false;
   }
 
@@ -205,7 +205,7 @@ bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
   hr = cd.device->GetCapabilities(&caps);
   if (FAILED(hr))
   {
-    Log_ErrorFmt("Failed to get capabilities for '{}'", name);
+    ERROR_LOG("Failed to get capabilities for '{}'", name);
     return false;
   }
 
@@ -239,14 +239,14 @@ bool DInputSource::AddDevice(ControllerData& cd, const std::string& name)
   if (hr == DI_NOEFFECT)
     cd.needs_poll = false;
   else if (hr != DI_OK)
-    Log_WarningFmt("Polling device '{}' failed: {:08X}", name, static_cast<unsigned>(hr));
+    WARNING_LOG("Polling device '{}' failed: {:08X}", name, static_cast<unsigned>(hr));
 
   hr = cd.device->GetDeviceState(sizeof(cd.last_state), &cd.last_state);
   if (hr != DI_OK)
-    Log_WarningFmt("GetDeviceState() for '{}' failed: {:08X}", name, static_cast<unsigned>(hr));
+    WARNING_LOG("GetDeviceState() for '{}' failed: {:08X}", name, static_cast<unsigned>(hr));
 
-  Log_InfoFmt("{} has {} buttons, {} axes, {} hats", name, cd.num_buttons, static_cast<u32>(cd.axis_offsets.size()),
-              cd.num_hats);
+  INFO_LOG("{} has {} buttons, {} axes, {} hats", name, cd.num_buttons, static_cast<u32>(cd.axis_offsets.size()),
+           cd.num_hats);
 
   return (cd.num_buttons > 0 || !cd.axis_offsets.empty() || cd.num_hats > 0);
 }
@@ -278,7 +278,7 @@ void DInputSource::PollEvents()
     }
     else if (hr != DI_OK)
     {
-      Log_WarningFmt("GetDeviceState() failed: {:08X}", static_cast<unsigned>(hr));
+      WARNING_LOG("GetDeviceState() failed: {:08X}", static_cast<unsigned>(hr));
       i++;
       continue;
     }
diff --git a/src/util/gpu_device.cpp b/src/util/gpu_device.cpp
index 4e06b48e5..d64f0615f 100644
--- a/src/util/gpu_device.cpp
+++ b/src/util/gpu_device.cpp
@@ -241,7 +241,7 @@ RenderAPI GPUDevice::GetPreferredAPI()
     preferred_renderer = RenderAPI::Vulkan;
 #else
     // Uhhh, what?
-    Log_ErrorPrint("Somehow don't have any renderers available...");
+    ERROR_LOG("Somehow don't have any renderers available...");
     preferred_renderer = RenderAPI::None;
 #endif
   }
@@ -296,7 +296,7 @@ bool GPUDevice::Create(std::string_view adapter, std::string_view shader_cache_p
     return false;
   }
 
-  Log_InfoFmt("Graphics Driver Info:\n{}", GetDriverInfo());
+  INFO_LOG("Graphics Driver Info:\n{}", GetDriverInfo());
 
   OpenShaderCache(shader_cache_path, shader_cache_version);
 
@@ -332,9 +332,9 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
     const std::string filename = Path::Combine(base_path, basename);
     if (!m_shader_cache.Open(filename.c_str(), version))
     {
-      Log_WarningPrint("Failed to open shader cache. Creating new cache.");
+      WARNING_LOG("Failed to open shader cache. Creating new cache.");
       if (!m_shader_cache.Create())
-        Log_ErrorPrint("Failed to create new shader cache.");
+        ERROR_LOG("Failed to create new shader cache.");
 
       // Squish the pipeline cache too, it's going to be stale.
       if (m_features.pipeline_cache)
@@ -343,7 +343,7 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
           Path::Combine(base_path, TinyString::from_format("{}.bin", GetShaderCacheBaseName("pipelines")));
         if (FileSystem::FileExists(pc_filename.c_str()))
         {
-          Log_InfoFmt("Removing old pipeline cache '{}'", Path::GetFileName(pc_filename));
+          INFO_LOG("Removing old pipeline cache '{}'", Path::GetFileName(pc_filename));
           FileSystem::DeleteFile(pc_filename.c_str());
         }
       }
@@ -363,7 +363,7 @@ void GPUDevice::OpenShaderCache(std::string_view base_path, u32 version)
     if (ReadPipelineCache(filename))
       s_pipeline_cache_path = std::move(filename);
     else
-      Log_WarningPrint("Failed to read pipeline cache.");
+      WARNING_LOG("Failed to read pipeline cache.");
   }
 }
 
@@ -380,14 +380,13 @@ void GPUDevice::CloseShaderCache()
       FILESYSTEM_STAT_DATA sd;
       if (!FileSystem::StatFile(s_pipeline_cache_path.c_str(), &sd) || sd.Size != static_cast<s64>(data.size()))
       {
-        Log_InfoFmt("Writing {} bytes to '{}'", data.size(), Path::GetFileName(s_pipeline_cache_path));
+        INFO_LOG("Writing {} bytes to '{}'", data.size(), Path::GetFileName(s_pipeline_cache_path));
         if (!FileSystem::WriteBinaryFile(s_pipeline_cache_path.c_str(), data.data(), data.size()))
-          Log_ErrorFmt("Failed to write pipeline cache to '{}'", Path::GetFileName(s_pipeline_cache_path));
+          ERROR_LOG("Failed to write pipeline cache to '{}'", Path::GetFileName(s_pipeline_cache_path));
       }
       else
       {
-        Log_InfoFmt("Skipping updating pipeline cache '{}' due to no changes.",
-                    Path::GetFileName(s_pipeline_cache_path));
+        INFO_LOG("Skipping updating pipeline cache '{}' due to no changes.", Path::GetFileName(s_pipeline_cache_path));
       }
     }
 
@@ -455,7 +454,7 @@ bool GPUDevice::AcquireWindow(bool recreate_window)
   if (!wi.has_value())
     return false;
 
-  Log_InfoFmt("Render window is {}x{}.", wi->surface_width, wi->surface_height);
+  INFO_LOG("Render window is {}x{}.", wi->surface_width, wi->surface_height);
   m_window_info = wi.value();
   return true;
 }
@@ -506,7 +505,7 @@ bool GPUDevice::CreateResources()
   m_imgui_pipeline = CreatePipeline(plconfig);
   if (!m_imgui_pipeline)
   {
-    Log_ErrorPrint("Failed to compile ImGui pipeline.");
+    ERROR_LOG("Failed to compile ImGui pipeline.");
     return false;
   }
   GL_OBJECT_NAME(m_imgui_pipeline, "ImGui Pipeline");
@@ -666,7 +665,7 @@ std::unique_ptr<GPUShader> GPUDevice::CreateShader(GPUShaderStage stage, std::st
     if (shader)
       return shader;
 
-    Log_ErrorPrint("Failed to create shader from binary (driver changed?). Clearing cache.");
+    ERROR_LOG("Failed to create shader from binary (driver changed?). Clearing cache.");
     m_shader_cache.Clear();
   }
 
@@ -874,7 +873,7 @@ std::unique_ptr<GPUTexture> GPUDevice::FetchTexture(u32 width, u32 height, u32 l
     else
     {
       // This shouldn't happen...
-      Log_ErrorFmt("Failed to upload {}x{} to pooled texture", width, height);
+      ERROR_LOG("Failed to upload {}x{} to pooled texture", width, height);
     }
   }
 
@@ -913,7 +912,7 @@ void GPUDevice::RecycleTexture(std::unique_ptr<GPUTexture> texture)
   const u32 max_size = is_texture ? MAX_TEXTURE_POOL_SIZE : MAX_TARGET_POOL_SIZE;
   while (pool.size() > max_size)
   {
-    Log_ProfileFmt("Trim {}x{} texture from pool", pool.front().texture->GetWidth(), pool.front().texture->GetHeight());
+    DEBUG_LOG("Trim {}x{} texture from pool", pool.front().texture->GetWidth(), pool.front().texture->GetHeight());
     pool.pop_front();
   }
 }
@@ -931,8 +930,8 @@ void GPUDevice::TrimTexturePool()
   GL_INS_FMT("Target Pool Size: {}", m_target_pool.size());
   GL_INS_FMT("VRAM Usage: {:.2f} MB", s_total_vram_usage / 1048576.0);
 
-  Log_DebugFmt("Texture Pool Size: {} Target Pool Size: {} VRAM: {:.2f} MB", m_texture_pool.size(),
-               m_target_pool.size(), s_total_vram_usage / 1048756.0);
+  DEBUG_LOG("Texture Pool Size: {} Target Pool Size: {} VRAM: {:.2f} MB", m_texture_pool.size(), m_target_pool.size(),
+            s_total_vram_usage / 1048756.0);
 
   if (m_texture_pool.empty() && m_target_pool.empty())
     return;
@@ -947,7 +946,7 @@ void GPUDevice::TrimTexturePool()
       if (delta < POOL_PURGE_DELAY)
         break;
 
-      Log_ProfileFmt("Trim {}x{} texture from pool", it->texture->GetWidth(), it->texture->GetHeight());
+      DEBUG_LOG("Trim {}x{} texture from pool", it->texture->GetWidth(), it->texture->GetHeight());
       it = pool.erase(it);
     }
   }
@@ -990,7 +989,7 @@ bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u
   std::unique_ptr<GPUTexture> new_tex = FetchTexture(new_width, new_height, 1, 1, 1, type, format);
   if (!new_tex) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to create new {}x{} texture", new_width, new_height);
+    ERROR_LOG("Failed to create new {}x{} texture", new_width, new_height);
     return false;
   }
 
@@ -1157,14 +1156,14 @@ bool dyn_shaderc::Open()
 #endif
   if (!s_library.Open(libname.c_str(), &error))
   {
-    Log_ErrorFmt("Failed to load shaderc: {}", error.GetDescription());
+    ERROR_LOG("Failed to load shaderc: {}", error.GetDescription());
     return false;
   }
 
 #define LOAD_FUNC(F)                                                                                                   \
   if (!s_library.GetSymbol(#F, &F))                                                                                    \
   {                                                                                                                    \
-    Log_ErrorFmt("Failed to find function {}", #F);                                                                    \
+    ERROR_LOG("Failed to find function {}", #F);                                                                       \
     Close();                                                                                                           \
     return false;                                                                                                      \
   }
@@ -1175,7 +1174,7 @@ bool dyn_shaderc::Open()
   s_compiler = shaderc_compiler_initialize();
   if (!s_compiler)
   {
-    Log_ErrorPrint("shaderc_compiler_initialize() failed");
+    ERROR_LOG("shaderc_compiler_initialize() failed");
     Close();
     return false;
   }
@@ -1233,15 +1232,15 @@ bool GPUDevice::CompileGLSLShaderToVulkanSpv(GPUShaderStage stage, std::string_v
   {
     const std::string_view errors(result ? dyn_shaderc::shaderc_result_get_error_message(result) :
                                            "null result object");
-    Log_ErrorFmt("Failed to compile shader to SPIR-V: {}\n{}",
-                 dyn_shaderc::shaderc_compilation_status_to_string(status), errors);
+    ERROR_LOG("Failed to compile shader to SPIR-V: {}\n{}", dyn_shaderc::shaderc_compilation_status_to_string(status),
+              errors);
     DumpBadShader(source, errors);
   }
   else
   {
     const size_t num_warnings = dyn_shaderc::shaderc_result_get_num_warnings(result);
     if (num_warnings > 0)
-      Log_WarningFmt("Shader compiled with warnings:\n{}", dyn_shaderc::shaderc_result_get_error_message(result));
+      WARNING_LOG("Shader compiled with warnings:\n{}", dyn_shaderc::shaderc_result_get_error_message(result));
 
     const size_t spirv_size = dyn_shaderc::shaderc_result_get_length(result);
     DebugAssert(spirv_size > 0);
diff --git a/src/util/gpu_shader_cache.cpp b/src/util/gpu_shader_cache.cpp
index a83c97606..a92d2bcde 100644
--- a/src/util/gpu_shader_cache.cpp
+++ b/src/util/gpu_shader_cache.cpp
@@ -98,7 +98,7 @@ void GPUShaderCache::Clear()
 
   Close();
 
-  Log_WarningFmt("Clearing shader cache at {}.", Path::GetFileName(m_base_filename));
+  WARNING_LOG("Clearing shader cache at {}.", Path::GetFileName(m_base_filename));
 
   const std::string index_filename = fmt::format("{}.idx", m_base_filename);
   const std::string blob_filename = fmt::format("{}.bin", m_base_filename);
@@ -109,25 +109,25 @@ bool GPUShaderCache::CreateNew(const std::string& index_filename, const std::str
 {
   if (FileSystem::FileExists(index_filename.c_str()))
   {
-    Log_WarningFmt("Removing existing index file '{}'", Path::GetFileName(index_filename));
+    WARNING_LOG("Removing existing index file '{}'", Path::GetFileName(index_filename));
     FileSystem::DeleteFile(index_filename.c_str());
   }
   if (FileSystem::FileExists(blob_filename.c_str()))
   {
-    Log_WarningFmt("Removing existing blob file '{}'", Path::GetFileName(blob_filename));
+    WARNING_LOG("Removing existing blob file '{}'", Path::GetFileName(blob_filename));
     FileSystem::DeleteFile(blob_filename.c_str());
   }
 
   m_index_file = FileSystem::OpenCFile(index_filename.c_str(), "wb");
   if (!m_index_file) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to open index file '{}' for writing", Path::GetFileName(index_filename));
+    ERROR_LOG("Failed to open index file '{}' for writing", Path::GetFileName(index_filename));
     return false;
   }
 
   if (std::fwrite(&m_version, sizeof(m_version), 1, m_index_file) != 1) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to write version to index file '{}'", Path::GetFileName(index_filename));
+    ERROR_LOG("Failed to write version to index file '{}'", Path::GetFileName(index_filename));
     std::fclose(m_index_file);
     m_index_file = nullptr;
     FileSystem::DeleteFile(index_filename.c_str());
@@ -137,7 +137,7 @@ bool GPUShaderCache::CreateNew(const std::string& index_filename, const std::str
   m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "w+b");
   if (!m_blob_file) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to open blob file '{}' for writing", Path::GetFileName(blob_filename));
+    ERROR_LOG("Failed to open blob file '{}' for writing", Path::GetFileName(blob_filename));
     std::fclose(m_index_file);
     m_index_file = nullptr;
     FileSystem::DeleteFile(index_filename.c_str());
@@ -156,7 +156,7 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::
     // we don't want to blow away the cache. so just continue without a cache.
     if (errno == EACCES)
     {
-      Log_WarningPrint("Failed to open shader cache index with EACCES, are you running two instances?");
+      WARNING_LOG("Failed to open shader cache index with EACCES, are you running two instances?");
       return true;
     }
 
@@ -166,7 +166,7 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::
   u32 file_version = 0;
   if (std::fread(&file_version, sizeof(file_version), 1, m_index_file) != 1 || file_version != m_version) [[unlikely]]
   {
-    Log_ErrorFmt("Bad file/data version in '{}'", Path::GetFileName(index_filename));
+    ERROR_LOG("Bad file/data version in '{}'", Path::GetFileName(index_filename));
     std::fclose(m_index_file);
     m_index_file = nullptr;
     return false;
@@ -175,7 +175,7 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::
   m_blob_file = FileSystem::OpenCFile(blob_filename.c_str(), "a+b");
   if (!m_blob_file) [[unlikely]]
   {
-    Log_ErrorFmt("Blob file '{}' is missing", Path::GetFileName(blob_filename));
+    ERROR_LOG("Blob file '{}' is missing", Path::GetFileName(blob_filename));
     std::fclose(m_index_file);
     m_index_file = nullptr;
     return false;
@@ -193,7 +193,7 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::
       if (std::feof(m_index_file))
         break;
 
-      Log_ErrorFmt("Failed to read entry from '{}', corrupt file?", Path::GetFileName(index_filename));
+      ERROR_LOG("Failed to read entry from '{}', corrupt file?", Path::GetFileName(index_filename));
       m_index.clear();
       std::fclose(m_blob_file);
       m_blob_file = nullptr;
@@ -211,7 +211,7 @@ bool GPUShaderCache::ReadExisting(const std::string& index_filename, const std::
   // ensure we don't write before seeking
   std::fseek(m_index_file, 0, SEEK_END);
 
-  Log_DevFmt("Read {} entries from '{}'", m_index.size(), Path::GetFileName(index_filename));
+  DEV_LOG("Read {} entries from '{}'", m_index.size(), Path::GetFileName(index_filename));
   return true;
 }
 
@@ -260,8 +260,8 @@ bool GPUShaderCache::Lookup(const CacheIndexKey& key, ShaderBinary* binary)
   if (std::fseek(m_blob_file, iter->second.file_offset, SEEK_SET) != 0 ||
       std::fread(compressed_data.data(), iter->second.compressed_size, 1, m_blob_file) != 1) [[unlikely]]
   {
-    Log_ErrorFmt("Read {} byte {} shader from file failed", iter->second.compressed_size,
-                 GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)));
+    ERROR_LOG("Read {} byte {} shader from file failed", iter->second.compressed_size,
+              GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)));
     return false;
   }
 
@@ -269,7 +269,7 @@ bool GPUShaderCache::Lookup(const CacheIndexKey& key, ShaderBinary* binary)
     ZSTD_decompress(binary->data(), binary->size(), compressed_data.data(), compressed_data.size());
   if (ZSTD_isError(decompress_result)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to decompress shader: {}", ZSTD_getErrorName(decompress_result));
+    ERROR_LOG("Failed to decompress shader: {}", ZSTD_getErrorName(decompress_result));
     return false;
   }
 
@@ -282,7 +282,7 @@ bool GPUShaderCache::Insert(const CacheIndexKey& key, const void* data, u32 data
   const size_t compress_result = ZSTD_compress(compress_buffer.data(), compress_buffer.size(), data, data_size, 0);
   if (ZSTD_isError(compress_result)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to compress shader: {}", ZSTD_getErrorName(compress_result));
+    ERROR_LOG("Failed to compress shader: {}", ZSTD_getErrorName(compress_result));
     return false;
   }
 
@@ -308,13 +308,13 @@ bool GPUShaderCache::Insert(const CacheIndexKey& key, const void* data, u32 data
   if (std::fwrite(compress_buffer.data(), compress_result, 1, m_blob_file) != 1 || std::fflush(m_blob_file) != 0 ||
       std::fwrite(&entry, sizeof(entry), 1, m_index_file) != 1 || std::fflush(m_index_file) != 0) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to write {} byte {} shader blob to file", data_size,
-                 GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)));
+    ERROR_LOG("Failed to write {} byte {} shader blob to file", data_size,
+              GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)));
     return false;
   }
 
-  Log_DevFmt("Cached compressed {} shader: {} -> {} bytes",
-             GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)), data_size, compress_result);
+  DEV_LOG("Cached compressed {} shader: {} -> {} bytes",
+          GPUShader::GetStageName(static_cast<GPUShaderStage>(key.shader_type)), data_size, compress_result);
   m_index.emplace(key, idata);
   return true;
 }
diff --git a/src/util/gpu_texture.cpp b/src/util/gpu_texture.cpp
index f2e540c52..da8e8588f 100644
--- a/src/util/gpu_texture.cpp
+++ b/src/util/gpu_texture.cpp
@@ -190,39 +190,39 @@ bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u
 {
   if (width > MAX_WIDTH || height > MAX_HEIGHT || layers > MAX_LAYERS || levels > MAX_LEVELS || samples > MAX_SAMPLES)
   {
-    Log_ErrorFmt("Invalid dimensions: {}x{}x{} {} {}.", width, height, layers, levels, samples);
+    ERROR_LOG("Invalid dimensions: {}x{}x{} {} {}.", width, height, layers, levels, samples);
     return false;
   }
 
   const u32 max_texture_size = g_gpu_device->GetMaxTextureSize();
   if (width > max_texture_size || height > max_texture_size)
   {
-    Log_ErrorFmt("Texture width ({}) or height ({}) exceeds max texture size ({}).", width, height, max_texture_size);
+    ERROR_LOG("Texture width ({}) or height ({}) exceeds max texture size ({}).", width, height, max_texture_size);
     return false;
   }
 
   const u32 max_samples = g_gpu_device->GetMaxMultisamples();
   if (samples > max_samples)
   {
-    Log_ErrorFmt("Texture samples ({}) exceeds max samples ({}).", samples, max_samples);
+    ERROR_LOG("Texture samples ({}) exceeds max samples ({}).", samples, max_samples);
     return false;
   }
 
   if (samples > 1 && levels > 1)
   {
-    Log_ErrorPrint("Multisampled textures can't have mip levels.");
+    ERROR_LOG("Multisampled textures can't have mip levels.");
     return false;
   }
 
   if (layers > 1 && type != Type::Texture && type != Type::DynamicTexture)
   {
-    Log_ErrorPrint("Texture arrays are not supported on targets.");
+    ERROR_LOG("Texture arrays are not supported on targets.");
     return false;
   }
 
   if (levels > 1 && type != Type::Texture && type != Type::DynamicTexture)
   {
-    Log_ErrorPrint("Mipmaps are not supported on targets.");
+    ERROR_LOG("Mipmaps are not supported on targets.");
     return false;
   }
 
@@ -317,7 +317,7 @@ bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u3
     }
 
     default:
-      [[unlikely]] Log_ErrorFmt("Unknown pixel format {}", static_cast<u32>(format));
+      [[unlikely]] ERROR_LOG("Unknown pixel format {}", static_cast<u32>(format));
       return false;
   }
 }
diff --git a/src/util/host.cpp b/src/util/host.cpp
index 8429bd7e0..423370689 100644
--- a/src/util/host.cpp
+++ b/src/util/host.cpp
@@ -73,7 +73,7 @@ add_string:
          Internal::GetTranslatedStringImpl(context, msg, &s_translation_string_cache[s_translation_string_cache_pos],
                                            TRANSLATION_STRING_CACHE_SIZE - 1 - s_translation_string_cache_pos)) < 0)
   {
-    Log_ErrorPrint("WARNING: Clearing translation string cache, it might need to be larger.");
+    ERROR_LOG("WARNING: Clearing translation string cache, it might need to be larger.");
     s_translation_string_cache_pos = 0;
     if ((len =
            Internal::GetTranslatedStringImpl(context, msg, &s_translation_string_cache[s_translation_string_cache_pos],
diff --git a/src/util/http_downloader.cpp b/src/util/http_downloader.cpp
index 479bc4cdb..7e2e8903b 100644
--- a/src/util/http_downloader.cpp
+++ b/src/util/http_downloader.cpp
@@ -103,7 +103,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
         Common::Timer::ConvertValueToSeconds(current_time - req->start_time) >= m_timeout)
     {
       // request timed out
-      Log_ErrorFmt("Request for '{}' timed out", req->url);
+      ERROR_LOG("Request for '{}' timed out", req->url);
 
       req->state.store(Request::State::Cancelled);
       m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
@@ -120,7 +120,7 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
              req->progress->IsCancelled())
     {
       // request timed out
-      Log_ErrorFmt("Request for '{}' cancelled", req->url);
+      ERROR_LOG("Request for '{}' cancelled", req->url);
 
       req->state.store(Request::State::Cancelled);
       m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
@@ -153,8 +153,8 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
     }
 
     // request complete
-    Log_VerboseFmt("Request for '{}' complete, returned status code {} and {} bytes", req->url, req->status_code,
-                   req->data.size());
+    VERBOSE_LOG("Request for '{}' complete, returned status code {} and {} bytes", req->url, req->status_code,
+                req->data.size());
     m_pending_http_requests.erase(m_pending_http_requests.begin() + index);
 
     // run callback with lock unheld
diff --git a/src/util/http_downloader_curl.cpp b/src/util/http_downloader_curl.cpp
index 70d53e96e..473e543e8 100644
--- a/src/util/http_downloader_curl.cpp
+++ b/src/util/http_downloader_curl.cpp
@@ -53,7 +53,7 @@ bool HTTPDownloaderCurl::Initialize(std::string user_agent)
     });
     if (!s_curl_initialized)
     {
-      Log_ErrorPrint("curl_global_init() failed");
+      ERROR_LOG("curl_global_init() failed");
       return false;
     }
   }
@@ -61,7 +61,7 @@ bool HTTPDownloaderCurl::Initialize(std::string user_agent)
   m_multi_handle = curl_multi_init();
   if (!m_multi_handle)
   {
-    Log_ErrorPrint("curl_multi_init() failed");
+    ERROR_LOG("curl_multi_init() failed");
     return false;
   }
 
@@ -111,12 +111,12 @@ void HTTPDownloaderCurl::InternalPollRequests()
   sigemptyset(&new_block_mask);
   sigaddset(&new_block_mask, SIGPIPE);
   if (pthread_sigmask(SIG_BLOCK, &new_block_mask, &old_block_mask) != 0)
-    Log_WarningPrint("Failed to block SIGPIPE");
+    WARNING_LOG("Failed to block SIGPIPE");
 
   int running_handles;
   const CURLMcode err = curl_multi_perform(m_multi_handle, &running_handles);
   if (err != CURLM_OK)
-    Log_ErrorFmt("curl_multi_perform() returned {}", static_cast<int>(err));
+    ERROR_LOG("curl_multi_perform() returned {}", static_cast<int>(err));
 
   for (;;)
   {
@@ -127,14 +127,14 @@ void HTTPDownloaderCurl::InternalPollRequests()
 
     if (msg->msg != CURLMSG_DONE)
     {
-      Log_WarningFmt("Unexpected multi message {}", static_cast<int>(msg->msg));
+      WARNING_LOG("Unexpected multi message {}", static_cast<int>(msg->msg));
       continue;
     }
 
     Request* req;
     if (curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &req) != CURLE_OK)
     {
-      Log_ErrorPrint("curl_easy_getinfo() failed");
+      ERROR_LOG("curl_easy_getinfo() failed");
       continue;
     }
 
@@ -148,18 +148,18 @@ void HTTPDownloaderCurl::InternalPollRequests()
       if (curl_easy_getinfo(req->handle, CURLINFO_CONTENT_TYPE, &content_type) == CURLE_OK && content_type)
         req->content_type = content_type;
 
-      Log_DevFmt("Request for '{}' returned status code {} and {} bytes", req->url, req->status_code, req->data.size());
+      DEV_LOG("Request for '{}' returned status code {} and {} bytes", req->url, req->status_code, req->data.size());
     }
     else
     {
-      Log_ErrorFmt("Request for '{}' returned error {}", req->url, static_cast<int>(msg->data.result));
+      ERROR_LOG("Request for '{}' returned error {}", req->url, static_cast<int>(msg->data.result));
     }
 
     req->state.store(Request::State::Complete, std::memory_order_release);
   }
 
   if (pthread_sigmask(SIG_UNBLOCK, &new_block_mask, &old_block_mask) != 0)
-    Log_WarningPrint("Failed to unblock SIGPIPE");
+    WARNING_LOG("Failed to unblock SIGPIPE");
 }
 
 bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
@@ -179,14 +179,14 @@ bool HTTPDownloaderCurl::StartRequest(HTTPDownloader::Request* request)
     curl_easy_setopt(req->handle, CURLOPT_POSTFIELDS, request->post_data.c_str());
   }
 
-  Log_DevFmt("Started HTTP request for '{}'", req->url);
+  DEV_LOG("Started HTTP request for '{}'", req->url);
   req->state.store(Request::State::Started, std::memory_order_release);
   req->start_time = Common::Timer::GetCurrentValue();
 
   const CURLMcode err = curl_multi_add_handle(m_multi_handle, req->handle);
   if (err != CURLM_OK)
   {
-    Log_ErrorFmt("curl_multi_add_handle() returned {}", static_cast<int>(err));
+    ERROR_LOG("curl_multi_add_handle() returned {}", static_cast<int>(err));
     req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
     curl_easy_cleanup(req->handle);
     delete req;
diff --git a/src/util/http_downloader_winhttp.cpp b/src/util/http_downloader_winhttp.cpp
index c8a593694..c440649dc 100644
--- a/src/util/http_downloader_winhttp.cpp
+++ b/src/util/http_downloader_winhttp.cpp
@@ -42,7 +42,7 @@ bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
                            WINHTTP_FLAG_ASYNC);
   if (m_hSession == NULL)
   {
-    Log_ErrorFmt("WinHttpOpen() failed: {}", GetLastError());
+    ERROR_LOG("WinHttpOpen() failed: {}", GetLastError());
     return false;
   }
 
@@ -51,7 +51,7 @@ bool HTTPDownloaderWinHttp::Initialize(std::string user_agent)
   if (WinHttpSetStatusCallback(m_hSession, HTTPStatusCallback, notification_flags, NULL) ==
       WINHTTP_INVALID_STATUS_CALLBACK)
   {
-    Log_ErrorFmt("WinHttpSetStatusCallback() failed: {}", GetLastError());
+    ERROR_LOG("WinHttpSetStatusCallback() failed: {}", GetLastError());
     return false;
   }
 
@@ -89,17 +89,17 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
     case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
     {
       const WINHTTP_ASYNC_RESULT* res = reinterpret_cast<const WINHTTP_ASYNC_RESULT*>(lpvStatusInformation);
-      Log_ErrorFmt("WinHttp async function {} returned error {}", res->dwResult, res->dwError);
+      ERROR_LOG("WinHttp async function {} returned error {}", res->dwResult, res->dwError);
       req->status_code = HTTP_STATUS_ERROR;
       req->state.store(Request::State::Complete);
       return;
     }
     case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE:
     {
-      Log_DevPrint("SendRequest complete");
+      DEV_LOG("SendRequest complete");
       if (!WinHttpReceiveResponse(hRequest, nullptr))
       {
-        Log_ErrorFmt("WinHttpReceiveResponse() failed: {}", GetLastError());
+        ERROR_LOG("WinHttpReceiveResponse() failed: {}", GetLastError());
         req->status_code = HTTP_STATUS_ERROR;
         req->state.store(Request::State::Complete);
       }
@@ -108,13 +108,13 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
     }
     case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
     {
-      Log_DevPrint("Headers available");
+      DEV_LOG("Headers available");
 
       DWORD buffer_size = sizeof(req->status_code);
       if (!WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
                                WINHTTP_HEADER_NAME_BY_INDEX, &req->status_code, &buffer_size, WINHTTP_NO_HEADER_INDEX))
       {
-        Log_ErrorFmt("WinHttpQueryHeaders() for status code failed: {}", GetLastError());
+        ERROR_LOG("WinHttpQueryHeaders() for status code failed: {}", GetLastError());
         req->status_code = HTTP_STATUS_ERROR;
         req->state.store(Request::State::Complete);
         return;
@@ -126,7 +126,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
                                WINHTTP_NO_HEADER_INDEX))
       {
         if (GetLastError() != ERROR_WINHTTP_HEADER_NOT_FOUND)
-          Log_WarningFmt("WinHttpQueryHeaders() for content length failed: {}", GetLastError());
+          WARNING_LOG("WinHttpQueryHeaders() for content length failed: {}", GetLastError());
 
         req->content_length = 0;
       }
@@ -145,14 +145,14 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
         }
       }
 
-      Log_DevFmt("Status code {}, content-length is {}", req->status_code, req->content_length);
+      DEV_LOG("Status code {}, content-length is {}", req->status_code, req->content_length);
       req->data.reserve(req->content_length);
       req->state = Request::State::Receiving;
 
       // start reading
       if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
       {
-        Log_ErrorFmt("WinHttpQueryDataAvailable() failed: {}", GetLastError());
+        ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError());
         req->status_code = HTTP_STATUS_ERROR;
         req->state.store(Request::State::Complete);
       }
@@ -166,19 +166,19 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
       if (bytes_available == 0)
       {
         // end of request
-        Log_DevFmt("End of request '{}', {} bytes received", req->url, req->data.size());
+        DEV_LOG("End of request '{}', {} bytes received", req->url, req->data.size());
         req->state.store(Request::State::Complete);
         return;
       }
 
       // start the transfer
-      Log_DevFmt("{} bytes available", bytes_available);
+      DEV_LOG("{} bytes available", bytes_available);
       req->io_position = static_cast<u32>(req->data.size());
       req->data.resize(req->io_position + bytes_available);
       if (!WinHttpReadData(hRequest, req->data.data() + req->io_position, bytes_available, nullptr) &&
           GetLastError() != ERROR_IO_PENDING)
       {
-        Log_ErrorFmt("WinHttpReadData() failed: {}", GetLastError());
+        ERROR_LOG("WinHttpReadData() failed: {}", GetLastError());
         req->status_code = HTTP_STATUS_ERROR;
         req->state.store(Request::State::Complete);
       }
@@ -187,7 +187,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
     }
     case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
     {
-      Log_DevFmt("Read of {} complete", dwStatusInformationLength);
+      DEV_LOG("Read of {} complete", dwStatusInformationLength);
 
       const u32 new_size = req->io_position + dwStatusInformationLength;
       Assert(new_size <= req->data.size());
@@ -196,7 +196,7 @@ void CALLBACK HTTPDownloaderWinHttp::HTTPStatusCallback(HINTERNET hRequest, DWOR
 
       if (!WinHttpQueryDataAvailable(hRequest, nullptr) && GetLastError() != ERROR_IO_PENDING)
       {
-        Log_ErrorFmt("WinHttpQueryDataAvailable() failed: {}", GetLastError());
+        ERROR_LOG("WinHttpQueryDataAvailable() failed: {}", GetLastError());
         req->status_code = HTTP_STATUS_ERROR;
         req->state.store(Request::State::Complete);
       }
@@ -238,7 +238,7 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
   const std::wstring url_wide(StringUtil::UTF8StringToWideString(req->url));
   if (!WinHttpCrackUrl(url_wide.c_str(), static_cast<DWORD>(url_wide.size()), 0, &uc))
   {
-    Log_ErrorFmt("WinHttpCrackUrl() failed: {}", GetLastError());
+    ERROR_LOG("WinHttpCrackUrl() failed: {}", GetLastError());
     req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
     delete req;
     return false;
@@ -250,7 +250,7 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
   req->hConnection = WinHttpConnect(m_hSession, host_name.c_str(), uc.nPort, 0);
   if (!req->hConnection)
   {
-    Log_ErrorFmt("Failed to start HTTP request for '{}': {}", req->url, GetLastError());
+    ERROR_LOG("Failed to start HTTP request for '{}': {}", req->url, GetLastError());
     req->callback(HTTP_STATUS_ERROR, std::string(), req->data);
     delete req;
     return false;
@@ -262,7 +262,7 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
                        req->object_name.c_str(), NULL, NULL, NULL, request_flags);
   if (!req->hRequest)
   {
-    Log_ErrorFmt("WinHttpOpenRequest() failed: {}", GetLastError());
+    ERROR_LOG("WinHttpOpenRequest() failed: {}", GetLastError());
     WinHttpCloseHandle(req->hConnection);
     return false;
   }
@@ -283,12 +283,12 @@ bool HTTPDownloaderWinHttp::StartRequest(HTTPDownloader::Request* request)
 
   if (!result && GetLastError() != ERROR_IO_PENDING)
   {
-    Log_ErrorFmt("WinHttpSendRequest() failed: {}", GetLastError());
+    ERROR_LOG("WinHttpSendRequest() failed: {}", GetLastError());
     req->status_code = HTTP_STATUS_ERROR;
     req->state.store(Request::State::Complete);
   }
 
-  Log_DevFmt("Started HTTP request for '{}'", req->url);
+  DEV_LOG("Started HTTP request for '{}'", req->url);
   req->state = Request::State::Started;
   req->start_time = Common::Timer::GetCurrentValue();
   return true;
diff --git a/src/util/image.cpp b/src/util/image.cpp
index 7b77608ac..7584008b4 100644
--- a/src/util/image.cpp
+++ b/src/util/image.cpp
@@ -131,7 +131,7 @@ bool RGBA8Image::LoadFromFile(std::string_view filename, std::FILE* fp)
   const FormatHandler* handler = GetFormatHandler(extension);
   if (!handler || !handler->file_loader)
   {
-    Log_ErrorFmt("Unknown extension '{}'", extension);
+    ERROR_LOG("Unknown extension '{}'", extension);
     return false;
   }
 
@@ -144,7 +144,7 @@ bool RGBA8Image::LoadFromBuffer(std::string_view filename, const void* buffer, s
   const FormatHandler* handler = GetFormatHandler(extension);
   if (!handler || !handler->buffer_loader)
   {
-    Log_ErrorFmt("Unknown extension '{}'", extension);
+    ERROR_LOG("Unknown extension '{}'", extension);
     return false;
   }
 
@@ -157,7 +157,7 @@ bool RGBA8Image::SaveToFile(std::string_view filename, std::FILE* fp, u8 quality
   const FormatHandler* handler = GetFormatHandler(extension);
   if (!handler || !handler->file_saver)
   {
-    Log_ErrorFmt("Unknown extension '{}'", extension);
+    ERROR_LOG("Unknown extension '{}'", extension);
     return false;
   }
 
@@ -175,7 +175,7 @@ std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(std::string_view filenam
   const FormatHandler* handler = GetFormatHandler(extension);
   if (!handler || !handler->file_saver)
   {
-    Log_ErrorFmt("Unknown extension '{}'", extension);
+    ERROR_LOG("Unknown extension '{}'", extension);
     return ret;
   }
 
@@ -438,7 +438,7 @@ static bool HandleJPEGError(JPEGErrorHandler* eh)
     JPEGErrorHandler* eh = (JPEGErrorHandler*)cinfo->err;
     char msg[JMSG_LENGTH_MAX];
     eh->err.format_message(cinfo, msg);
-    Log_ErrorFmt("libjpeg fatal error: {}", msg);
+    ERROR_LOG("libjpeg fatal error: {}", msg);
     longjmp(eh->jbuf, 1);
   };
 
@@ -465,13 +465,13 @@ static bool WrapJPEGDecompress(RGBA8Image* image, T setup_func)
   const int herr = jpeg_read_header(&info, TRUE);
   if (herr != JPEG_HEADER_OK)
   {
-    Log_ErrorFmt("jpeg_read_header() returned {}", herr);
+    ERROR_LOG("jpeg_read_header() returned {}", herr);
     return false;
   }
 
   if (info.image_width == 0 || info.image_height == 0 || info.num_components < 3)
   {
-    Log_ErrorFmt("Invalid image dimensions: {}x{}x{}", info.image_width, info.image_height, info.num_components);
+    ERROR_LOG("Invalid image dimensions: {}x{}x{}", info.image_width, info.image_height, info.num_components);
     return false;
   }
 
@@ -480,7 +480,7 @@ static bool WrapJPEGDecompress(RGBA8Image* image, T setup_func)
 
   if (!jpeg_start_decompress(&info))
   {
-    Log_ErrorFmt("jpeg_start_decompress() returned failure");
+    ERROR_LOG("jpeg_start_decompress() returned failure");
     return false;
   }
 
@@ -493,7 +493,7 @@ static bool WrapJPEGDecompress(RGBA8Image* image, T setup_func)
   {
     if (jpeg_read_scanlines(&info, scanline_buffer, 1) != 1)
     {
-      Log_ErrorFmt("jpeg_read_scanlines() failed at row {}", y);
+      ERROR_LOG("jpeg_read_scanlines() failed at row {}", y);
       result = false;
       break;
     }
@@ -622,7 +622,7 @@ static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, T setup_func)
 
     if (jpeg_write_scanlines(&info, scanline_buffer, 1) != 1)
     {
-      Log_ErrorFmt("jpeg_write_scanlines() failed at row {}", y);
+      ERROR_LOG("jpeg_write_scanlines() failed at row {}", y);
       result = false;
       break;
     }
@@ -723,7 +723,7 @@ bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
   int width, height;
   if (!WebPGetInfo(static_cast<const u8*>(buffer), buffer_size, &width, &height) || width <= 0 || height <= 0)
   {
-    Log_ErrorPrint("WebPGetInfo() failed");
+    ERROR_LOG("WebPGetInfo() failed");
     return false;
   }
 
@@ -732,7 +732,7 @@ bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
   if (!WebPDecodeRGBAInto(static_cast<const u8*>(buffer), buffer_size, reinterpret_cast<u8*>(pixels.data()),
                           sizeof(u32) * pixels.size(), sizeof(u32) * static_cast<u32>(width)))
   {
-    Log_ErrorPrint("WebPDecodeRGBAInto() failed");
+    ERROR_LOG("WebPDecodeRGBAInto() failed");
     return false;
   }
 
diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp
index fef8c6517..35fdf9715 100644
--- a/src/util/imgui_fullscreen.cpp
+++ b/src/util/imgui_fullscreen.cpp
@@ -219,7 +219,7 @@ bool ImGuiFullscreen::Initialize(const char* placeholder_image_path)
   s_placeholder_texture = LoadTexture(placeholder_image_path);
   if (!s_placeholder_texture)
   {
-    Log_ErrorFmt("Missing placeholder texture '{}', cannot continue", placeholder_image_path);
+    ERROR_LOG("Missing placeholder texture '{}', cannot continue", placeholder_image_path);
     return false;
   }
 
@@ -295,11 +295,11 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
     {
       image = RGBA8Image();
       if (!image->LoadFromFile(path_str.c_str(), fp.get()))
-        Log_ErrorFmt("Failed to read texture file '{}'", path);
+        ERROR_LOG("Failed to read texture file '{}'", path);
     }
     else
     {
-      Log_ErrorFmt("Failed to open texture file '{}': {}", path, error.GetDescription());
+      ERROR_LOG("Failed to open texture file '{}': {}", path, error.GetDescription());
     }
   }
   else
@@ -310,13 +310,13 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
       image = RGBA8Image();
       if (!image->LoadFromBuffer(path, data->data(), data->size()))
       {
-        Log_ErrorFmt("Failed to read texture resource '{}'", path);
+        ERROR_LOG("Failed to read texture resource '{}'", path);
         image.reset();
       }
     }
     else
     {
-      Log_ErrorFmt("Failed to open texture resource '{}'", path);
+      ERROR_LOG("Failed to open texture resource '{}'", path);
     }
   }
 
@@ -330,11 +330,11 @@ std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path
                                GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
   if (!texture)
   {
-    Log_ErrorFmt("failed to create {}x{} texture for resource", image.GetWidth(), image.GetHeight());
+    ERROR_LOG("failed to create {}x{} texture for resource", image.GetWidth(), image.GetHeight());
     return {};
   }
 
-  Log_DevFmt("Uploaded texture resource '{}' ({}x{})", path, image.GetWidth(), image.GetHeight());
+  DEV_LOG("Uploaded texture resource '{}' ({}x{})", path, image.GetWidth(), image.GetHeight());
   return std::shared_ptr<GPUTexture>(texture.release(), GPUDevice::PooledTextureDeleter());
 }
 
diff --git a/src/util/imgui_manager.cpp b/src/util/imgui_manager.cpp
index 14afee7ae..7f54c8185 100644
--- a/src/util/imgui_manager.cpp
+++ b/src/util/imgui_manager.cpp
@@ -663,7 +663,7 @@ bool ImGuiManager::AddFullscreenFontsIfMissing()
 
   if (!AddImGuiFonts(true))
   {
-    Log_ErrorPrint("Failed to lazily allocate fullscreen fonts.");
+    ERROR_LOG("Failed to lazily allocate fullscreen fonts.");
     AddImGuiFonts(false);
   }
 
@@ -686,9 +686,9 @@ void Host::AddOSDMessage(std::string message, float duration /*= 2.0f*/)
 void Host::AddKeyedOSDMessage(std::string key, std::string message, float duration /* = 2.0f */)
 {
   if (!key.empty())
-    Log_InfoFmt("OSD [{}]: {}", key, message);
+    INFO_LOG("OSD [{}]: {}", key, message);
   else
-    Log_InfoFmt("OSD: {}", message);
+    INFO_LOG("OSD: {}", message);
 
   if (!ImGuiManager::s_show_osd_messages)
     return;
@@ -1045,7 +1045,7 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
   RGBA8Image image;
   if (!image.LoadFromFile(sc.image_path.c_str()))
   {
-    Log_ErrorFmt("Failed to load software cursor {} image '{}'", index, sc.image_path);
+    ERROR_LOG("Failed to load software cursor {} image '{}'", index, sc.image_path);
     return;
   }
   g_gpu_device->RecycleTexture(std::move(sc.texture));
@@ -1053,8 +1053,8 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
                                           GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
   if (!sc.texture)
   {
-    Log_ErrorFmt("Failed to upload {}x{} software cursor {} image '{}'", image.GetWidth(), image.GetHeight(), index,
-                 sc.image_path);
+    ERROR_LOG("Failed to upload {}x{} software cursor {} image '{}'", image.GetWidth(), image.GetHeight(), index,
+              sc.image_path);
     return;
   }
 
diff --git a/src/util/ini_settings_interface.cpp b/src/util/ini_settings_interface.cpp
index b44b07dce..ab9ddabbd 100644
--- a/src/util/ini_settings_interface.cpp
+++ b/src/util/ini_settings_interface.cpp
@@ -122,7 +122,7 @@ bool INISettingsInterface::Save(Error* error /* = nullptr */)
 
   if (err != SI_OK)
   {
-    Log_WarningFmt("Failed to save settings to '{}'.", m_filename);
+    WARNING_LOG("Failed to save settings to '{}'.", m_filename);
     return false;
   }
 
@@ -363,7 +363,7 @@ std::vector<std::pair<std::string, std::string>> INISettingsInterface::GetKeyVal
     {
       if (!m_ini.GetAllValues(section, key.pItem, values)) // [[unlikely]]
       {
-        Log_ErrorPrint("Got no values for a key returned from GetAllKeys!");
+        ERROR_LOG("Got no values for a key returned from GetAllKeys!");
         continue;
       }
       for (const Entry& value : values)
diff --git a/src/util/input_manager.cpp b/src/util/input_manager.cpp
index ccea62b7b..602284e79 100644
--- a/src/util/input_manager.cpp
+++ b/src/util/input_manager.cpp
@@ -222,7 +222,7 @@ bool InputManager::SplitBinding(std::string_view binding, std::string_view* sour
   const std::string_view::size_type slash_pos = binding.find('/');
   if (slash_pos == std::string_view::npos)
   {
-    Log_WarningFmt("Malformed binding: '{}'", binding);
+    WARNING_LOG("Malformed binding: '{}'", binding);
     return false;
   }
 
@@ -493,7 +493,7 @@ void InputManager::AddBinding(std::string_view binding, const InputEventHandler&
     std::optional<InputBindingKey> key = ParseInputBindingKey(chord_binding);
     if (!key.has_value())
     {
-      Log_ErrorFmt("Invalid binding: '{}'", binding);
+      ERROR_LOG("Invalid binding: '{}'", binding);
       ibinding.reset();
       break;
     }
@@ -506,7 +506,7 @@ void InputManager::AddBinding(std::string_view binding, const InputEventHandler&
 
     if (ibinding->num_keys == MAX_KEYS_PER_BINDING)
     {
-      Log_ErrorFmt("Too many chord parts, max is {} ({})", static_cast<unsigned>(MAX_KEYS_PER_BINDING), binding.size());
+      ERROR_LOG("Too many chord parts, max is {} ({})", static_cast<unsigned>(MAX_KEYS_PER_BINDING), binding.size());
       ibinding.reset();
       break;
     }
@@ -861,7 +861,7 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
       break;
 
       default:
-        Log_ErrorFmt("Unhandled binding info type {}", static_cast<u32>(bi.type));
+        ERROR_LOG("Unhandled binding info type {}", static_cast<u32>(bi.type));
         break;
     }
   }
@@ -1378,7 +1378,7 @@ static u32 TryMapGenericMapping(SettingsInterface& si, const std::string& sectio
 
   if (found_mapping)
   {
-    Log_InfoFmt("Map {}/{} to '{}'", section, bind_name, *found_mapping);
+    INFO_LOG("Map {}/{} to '{}'", section, bind_name, *found_mapping);
     si.SetStringValue(section.c_str(), bind_name, found_mapping->c_str());
     return 1;
   }
@@ -1605,7 +1605,7 @@ void InputManager::LoadMacroButtonConfig(SettingsInterface& si, const std::strin
       }
       if (!binding)
       {
-        Log_DevFmt("Invalid bind '{}' in macro button {} for pad {}", button, pad, i);
+        DEV_LOG("Invalid bind '{}' in macro button {} for pad {}", button, pad, i);
         continue;
       }
 
@@ -1927,7 +1927,7 @@ void InputManager::UpdateInputSourceState(SettingsInterface& si, std::unique_loc
       std::unique_ptr<InputSource> source(factory_function());
       if (!source->Initialize(si, settings_lock))
       {
-        Log_ErrorFmt("Source '{}' failed to initialize.", InputManager::InputSourceToString(type));
+        ERROR_LOG("Source '{}' failed to initialize.", InputManager::InputSourceToString(type));
         return;
       }
 
diff --git a/src/util/iso_reader.cpp b/src/util/iso_reader.cpp
index 176dd7089..d908bb9f5 100644
--- a/src/util/iso_reader.cpp
+++ b/src/util/iso_reader.cpp
@@ -74,7 +74,7 @@ bool IsoReader::ReadPVD(Error* error)
 
     m_pvd_lba = START_SECTOR + i;
     std::memcpy(&m_pvd, buffer, sizeof(ISOPrimaryVolumeDescriptor));
-    Log_DevFmt("ISOReader: PVD found at index {}", i);
+    DEV_LOG("ISOReader: PVD found at index {}", i);
     return true;
   }
 
diff --git a/src/util/jit_code_buffer.cpp b/src/util/jit_code_buffer.cpp
index a37b9451d..052f02573 100644
--- a/src/util/jit_code_buffer.cpp
+++ b/src/util/jit_code_buffer.cpp
@@ -61,19 +61,19 @@ bool JitCodeBuffer::Allocate(u32 size /* = 64 * 1024 * 1024 */, u32 far_code_siz
   for (u32 offset = 0; offset < steps; offset++)
   {
     const u8* addr = max_address - (offset * step);
-    Log_VerboseFmt("Trying {} (base {}, offset {}, displacement 0x{:X})", static_cast<const void*>(addr),
-                   static_cast<const void*>(base), offset, static_cast<ptrdiff_t>(addr - base));
+    VERBOSE_LOG("Trying {} (base {}, offset {}, displacement 0x{:X})", static_cast<const void*>(addr),
+                static_cast<const void*>(base), offset, static_cast<ptrdiff_t>(addr - base));
     if (TryAllocateAt(addr))
       break;
   }
   if (m_code_ptr)
   {
-    Log_InfoFmt("Allocated JIT buffer of size {} at {} (0x{:X} bytes away)", m_total_size,
-                static_cast<void*>(m_code_ptr), static_cast<ptrdiff_t>(m_code_ptr - base));
+    INFO_LOG("Allocated JIT buffer of size {} at {} (0x{:X} bytes away)", m_total_size, static_cast<void*>(m_code_ptr),
+             static_cast<ptrdiff_t>(m_code_ptr - base));
   }
   else
   {
-    Log_ErrorPrint("Failed to allocate JIT buffer in range, expect crashes.");
+    ERROR_LOG("Failed to allocate JIT buffer in range, expect crashes.");
     if (!TryAllocateAt(nullptr))
       return false;
   }
@@ -104,7 +104,7 @@ bool JitCodeBuffer::TryAllocateAt(const void* addr)
   if (!m_code_ptr)
   {
     if (!addr)
-      Log_ErrorFmt("VirtualAlloc(RWX, %u) for internal buffer failed: {}", m_total_size, GetLastError());
+      ERROR_LOG("VirtualAlloc(RWX, %u) for internal buffer failed: {}", m_total_size, GetLastError());
     return false;
   }
 
@@ -134,14 +134,14 @@ bool JitCodeBuffer::TryAllocateAt(const void* addr)
   if (!m_code_ptr)
   {
     if (!addr)
-      Log_ErrorFmt("mmap(RWX, %u) for internal buffer failed: {}", m_total_size, errno);
+      ERROR_LOG("mmap(RWX, %u) for internal buffer failed: {}", m_total_size, errno);
 
     return false;
   }
   else if (addr && m_code_ptr != addr)
   {
     if (munmap(m_code_ptr, m_total_size) != 0)
-      Log_ErrorFmt("Failed to munmap() incorrectly hinted allocation: {}", errno);
+      ERROR_LOG("Failed to munmap() incorrectly hinted allocation: {}", errno);
     m_code_ptr = nullptr;
     return false;
   }
@@ -163,7 +163,7 @@ bool JitCodeBuffer::Initialize(void* buffer, u32 size, u32 far_code_size /* = 0
   DWORD old_protect = 0;
   if (!VirtualProtect(buffer, size, PAGE_EXECUTE_READWRITE, &old_protect))
   {
-    Log_ErrorFmt("VirtualProtect(RWX) for external buffer failed: {}", GetLastError());
+    ERROR_LOG("VirtualProtect(RWX) for external buffer failed: {}", GetLastError());
     return false;
   }
 
@@ -174,7 +174,7 @@ bool JitCodeBuffer::Initialize(void* buffer, u32 size, u32 far_code_size /* = 0
     if (!VirtualProtect(buffer, guard_size, PAGE_NOACCESS, &old_guard_protect) ||
         !VirtualProtect(guard_at_end, guard_size, PAGE_NOACCESS, &old_guard_protect))
     {
-      Log_ErrorFmt("VirtualProtect(NOACCESS) for guard page failed: {}", GetLastError());
+      ERROR_LOG("VirtualProtect(NOACCESS) for guard page failed: {}", GetLastError());
       return false;
     }
   }
@@ -184,7 +184,7 @@ bool JitCodeBuffer::Initialize(void* buffer, u32 size, u32 far_code_size /* = 0
 #elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__HAIKU__) || defined(__FreeBSD__)
   if (mprotect(buffer, size, PROT_READ | PROT_WRITE | PROT_EXEC) != 0)
   {
-    Log_ErrorFmt("mprotect(RWX) for external buffer failed: {}", errno);
+    ERROR_LOG("mprotect(RWX) for external buffer failed: {}", errno);
     return false;
   }
 
@@ -193,7 +193,7 @@ bool JitCodeBuffer::Initialize(void* buffer, u32 size, u32 far_code_size /* = 0
     u8* guard_at_end = (static_cast<u8*>(buffer) + size) - guard_size;
     if (mprotect(buffer, guard_size, PROT_NONE) != 0 || mprotect(guard_at_end, guard_size, PROT_NONE) != 0)
     {
-      Log_ErrorFmt("mprotect(NONE) for guard page failed: {}", errno);
+      ERROR_LOG("mprotect(NONE) for guard page failed: {}", errno);
       return false;
     }
   }
@@ -229,10 +229,10 @@ void JitCodeBuffer::Destroy()
   {
 #if defined(_WIN32)
     if (!VirtualFree(m_code_ptr, 0, MEM_RELEASE))
-      Log_ErrorFmt("Failed to free code pointer %p", static_cast<void*>(m_code_ptr));
+      ERROR_LOG("Failed to free code pointer %p", static_cast<void*>(m_code_ptr));
 #elif defined(__linux__) || defined(__ANDROID__) || defined(__APPLE__) || defined(__HAIKU__) || defined(__FreeBSD__)
     if (munmap(m_code_ptr, m_total_size) != 0)
-      Log_ErrorFmt("Failed to free code pointer %p", static_cast<void*>(m_code_ptr));
+      ERROR_LOG("Failed to free code pointer %p", static_cast<void*>(m_code_ptr));
 #endif
   }
   else if (m_code_ptr)
@@ -240,10 +240,10 @@ void JitCodeBuffer::Destroy()
 #if defined(_WIN32)
     DWORD old_protect = 0;
     if (!VirtualProtect(m_code_ptr, m_total_size, m_old_protection, &old_protect))
-      Log_ErrorFmt("Failed to restore protection on %p", static_cast<void*>(m_code_ptr));
+      ERROR_LOG("Failed to restore protection on %p", static_cast<void*>(m_code_ptr));
 #else
     if (mprotect(m_code_ptr, m_total_size, m_old_protection) != 0)
-      Log_ErrorFmt("Failed to restore protection on %p", static_cast<void*>(m_code_ptr));
+      ERROR_LOG("Failed to restore protection on %p", static_cast<void*>(m_code_ptr));
 #endif
   }
 
diff --git a/src/util/metal_device.mm b/src/util/metal_device.mm
index fd5b402e9..135d79d2e 100644
--- a/src/util/metal_device.mm
+++ b/src/util/metal_device.mm
@@ -68,15 +68,11 @@ static NSString* StringViewToNSString(std::string_view str)
                                               encoding:NSUTF8StringEncoding];
 }
 
-static void LogNSError(NSError* error, const char* desc, ...)
+static void LogNSError(NSError* error, std::string_view message)
 {
-  std::va_list ap;
-  va_start(ap, desc);
-  Log::Writev("MetalDevice", "", LOGLEVEL_ERROR, desc, ap);
-  va_end(ap);
-
-  Log::Writef("MetalDevice", "", LOGLEVEL_ERROR, "  NSError Code: %u", static_cast<u32>(error.code));
-  Log::Writef("MetalDevice", "", LOGLEVEL_ERROR, "  NSError Description: %s", [error.description UTF8String]);
+  Log::FastWrite("MetalDevice", LOGLEVEL_ERROR, message);
+  Log::FastWrite("MetalDevice", LOGLEVEL_ERROR, "  NSError Code: {}", static_cast<u32>(error.code));
+  Log::FastWrite("MetalDevice", LOGLEVEL_ERROR, "  NSError Description: {}", [error.description UTF8String]);
 }
 
 static GPUTexture::Format GetTextureFormatForMTLFormat(MTLPixelFormat fmt)
@@ -156,7 +152,7 @@ bool MetalDevice::CreateDevice(std::string_view adapter, bool threaded_presentat
       }
 
       if (device == nil)
-        Log_ErrorFmt("Failed to find device named '{}'. Trying default.", adapter);
+        ERROR_LOG("Failed to find device named '{}'. Trying default.", adapter);
     }
 
     if (device == nil)
@@ -178,7 +174,7 @@ bool MetalDevice::CreateDevice(std::string_view adapter, bool threaded_presentat
 
     m_device = [device retain];
     m_queue = [queue retain];
-    Log_InfoFmt("Metal Device: {}", [[m_device name] UTF8String]);
+    INFO_LOG("Metal Device: {}", [[m_device name] UTF8String]);
 
     SetFeatures(disabled_features);
 
@@ -381,7 +377,7 @@ bool MetalDevice::CreateLayer()
     RunOnMainThread([this]() {
       @autoreleasepool
       {
-        Log_InfoFmt("Creating a {}x{} Metal layer.", m_window_info.surface_width, m_window_info.surface_height);
+        INFO_LOG("Creating a {}x{} Metal layer.", m_window_info.surface_width, m_window_info.surface_height);
         const auto size =
           CGSizeMake(static_cast<float>(m_window_info.surface_width), static_cast<float>(m_window_info.surface_height));
         m_layer = [CAMetalLayer layer];
@@ -393,12 +389,12 @@ bool MetalDevice::CreateLayer()
         m_window_info.surface_format = GetTextureFormatForMTLFormat(layer_fmt);
         if (m_window_info.surface_format == GPUTexture::Format::Unknown)
         {
-          Log_ErrorFmt("Invalid pixel format {} in layer, using BGRA8.", static_cast<u32>(layer_fmt));
+          ERROR_LOG("Invalid pixel format {} in layer, using BGRA8.", static_cast<u32>(layer_fmt));
           [m_layer setPixelFormat:MTLPixelFormatBGRA8Unorm];
           m_window_info.surface_format = GPUTexture::Format::BGRA8;
         }
 
-        Log_VerboseFmt("Metal layer pixel format is {}.", GPUTexture::GetFormatName(m_window_info.surface_format));
+        VERBOSE_LOG("Metal layer pixel format is {}.", GPUTexture::GetFormatName(m_window_info.surface_format));
 
         NSView* view = GetWindowView();
         [view setWantsLayer:TRUE];
@@ -469,7 +465,7 @@ bool MetalDevice::UpdateWindow()
 
   if (m_window_info.type != WindowInfo::Type::Surfaceless && !CreateLayer())
   {
-    Log_ErrorPrint("Failed to create layer on updated window");
+    ERROR_LOG("Failed to create layer on updated window");
     return false;
   }
 
@@ -515,7 +511,7 @@ bool MetalDevice::CreateBuffers()
       !m_uniform_buffer.Create(m_device, UNIFORM_BUFFER_SIZE) ||
       !m_texture_upload_buffer.Create(m_device, TEXTURE_STREAM_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to create vertex/index/uniform buffers.");
+    ERROR_LOG("Failed to create vertex/index/uniform buffers.");
     return false;
   }
 
@@ -625,7 +621,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromMSL(GPUShaderStage stage
     id<MTLLibrary> library = [m_device newLibraryWithSource:ns_source options:nil error:&error];
     if (!library)
     {
-      LogNSError(error, "Failed to compile %s shader", GPUShader::GetStageName(stage));
+      LogNSError(error, TinyString::from_format("Failed to compile {} shader", GPUShader::GetStageName(stage)));
 
       const char* utf_error = [error.description UTF8String];
       DumpBadShader(source, fmt::format("Error {}: {}", static_cast<u32>(error.code), utf_error ? utf_error : ""));
@@ -635,7 +631,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromMSL(GPUShaderStage stage
     id<MTLFunction> function = [library newFunctionWithName:StringViewToNSString(entry_point)];
     if (!function)
     {
-      Log_ErrorPrint("Failed to get main function in compiled library");
+      ERROR_LOG("Failed to get main function in compiled library");
       return {};
     }
 
@@ -666,19 +662,19 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
   spvc_result sres;
   if ((sres = spvc_context_create(&sctx)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_context_create() failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_context_create() failed: {}", static_cast<int>(sres));
     return {};
   }
 
   const ScopedGuard sctx_guard = [&sctx]() { spvc_context_destroy(sctx); };
 
   spvc_context_set_error_callback(
-    sctx, [](void*, const char* error) { Log_ErrorFmt("SPIRV-Cross reported an error: {}", error); }, nullptr);
+    sctx, [](void*, const char* error) { ERROR_LOG("SPIRV-Cross reported an error: {}", error); }, nullptr);
 
   spvc_parsed_ir sir;
   if ((sres = spvc_context_parse_spirv(sctx, reinterpret_cast<const u32*>(dest_binary->data()), dest_binary->size() / 4, &sir)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_context_parse_spirv() failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_context_parse_spirv() failed: {}", static_cast<int>(sres));
     DumpBadShader(source, std::string_view());
     return {};
   }
@@ -687,21 +683,21 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
   if ((sres = spvc_context_create_compiler(sctx, SPVC_BACKEND_MSL, sir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP,
                                            &scompiler)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_context_create_compiler() failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_context_create_compiler() failed: {}", static_cast<int>(sres));
     return {};
   }
 
   spvc_compiler_options soptions;
   if ((sres = spvc_compiler_create_compiler_options(scompiler, &soptions)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_compiler_create_compiler_options() failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_compiler_create_compiler_options() failed: {}", static_cast<int>(sres));
     return {};
   }
 
   if ((sres = spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS,
                                              true)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS) failed: {}",
+    ERROR_LOG("spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_PAD_FRAGMENT_OUTPUT_COMPONENTS) failed: {}",
                  static_cast<int>(sres));
     return {};
   }
@@ -709,7 +705,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
   if ((sres = spvc_compiler_options_set_bool(soptions, SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS,
                                              m_features.framebuffer_fetch)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS) failed: {}",
+    ERROR_LOG("spvc_compiler_options_set_bool(SPVC_COMPILER_OPTION_MSL_FRAMEBUFFER_FETCH_SUBPASS) failed: {}",
                  static_cast<int>(sres));
     return {};
   }
@@ -718,7 +714,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
       ((sres = spvc_compiler_options_set_uint(soptions, SPVC_COMPILER_OPTION_MSL_VERSION,
                                               SPVC_MAKE_MSL_VERSION(2, 3, 0))) != SPVC_SUCCESS))
   {
-    Log_ErrorFmt("spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_compiler_options_set_uint(SPVC_COMPILER_OPTION_MSL_VERSION) failed: {}", static_cast<int>(sres));
     return {};
   }
 
@@ -735,7 +731,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
 
       if ((sres = spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
       {
-        Log_ErrorFmt("spvc_compiler_msl_add_resource_binding() failed: {}", static_cast<int>(sres));
+        ERROR_LOG("spvc_compiler_msl_add_resource_binding() failed: {}", static_cast<int>(sres));
         return {};
       }
     }
@@ -747,7 +743,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
 
       if ((sres = spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
       {
-        Log_ErrorFmt("spvc_compiler_msl_add_resource_binding() for FB failed: {}", static_cast<int>(sres));
+        ERROR_LOG("spvc_compiler_msl_add_resource_binding() for FB failed: {}", static_cast<int>(sres));
         return {};
       }
     }
@@ -755,14 +751,14 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
 
   if ((sres = spvc_compiler_install_compiler_options(scompiler, soptions)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_compiler_install_compiler_options() failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_compiler_install_compiler_options() failed: {}", static_cast<int>(sres));
     return {};
   }
 
   const char* msl;
   if ((sres = spvc_compiler_compile(scompiler, &msl)) != SPVC_SUCCESS)
   {
-    Log_ErrorFmt("spvc_compiler_compile() failed: {}", static_cast<int>(sres));
+    ERROR_LOG("spvc_compiler_compile() failed: {}", static_cast<int>(sres));
     DumpBadShader(source, std::string_view());
     return {};
   }
@@ -770,7 +766,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
   const size_t msl_length = msl ? std::strlen(msl) : 0;
   if (msl_length == 0)
   {
-    Log_ErrorPrint("Failed to compile SPIR-V to MSL.");
+    ERROR_LOG("Failed to compile SPIR-V to MSL.");
     DumpBadShader(source, std::string_view());
     return {};
   }
@@ -835,7 +831,7 @@ id<MTLDepthStencilState> MetalDevice::GetDepthState(const GPUPipeline::DepthStat
     id<MTLDepthStencilState> state = [m_device newDepthStencilStateWithDescriptor:desc];
     m_depth_states.emplace(ds.key, state);
     if (state == nil) [[unlikely]]
-      Log_ErrorPrint("Failed to create depth-stencil state.");
+      ERROR_LOG("Failed to create depth-stencil state.");
 
     return state;
   }
@@ -1216,7 +1212,7 @@ std::unique_ptr<GPUTexture> MetalDevice::CreateTexture(u32 width, u32 height, u3
     id<MTLTexture> tex = [m_device newTextureWithDescriptor:desc];
     if (tex == nil)
     {
-      Log_ErrorFmt("Failed to create {}x{} texture.", width, height);
+      ERROR_LOG("Failed to create {}x{} texture.", width, height);
       return {};
     }
 
@@ -1269,7 +1265,7 @@ std::unique_ptr<MetalDownloadTexture> MetalDownloadTexture::Create(u32 width, u3
       buffer = [[dev.m_device newBufferWithLength:buffer_size options:options] retain];
       if (buffer == nil)
       {
-        Log_ErrorFmt("Failed to create {} byte buffer", buffer_size);
+        ERROR_LOG("Failed to create {} byte buffer", buffer_size);
         return {};
       }
 
@@ -1286,7 +1282,7 @@ std::unique_ptr<MetalDownloadTexture> MetalDownloadTexture::Create(u32 width, u3
         reinterpret_cast<void*>(Common::AlignDownPow2(reinterpret_cast<uintptr_t>(memory), HOST_PAGE_SIZE));
       const size_t page_offset = static_cast<size_t>(static_cast<u8*>(memory) - static_cast<u8*>(page_aligned_memory));
       const size_t page_aligned_size = Common::AlignUpPow2(page_offset + memory_size, HOST_PAGE_SIZE);
-      Log_DevFmt("Trying to import {} bytes of memory at {} for download texture", page_aligned_memory,
+      DEV_LOG("Trying to import {} bytes of memory at {} for download texture", page_aligned_memory,
                  page_aligned_size);
 
       buffer = [[dev.m_device newBufferWithBytesNoCopy:page_aligned_memory
@@ -1295,7 +1291,7 @@ std::unique_ptr<MetalDownloadTexture> MetalDownloadTexture::Create(u32 width, u3
                                            deallocator:nil] retain];
       if (buffer == nil)
       {
-        Log_ErrorFmt("Failed to import {} byte buffer", page_aligned_size);
+        ERROR_LOG("Failed to import {} byte buffer", page_aligned_size);
         return {};
       }
 
@@ -1460,7 +1456,7 @@ std::unique_ptr<GPUSampler> MetalDevice::CreateSampler(const GPUSampler::Config&
       }
       if (i == std::size(border_color_mapping))
       {
-        Log_ErrorFmt("Unsupported border color: {:08X}", config.border_color.GetValue());
+        ERROR_LOG("Unsupported border color: {:08X}", config.border_color.GetValue());
         return {};
       }
 
@@ -1471,7 +1467,7 @@ std::unique_ptr<GPUSampler> MetalDevice::CreateSampler(const GPUSampler::Config&
     id<MTLSamplerState> ss = [m_device newSamplerStateWithDescriptor:desc];
     if (ss == nil)
     {
-      Log_ErrorPrint("Failed to create sampler state.");
+      ERROR_LOG("Failed to create sampler state.");
       return {};
     }
 
@@ -2029,7 +2025,7 @@ void MetalDevice::UnbindTexture(MetalTexture* tex)
     {
       if (m_current_render_targets[i] == tex)
       {
-        Log_WarningPrint("Unbinding current RT");
+        WARNING_LOG("Unbinding current RT");
         SetRenderTargets(nullptr, 0, m_current_depth_target, GPUPipeline::NoRenderPassFlags); // TODO: Wrong
         break;
       }
@@ -2039,7 +2035,7 @@ void MetalDevice::UnbindTexture(MetalTexture* tex)
   {
     if (m_current_depth_target == tex)
     {
-      Log_WarningPrint("Unbinding current DS");
+      WARNING_LOG("Unbinding current DS");
       SetRenderTargets(nullptr, 0, nullptr, GPUPipeline::NoRenderPassFlags);
     }
   }
@@ -2557,7 +2553,7 @@ void MetalDevice::SubmitCommandBuffer(bool wait_for_completion)
 
 void MetalDevice::SubmitCommandBufferAndRestartRenderPass(const char* reason)
 {
-  Log_DevFmt("Submitting command buffer and restarting render pass due to {}", reason);
+  DEV_LOG("Submitting command buffer and restarting render pass due to {}", reason);
 
   const bool in_render_pass = InRenderPass();
   SubmitCommandBuffer();
diff --git a/src/util/metal_stream_buffer.mm b/src/util/metal_stream_buffer.mm
index 4f4be284e..453f0a652 100644
--- a/src/util/metal_stream_buffer.mm
+++ b/src/util/metal_stream_buffer.mm
@@ -27,7 +27,7 @@ bool MetalStreamBuffer::Create(id<MTLDevice> device, u32 size)
     id<MTLBuffer> new_buffer = [device newBufferWithLength:size options:options];
     if (new_buffer == nil)
     {
-      Log_ErrorPrint("Failed to create buffer.");
+      ERROR_LOG("Failed to create buffer.");
       return false;
     }
 
@@ -63,7 +63,7 @@ bool MetalStreamBuffer::ReserveMemory(u32 num_bytes, u32 alignment)
   // Check for sane allocations
   if (required_bytes > m_size) [[unlikely]]
   {
-    Log_ErrorFmt("Attempting to allocate {} bytes from a {} byte stream buffer", num_bytes, m_size);
+    ERROR_LOG("Attempting to allocate {} bytes from a {} byte stream buffer", num_bytes, m_size);
     Panic("Stream buffer overflow");
     return false;
   }
diff --git a/src/util/opengl_context.cpp b/src/util/opengl_context.cpp
index 44e5f761b..4aab851c8 100644
--- a/src/util/opengl_context.cpp
+++ b/src/util/opengl_context.cpp
@@ -67,13 +67,13 @@ static void DisableBrokenExtensions(const char* gl_vendor, const char* gl_render
       // Log_VerbosePrint("Keeping copy_image for driver version '%s'", gl_version);
 
       // Framebuffer blits still end up faster.
-      Log_VerbosePrint("Newer Mali driver detected, disabling GL_{EXT,OES}_copy_image.");
+      VERBOSE_LOG("Newer Mali driver detected, disabling GL_{EXT,OES}_copy_image.");
       GLAD_GL_EXT_copy_image = 0;
       GLAD_GL_OES_copy_image = 0;
     }
     else
     {
-      Log_VerbosePrint("Older Mali driver detected, disabling GL_{EXT,OES}_copy_image, disjoint_timer_query.");
+      VERBOSE_LOG("Older Mali driver detected, disabling GL_{EXT,OES}_copy_image, disjoint_timer_query.");
       GLAD_GL_EXT_copy_image = 0;
       GLAD_GL_OES_copy_image = 0;
       GLAD_GL_EXT_disjoint_timer_query = 0;
@@ -86,13 +86,13 @@ static void DisableBrokenExtensions(const char* gl_vendor, const char* gl_render
     if ((std::sscanf(gl_version, "OpenGL ES %d.%d V@%d", &gl_major_version, &gl_minor_version, &major_version) == 3 &&
          gl_major_version >= 3 && gl_minor_version >= 2 && major_version < 502))
     {
-      Log_VerboseFmt("Disabling GL_EXT_shader_framebuffer_fetch on Adreno version {}", major_version);
+      VERBOSE_LOG("Disabling GL_EXT_shader_framebuffer_fetch on Adreno version {}", major_version);
       GLAD_GL_EXT_shader_framebuffer_fetch = 0;
       GLAD_GL_ARM_shader_framebuffer_fetch = 0;
     }
     else
     {
-      Log_VerboseFmt("Keeping GL_EXT_shader_framebuffer_fetch on Adreno version {}", major_version);
+      VERBOSE_LOG("Keeping GL_EXT_shader_framebuffer_fetch on Adreno version {}", major_version);
     }
   }
 
@@ -175,7 +175,7 @@ std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error
   if (!context)
     return nullptr;
 
-  Log_InfoPrint(context->IsGLES() ? "Created an OpenGL ES context" : "Created an OpenGL context");
+  INFO_LOG(context->IsGLES() ? "Created an OpenGL ES context" : "Created an OpenGL context");
 
   // TODO: Not thread-safe.
   static OpenGLContext* context_being_created;
@@ -203,10 +203,10 @@ std::unique_ptr<OpenGLContext> OpenGLContext::Create(const WindowInfo& wi, Error
   const char* gl_renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
   const char* gl_version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
   const char* gl_shading_language_version = reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION));
-  Log_InfoFmt("GL_VENDOR: {}", gl_vendor);
-  Log_InfoFmt("GL_RENDERER: {}", gl_renderer);
-  Log_InfoFmt("GL_VERSION: {}", gl_version);
-  Log_InfoFmt("GL_SHADING_LANGUAGE_VERSION: {}", gl_shading_language_version);
+  INFO_LOG("GL_VENDOR: {}", gl_vendor);
+  INFO_LOG("GL_RENDERER: {}", gl_renderer);
+  INFO_LOG("GL_VERSION: {}", gl_version);
+  INFO_LOG("GL_SHADING_LANGUAGE_VERSION: {}", gl_shading_language_version);
 
   DisableBrokenExtensions(gl_vendor, gl_renderer, gl_version);
 
diff --git a/src/util/opengl_context_agl.mm b/src/util/opengl_context_agl.mm
index 4174c8553..96de6a2f0 100644
--- a/src/util/opengl_context_agl.mm
+++ b/src/util/opengl_context_agl.mm
@@ -15,7 +15,7 @@ OpenGLContextAGL::OpenGLContextAGL(const WindowInfo& wi) : OpenGLContext(wi)
 {
   m_opengl_module_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL", RTLD_NOW);
   if (!m_opengl_module_handle)
-    Log_ErrorPrint("Could not open OpenGL.framework, function lookups will probably fail");
+    ERROR_LOG("Could not open OpenGL.framework, function lookups will probably fail");
 }
 
 OpenGLContextAGL::~OpenGLContextAGL()
diff --git a/src/util/opengl_context_egl.cpp b/src/util/opengl_context_egl.cpp
index ffe898599..2049b7d4e 100644
--- a/src/util/opengl_context_egl.cpp
+++ b/src/util/opengl_context_egl.cpp
@@ -27,16 +27,16 @@ static bool LoadEGL()
     DebugAssert(!s_egl_library.IsOpen());
 
     std::string egl_libname = DynamicLibrary::GetVersionedFilename("libEGL");
-    Log_InfoFmt("Loading EGL from {}...", egl_libname);
+    INFO_LOG("Loading EGL from {}...", egl_libname);
 
     Error error;
     if (!s_egl_library.Open(egl_libname.c_str(), &error))
     {
       // Try versioned.
       egl_libname = DynamicLibrary::GetVersionedFilename("libEGL", 1);
-      Log_InfoFmt("Loading EGL from {}...", egl_libname);
+      INFO_LOG("Loading EGL from {}...", egl_libname);
       if (!s_egl_library.Open(egl_libname.c_str(), &error))
-        Log_ErrorFmt("Failed to load EGL: {}", error.GetDescription());
+        ERROR_LOG("Failed to load EGL: {}", error.GetDescription());
     }
   }
 
@@ -48,7 +48,7 @@ static void UnloadEGL()
   DebugAssert(s_egl_refcount.load(std::memory_order_acquire) > 0);
   if (s_egl_refcount.fetch_sub(1, std::memory_order_acq_rel) == 1)
   {
-    Log_InfoPrint("Unloading EGL.");
+    INFO_LOG("Unloading EGL.");
     s_egl_library.Close();
   }
 }
@@ -63,7 +63,7 @@ static bool LoadGLADEGL(EGLDisplay display, Error* error)
     return false;
   }
 
-  Log_DevFmt("GLAD EGL Version: {}.{}", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
+  DEV_LOG("GLAD EGL Version: {}.{}", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
   return true;
 }
 
@@ -106,14 +106,14 @@ bool OpenGLContextEGL::Initialize(std::span<const Version> versions_to_try, Erro
     return false;
   }
 
-  Log_DevFmt("eglInitialize() version: {}.{}", egl_major, egl_minor);
+  DEV_LOG("eglInitialize() version: {}.{}", egl_major, egl_minor);
 
   // Re-initialize EGL/GLAD.
   if (!LoadGLADEGL(m_display, error))
     return false;
 
   if (!GLAD_EGL_KHR_surfaceless_context)
-    Log_WarningPrint("EGL implementation does not support surfaceless contexts, emulating with pbuffers");
+    WARNING_LOG("EGL implementation does not support surfaceless contexts, emulating with pbuffers");
 
   for (const Version& cv : versions_to_try)
   {
@@ -147,14 +147,14 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char*
   const char* extensions_str = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
   if (!extensions_str)
   {
-    Log_ErrorPrint("No extensions supported.");
+    ERROR_LOG("No extensions supported.");
     return EGL_NO_DISPLAY;
   }
 
   EGLDisplay dpy = EGL_NO_DISPLAY;
   if (platform_ext && std::strstr(extensions_str, platform_ext))
   {
-    Log_DevFmt("Using EGL platform {}.", platform_ext);
+    DEV_LOG("Using EGL platform {}.", platform_ext);
 
     PFNEGLGETPLATFORMDISPLAYEXTPROC get_platform_display_ext =
       (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
@@ -165,17 +165,17 @@ EGLDisplay OpenGLContextEGL::TryGetPlatformDisplay(EGLenum platform, const char*
       if (!m_use_ext_platform_base)
       {
         const EGLint err = eglGetError();
-        Log_ErrorFmt("eglGetPlatformDisplayEXT() failed: {} (0x{:X})", err, err);
+        ERROR_LOG("eglGetPlatformDisplayEXT() failed: {} (0x{:X})", err, err);
       }
     }
     else
     {
-      Log_WarningPrint("eglGetPlatformDisplayEXT() was not found");
+      WARNING_LOG("eglGetPlatformDisplayEXT() was not found");
     }
   }
   else
   {
-    Log_WarningFmt("{} is not supported.", platform_ext);
+    WARNING_LOG("{} is not supported.", platform_ext);
   }
 
   return dpy;
@@ -199,7 +199,7 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi
     }
     else
     {
-      Log_ErrorPrint("eglCreatePlatformWindowSurfaceEXT() not found");
+      ERROR_LOG("eglCreatePlatformWindowSurfaceEXT() not found");
     }
   }
 
@@ -208,7 +208,7 @@ EGLSurface OpenGLContextEGL::TryCreatePlatformSurface(EGLConfig config, void* wi
 
 EGLDisplay OpenGLContextEGL::GetFallbackDisplay(Error* error)
 {
-  Log_WarningPrint("Using fallback eglGetDisplay() path.");
+  WARNING_LOG("Using fallback eglGetDisplay() path.");
 
   EGLDisplay dpy = eglGetDisplay(m_wi.display_connection);
   if (dpy == EGL_NO_DISPLAY)
@@ -222,7 +222,7 @@ EGLDisplay OpenGLContextEGL::GetFallbackDisplay(Error* error)
 
 EGLSurface OpenGLContextEGL::CreateFallbackSurface(EGLConfig config, void* win, Error* error)
 {
-  Log_WarningPrint("Using fallback eglCreateWindowSurface() path.");
+  WARNING_LOG("Using fallback eglCreateWindowSurface() path.");
 
   EGLSurface surface = eglCreateWindowSurface(m_display, config, (EGLNativeWindowType)win, nullptr);
   if (surface == EGL_NO_SURFACE)
@@ -257,7 +257,7 @@ bool OpenGLContextEGL::ChangeSurface(const WindowInfo& new_wi)
 
   if (was_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
   {
-    Log_ErrorPrint("Failed to make context current again after surface change");
+    ERROR_LOG("Failed to make context current again after surface change");
     return false;
   }
 
@@ -278,7 +278,7 @@ void OpenGLContextEGL::ResizeSurface(u32 new_surface_width /*= 0*/, u32 new_surf
     }
     else
     {
-      Log_ErrorFmt("eglQuerySurface() failed: 0x{:X}", eglGetError());
+      ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
     }
   }
 
@@ -300,7 +300,7 @@ bool OpenGLContextEGL::MakeCurrent()
 {
   if (!eglMakeCurrent(m_display, m_surface, m_surface, m_context)) [[unlikely]]
   {
-    Log_ErrorFmt("eglMakeCurrent() failed: 0x{:X}", eglGetError());
+    ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError());
     return false;
   }
 
@@ -350,7 +350,7 @@ bool OpenGLContextEGL::CreateSurface()
   m_surface = CreatePlatformSurface(m_config, m_wi.window_handle, &error);
   if (m_surface == EGL_NO_SURFACE)
   {
-    Log_ErrorFmt("Failed to create platform surface: {}", error.GetDescription());
+    ERROR_LOG("Failed to create platform surface: {}", error.GetDescription());
     return false;
   }
 
@@ -364,7 +364,7 @@ bool OpenGLContextEGL::CreateSurface()
   }
   else
   {
-    Log_ErrorFmt("eglQuerySurface() failed: 0x{:X}", eglGetError());
+    ERROR_LOG("eglQuerySurface() failed: 0x{:X}", eglGetError());
   }
 
   m_wi.surface_format = GetSurfaceTextureFormat();
@@ -385,13 +385,13 @@ bool OpenGLContextEGL::CreatePBufferSurface()
   m_surface = eglCreatePbufferSurface(m_display, m_config, attrib_list);
   if (!m_surface) [[unlikely]]
   {
-    Log_ErrorFmt("eglCreatePbufferSurface() failed: {}", eglGetError());
+    ERROR_LOG("eglCreatePbufferSurface() failed: {}", eglGetError());
     return false;
   }
 
   m_wi.surface_format = GetSurfaceTextureFormat();
 
-  Log_DevFmt("Created {}x{} pbuffer surface", width, height);
+  DEV_LOG("Created {}x{} pbuffer surface", width, height);
   return true;
 }
 
@@ -447,7 +447,7 @@ GPUTexture::Format OpenGLContextEGL::GetSurfaceTextureFormat() const
   }
   else
   {
-    Log_ErrorFmt("Unknown surface format: R={}, G={}, B={}, A={}", red_size, green_size, blue_size, alpha_size);
+    ERROR_LOG("Unknown surface format: R={}, G={}, B={}, A={}", red_size, green_size, blue_size, alpha_size);
     return GPUTexture::Format::RGBA8;
   }
 }
@@ -478,10 +478,10 @@ void OpenGLContextEGL::DestroySurface()
 
 bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_context)
 {
-  Log_DevFmt("Trying version {}.{} ({})", version.major_version, version.minor_version,
-             version.profile == OpenGLContext::Profile::ES ?
-               "ES" :
-               (version.profile == OpenGLContext::Profile::Core ? "Core" : "None"));
+  DEV_LOG("Trying version {}.{} ({})", version.major_version, version.minor_version,
+          version.profile == OpenGLContext::Profile::ES ?
+            "ES" :
+            (version.profile == OpenGLContext::Profile::Core ? "Core" : "None"));
   int surface_attribs[16] = {
     EGL_RENDERABLE_TYPE,
     (version.profile == Profile::ES) ?
@@ -496,7 +496,7 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
   const GPUTexture::Format format = m_wi.surface_format;
   if (format == GPUTexture::Format::Unknown)
   {
-    Log_WarningPrint("Surface format not specified, assuming RGBA8.");
+    WARNING_LOG("Surface format not specified, assuming RGBA8.");
     m_wi.surface_format = GPUTexture::Format::RGBA8;
   }
 
@@ -536,14 +536,14 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
   EGLint num_configs;
   if (!eglChooseConfig(m_display, surface_attribs, nullptr, 0, &num_configs) || num_configs == 0)
   {
-    Log_ErrorFmt("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
+    ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
     return false;
   }
 
   std::vector<EGLConfig> configs(static_cast<u32>(num_configs));
   if (!eglChooseConfig(m_display, surface_attribs, configs.data(), num_configs, &num_configs))
   {
-    Log_ErrorFmt("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
+    ERROR_LOG("eglChooseConfig() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
     return false;
   }
   configs.resize(static_cast<u32>(num_configs));
@@ -560,7 +560,7 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
 
   if (!config.has_value())
   {
-    Log_WarningPrint("No EGL configs matched exactly, using first.");
+    WARNING_LOG("No EGL configs matched exactly, using first.");
     config = configs.front();
   }
 
@@ -578,33 +578,33 @@ bool OpenGLContextEGL::CreateContext(const Version& version, EGLContext share_co
 
   if (!eglBindAPI((version.profile == Profile::ES) ? EGL_OPENGL_ES_API : EGL_OPENGL_API))
   {
-    Log_ErrorFmt("eglBindAPI({}) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API");
+    ERROR_LOG("eglBindAPI({}) failed", (version.profile == Profile::ES) ? "EGL_OPENGL_ES_API" : "EGL_OPENGL_API");
     return false;
   }
 
   m_context = eglCreateContext(m_display, config.value(), share_context, attribs);
   if (!m_context)
   {
-    Log_ErrorFmt("eglCreateContext() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
+    ERROR_LOG("eglCreateContext() failed: 0x{:x}", static_cast<unsigned>(eglGetError()));
     return false;
   }
 
-  Log_InfoFmt("Got version {}.{} ({})", version.major_version, version.minor_version,
-              version.profile == OpenGLContext::Profile::ES ?
-                "ES" :
-                (version.profile == OpenGLContext::Profile::Core ? "Core" : "None"));
+  INFO_LOG("Got version {}.{} ({})", version.major_version, version.minor_version,
+           version.profile == OpenGLContext::Profile::ES ?
+             "ES" :
+             (version.profile == OpenGLContext::Profile::Core ? "Core" : "None"));
 
   EGLint min_swap_interval, max_swap_interval;
   m_supports_negative_swap_interval = false;
   if (eglGetConfigAttrib(m_display, config.value(), EGL_MIN_SWAP_INTERVAL, &min_swap_interval) &&
       eglGetConfigAttrib(m_display, config.value(), EGL_MAX_SWAP_INTERVAL, &max_swap_interval))
   {
-    Log_VerboseFmt("EGL_MIN_SWAP_INTERVAL = {}", min_swap_interval);
-    Log_VerboseFmt("EGL_MAX_SWAP_INTERVAL = {}", max_swap_interval);
+    VERBOSE_LOG("EGL_MIN_SWAP_INTERVAL = {}", min_swap_interval);
+    VERBOSE_LOG("EGL_MAX_SWAP_INTERVAL = {}", max_swap_interval);
     m_supports_negative_swap_interval = (min_swap_interval <= -1);
   }
 
-  Log_InfoFmt("Negative swap interval/tear-control is {}supported", m_supports_negative_swap_interval ? "" : "NOT ");
+  INFO_LOG("Negative swap interval/tear-control is {}supported", m_supports_negative_swap_interval ? "" : "NOT ");
 
   m_config = config.value();
   m_version = version;
@@ -618,7 +618,7 @@ bool OpenGLContextEGL::CreateContextAndSurface(const Version& version, EGLContex
 
   if (!CreateSurface())
   {
-    Log_ErrorPrint("Failed to create surface for context");
+    ERROR_LOG("Failed to create surface for context");
     eglDestroyContext(m_display, m_context);
     m_context = EGL_NO_CONTEXT;
     return false;
@@ -626,7 +626,7 @@ bool OpenGLContextEGL::CreateContextAndSurface(const Version& version, EGLContex
 
   if (make_current && !eglMakeCurrent(m_display, m_surface, m_surface, m_context))
   {
-    Log_ErrorFmt("eglMakeCurrent() failed: 0x{:X}", eglGetError());
+    ERROR_LOG("eglMakeCurrent() failed: 0x{:X}", eglGetError());
     if (m_surface != EGL_NO_SURFACE)
     {
       eglDestroySurface(m_display, m_surface);
diff --git a/src/util/opengl_context_wgl.cpp b/src/util/opengl_context_wgl.cpp
index dab38c295..a44e07bc3 100644
--- a/src/util/opengl_context_wgl.cpp
+++ b/src/util/opengl_context_wgl.cpp
@@ -29,7 +29,7 @@ static bool ReloadWGL(HDC dc)
 {
   if (!gladLoadWGL(dc, [](const char* name) { return (GLADapiproc)wglGetProcAddress(name); }))
   {
-    Log_ErrorPrint("Loading GLAD WGL functions failed");
+    ERROR_LOG("Loading GLAD WGL functions failed");
     return false;
   }
 
@@ -112,14 +112,14 @@ bool OpenGLContextWGL::ChangeSurface(const WindowInfo& new_wi)
   m_wi = new_wi;
   if (!InitializeDC(&error))
   {
-    Log_ErrorFmt("Failed to change surface: {}", error.GetDescription());
+    ERROR_LOG("Failed to change surface: {}", error.GetDescription());
     return false;
   }
 
   if (was_current && !wglMakeCurrent(m_dc, m_rc))
   {
     error.SetWin32(GetLastError());
-    Log_ErrorFmt("Failed to make context current again after surface change: {}", error.GetDescription());
+    ERROR_LOG("Failed to make context current again after surface change: {}", error.GetDescription());
     return false;
   }
 
@@ -148,7 +148,7 @@ bool OpenGLContextWGL::MakeCurrent()
 {
   if (!wglMakeCurrent(m_dc, m_rc))
   {
-    Log_ErrorFmt("wglMakeCurrent() failed: {}", GetLastError());
+    ERROR_LOG("wglMakeCurrent() failed: {}", GetLastError());
     return false;
   }
 
diff --git a/src/util/opengl_device.cpp b/src/util/opengl_device.cpp
index 8bde3b6d0..f869821e6 100644
--- a/src/util/opengl_device.cpp
+++ b/src/util/opengl_device.cpp
@@ -254,13 +254,13 @@ static void GLAD_API_PTR GLDebugCallback(GLenum source, GLenum type, GLuint id,
   switch (severity)
   {
     case GL_DEBUG_SEVERITY_HIGH_KHR:
-      Log_ErrorPrint(message);
+      ERROR_LOG(message);
       break;
     case GL_DEBUG_SEVERITY_MEDIUM_KHR:
-      Log_WarningPrint(message);
+      WARNING_LOG(message);
       break;
     case GL_DEBUG_SEVERITY_LOW_KHR:
-      Log_InfoPrint(message);
+      INFO_LOG(message);
       break;
     case GL_DEBUG_SEVERITY_NOTIFICATION:
       // Log_DebugPrint(message);
@@ -280,7 +280,7 @@ bool OpenGLDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
   m_gl_context = OpenGLContext::Create(m_window_info, error);
   if (!m_gl_context)
   {
-    Log_ErrorPrint("Failed to create any GL context");
+    ERROR_LOG("Failed to create any GL context");
     m_gl_context.reset();
     return false;
   }
@@ -351,32 +351,32 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
   if (std::strstr(vendor, "Advanced Micro Devices") || std::strstr(vendor, "ATI Technologies Inc.") ||
       std::strstr(vendor, "ATI"))
   {
-    Log_InfoPrint("AMD GPU detected.");
+    INFO_LOG("AMD GPU detected.");
     vendor_id_amd = true;
   }
   else if (std::strstr(vendor, "NVIDIA Corporation"))
   {
-    Log_InfoPrint("NVIDIA GPU detected.");
+    INFO_LOG("NVIDIA GPU detected.");
     // vendor_id_nvidia = true;
   }
   else if (std::strstr(vendor, "Intel"))
   {
-    Log_InfoPrint("Intel GPU detected.");
+    INFO_LOG("Intel GPU detected.");
     vendor_id_intel = true;
   }
   else if (std::strstr(vendor, "ARM"))
   {
-    Log_InfoPrint("ARM GPU detected.");
+    INFO_LOG("ARM GPU detected.");
     vendor_id_arm = true;
   }
   else if (std::strstr(vendor, "Qualcomm"))
   {
-    Log_InfoPrint("Qualcomm GPU detected.");
+    INFO_LOG("Qualcomm GPU detected.");
     vendor_id_qualcomm = true;
   }
   else if (std::strstr(vendor, "Imagination Technologies") || std::strstr(renderer, "PowerVR"))
   {
-    Log_InfoPrint("PowerVR GPU detected.");
+    INFO_LOG("PowerVR GPU detected.");
     vendor_id_powervr = true;
   }
 
@@ -387,14 +387,14 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
   m_disable_pbo =
     (!GLAD_GL_VERSION_4_4 && !GLAD_GL_ARB_buffer_storage && !GLAD_GL_EXT_buffer_storage) || is_shitty_mobile_driver;
   if (m_disable_pbo && !is_shitty_mobile_driver)
-    Log_WarningPrint("Not using PBOs for texture uploads because buffer_storage is unavailable.");
+    WARNING_LOG("Not using PBOs for texture uploads because buffer_storage is unavailable.");
 
   GLint max_texture_size = 1024;
   GLint max_samples = 1;
   glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
-  Log_DevFmt("GL_MAX_TEXTURE_SIZE: {}", max_texture_size);
+  DEV_LOG("GL_MAX_TEXTURE_SIZE: {}", max_texture_size);
   glGetIntegerv(GL_MAX_SAMPLES, &max_samples);
-  Log_DevFmt("GL_MAX_SAMPLES: {}", max_samples);
+  DEV_LOG("GL_MAX_SAMPLES: {}", max_samples);
   m_max_texture_size = std::max(1024u, static_cast<u32>(max_texture_size));
   m_max_multisamples = std::max(1u, static_cast<u32>(max_samples));
 
@@ -423,11 +423,11 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
   {
     GLint max_texel_buffer_size = 0;
     glGetIntegerv(GL_MAX_TEXTURE_BUFFER_SIZE, reinterpret_cast<GLint*>(&max_texel_buffer_size));
-    Log_DevFmt("GL_MAX_TEXTURE_BUFFER_SIZE: {}", max_texel_buffer_size);
+    DEV_LOG("GL_MAX_TEXTURE_BUFFER_SIZE: {}", max_texel_buffer_size);
     if (max_texel_buffer_size < static_cast<GLint>(MIN_TEXEL_BUFFER_ELEMENTS))
     {
-      Log_WarningFmt("GL_MAX_TEXTURE_BUFFER_SIZE ({}) is below required minimum ({}), not using texture buffers.",
-                     max_texel_buffer_size, MIN_TEXEL_BUFFER_ELEMENTS);
+      WARNING_LOG("GL_MAX_TEXTURE_BUFFER_SIZE ({}) is below required minimum ({}), not using texture buffers.",
+                  max_texel_buffer_size, MIN_TEXEL_BUFFER_ELEMENTS);
       m_features.supports_texture_buffers = false;
     }
   }
@@ -444,18 +444,18 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
       glGetInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size);
     }
 
-    Log_DevFmt("GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS: {}", max_fragment_storage_blocks);
-    Log_DevFmt("GL_MAX_SHADER_STORAGE_BLOCK_SIZE: {}", max_ssbo_size);
+    DEV_LOG("GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS: {}", max_fragment_storage_blocks);
+    DEV_LOG("GL_MAX_SHADER_STORAGE_BLOCK_SIZE: {}", max_ssbo_size);
     m_features.texture_buffers_emulated_with_ssbo =
       (max_fragment_storage_blocks > 0 && max_ssbo_size >= static_cast<GLint64>(1024 * 512 * sizeof(u16)));
     if (m_features.texture_buffers_emulated_with_ssbo)
     {
-      Log_InfoPrint("Using shader storage buffers for VRAM writes.");
+      INFO_LOG("Using shader storage buffers for VRAM writes.");
       m_features.supports_texture_buffers = true;
     }
     else
     {
-      Log_WarningPrint("Both texture buffers and SSBOs are not supported. Performance will suffer.");
+      WARNING_LOG("Both texture buffers and SSBOs are not supported. Performance will suffer.");
     }
   }
 
@@ -490,14 +490,14 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
     // check that there's at least one format and the extension isn't being "faked"
     GLint num_formats = 0;
     glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &num_formats);
-    Log_DevFmt("{} program binary formats supported by driver", num_formats);
+    DEV_LOG("{} program binary formats supported by driver", num_formats);
     m_features.pipeline_cache = (num_formats > 0);
   }
 
   if (!m_features.pipeline_cache)
   {
-    Log_WarningPrint("Your GL driver does not support program binaries. Hopefully it has a built-in cache, otherwise "
-                     "startup will be slow due to compiling shaders.");
+    WARNING_LOG("Your GL driver does not support program binaries. Hopefully it has a built-in cache, otherwise "
+                "startup will be slow due to compiling shaders.");
   }
 
   // Mobile drivers prefer textures to not be updated mid-frame.
@@ -506,7 +506,7 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
   if (vendor_id_intel)
   {
     // Intel drivers corrupt image on readback when syncs are used for downloads.
-    Log_WarningPrint("Disabling async downloads with PBOs due to it being broken on Intel drivers.");
+    WARNING_LOG("Disabling async downloads with PBOs due to it being broken on Intel drivers.");
     m_disable_async_download = true;
   }
 
@@ -536,7 +536,7 @@ bool OpenGLDevice::UpdateWindow()
 
   if (!m_gl_context->ChangeSurface(m_window_info))
   {
-    Log_ErrorPrint("Failed to change surface");
+    ERROR_LOG("Failed to change surface");
     return false;
   }
 
@@ -590,7 +590,7 @@ void OpenGLDevice::SetSwapInterval()
   glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
 
   if (!m_gl_context->SetSwapInterval(interval))
-    Log_WarningFmt("Failed to set swap interval to {}", interval);
+    WARNING_LOG("Failed to set swap interval to {}", interval);
 
   glBindFramebuffer(GL_DRAW_FRAMEBUFFER, current_fbo);
 }
@@ -643,7 +643,7 @@ GLuint OpenGLDevice::CreateFramebuffer(GPUTexture* const* rts, u32 num_rts, GPUT
 
   if (glGetError() != GL_NO_ERROR || glCheckFramebufferStatus(GL_DRAW_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
   {
-    Log_ErrorFmt("Failed to create GL framebuffer: {}", static_cast<s32>(glGetError()));
+    ERROR_LOG("Failed to create GL framebuffer: {}", static_cast<s32>(glGetError()));
     glBindFramebuffer(GL_DRAW_FRAMEBUFFER, OpenGLDevice::GetInstance().m_current_fbo);
     glDeleteFramebuffers(1, &fbo_id);
     return {};
@@ -681,7 +681,7 @@ void OpenGLDevice::DestroySurface()
 
   m_window_info.SetSurfaceless();
   if (!m_gl_context->ChangeSurface(m_window_info))
-    Log_ErrorPrint("Failed to switch to surfaceless");
+    ERROR_LOG("Failed to switch to surfaceless");
 }
 
 bool OpenGLDevice::CreateBuffers()
@@ -690,7 +690,7 @@ bool OpenGLDevice::CreateBuffers()
       !(m_index_buffer = OpenGLStreamBuffer::Create(GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE)) ||
       !(m_uniform_buffer = OpenGLStreamBuffer::Create(GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE))) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to create one or more device buffers.");
+    ERROR_LOG("Failed to create one or more device buffers.");
     return false;
   }
 
@@ -705,7 +705,7 @@ bool OpenGLDevice::CreateBuffers()
     if (!(m_texture_stream_buffer = OpenGLStreamBuffer::Create(GL_PIXEL_UNPACK_BUFFER, TEXTURE_STREAM_BUFFER_SIZE)))
       [[unlikely]]
     {
-      Log_ErrorPrint("Failed to create texture stream buffer");
+      ERROR_LOG("Failed to create texture stream buffer");
       return false;
     }
 
@@ -720,7 +720,7 @@ bool OpenGLDevice::CreateBuffers()
   glGenFramebuffers(static_cast<GLsizei>(std::size(fbos)), fbos);
   if (const GLenum err = glGetError(); err != GL_NO_ERROR) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to create framebuffers: {}", err);
+    ERROR_LOG("Failed to create framebuffers: {}", err);
     return false;
   }
   m_read_fbo = fbos[0];
@@ -839,7 +839,7 @@ void OpenGLDevice::PopTimestampQuery()
     glGetIntegerv(GL_GPU_DISJOINT_EXT, &disjoint);
     if (disjoint)
     {
-      Log_VerbosePrint("GPU timing disjoint, resetting.");
+      VERBOSE_LOG("GPU timing disjoint, resetting.");
       if (m_timestamp_query_started)
         glEndQueryEXT(GL_TIME_ELAPSED);
 
@@ -953,7 +953,7 @@ void OpenGLDevice::UnbindTexture(OpenGLTexture* tex)
     {
       if (m_current_render_targets[i] == tex)
       {
-        Log_WarningPrint("Unbinding current RT");
+        WARNING_LOG("Unbinding current RT");
         SetRenderTargets(nullptr, 0, m_current_depth_target);
         break;
       }
@@ -965,7 +965,7 @@ void OpenGLDevice::UnbindTexture(OpenGLTexture* tex)
   {
     if (m_current_depth_target == tex)
     {
-      Log_WarningPrint("Unbinding current DS");
+      WARNING_LOG("Unbinding current DS");
       SetRenderTargets(nullptr, 0, nullptr);
     }
 
@@ -1130,7 +1130,7 @@ void OpenGLDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUText
     {
       if ((fbo = m_framebuffer_manager.Lookup(rts, num_rts, ds, 0)) == 0)
       {
-        Log_ErrorFmt("Failed to get FBO for {} render targets", num_rts);
+        ERROR_LOG("Failed to get FBO for {} render targets", num_rts);
         m_current_fbo = 0;
         std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
         m_num_current_render_targets = 0;
diff --git a/src/util/opengl_pipeline.cpp b/src/util/opengl_pipeline.cpp
index 044c602dd..4cd2bef1e 100644
--- a/src/util/opengl_pipeline.cpp
+++ b/src/util/opengl_pipeline.cpp
@@ -110,7 +110,7 @@ bool OpenGLShader::Compile()
   GLuint shader = glCreateShader(GetGLShaderType(m_stage));
   if (GLenum err = glGetError(); err != GL_NO_ERROR)
   {
-    Log_ErrorFmt("glCreateShader() failed: {}", err);
+    ERROR_LOG("glCreateShader() failed: {}", err);
     return false;
   }
 
@@ -133,11 +133,11 @@ bool OpenGLShader::Compile()
 
     if (status == GL_TRUE)
     {
-      Log_ErrorFmt("Shader compiled with warnings:\n{}", info_log);
+      ERROR_LOG("Shader compiled with warnings:\n{}", info_log);
     }
     else
     {
-      Log_ErrorFmt("Shader failed to compile:\n{}", info_log);
+      ERROR_LOG("Shader failed to compile:\n{}", info_log);
       GPUDevice::DumpBadShader(m_source, info_log);
       glDeleteShader(shader);
       return false;
@@ -170,7 +170,7 @@ std::unique_ptr<GPUShader> OpenGLDevice::CreateShaderFromSource(GPUShaderStage s
 {
   if (std::strcmp(entry_point, "main") != 0)
   {
-    Log_ErrorFmt("Entry point must be 'main', but got '{}' instead.", entry_point);
+    ERROR_LOG("Entry point must be 'main', but got '{}' instead.", entry_point);
     return {};
   }
 
@@ -265,7 +265,7 @@ GLuint OpenGLDevice::LookupProgramCache(const OpenGLPipeline::ProgramCacheKey& k
     it->second.program_id = CreateProgramFromPipelineCache(it->second, plconfig);
     if (it->second.program_id == 0)
     {
-      Log_ErrorPrint("Failed to create program from binary.");
+      ERROR_LOG("Failed to create program from binary.");
       m_program_cache.erase(it);
       it = m_program_cache.end();
       DiscardPipelineCache();
@@ -309,7 +309,7 @@ GLuint OpenGLDevice::CompileProgram(const GPUPipeline::GraphicsConfig& plconfig)
   if (!vertex_shader || !fragment_shader || !vertex_shader->Compile() || !fragment_shader->Compile() ||
       (geometry_shader && !geometry_shader->Compile())) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to compile shaders.");
+    ERROR_LOG("Failed to compile shaders.");
     return 0;
   }
 
@@ -317,7 +317,7 @@ GLuint OpenGLDevice::CompileProgram(const GPUPipeline::GraphicsConfig& plconfig)
   const GLuint program_id = glCreateProgram();
   if (glGetError() != GL_NO_ERROR) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to create program object.");
+    ERROR_LOG("Failed to create program object.");
     return 0;
   }
 
@@ -381,11 +381,11 @@ GLuint OpenGLDevice::CompileProgram(const GPUPipeline::GraphicsConfig& plconfig)
 
     if (status == GL_TRUE)
     {
-      Log_ErrorFmt("Program linked with warnings:\n{}", info_log.c_str());
+      ERROR_LOG("Program linked with warnings:\n{}", info_log.c_str());
     }
     else
     {
-      Log_ErrorFmt("Program failed to link:\n{}", info_log.c_str());
+      ERROR_LOG("Program failed to link:\n{}", info_log.c_str());
       glDeleteProgram(program_id);
       return 0;
     }
@@ -469,7 +469,7 @@ GLuint OpenGLDevice::CreateVAO(std::span<const GPUPipeline::VertexAttribute> att
   glGenVertexArrays(1, &vao);
   if (const GLenum err = glGetError(); err != GL_NO_ERROR)
   {
-    Log_ErrorFmt("Failed to create vertex array object: {}", err);
+    ERROR_LOG("Failed to create vertex array object: {}", err);
     return 0;
   }
 
@@ -737,12 +737,12 @@ bool OpenGLDevice::ReadPipelineCache(const std::string& filename)
     // If it doesn't exist, we're going to create it.
     if (errno != ENOENT)
     {
-      Log_WarningFmt("Failed to open shader cache: {}", error.GetDescription());
+      WARNING_LOG("Failed to open shader cache: {}", error.GetDescription());
       m_pipeline_disk_cache_filename = {};
       return false;
     }
 
-    Log_WarningPrint("Disk cache does not exist, creating.");
+    WARNING_LOG("Disk cache does not exist, creating.");
     return DiscardPipelineCache();
   }
 
@@ -758,7 +758,7 @@ bool OpenGLDevice::ReadPipelineCache(const std::string& filename)
   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, size - sizeof(PipelineDiskCacheFooter), SEEK_SET) != 0 ||
       std::fread(&file_footer, sizeof(file_footer), 1, m_pipeline_disk_cache_file) != 1)
   {
-    Log_ErrorPrint("Failed to read disk cache footer.");
+    ERROR_LOG("Failed to read disk cache footer.");
     return DiscardPipelineCache();
   }
 
@@ -773,7 +773,7 @@ bool OpenGLDevice::ReadPipelineCache(const std::string& filename)
       std::strncmp(file_footer.driver_version, expected_footer.driver_version, std::size(file_footer.driver_version)) !=
         0)
   {
-    Log_ErrorPrint("Disk cache does not match expected driver/version.");
+    ERROR_LOG("Disk cache does not match expected driver/version.");
     return DiscardPipelineCache();
   }
 
@@ -782,7 +782,7 @@ bool OpenGLDevice::ReadPipelineCache(const std::string& filename)
   if (m_pipeline_disk_cache_data_end < 0 ||
       FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0)
   {
-    Log_ErrorPrint("Failed to seek to start of index entries.");
+    ERROR_LOG("Failed to seek to start of index entries.");
     return DiscardPipelineCache();
   }
 
@@ -793,13 +793,13 @@ bool OpenGLDevice::ReadPipelineCache(const std::string& filename)
     if (std::fread(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1 ||
         (static_cast<s64>(entry.offset) + static_cast<s64>(entry.compressed_size)) >= size)
     {
-      Log_ErrorPrint("Failed to read disk cache entry.");
+      ERROR_LOG("Failed to read disk cache entry.");
       return DiscardPipelineCache();
     }
 
     if (m_program_cache.find(entry.key) != m_program_cache.end())
     {
-      Log_ErrorPrint("Duplicate program in disk cache.");
+      ERROR_LOG("Duplicate program in disk cache.");
       return DiscardPipelineCache();
     }
 
@@ -813,7 +813,7 @@ bool OpenGLDevice::ReadPipelineCache(const std::string& filename)
     m_program_cache.emplace(entry.key, pitem);
   }
 
-  Log_VerboseFmt("Read {} programs from disk cache.", m_program_cache.size());
+  VERBOSE_LOG("Read {} programs from disk cache.", m_program_cache.size());
   return true;
 }
 
@@ -832,7 +832,7 @@ GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::Progra
   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, it.file_offset, SEEK_SET) != 0 ||
       std::fread(compressed_data.data(), it.file_compressed_size, 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to read program from disk cache.");
+    ERROR_LOG("Failed to read program from disk cache.");
     return 0;
   }
 
@@ -840,7 +840,7 @@ GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::Progra
     ZSTD_decompress(data.data(), data.size(), compressed_data.data(), compressed_data.size());
   if (ZSTD_isError(decompress_result)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to decompress program from disk cache: {}", ZSTD_getErrorName(decompress_result));
+    ERROR_LOG("Failed to decompress program from disk cache: {}", ZSTD_getErrorName(decompress_result));
     return 0;
   }
   compressed_data.deallocate();
@@ -849,7 +849,7 @@ GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::Progra
   GLuint prog = glCreateProgram();
   if (const GLenum err = glGetError(); err != GL_NO_ERROR) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to create program object: {}", err);
+    ERROR_LOG("Failed to create program object: {}", err);
     return 0;
   }
 
@@ -859,7 +859,7 @@ GLuint OpenGLDevice::CreateProgramFromPipelineCache(const OpenGLPipeline::Progra
   glGetProgramiv(prog, GL_LINK_STATUS, &link_status);
   if (link_status != GL_TRUE) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to create GL program from binary: status {}, discarding cache.", link_status);
+    ERROR_LOG("Failed to create GL program from binary: status {}, discarding cache.", link_status);
     glDeleteProgram(prog);
     return 0;
   }
@@ -878,7 +878,7 @@ void OpenGLDevice::AddToPipelineCache(OpenGLPipeline::ProgramCacheItem* it)
   glGetProgramiv(it->program_id, GL_PROGRAM_BINARY_LENGTH, &binary_size);
   if (binary_size == 0)
   {
-    Log_WarningPrint("glGetProgramiv(GL_PROGRAM_BINARY_LENGTH) returned 0");
+    WARNING_LOG("glGetProgramiv(GL_PROGRAM_BINARY_LENGTH) returned 0");
     return;
   }
 
@@ -887,12 +887,12 @@ void OpenGLDevice::AddToPipelineCache(OpenGLPipeline::ProgramCacheItem* it)
   glGetProgramBinary(it->program_id, binary_size, &binary_size, &format, uncompressed_data.data());
   if (binary_size == 0)
   {
-    Log_WarningPrint("glGetProgramBinary() failed");
+    WARNING_LOG("glGetProgramBinary() failed");
     return;
   }
   else if (static_cast<size_t>(binary_size) != uncompressed_data.size()) [[unlikely]]
   {
-    Log_WarningFmt("Size changed from {} to {} after glGetProgramBinary()", uncompressed_data.size(), binary_size);
+    WARNING_LOG("Size changed from {} to {} after glGetProgramBinary()", uncompressed_data.size(), binary_size);
   }
 
   DynamicHeapArray<u8> compressed_data(ZSTD_compressBound(binary_size));
@@ -900,17 +900,16 @@ void OpenGLDevice::AddToPipelineCache(OpenGLPipeline::ProgramCacheItem* it)
     ZSTD_compress(compressed_data.data(), compressed_data.size(), uncompressed_data.data(), binary_size, 0);
   if (ZSTD_isError(compress_result)) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to compress program: {}", ZSTD_getErrorName(compress_result));
+    ERROR_LOG("Failed to compress program: {}", ZSTD_getErrorName(compress_result));
     return;
   }
 
-  Log_DevFmt("Program binary retrieved and compressed, {} -> {} bytes, format {}", binary_size, compress_result,
-             format);
+  DEV_LOG("Program binary retrieved and compressed, {} -> {} bytes, format {}", binary_size, compress_result, format);
 
   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0 ||
       std::fwrite(compressed_data.data(), compress_result, 1, m_pipeline_disk_cache_file) != 1)
   {
-    Log_ErrorPrint("Failed to write binary to disk cache.");
+    ERROR_LOG("Failed to write binary to disk cache.");
   }
 
   it->file_format = format;
@@ -947,7 +946,7 @@ bool OpenGLDevice::DiscardPipelineCache()
   m_pipeline_disk_cache_file = FileSystem::OpenCFile(m_pipeline_disk_cache_filename.c_str(), "w+b", &error);
   if (!m_pipeline_disk_cache_file) [[unlikely]]
   {
-    Log_ErrorFmt("Failed to reopen pipeline cache: {}", error.GetDescription());
+    ERROR_LOG("Failed to reopen pipeline cache: {}", error.GetDescription());
     m_pipeline_disk_cache_filename = {};
     return false;
   }
@@ -967,13 +966,13 @@ void OpenGLDevice::ClosePipelineCache()
 
   if (!m_pipeline_disk_cache_changed)
   {
-    Log_VerbosePrint("Not updating pipeline cache because it has not changed.");
+    VERBOSE_LOG("Not updating pipeline cache because it has not changed.");
     return;
   }
 
   if (FileSystem::FSeek64(m_pipeline_disk_cache_file, m_pipeline_disk_cache_data_end, SEEK_SET) != 0) [[unlikely]]
   {
-    Log_ErrorPrint("Failed to seek to data end.");
+    ERROR_LOG("Failed to seek to data end.");
     return;
   }
 
@@ -993,7 +992,7 @@ void OpenGLDevice::ClosePipelineCache()
 
     if (std::fwrite(&entry, sizeof(entry), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
     {
-      Log_ErrorPrint("Failed to write index entry.");
+      ERROR_LOG("Failed to write index entry.");
       return;
     }
 
@@ -1005,5 +1004,5 @@ void OpenGLDevice::ClosePipelineCache()
   footer.num_programs = count;
 
   if (std::fwrite(&footer, sizeof(footer), 1, m_pipeline_disk_cache_file) != 1) [[unlikely]]
-    Log_ErrorPrint("Failed to write footer.");
+    ERROR_LOG("Failed to write footer.");
 }
diff --git a/src/util/opengl_texture.cpp b/src/util/opengl_texture.cpp
index f9e18d866..a0f4401e1 100644
--- a/src/util/opengl_texture.cpp
+++ b/src/util/opengl_texture.cpp
@@ -119,7 +119,7 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
 
   if (layers > 1 && data)
   {
-    Log_ErrorPrint("Loading texture array data not currently supported");
+    ERROR_LOG("Loading texture array data not currently supported");
     return nullptr;
   }
 
@@ -224,7 +224,7 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
   GLenum error = glGetError();
   if (error != GL_NO_ERROR)
   {
-    Log_ErrorFmt("Failed to create texture: 0x{:X}", error);
+    ERROR_LOG("Failed to create texture: 0x{:X}", error);
     glDeleteTextures(1, &id);
     return nullptr;
   }
@@ -411,7 +411,7 @@ std::unique_ptr<GPUSampler> OpenGLDevice::CreateSampler(const GPUSampler::Config
   glGenSamplers(1, &sampler);
   if (glGetError() != GL_NO_ERROR)
   {
-    Log_ErrorFmt("Failed to create sampler: {:X}", sampler);
+    ERROR_LOG("Failed to create sampler: {:X}", sampler);
     return {};
   }
 
@@ -630,7 +630,7 @@ bool OpenGLTextureBuffer::CreateBuffer()
     glGenTextures(1, &m_texture_id);
     if (const GLenum err = glGetError(); err != GL_NO_ERROR)
     {
-      Log_ErrorFmt("Failed to create texture for buffer: 0x{:X}", err);
+      ERROR_LOG("Failed to create texture for buffer: 0x{:X}", err);
       return false;
     }
 
@@ -683,7 +683,7 @@ std::unique_ptr<GPUTextureBuffer> OpenGLDevice::CreateTextureBuffer(GPUTextureBu
     glGetInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size);
     if (static_cast<GLint64>(buffer_size) > max_ssbo_size)
     {
-      Log_ErrorFmt("Buffer size of {} not supported, max is {}", buffer_size, max_ssbo_size);
+      ERROR_LOG("Buffer size of {} not supported, max is {}", buffer_size, max_ssbo_size);
       return {};
     }
   }
@@ -701,7 +701,7 @@ std::unique_ptr<GPUTextureBuffer> OpenGLDevice::CreateTextureBuffer(GPUTextureBu
     glGenTextures(1, &texture_id);
     if (const GLenum err = glGetError(); err != GL_NO_ERROR)
     {
-      Log_ErrorFmt("Failed to create texture for buffer: 0x{:X}", err);
+      ERROR_LOG("Failed to create texture for buffer: 0x{:X}", err);
       return {};
     }
 
@@ -776,7 +776,7 @@ std::unique_ptr<OpenGLDownloadTexture> OpenGLDownloadTexture::Create(u32 width,
 
     if (!buffer_map)
     {
-      Log_ErrorPrint("Failed to map persistent download buffer");
+      ERROR_LOG("Failed to map persistent download buffer");
       glDeleteBuffers(1, &buffer_id);
       return {};
     }
diff --git a/src/util/platform_misc_mac.mm b/src/util/platform_misc_mac.mm
index 611bca4f2..38ce1a297 100644
--- a/src/util/platform_misc_mac.mm
+++ b/src/util/platform_misc_mac.mm
@@ -32,7 +32,7 @@ static bool SetScreensaverInhibitMacOS(bool inhibit)
     if (IOPMAssertionCreateWithName(kIOPMAssertionTypePreventUserIdleDisplaySleep, kIOPMAssertionLevelOn, reason,
                                     &s_prevent_idle_assertion) != kIOReturnSuccess)
     {
-      Log_ErrorPrint("IOPMAssertionCreateWithName() failed");
+      ERROR_LOG("IOPMAssertionCreateWithName() failed");
       return false;
     }
 
@@ -54,7 +54,7 @@ void PlatformMisc::SuspendScreensaver()
 
     if (!SetScreensaverInhibitMacOS(true))
     {
-      Log_ErrorPrint("Failed to suspend screensaver.");
+      ERROR_LOG("Failed to suspend screensaver.");
       return;
     }
 
@@ -67,7 +67,7 @@ void PlatformMisc::ResumeScreensaver()
     return;
 
   if (!SetScreensaverInhibitMacOS(false))
-    Log_ErrorPrint("Failed to resume screensaver.");
+    ERROR_LOG("Failed to resume screensaver.");
 
   s_screensaver_suspended = false;
 }
@@ -116,7 +116,7 @@ bool CocoaTools::CreateMetalLayer(WindowInfo* wi)
   CAMetalLayer* layer = [CAMetalLayer layer];
   if (layer == nil)
   {
-    Log_ErrorPrint("Failed to create CAMetalLayer");
+    ERROR_LOG("Failed to create CAMetalLayer");
     return false;
   }
 
diff --git a/src/util/platform_misc_unix.cpp b/src/util/platform_misc_unix.cpp
index bcef15925..77706d114 100644
--- a/src/util/platform_misc_unix.cpp
+++ b/src/util/platform_misc_unix.cpp
@@ -30,7 +30,7 @@ static bool SetScreensaverInhibitDBus(const bool inhibit_requested, const char*
   ScopedGuard cleanup = [&]() {
     if (dbus_error_is_set(&error))
     {
-      Log_ErrorFmt("SetScreensaverInhibitDBus error: {}", error.message);
+      ERROR_LOG("SetScreensaverInhibitDBus error: {}", error.message);
       dbus_error_free(&error);
     }
     if (message)
@@ -103,7 +103,7 @@ void PlatformMisc::SuspendScreensaver()
 
   if (!SetScreensaverInhibit(true))
   {
-    Log_ErrorPrint("Failed to suspend screensaver.");
+    ERROR_LOG("Failed to suspend screensaver.");
     return;
   }
 
@@ -116,7 +116,7 @@ void PlatformMisc::ResumeScreensaver()
     return;
 
   if (!SetScreensaverInhibit(false))
-    Log_ErrorPrint("Failed to resume screensaver.");
+    ERROR_LOG("Failed to resume screensaver.");
 
   s_screensaver_suspended = false;
 }
@@ -179,8 +179,8 @@ bool PlatformMisc::PlaySoundAsync(const char* path)
   if (res == 0)
     return true;
 
-  Log_ErrorFmt("Failed to play sound effect {}. Make sure you have aplay, gst-play-1.0, or gst-launch-1.0 available.",
-               path);
+  ERROR_LOG("Failed to play sound effect {}. Make sure you have aplay, gst-play-1.0, or gst-launch-1.0 available.",
+            path);
   return false;
 #else
   return false;
diff --git a/src/util/platform_misc_win32.cpp b/src/util/platform_misc_win32.cpp
index fc023164e..6990d11f2 100644
--- a/src/util/platform_misc_win32.cpp
+++ b/src/util/platform_misc_win32.cpp
@@ -21,7 +21,7 @@ static bool SetScreensaverInhibitWin32(bool inhibit)
 {
   if (SetThreadExecutionState(ES_CONTINUOUS | (inhibit ? (ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED) : 0)) == NULL)
   {
-    Log_ErrorFmt("SetThreadExecutionState() failed: {}", GetLastError());
+    ERROR_LOG("SetThreadExecutionState() failed: {}", GetLastError());
     return false;
   }
 
@@ -37,7 +37,7 @@ void PlatformMisc::SuspendScreensaver()
 
   if (!SetScreensaverInhibitWin32(true))
   {
-    Log_ErrorPrint("Failed to suspend screensaver.");
+    ERROR_LOG("Failed to suspend screensaver.");
     return;
   }
 
@@ -50,7 +50,7 @@ void PlatformMisc::ResumeScreensaver()
     return;
 
   if (!SetScreensaverInhibitWin32(false))
-    Log_ErrorPrint("Failed to resume screensaver.");
+    ERROR_LOG("Failed to resume screensaver.");
 
   s_screensaver_suspended = false;
 }
diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp
index eae0e68ae..4ade7426f 100644
--- a/src/util/postprocessing.cpp
+++ b/src/util/postprocessing.cpp
@@ -422,7 +422,7 @@ std::unique_ptr<PostProcessing::Shader> PostProcessing::TryLoadingShader(const s
       return shader;
   }
 
-  Log_ErrorFmt("Failed to load shader '{}'", shader_name);
+  ERROR_LOG("Failed to load shader '{}'", shader_name);
   return {};
 }
 
@@ -499,7 +499,7 @@ void PostProcessing::LoadStages()
   if (stage_count > 0)
   {
     s_timer.Reset();
-    Log_DevFmt("Loaded {} post-processing stages.", stage_count);
+    DEV_LOG("Loaded {} post-processing stages.", stage_count);
   }
 
   // precompile shaders
@@ -577,7 +577,7 @@ void PostProcessing::UpdateSettings()
   if (stage_count > 0)
   {
     s_timer.Reset();
-    Log_DevFmt("Loaded {} post-processing stages.", stage_count);
+    DEV_LOG("Loaded {} post-processing stages.", stage_count);
   }
 }
 
@@ -647,7 +647,7 @@ GPUSampler* PostProcessing::GetSampler(const GPUSampler::Config& config)
 
   std::unique_ptr<GPUSampler> sampler = g_gpu_device->CreateSampler(config);
   if (!sampler)
-    Log_ErrorFmt("Failed to create GPU sampler with config={:X}", config.key);
+    ERROR_LOG("Failed to create GPU sampler with config={:X}", config.key);
 
   it = s_samplers.emplace(config.key, std::move(sampler)).first;
   return it->second.get();
@@ -662,7 +662,7 @@ GPUTexture* PostProcessing::GetDummyTexture()
   s_dummy_texture = g_gpu_device->FetchTexture(1, 1, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
                                                &zero, sizeof(zero));
   if (!s_dummy_texture)
-    Log_ErrorPrint("Failed to create dummy texture.");
+    ERROR_LOG("Failed to create dummy texture.");
 
   return s_dummy_texture.get();
 }
@@ -700,7 +700,7 @@ bool PostProcessing::CheckTargets(GPUTexture::Format target_format, u32 target_w
     if (!shader->CompilePipeline(target_format, target_width, target_height, progress) ||
         !shader->ResizeOutput(target_format, target_width, target_height))
     {
-      Log_ErrorPrint("Failed to compile one or more post-processing shaders, disabling.");
+      ERROR_LOG("Failed to compile one or more post-processing shaders, disabling.");
       Host::AddIconOSDMessage(
         "PostProcessLoadFail", ICON_FA_EXCLAMATION_TRIANGLE,
         fmt::format("Failed to compile post-processing shader '{}'. Disabling post-processing.", shader->GetName()));
diff --git a/src/util/postprocessing_shader.cpp b/src/util/postprocessing_shader.cpp
index 06ba7c0a9..0e96e6f4f 100644
--- a/src/util/postprocessing_shader.cpp
+++ b/src/util/postprocessing_shader.cpp
@@ -91,8 +91,8 @@ void PostProcessing::Shader::LoadOptions(const SettingsInterface& si, const char
                                         ShaderOption::ParseFloatVector(config_value, &value);
         if (value_vector_size != option.vector_size)
         {
-          Log_WarningFmt("Only got {} of {} elements for '{}' in config section %s.", value_vector_size,
-                         option.vector_size, option.name, section);
+          WARNING_LOG("Only got {} of {} elements for '{}' in config section %s.", value_vector_size,
+                      option.vector_size, option.name, section);
         }
       }
 
diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp
index 0e85146cf..af1add1cf 100644
--- a/src/util/postprocessing_shader_fx.cpp
+++ b/src/util/postprocessing_shader_fx.cpp
@@ -286,7 +286,7 @@ bool PostProcessing::ReShadeFXShader::LoadFromFile(std::string name, std::string
   std::optional<std::string> data = FileSystem::ReadFileToString(filename.c_str(), error);
   if (!data.has_value())
   {
-    Log_ErrorFmt("Failed to read '{}'.", filename);
+    ERROR_LOG("Failed to read '{}'.", filename);
     return false;
   }
 
@@ -526,8 +526,8 @@ GetVectorAnnotationValue(const reshadefx::uniform_info& uniform, const std::stri
       }
       else
       {
-        Log_ErrorFmt("Unhandled string value for '{}' (annotation type: {}, uniform type {})", uniform.name,
-                     an.type.description(), uniform.type.description());
+        ERROR_LOG("Unhandled string value for '{}' (annotation type: {}, uniform type {})", uniform.name,
+                  an.type.description(), uniform.type.description());
       }
 
       break;
@@ -576,7 +576,7 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod
       return false;
     if (so != SourceOptionType::None)
     {
-      Log_DevFmt("Add source based option {} at offset {} ({})", static_cast<u32>(so), ui.offset, ui.name);
+      DEV_LOG("Add source based option {} at offset {} ({})", static_cast<u32>(so), ui.offset, ui.name);
 
       SourceOption sopt;
       sopt.source = so;
@@ -709,8 +709,8 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod
 
     if (!ui_type.empty() && opt.vector_size > 1)
     {
-      Log_WarningFmt("Uniform '{}' has UI type of '{}' but is vector not scalar ({}), ignoring", opt.name, ui_type,
-                     opt.vector_size);
+      WARNING_LOG("Uniform '{}' has UI type of '{}' but is vector not scalar ({}), ignoring", opt.name, ui_type,
+                  opt.vector_size);
     }
     else if (!ui_type.empty())
     {
@@ -746,7 +746,7 @@ bool PostProcessing::ReShadeFXShader::CreateOptions(const reshadefx::module& mod
             [](const ShaderOption& lhs, const ShaderOption& rhs) { return lhs.category < rhs.category; });
 
   m_uniforms_size = mod.total_uniform_size;
-  Log_DevFmt("{}: {} options", m_filename, m_options.size());
+  DEV_LOG("{}: {} options", m_filename, m_options.size());
   return true;
 }
 
@@ -818,7 +818,7 @@ bool PostProcessing::ReShadeFXShader::GetSourceOption(const reshadefx::uniform_i
     }
     else if (source == "mousebutton")
     {
-      Log_WarningFmt("Ignoring mousebutton source in uniform '{}', not supported.", ui.name);
+      WARNING_LOG("Ignoring mousebutton source in uniform '{}', not supported.", ui.name);
       *si = SourceOptionType::Zero;
       return true;
     }
@@ -910,14 +910,14 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
 
     if (!ti.semantic.empty())
     {
-      Log_DevFmt("Ignoring semantic {} texture {}", ti.semantic, ti.unique_name);
+      DEV_LOG("Ignoring semantic {} texture {}", ti.semantic, ti.unique_name);
       continue;
     }
     if (ti.render_target)
     {
       tex.rt_scale = 1.0f;
       tex.format = MapTextureFormat(ti.format);
-      Log_DevFmt("Creating render target '{}' {}", ti.unique_name, GPUTexture::GetFormatName(tex.format));
+      DEV_LOG("Creating render target '{}' {}", ti.unique_name, GPUTexture::GetFormatName(tex.format));
     }
     else
     {
@@ -953,7 +953,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
         return false;
       }
 
-      Log_DevFmt("Loaded {}x{} texture ({})", image.GetWidth(), image.GetHeight(), source);
+      DEV_LOG("Loaded {}x{} texture ({})", image.GetWidth(), image.GetHeight(), source);
     }
 
     tex.reshade_name = ti.unique_name;
@@ -1028,7 +1028,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
             }
             else if (ti.semantic == "DEPTH")
             {
-              Log_WarningFmt("Shader '{}' uses input depth as '{}' which is not supported.", m_name, si.texture_name);
+              WARNING_LOG("Shader '{}' uses input depth as '{}' which is not supported.", m_name, si.texture_name);
               sampler.texture_id = INPUT_DEPTH_TEXTURE;
               break;
             }
@@ -1059,7 +1059,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
           return false;
         }
 
-        Log_DevFmt("Pass {} Texture {} => {}", pi.name, si.texture_name, sampler.texture_id);
+        DEV_LOG("Pass {} Texture {} => {}", pi.name, si.texture_name, sampler.texture_id);
 
         sampler.sampler = GetSampler(MapSampler(si));
         if (!sampler.sampler)
@@ -1138,7 +1138,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
   std::string fxcode;
   if (!PreprocessorReadFileCallback(m_filename, fxcode))
   {
-    Log_ErrorFmt("Failed to re-read shader for pipeline: '{}'", m_filename);
+    ERROR_LOG("Failed to re-read shader for pipeline: '{}'", m_filename);
     return false;
   }
 
@@ -1150,7 +1150,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
   reshadefx::module mod;
   if (!CreateModule(width, height, &mod, std::move(fxcode), &error))
   {
-    Log_ErrorFmt("Failed to create module for '{}': {}", m_name, error.GetDescription());
+    ERROR_LOG("Failed to create module for '{}': {}", m_name, error.GetDescription());
     return false;
   }
 
@@ -1158,7 +1158,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
 
   if (!CreatePasses(format, mod, &error))
   {
-    Log_ErrorFmt("Failed to create passes for '{}': {}", m_name, error.GetDescription());
+    ERROR_LOG("Failed to create passes for '{}': {}", m_name, error.GetDescription());
     return false;
   }
 
@@ -1213,7 +1213,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
     std::unique_ptr<GPUShader> sshader =
       g_gpu_device->CreateShader(stage, real_code, needs_main_defn ? "main" : name.c_str());
     if (!sshader)
-      Log_ErrorFmt("Failed to compile function '{}'", name);
+      ERROR_LOG("Failed to compile function '{}'", name);
 
     return sshader;
   };
@@ -1276,7 +1276,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
       pass.pipeline = g_gpu_device->CreatePipeline(plconfig);
       if (!pass.pipeline)
       {
-        Log_ErrorFmt("Failed to create pipeline for pass '{}'", info.name);
+        ERROR_LOG("Failed to create pipeline for pass '{}'", info.name);
         progress->PopState();
         return false;
       }
@@ -1307,7 +1307,7 @@ bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u3
     tex.texture = g_gpu_device->FetchTexture(t_width, t_height, 1, 1, 1, GPUTexture::Type::RenderTarget, tex.format);
     if (!tex.texture)
     {
-      Log_ErrorFmt("Failed to create {}x{} texture", t_width, t_height);
+      ERROR_LOG("Failed to create {}x{} texture", t_width, t_height);
       return {};
     }
   }
diff --git a/src/util/postprocessing_shader_glsl.cpp b/src/util/postprocessing_shader_glsl.cpp
index e574fc38c..6f4eb1ea2 100644
--- a/src/util/postprocessing_shader_glsl.cpp
+++ b/src/util/postprocessing_shader_glsl.cpp
@@ -112,7 +112,8 @@ void PostProcessing::GLSLShader::FillUniformBuffer(void* buffer, u32 texture_wid
   }
 }
 
-bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress)
+bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height,
+                                                 ProgressCallback* progress)
 {
   if (m_pipeline)
     m_pipeline.reset();
@@ -254,7 +255,7 @@ void PostProcessing::GLSLShader::LoadOptions()
           else if (sub == "OptionRangeInteger")
             current_option.type = ShaderOption::Type::Int;
           else
-            Log_ErrorFmt("Invalid option type: '{}'", line_str);
+            ERROR_LOG("Invalid option type: '{}'", line_str);
 
           continue;
         }
@@ -304,7 +305,7 @@ void PostProcessing::GLSLShader::LoadOptions()
         }
         else
         {
-          Log_ErrorFmt("Invalid option key: '{}'", line_str);
+          ERROR_LOG("Invalid option key: '{}'", line_str);
         }
       }
     }
diff --git a/src/util/sdl_audio_stream.cpp b/src/util/sdl_audio_stream.cpp
index 43ea953c8..3fbd3623a 100644
--- a/src/util/sdl_audio_stream.cpp
+++ b/src/util/sdl_audio_stream.cpp
@@ -4,8 +4,8 @@
 #include "audio_stream.h"
 
 #include "common/assert.h"
-#include "common/log.h"
 #include "common/error.h"
+#include "common/log.h"
 
 #include <SDL.h>
 
@@ -62,7 +62,8 @@ SDLAudioStream::~SDLAudioStream()
     SDLAudioStream::CloseDevice();
 }
 
-std::unique_ptr<AudioStream> AudioStream::CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters, Error* error)
+std::unique_ptr<AudioStream> AudioStream::CreateSDLAudioStream(u32 sample_rate, const AudioStreamParameters& parameters,
+                                                               Error* error)
 {
   if (!InitializeSDLAudio(error))
     return {};
@@ -116,7 +117,7 @@ bool SDLAudioStream::OpenDevice(Error* error)
     return false;
   }
 
-  Log_DevFmt("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
+  DEV_LOG("Requested {} frame buffer, got {} frame buffer", spec.samples, obtained_spec.samples);
 
   BaseInitialize(sample_readers[static_cast<size_t>(m_parameters.expansion_mode)]);
   SDL_PauseAudioDevice(m_device_id, 0);
diff --git a/src/util/sdl_input_source.cpp b/src/util/sdl_input_source.cpp
index ec1a07efa..cb0260252 100644
--- a/src/util/sdl_input_source.cpp
+++ b/src/util/sdl_input_source.cpp
@@ -259,18 +259,18 @@ void SDLInputSource::SetHints()
   if (const std::string upath = Path::Combine(EmuFolders::DataRoot, CONTROLLER_DB_FILENAME);
       FileSystem::FileExists(upath.c_str()))
   {
-    Log_InfoFmt("Using Controller DB from user directory: '{}'", upath);
+    INFO_LOG("Using Controller DB from user directory: '{}'", upath);
     SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE, upath.c_str());
   }
   else if (const std::string rpath = EmuFolders::GetOverridableResourcePath(CONTROLLER_DB_FILENAME);
            FileSystem::FileExists(rpath.c_str()))
   {
-    Log_InfoPrint("Using Controller DB from resources.");
+    INFO_LOG("Using Controller DB from resources.");
     SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG_FILE, rpath.c_str());
   }
   else
   {
-    Log_ErrorFmt("Controller DB not found, it should be named '{}'", CONTROLLER_DB_FILENAME);
+    ERROR_LOG("Controller DB not found, it should be named '{}'", CONTROLLER_DB_FILENAME);
   }
 
   SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, m_controller_enhanced_mode ? "1" : "0");
@@ -279,8 +279,8 @@ void SDLInputSource::SetHints()
   SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS3, "1");
 
 #ifdef __APPLE__
-  Log_InfoFmt("IOKit is {}, MFI is {}.", m_enable_iokit_driver ? "enabled" : "disabled",
-              m_enable_mfi_driver ? "enabled" : "disabled");
+  INFO_LOG("IOKit is {}, MFI is {}.", m_enable_iokit_driver ? "enabled" : "disabled",
+           m_enable_mfi_driver ? "enabled" : "disabled");
   SDL_SetHint(SDL_HINT_JOYSTICK_IOKIT, m_enable_iokit_driver ? "1" : "0");
   SDL_SetHint(SDL_HINT_JOYSTICK_MFI, m_enable_mfi_driver ? "1" : "0");
 #endif
@@ -293,7 +293,7 @@ bool SDLInputSource::InitializeSubsystem()
 {
   if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) < 0)
   {
-    Log_ErrorPrint("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed");
+    ERROR_LOG("SDL_InitSubSystem(SDL_INIT_JOYSTICK |SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) failed");
     return false;
   }
 
@@ -306,7 +306,7 @@ bool SDLInputSource::InitializeSubsystem()
 
   // we should open the controllers as the connected events come in, so no need to do any more here
   m_sdl_subsystem_initialized = true;
-  Log_InfoFmt("{} controller mappings are loaded.", SDL_GameControllerNumMappings());
+  INFO_LOG("{} controller mappings are loaded.", SDL_GameControllerNumMappings());
   return true;
 }
 
@@ -580,14 +580,14 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
   {
     case SDL_CONTROLLERDEVICEADDED:
     {
-      Log_InfoFmt("Controller {} inserted", event->cdevice.which);
+      INFO_LOG("Controller {} inserted", event->cdevice.which);
       OpenDevice(event->cdevice.which, true);
       return true;
     }
 
     case SDL_CONTROLLERDEVICEREMOVED:
     {
-      Log_InfoFmt("Controller {} removed", event->cdevice.which);
+      INFO_LOG("Controller {} removed", event->cdevice.which);
       CloseDevice(event->cdevice.which);
       return true;
     }
@@ -598,7 +598,7 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
       if (SDL_IsGameController(event->jdevice.which))
         return false;
 
-      Log_InfoFmt("Joystick {} inserted", event->jdevice.which);
+      INFO_LOG("Joystick {} inserted", event->jdevice.which);
       OpenDevice(event->cdevice.which, false);
       return true;
     }
@@ -610,7 +610,7 @@ bool SDLInputSource::ProcessSDLEvent(const SDL_Event* event)
           it != m_controllers.end() && it->game_controller)
         return false;
 
-      Log_InfoFmt("Joystick {} removed", event->jdevice.which);
+      INFO_LOG("Joystick {} removed", event->jdevice.which);
       CloseDevice(event->cdevice.which);
       return true;
     }
@@ -700,7 +700,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
 
   if (!gcontroller && !joystick)
   {
-    Log_ErrorFmt("Failed to open controller {}", index);
+    ERROR_LOG("Failed to open controller {}", index);
     if (gcontroller)
       SDL_GameControllerClose(gcontroller);
 
@@ -712,9 +712,8 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
   if (player_id < 0 || GetControllerDataForPlayerId(player_id) != m_controllers.end())
   {
     const int free_player_id = GetFreePlayerId();
-    Log_WarningFmt(
-      "Controller {} (joystick {}) returned player ID {}, which is invalid or in use. Using ID {} instead.", index,
-      joystick_id, player_id, free_player_id);
+    WARNING_LOG("Controller {} (joystick {}) returned player ID {}, which is invalid or in use. Using ID {} instead.",
+                index, joystick_id, player_id, free_player_id);
     player_id = free_player_id;
   }
 
@@ -722,8 +721,8 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
   if (!name)
     name = "Unknown Device";
 
-  Log_VerboseFmt("Opened {} {} (instance id {}, player id {}): {}", is_gamecontroller ? "game controller" : "joystick",
-                 index, joystick_id, player_id, name);
+  VERBOSE_LOG("Opened {} {} (instance id {}, player id {}): {}", is_gamecontroller ? "game controller" : "joystick",
+              index, joystick_id, player_id, name);
 
   ControllerData cd = {};
   cd.player_id = player_id;
@@ -749,7 +748,7 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
     for (size_t i = 0; i < std::size(s_sdl_button_names); i++)
       mark_bind(SDL_GameControllerGetBindForButton(gcontroller, static_cast<SDL_GameControllerButton>(i)));
 
-    Log_VerboseFmt("Controller {} has {} axes and {} buttons", player_id, num_axes, num_buttons);
+    VERBOSE_LOG("Controller {} has {} axes and {} buttons", player_id, num_axes, num_buttons);
   }
   else
   {
@@ -758,14 +757,14 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
     if (num_hats > 0)
       cd.last_hat_state.resize(static_cast<size_t>(num_hats), u8(0));
 
-    Log_VerboseFmt("Joystick {} has {} axes, {} buttons and {} hats", player_id, SDL_JoystickNumAxes(joystick),
-                   SDL_JoystickNumButtons(joystick), num_hats);
+    VERBOSE_LOG("Joystick {} has {} axes, {} buttons and {} hats", player_id, SDL_JoystickNumAxes(joystick),
+                SDL_JoystickNumButtons(joystick), num_hats);
   }
 
   cd.use_game_controller_rumble = (gcontroller && SDL_GameControllerRumble(gcontroller, 0, 0, 0) == 0);
   if (cd.use_game_controller_rumble)
   {
-    Log_VerboseFmt("Rumble is supported on '{}' via gamecontroller", name);
+    VERBOSE_LOG("Rumble is supported on '{}' via gamecontroller", name);
   }
   else
   {
@@ -784,25 +783,25 @@ bool SDLInputSource::OpenDevice(int index, bool is_gamecontroller)
       }
       else
       {
-        Log_ErrorFmt("Failed to create haptic left/right effect: {}", SDL_GetError());
+        ERROR_LOG("Failed to create haptic left/right effect: {}", SDL_GetError());
         if (SDL_HapticRumbleSupported(haptic) && SDL_HapticRumbleInit(haptic) != 0)
         {
           cd.haptic = haptic;
         }
         else
         {
-          Log_ErrorFmt("No haptic rumble supported: {}", SDL_GetError());
+          ERROR_LOG("No haptic rumble supported: {}", SDL_GetError());
           SDL_HapticClose(haptic);
         }
       }
     }
 
     if (cd.haptic)
-      Log_VerboseFmt("Rumble is supported on '{}' via haptic", name);
+      VERBOSE_LOG("Rumble is supported on '{}' via haptic", name);
   }
 
   if (!cd.haptic && !cd.use_game_controller_rumble)
-    Log_VerboseFmt("Rumble is not supported on '{}'", name);
+    VERBOSE_LOG("Rumble is not supported on '{}'", name);
 
   if (player_id >= 0 && static_cast<u32>(player_id) < MAX_LED_COLORS && gcontroller &&
       SDL_GameControllerHasLED(gcontroller))
diff --git a/src/util/shadergen.cpp b/src/util/shadergen.cpp
index ef56c2590..c697eb57d 100644
--- a/src/util/shadergen.cpp
+++ b/src/util/shadergen.cpp
@@ -100,7 +100,7 @@ TinyString ShaderGen::GetGLSLVersionString(RenderAPI render_api)
   }
   else
   {
-    Log_ErrorFmt("Invalid GLSL version string: '{}' ('{}')", glsl_version, glsl_version_start);
+    ERROR_LOG("Invalid GLSL version string: '{}' ('{}')", glsl_version, glsl_version_start);
     if (glsl_es)
     {
       major_version = 3;
diff --git a/src/util/state_wrapper.cpp b/src/util/state_wrapper.cpp
index 71f814def..dba2fb9c5 100644
--- a/src/util/state_wrapper.cpp
+++ b/src/util/state_wrapper.cpp
@@ -87,6 +87,6 @@ bool StateWrapper::DoMarker(const char* marker)
   if (m_mode == Mode::Write || file_value.equals(marker))
     return true;
 
-  Log_ErrorFmt("Marker mismatch at offset {}: found '{}' expected '{}'", m_stream->GetPosition(), file_value, marker);
+  ERROR_LOG("Marker mismatch at offset {}: found '{}' expected '{}'", m_stream->GetPosition(), file_value, marker);
   return false;
 }
diff --git a/src/util/vulkan_builders.cpp b/src/util/vulkan_builders.cpp
index be83c9951..b37677b94 100644
--- a/src/util/vulkan_builders.cpp
+++ b/src/util/vulkan_builders.cpp
@@ -105,8 +105,8 @@ const char* Vulkan::VkResultToString(VkResult res)
 
 void Vulkan::LogVulkanResult(const char* func_name, VkResult res, std::string_view msg)
 {
-  Log::WriteFmt("VulkanDevice", func_name, LOGLEVEL_ERROR, "{} (0x{:08X}: {})", msg, static_cast<unsigned>(res),
-                VkResultToString(res));
+  Log::FastWrite("VulkanDevice", func_name, LOGLEVEL_ERROR, "{} (0x{:08X}: {})", msg, static_cast<unsigned>(res),
+                 VkResultToString(res));
 }
 
 Vulkan::DescriptorSetLayoutBuilder::DescriptorSetLayoutBuilder()
diff --git a/src/util/vulkan_device.cpp b/src/util/vulkan_device.cpp
index e564d7fb0..a7ebbdc2e 100644
--- a/src/util/vulkan_device.cpp
+++ b/src/util/vulkan_device.cpp
@@ -149,14 +149,14 @@ VkInstance VulkanDevice::CreateVulkanInstance(const WindowInfo& wi, OptionalExte
   }
   else
   {
-    Log_WarningPrint("Driver does not provide vkEnumerateInstanceVersion().");
+    WARNING_LOG("Driver does not provide vkEnumerateInstanceVersion().");
   }
 
   // Cap out at 1.1 for consistency.
   const u32 apiVersion = std::min(maxApiVersion, VK_API_VERSION_1_1);
-  Log_InfoFmt("Supported instance version: {}.{}.{}, requesting version {}.{}.{}", VK_API_VERSION_MAJOR(maxApiVersion),
-              VK_API_VERSION_MINOR(maxApiVersion), VK_API_VERSION_PATCH(maxApiVersion),
-              VK_API_VERSION_MAJOR(apiVersion), VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion));
+  INFO_LOG("Supported instance version: {}.{}.{}, requesting version {}.{}.{}", VK_API_VERSION_MAJOR(maxApiVersion),
+           VK_API_VERSION_MINOR(maxApiVersion), VK_API_VERSION_PATCH(maxApiVersion), VK_API_VERSION_MAJOR(apiVersion),
+           VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion));
 
   // Remember to manually update this every release. We don't pull in svnrev.h here, because
   // it's only the major/minor version, and rebuilding the file every time something else changes
@@ -212,7 +212,7 @@ bool VulkanDevice::SelectInstanceExtensions(ExtensionList* extension_list, const
 
   if (extension_count == 0)
   {
-    Log_ErrorPrint("Vulkan: No extensions supported by instance.");
+    ERROR_LOG("Vulkan: No extensions supported by instance.");
     return false;
   }
 
@@ -226,13 +226,13 @@ bool VulkanDevice::SelectInstanceExtensions(ExtensionList* extension_list, const
                        return !strcmp(name, properties.extensionName);
                      }) != available_extension_list.end())
     {
-      Log_DevFmt("Enabling extension: {}", name);
+      DEV_LOG("Enabling extension: {}", name);
       extension_list->push_back(name);
       return true;
     }
 
     if (required)
-      Log_ErrorFmt("Vulkan: Missing required extension {}.", name);
+      ERROR_LOG("Vulkan: Missing required extension {}.", name);
 
     return false;
   };
@@ -264,7 +264,7 @@ bool VulkanDevice::SelectInstanceExtensions(ExtensionList* extension_list, const
 
   // VK_EXT_debug_utils
   if (enable_debug_utils && !SupportsExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false))
-    Log_WarningPrint("Vulkan: Debug report requested, but extension is not available.");
+    WARNING_LOG("Vulkan: Debug report requested, but extension is not available.");
 
   // Needed for exclusive fullscreen control.
   SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false);
@@ -291,8 +291,8 @@ VulkanDevice::GPUList VulkanDevice::EnumerateGPUs(VkInstance instance)
   res = vkEnumeratePhysicalDevices(instance, &gpu_count, physical_devices.data());
   if (res == VK_INCOMPLETE)
   {
-    Log_WarningFmt("First vkEnumeratePhysicalDevices() call returned {} devices, but second returned {}",
-                   physical_devices.size(), gpu_count);
+    WARNING_LOG("First vkEnumeratePhysicalDevices() call returned {} devices, but second returned {}",
+                physical_devices.size(), gpu_count);
   }
   else if (res != VK_SUCCESS)
   {
@@ -344,7 +344,7 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
 
   if (extension_count == 0)
   {
-    Log_ErrorPrint("Vulkan: No extensions supported by device.");
+    ERROR_LOG("Vulkan: No extensions supported by device.");
     return false;
   }
 
@@ -362,7 +362,7 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
       if (std::none_of(extension_list->begin(), extension_list->end(),
                        [&](const char* existing_name) { return (std::strcmp(existing_name, name) == 0); }))
       {
-        Log_DevFmt("Enabling extension: {}", name);
+        DEV_LOG("Enabling extension: {}", name);
         extension_list->push_back(name);
       }
 
@@ -370,7 +370,7 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
     }
 
     if (required)
-      Log_ErrorFmt("Vulkan: Missing required extension {}.", name);
+      ERROR_LOG("Vulkan: Missing required extension {}.", name);
 
     return false;
   };
@@ -413,8 +413,8 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
 #ifdef _WIN32
   m_optional_extensions.vk_ext_full_screen_exclusive =
     enable_surface && SupportsExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, false);
-  Log_InfoFmt("VK_EXT_full_screen_exclusive is {}",
-              m_optional_extensions.vk_ext_full_screen_exclusive ? "supported" : "NOT supported");
+  INFO_LOG("VK_EXT_full_screen_exclusive is {}",
+           m_optional_extensions.vk_ext_full_screen_exclusive ? "supported" : "NOT supported");
 #endif
 
   return true;
@@ -450,13 +450,13 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
   vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, nullptr);
   if (queue_family_count == 0)
   {
-    Log_ErrorPrint("No queue families found on specified vulkan physical device.");
+    ERROR_LOG("No queue families found on specified vulkan physical device.");
     return false;
   }
 
   std::vector<VkQueueFamilyProperties> queue_family_properties(queue_family_count);
   vkGetPhysicalDeviceQueueFamilyProperties(m_physical_device, &queue_family_count, queue_family_properties.data());
-  Log_DevFmt("{} vulkan queue families", queue_family_count);
+  DEV_LOG("{} vulkan queue families", queue_family_count);
 
   // Find graphics and present queues.
   m_graphics_queue_family_index = queue_family_count;
@@ -498,12 +498,12 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
   }
   if (m_graphics_queue_family_index == queue_family_count)
   {
-    Log_ErrorPrint("Vulkan: Failed to find an acceptable graphics queue.");
+    ERROR_LOG("Vulkan: Failed to find an acceptable graphics queue.");
     return false;
   }
   if (surface != VK_NULL_HANDLE && m_present_queue_family_index == queue_family_count)
   {
-    Log_ErrorPrint("Vulkan: Failed to find an acceptable present queue.");
+    ERROR_LOG("Vulkan: Failed to find an acceptable present queue.");
     return false;
   }
 
@@ -593,11 +593,11 @@ bool VulkanDevice::CreateDevice(VkSurfaceKHR surface, bool enable_validation_lay
   m_features.gpu_timing = (m_device_properties.limits.timestampComputeAndGraphics != 0 &&
                            queue_family_properties[m_graphics_queue_family_index].timestampValidBits > 0 &&
                            m_device_properties.limits.timestampPeriod > 0);
-  Log_DevFmt("GPU timing is {} (TS={} TS valid bits={}, TS period={})",
-             m_features.gpu_timing ? "supported" : "not supported",
-             static_cast<u32>(m_device_properties.limits.timestampComputeAndGraphics),
-             queue_family_properties[m_graphics_queue_family_index].timestampValidBits,
-             m_device_properties.limits.timestampPeriod);
+  DEV_LOG("GPU timing is {} (TS={} TS valid bits={}, TS period={})",
+          m_features.gpu_timing ? "supported" : "not supported",
+          static_cast<u32>(m_device_properties.limits.timestampComputeAndGraphics),
+          queue_family_properties[m_graphics_queue_family_index].timestampValidBits,
+          m_device_properties.limits.timestampPeriod);
 
   ProcessDeviceExtensions();
   return true;
@@ -631,8 +631,7 @@ void VulkanDevice::ProcessDeviceExtensions()
     if (!vkGetPhysicalDeviceFeatures2KHR || !vkGetPhysicalDeviceProperties2KHR ||
         !vkGetPhysicalDeviceMemoryProperties2KHR)
     {
-      Log_ErrorPrint(
-        "One or more functions from VK_KHR_get_physical_device_properties2 is missing, disabling extension.");
+      ERROR_LOG("One or more functions from VK_KHR_get_physical_device_properties2 is missing, disabling extension.");
       m_optional_extensions.vk_khr_get_physical_device_properties2 = false;
       vkGetPhysicalDeviceFeatures2 = nullptr;
       vkGetPhysicalDeviceProperties2 = nullptr;
@@ -691,43 +690,42 @@ void VulkanDevice::ProcessDeviceExtensions()
     {
       m_optional_extensions.vk_khr_dynamic_rendering = false;
       m_optional_extensions.vk_khr_dynamic_rendering_local_read = false;
-      Log_WarningPrint("Disabling VK_KHR_dynamic_rendering on broken mobile driver.");
+      WARNING_LOG("Disabling VK_KHR_dynamic_rendering on broken mobile driver.");
     }
     if (m_optional_extensions.vk_khr_push_descriptor)
     {
       m_optional_extensions.vk_khr_push_descriptor = false;
-      Log_WarningPrint("Disabling VK_KHR_push_descriptor on broken mobile driver.");
+      WARNING_LOG("Disabling VK_KHR_push_descriptor on broken mobile driver.");
     }
   }
 
-  Log_InfoFmt("VK_EXT_memory_budget is {}", m_optional_extensions.vk_ext_memory_budget ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_EXT_rasterization_order_attachment_access is {}",
-              m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_get_memory_requirements2 is {}",
-              m_optional_extensions.vk_khr_get_memory_requirements2 ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_bind_memory2 is {}", m_optional_extensions.vk_khr_bind_memory2 ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_get_physical_device_properties2 is {}",
-              m_optional_extensions.vk_khr_get_physical_device_properties2 ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_dedicated_allocation is {}",
-              m_optional_extensions.vk_khr_dedicated_allocation ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_dynamic_rendering is {}",
-              m_optional_extensions.vk_khr_dynamic_rendering ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_dynamic_rendering_local_read is {}",
-              m_optional_extensions.vk_khr_dynamic_rendering_local_read ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_KHR_push_descriptor is {}",
-              m_optional_extensions.vk_khr_push_descriptor ? "supported" : "NOT supported");
-  Log_InfoFmt("VK_EXT_external_memory_host is {}",
-              m_optional_extensions.vk_ext_external_memory_host ? "supported" : "NOT supported");
+  INFO_LOG("VK_EXT_memory_budget is {}", m_optional_extensions.vk_ext_memory_budget ? "supported" : "NOT supported");
+  INFO_LOG("VK_EXT_rasterization_order_attachment_access is {}",
+           m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_get_memory_requirements2 is {}",
+           m_optional_extensions.vk_khr_get_memory_requirements2 ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_bind_memory2 is {}", m_optional_extensions.vk_khr_bind_memory2 ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_get_physical_device_properties2 is {}",
+           m_optional_extensions.vk_khr_get_physical_device_properties2 ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_dedicated_allocation is {}",
+           m_optional_extensions.vk_khr_dedicated_allocation ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_dynamic_rendering is {}",
+           m_optional_extensions.vk_khr_dynamic_rendering ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_dynamic_rendering_local_read is {}",
+           m_optional_extensions.vk_khr_dynamic_rendering_local_read ? "supported" : "NOT supported");
+  INFO_LOG("VK_KHR_push_descriptor is {}",
+           m_optional_extensions.vk_khr_push_descriptor ? "supported" : "NOT supported");
+  INFO_LOG("VK_EXT_external_memory_host is {}",
+           m_optional_extensions.vk_ext_external_memory_host ? "supported" : "NOT supported");
 }
 
 bool VulkanDevice::CreateAllocator()
 {
   const u32 apiVersion = std::min(m_device_properties.apiVersion, VK_API_VERSION_1_1);
-  Log_InfoFmt("Supported device API version: {}.{}.{}, using version {}.{}.{} for allocator.",
-              VK_API_VERSION_MAJOR(m_device_properties.apiVersion),
-              VK_API_VERSION_MINOR(m_device_properties.apiVersion),
-              VK_API_VERSION_PATCH(m_device_properties.apiVersion), VK_API_VERSION_MAJOR(apiVersion),
-              VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion));
+  INFO_LOG("Supported device API version: {}.{}.{}, using version {}.{}.{} for allocator.",
+           VK_API_VERSION_MAJOR(m_device_properties.apiVersion), VK_API_VERSION_MINOR(m_device_properties.apiVersion),
+           VK_API_VERSION_PATCH(m_device_properties.apiVersion), VK_API_VERSION_MAJOR(apiVersion),
+           VK_API_VERSION_MINOR(apiVersion), VK_API_VERSION_PATCH(apiVersion));
 
   VmaAllocatorCreateInfo ci = {};
   ci.vulkanApiVersion = apiVersion;
@@ -740,19 +738,19 @@ bool VulkanDevice::CreateAllocator()
   {
     if (m_optional_extensions.vk_khr_get_memory_requirements2 && m_optional_extensions.vk_khr_dedicated_allocation)
     {
-      Log_DevPrint("Enabling VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT on < Vulkan 1.1.");
+      DEV_LOG("Enabling VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT on < Vulkan 1.1.");
       ci.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
     }
     if (m_optional_extensions.vk_khr_bind_memory2)
     {
-      Log_DevPrint("Enabling VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT on < Vulkan 1.1.");
+      DEV_LOG("Enabling VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT on < Vulkan 1.1.");
       ci.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT;
     }
   }
 
   if (m_optional_extensions.vk_ext_memory_budget)
   {
-    Log_DevPrint("Enabling VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT.");
+    DEV_LOG("Enabling VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT.");
     ci.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
   }
 
@@ -783,8 +781,8 @@ bool VulkanDevice::CreateAllocator()
 
       if (heap_size_limits[type.heapIndex] == VK_WHOLE_SIZE)
       {
-        Log_WarningFmt("Disabling allocation from upload heap #{} ({:.2f} MB) due to debug device.", type.heapIndex,
-                       static_cast<float>(heap.size) / 1048576.0f);
+        WARNING_LOG("Disabling allocation from upload heap #{} ({:.2f} MB) due to debug device.", type.heapIndex,
+                    static_cast<float>(heap.size) / 1048576.0f);
         heap_size_limits[type.heapIndex] = 0;
         has_upload_heap = true;
       }
@@ -1179,7 +1177,7 @@ void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
   // We might be waiting for the buffer we just submitted to the worker thread.
   if (m_queued_present.command_buffer_index == index && !m_present_done.load(std::memory_order_acquire))
   {
-    Log_WarningFmt("Waiting for threaded submission of cmdbuffer {}", index);
+    WARNING_LOG("Waiting for threaded submission of cmdbuffer {}", index);
     WaitForPresentComplete();
   }
 
@@ -1194,7 +1192,7 @@ void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
 
     if (res == VK_TIMEOUT && (++timeouts) <= MAX_TIMEOUTS)
     {
-      Log_ErrorFmt("vkWaitForFences() for cmdbuffer {} failed with VK_TIMEOUT, trying again.", index);
+      ERROR_LOG("vkWaitForFences() for cmdbuffer {} failed with VK_TIMEOUT, trying again.", index);
       continue;
     }
     else if (res != VK_SUCCESS)
@@ -1506,7 +1504,7 @@ void VulkanDevice::SubmitCommandBuffer(bool wait_for_completion, const char* rea
   const std::string reason_str(StringUtil::StdStringFromFormatV(reason, ap));
   va_end(ap);
 
-  Log_WarningFmt("Executing command buffer due to '{}'", reason_str);
+  WARNING_LOG("Executing command buffer due to '{}'", reason_str);
   SubmitCommandBuffer(wait_for_completion);
 }
 
@@ -1588,23 +1586,23 @@ VKAPI_ATTR VkBool32 VKAPI_CALL DebugMessengerCallback(VkDebugUtilsMessageSeverit
 {
   if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
   {
-    Log_ErrorFmt("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
-                 pCallbackData->pMessage);
+    ERROR_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
+              pCallbackData->pMessage);
   }
   else if (severity & (VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT))
   {
-    Log_WarningFmt("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
-                   pCallbackData->pMessage);
+    WARNING_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
+                pCallbackData->pMessage);
   }
   else if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)
   {
-    Log_InfoFmt("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
-                pCallbackData->pMessage);
+    INFO_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
+             pCallbackData->pMessage);
   }
   else
   {
-    Log_DevFmt("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
-               pCallbackData->pMessage);
+    DEV_LOG("Vulkan debug report: ({}) {}", pCallbackData->pMessageIdName ? pCallbackData->pMessageIdName : "",
+            pCallbackData->pMessage);
   }
 
   return VK_FALSE;
@@ -1894,12 +1892,12 @@ bool VulkanDevice::IsSuitableDefaultRenderer()
 
   // Check the first GPU, should be enough.
   const std::string& name = aml.adapter_names.front();
-  Log_InfoFmt("Using Vulkan GPU '{}' for automatic renderer check.", name);
+  INFO_LOG("Using Vulkan GPU '{}' for automatic renderer check.", name);
 
   // Any software rendering (LLVMpipe, SwiftShader).
   if (StringUtil::StartsWithNoCase(name, "llvmpipe") || StringUtil::StartsWithNoCase(name, "SwiftShader"))
   {
-    Log_InfoPrint("Not using Vulkan for software renderer.");
+    INFO_LOG("Not using Vulkan for software renderer.");
     return false;
   }
 
@@ -1907,11 +1905,11 @@ bool VulkanDevice::IsSuitableDefaultRenderer()
   // Plus, the Ivy Bridge and Haswell drivers are incomplete.
   if (StringUtil::StartsWithNoCase(name, "Intel"))
   {
-    Log_InfoPrint("Not using Vulkan for Intel GPU.");
+    INFO_LOG("Not using Vulkan for Intel GPU.");
     return false;
   }
 
-  Log_InfoPrint("Allowing Vulkan as default renderer.");
+  INFO_LOG("Allowing Vulkan as default renderer.");
   return true;
 #endif
 }
@@ -1957,13 +1955,13 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
         return false;
       }
 
-      Log_ErrorPrint("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
+      ERROR_LOG("Vulkan validation/debug layers requested but are unavailable. Creating non-debug device.");
     }
   }
 
   if (!Vulkan::LoadVulkanInstanceFunctions(m_instance))
   {
-    Log_ErrorPrint("Failed to load Vulkan instance functions");
+    ERROR_LOG("Failed to load Vulkan instance functions");
     Error::SetStringView(error, "Failed to load Vulkan instance functions");
     return false;
   }
@@ -1980,7 +1978,7 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
     u32 gpu_index = 0;
     for (; gpu_index < static_cast<u32>(gpus.size()); gpu_index++)
     {
-      Log_InfoFmt("GPU {}: {}", gpu_index, gpus[gpu_index].second);
+      INFO_LOG("GPU {}: {}", gpu_index, gpus[gpu_index].second);
       if (gpus[gpu_index].second == adapter)
       {
         m_physical_device = gpus[gpu_index].first;
@@ -1990,13 +1988,13 @@ bool VulkanDevice::CreateDevice(std::string_view adapter, bool threaded_presenta
 
     if (gpu_index == static_cast<u32>(gpus.size()))
     {
-      Log_WarningFmt("Requested GPU '{}' not found, using first ({})", adapter, gpus[0].second);
+      WARNING_LOG("Requested GPU '{}' not found, using first ({})", adapter, gpus[0].second);
       m_physical_device = gpus[0].first;
     }
   }
   else
   {
-    Log_InfoFmt("No GPU requested, using first ({})", gpus[0].second);
+    INFO_LOG("No GPU requested, using first ({})", gpus[0].second);
     m_physical_device = gpus[0].first;
   }
 
@@ -2144,33 +2142,33 @@ bool VulkanDevice::ValidatePipelineCacheHeader(const VK_PIPELINE_CACHE_HEADER& h
 {
   if (header.header_length < sizeof(VK_PIPELINE_CACHE_HEADER))
   {
-    Log_ErrorPrint("Pipeline cache failed validation: Invalid header length");
+    ERROR_LOG("Pipeline cache failed validation: Invalid header length");
     return false;
   }
 
   if (header.header_version != VK_PIPELINE_CACHE_HEADER_VERSION_ONE)
   {
-    Log_ErrorPrint("Pipeline cache failed validation: Invalid header version");
+    ERROR_LOG("Pipeline cache failed validation: Invalid header version");
     return false;
   }
 
   if (header.vendor_id != m_device_properties.vendorID)
   {
-    Log_ErrorFmt("Pipeline cache failed validation: Incorrect vendor ID (file: 0x{:X}, device: 0x{:X})",
-                 header.vendor_id, m_device_properties.vendorID);
+    ERROR_LOG("Pipeline cache failed validation: Incorrect vendor ID (file: 0x{:X}, device: 0x{:X})", header.vendor_id,
+              m_device_properties.vendorID);
     return false;
   }
 
   if (header.device_id != m_device_properties.deviceID)
   {
-    Log_ErrorFmt("Pipeline cache failed validation: Incorrect device ID (file: 0x{:X}, device: 0x{:X})",
-                 header.device_id, m_device_properties.deviceID);
+    ERROR_LOG("Pipeline cache failed validation: Incorrect device ID (file: 0x{:X}, device: 0x{:X})", header.device_id,
+              m_device_properties.deviceID);
     return false;
   }
 
   if (std::memcmp(header.uuid, m_device_properties.pipelineCacheUUID, VK_UUID_SIZE) != 0)
   {
-    Log_ErrorPrint("Pipeline cache failed validation: Incorrect UUID");
+    ERROR_LOG("Pipeline cache failed validation: Incorrect UUID");
     return false;
   }
 
@@ -2199,7 +2197,7 @@ bool VulkanDevice::ReadPipelineCache(const std::string& filename)
     {
       if (data->size() < sizeof(VK_PIPELINE_CACHE_HEADER))
       {
-        Log_ErrorFmt("Pipeline cache at '{}' is too small", Path::GetFileName(filename));
+        ERROR_LOG("Pipeline cache at '{}' is too small", Path::GetFileName(filename));
         return false;
       }
 
@@ -2266,14 +2264,14 @@ bool VulkanDevice::UpdateWindow()
   VkSurfaceKHR surface = VulkanSwapChain::CreateVulkanSurface(m_instance, m_physical_device, &m_window_info);
   if (surface == VK_NULL_HANDLE)
   {
-    Log_ErrorPrint("Failed to create new surface for swap chain");
+    ERROR_LOG("Failed to create new surface for swap chain");
     return false;
   }
 
   m_swap_chain = VulkanSwapChain::Create(m_window_info, surface, SelectPresentMode(), m_exclusive_fullscreen_control);
   if (!m_swap_chain)
   {
-    Log_ErrorPrint("Failed to create swap chain");
+    ERROR_LOG("Failed to create swap chain");
     VulkanSwapChain::DestroyVulkanSurface(m_instance, &m_window_info, surface);
     return false;
   }
@@ -2302,7 +2300,7 @@ void VulkanDevice::ResizeWindow(s32 new_window_width, s32 new_window_height, flo
   if (!m_swap_chain->ResizeSwapChain(new_window_width, new_window_height, new_window_scale))
   {
     // AcquireNextImage() will fail, and we'll recreate the surface.
-    Log_ErrorPrint("Failed to resize swap chain. Next present will fail.");
+    ERROR_LOG("Failed to resize swap chain. Next present will fail.");
     return;
   }
 
@@ -2405,10 +2403,10 @@ bool VulkanDevice::BeginPresent(bool frame_skip)
     }
     else if (res == VK_ERROR_SURFACE_LOST_KHR)
     {
-      Log_WarningPrint("Surface lost, attempting to recreate");
+      WARNING_LOG("Surface lost, attempting to recreate");
       if (!m_swap_chain->RecreateSurface(m_window_info))
       {
-        Log_ErrorPrint("Failed to recreate surface after loss");
+        ERROR_LOG("Failed to recreate surface after loss");
         SubmitCommandBuffer(false);
         TrimTexturePool();
         return false;
@@ -2544,7 +2542,7 @@ bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
     m_optional_extensions.vk_ext_rasterization_order_attachment_access;
 
   if (!m_features.dual_source_blend)
-    Log_WarningPrint("Vulkan driver is missing dual-source blending. This will have an impact on performance.");
+    WARNING_LOG("Vulkan driver is missing dual-source blending. This will have an impact on performance.");
 
   m_features.noperspective_interpolation = true;
   m_features.texture_copy_to_self = !(disabled_features & FEATURE_MASK_TEXTURE_COPY_TO_SELF);
@@ -2557,7 +2555,7 @@ bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
   m_features.texture_buffers_emulated_with_ssbo = true;
 #else
   const u32 max_texel_buffer_elements = m_device_properties.limits.maxTexelBufferElements;
-  Log_InfoFmt("Max texel buffer elements: {}", max_texel_buffer_elements);
+  INFO_LOG("Max texel buffer elements: {}", max_texel_buffer_elements);
   if (max_texel_buffer_elements < MIN_TEXEL_BUFFER_ELEMENTS)
   {
     m_features.texture_buffers_emulated_with_ssbo = true;
@@ -2565,7 +2563,7 @@ bool VulkanDevice::CheckFeatures(FeatureMask disabled_features)
 #endif
 
   if (m_features.texture_buffers_emulated_with_ssbo)
-    Log_WarningPrint("Emulating texture buffers with SSBOs.");
+    WARNING_LOG("Emulating texture buffers with SSBOs.");
 
   m_features.geometry_shaders =
     !(disabled_features & FEATURE_MASK_GEOMETRY_SHADERS) && m_device_features.geometryShader;
@@ -2773,25 +2771,25 @@ bool VulkanDevice::CreateBuffers()
 {
   if (!m_vertex_buffer.Create(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VERTEX_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate vertex buffer");
+    ERROR_LOG("Failed to allocate vertex buffer");
     return false;
   }
 
   if (!m_index_buffer.Create(VK_BUFFER_USAGE_INDEX_BUFFER_BIT, INDEX_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate index buffer");
+    ERROR_LOG("Failed to allocate index buffer");
     return false;
   }
 
   if (!m_uniform_buffer.Create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VERTEX_UNIFORM_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate uniform buffer");
+    ERROR_LOG("Failed to allocate uniform buffer");
     return false;
   }
 
   if (!m_texture_upload_buffer.Create(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, TEXTURE_BUFFER_SIZE))
   {
-    Log_ErrorPrint("Failed to allocate texture upload buffer");
+    ERROR_LOG("Failed to allocate texture upload buffer");
     return false;
   }
 
@@ -3069,7 +3067,7 @@ void VulkanDevice::RenderBlankFrame()
   VkResult res = m_swap_chain->AcquireNextImage();
   if (res != VK_SUCCESS)
   {
-    Log_ErrorPrint("Failed to acquire image for blank frame present");
+    ERROR_LOG("Failed to acquire image for blank frame present");
     return;
   }
 
@@ -3172,7 +3170,7 @@ bool VulkanDevice::TryImportHostMemory(void* data, size_t data_size, VkBufferUsa
   *out_memory = imported_memory;
   *out_buffer = imported_buffer;
   *out_offset = data_offset;
-  Log_DevFmt("Imported {} byte buffer covering {} bytes at {}", data_size, data_size_aligned, data);
+  DEV_LOG("Imported {} byte buffer covering {} bytes at {}", data_size, data_size_aligned, data);
   return true;
 }
 
@@ -3216,7 +3214,7 @@ void VulkanDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUText
         m_num_current_render_targets, m_current_depth_target, feedback_loop);
       if (m_current_framebuffer == VK_NULL_HANDLE)
       {
-        Log_ErrorPrint("Failed to create framebuffer");
+        ERROR_LOG("Failed to create framebuffer");
         return;
       }
     }
@@ -3355,7 +3353,7 @@ void VulkanDevice::BeginRenderPass()
         m_current_render_targets.data(), m_num_current_render_targets, m_current_depth_target, m_current_feedback_loop);
       if (bi.renderPass == VK_NULL_HANDLE)
       {
-        Log_ErrorPrint("Failed to create render pass");
+        ERROR_LOG("Failed to create render pass");
         return;
       }
 
@@ -3640,7 +3638,7 @@ void VulkanDevice::UnbindTexture(VulkanTexture* tex)
     {
       if (m_current_render_targets[i] == tex)
       {
-        Log_WarningPrint("Unbinding current RT");
+        WARNING_LOG("Unbinding current RT");
         SetRenderTargets(nullptr, 0, m_current_depth_target);
         break;
       }
@@ -3652,7 +3650,7 @@ void VulkanDevice::UnbindTexture(VulkanTexture* tex)
   {
     if (m_current_depth_target == tex)
     {
-      Log_WarningPrint("Unbinding current DS");
+      WARNING_LOG("Unbinding current DS");
       SetRenderTargets(nullptr, 0, nullptr);
     }
 
diff --git a/src/util/vulkan_loader.cpp b/src/util/vulkan_loader.cpp
index 87886fa51..22a13bd39 100644
--- a/src/util/vulkan_loader.cpp
+++ b/src/util/vulkan_loader.cpp
@@ -75,7 +75,7 @@ bool Vulkan::LoadVulkanLibrary(Error* error)
 #define VULKAN_MODULE_ENTRY_POINT(name, required)                                                                      \
   if (!s_vulkan_library.GetSymbol(#name, &name))                                                                       \
   {                                                                                                                    \
-    Log_ErrorFmt("Vulkan: Failed to load required module function {}", #name);                                         \
+    ERROR_LOG("Vulkan: Failed to load required module function {}", #name);                                            \
     required_functions_missing = true;                                                                                 \
   }
 #include "vulkan_entry_points.inl"
diff --git a/src/util/vulkan_stream_buffer.cpp b/src/util/vulkan_stream_buffer.cpp
index f68cd3b19..e2baa875f 100644
--- a/src/util/vulkan_stream_buffer.cpp
+++ b/src/util/vulkan_stream_buffer.cpp
@@ -116,7 +116,7 @@ bool VulkanStreamBuffer::ReserveMemory(u32 num_bytes, u32 alignment)
   // Check for sane allocations
   if (required_bytes > m_size) [[unlikely]]
   {
-    Log_ErrorFmt("Attempting to allocate {} bytes from a {} byte stream buffer", num_bytes, m_size);
+    ERROR_LOG("Attempting to allocate {} bytes from a {} byte stream buffer", num_bytes, m_size);
     Panic("Stream buffer overflow");
   }
 
diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp
index 527a142e8..bd26cc9b1 100644
--- a/src/util/vulkan_swap_chain.cpp
+++ b/src/util/vulkan_swap_chain.cpp
@@ -253,9 +253,9 @@ std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurface
       return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
   }
 
-  Log_ErrorPrint("Failed to find a suitable format for swap chain buffers. Available formats were:");
+  ERROR_LOG("Failed to find a suitable format for swap chain buffers. Available formats were:");
   for (const VkSurfaceFormatKHR& sf : surface_formats)
-    Log_ErrorFmt("  {}", static_cast<unsigned>(sf.format));
+    ERROR_LOG("  {}", static_cast<unsigned>(sf.format));
 
   return std::nullopt;
 }
@@ -302,8 +302,8 @@ std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR
     selected_mode = VK_PRESENT_MODE_FIFO_KHR;
   }
 
-  Log_DevFmt("Preferred present mode: {}, selected: {}", PresentModeToString(requested_mode),
-             PresentModeToString(selected_mode));
+  DEV_LOG("Preferred present mode: {}, selected: {}", PresentModeToString(requested_mode),
+          PresentModeToString(selected_mode));
 
   return selected_mode;
 }
@@ -333,7 +333,7 @@ bool VulkanSwapChain::CreateSwapChain()
   u32 image_count = std::clamp<u32>(
     (m_requested_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
     (surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::max() : surface_capabilities.maxImageCount);
-  Log_DevFmt("Creating a swap chain with {} images", image_count);
+  DEV_LOG("Creating a swap chain with {} images", image_count);
 
   // Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
   // determines window size? Android sometimes lags updating currentExtent, so don't use it.
@@ -367,7 +367,7 @@ bool VulkanSwapChain::CreateSwapChain()
   VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
   if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
   {
-    Log_ErrorPrint("Vulkan: Swap chain does not support usage as color attachment");
+    ERROR_LOG("Vulkan: Swap chain does not support usage as color attachment");
     return false;
   }
 
@@ -422,19 +422,19 @@ bool VulkanSwapChain::CreateSwapChain()
       exclusive_win32_info.hmonitor =
         MonitorFromWindow(reinterpret_cast<HWND>(m_window_info.window_handle), MONITOR_DEFAULTTONEAREST);
       if (!exclusive_win32_info.hmonitor)
-        Log_ErrorPrint("MonitorFromWindow() for exclusive fullscreen exclusive override failed.");
+        ERROR_LOG("MonitorFromWindow() for exclusive fullscreen exclusive override failed.");
 
       Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_info);
       Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_win32_info);
     }
     else
     {
-      Log_ErrorPrint("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported.");
+      ERROR_LOG("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported.");
     }
   }
 #else
   if (m_exclusive_fullscreen_control.has_value())
-    Log_ErrorPrint("Exclusive fullscreen control requested, but is not supported on this platform.");
+    ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform.");
 #endif
 
   res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain);
@@ -456,7 +456,7 @@ bool VulkanSwapChain::CreateSwapChain()
   m_actual_present_mode = present_mode.value();
   if (m_window_info.surface_format == GPUTexture::Format::Unknown)
   {
-    Log_ErrorFmt("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
+    ERROR_LOG("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
     return false;
   }
 
@@ -632,7 +632,7 @@ bool VulkanSwapChain::SetRequestedPresentMode(VkPresentModeKHR mode)
   m_requested_present_mode = mode;
 
   // Recreate the swap chain with the new present mode.
-  Log_VerbosePrint("Recreating swap chain to change present mode.");
+  VERBOSE_LOG("Recreating swap chain to change present mode.");
   DestroySwapChainImages();
   if (!CreateSwapChain())
   {
diff --git a/src/util/vulkan_texture.cpp b/src/util/vulkan_texture.cpp
index e0db45bc7..8b77edcdf 100644
--- a/src/util/vulkan_texture.cpp
+++ b/src/util/vulkan_texture.cpp
@@ -147,7 +147,7 @@ std::unique_ptr<VulkanTexture> VulkanTexture::Create(u32 width, u32 height, u32
   }
   if (res == VK_ERROR_OUT_OF_DEVICE_MEMORY)
   {
-    Log_ErrorFmt("Failed to allocate device memory for {}x{} texture", width, height);
+    ERROR_LOG("Failed to allocate device memory for {}x{} texture", width, height);
     return {};
   }
   else if (res != VK_SUCCESS)
@@ -334,7 +334,7 @@ bool VulkanTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data
       dev.SubmitCommandBuffer(false, "While waiting for %u bytes in texture upload buffer", required_size);
       if (!sbuffer.ReserveMemory(required_size, dev.GetBufferCopyOffsetAlignment()))
       {
-        Log_ErrorFmt("Failed to reserve texture upload memory ({} bytes).", required_size);
+        ERROR_LOG("Failed to reserve texture upload memory ({} bytes).", required_size);
         return false;
       }
     }
@@ -822,7 +822,7 @@ VkSampler VulkanDevice::GetSampler(const GPUSampler::Config& config)
     }
     if (i == std::size(border_color_mapping))
     {
-      Log_ErrorFmt("Unsupported border color: {:08X}", config.border_color.GetValue());
+      ERROR_LOG("Unsupported border color: {:08X}", config.border_color.GetValue());
       return {};
     }
 
@@ -938,7 +938,7 @@ std::unique_ptr<GPUTextureBuffer> VulkanDevice::CreateTextureBuffer(GPUTextureBu
   tb->m_descriptor_set = AllocatePersistentDescriptorSet(m_single_texture_buffer_ds_layout);
   if (tb->m_descriptor_set == VK_NULL_HANDLE)
   {
-    Log_ErrorPrint("Failed to allocate persistent descriptor set for texture buffer.");
+    ERROR_LOG("Failed to allocate persistent descriptor set for texture buffer.");
     tb->Destroy(false);
     return {};
   }
@@ -955,7 +955,7 @@ std::unique_ptr<GPUTextureBuffer> VulkanDevice::CreateTextureBuffer(GPUTextureBu
     bvb.Set(tb->GetBuffer(), format_mapping[static_cast<u8>(format)], 0, tb->GetSizeInBytes());
     if ((tb->m_buffer_view = bvb.Create(m_device, false)) == VK_NULL_HANDLE)
     {
-      Log_ErrorPrint("Failed to create buffer view for texture buffer.");
+      ERROR_LOG("Failed to create buffer view for texture buffer.");
       tb->Destroy(false);
       return {};
     }
diff --git a/src/util/wav_writer.cpp b/src/util/wav_writer.cpp
index add4ea328..998177236 100644
--- a/src/util/wav_writer.cpp
+++ b/src/util/wav_writer.cpp
@@ -57,7 +57,7 @@ bool WAVWriter::Open(const char* filename, u32 sample_rate, u32 num_channels)
 
   if (!WriteHeader())
   {
-    Log_ErrorPrint("Failed to write header to file");
+    ERROR_LOG("Failed to write header to file");
     m_sample_rate = 0;
     m_num_channels = 0;
     std::fclose(m_file);
@@ -74,7 +74,7 @@ void WAVWriter::Close()
     return;
 
   if (std::fseek(m_file, 0, SEEK_SET) != 0 || !WriteHeader())
-    Log_ErrorPrint("Failed to re-write header on file, file may be unplayable");
+    ERROR_LOG("Failed to re-write header on file, file may be unplayable");
 
   std::fclose(m_file);
   m_file = nullptr;
@@ -88,7 +88,7 @@ void WAVWriter::WriteFrames(const s16* samples, u32 num_frames)
   const u32 num_frames_written =
     static_cast<u32>(std::fwrite(samples, sizeof(s16) * m_num_channels, num_frames, m_file));
   if (num_frames_written != num_frames)
-    Log_ErrorFmt("Only wrote {} of {} frames to output file", num_frames_written, num_frames);
+    ERROR_LOG("Only wrote {} of {} frames to output file", num_frames_written, num_frames);
 
   m_num_frames += num_frames_written;
 }
diff --git a/src/util/win32_raw_input_source.cpp b/src/util/win32_raw_input_source.cpp
index 2c818c4f2..1f38d03ee 100644
--- a/src/util/win32_raw_input_source.cpp
+++ b/src/util/win32_raw_input_source.cpp
@@ -30,19 +30,19 @@ bool Win32RawInputSource::Initialize(SettingsInterface& si, std::unique_lock<std
 {
   if (!RegisterDummyClass())
   {
-    Log_ErrorPrint("Failed to register dummy window class");
+    ERROR_LOG("Failed to register dummy window class");
     return false;
   }
 
   if (!CreateDummyWindow())
   {
-    Log_ErrorPrint("Failed to create dummy window");
+    ERROR_LOG("Failed to create dummy window");
     return false;
   }
 
   if (!OpenDevices())
   {
-    Log_ErrorPrint("Failed to open devices");
+    ERROR_LOG("Failed to open devices");
     return false;
   }
 
@@ -182,7 +182,7 @@ bool Win32RawInputSource::OpenDevices()
       m_mice.push_back({rid.hDevice, 0u, 0, 0});
   }
 
-  Log_DevFmt("Found {} keyboards and {} mice", m_num_keyboards, m_mice.size());
+  DEV_LOG("Found {} keyboards and {} mice", m_num_keyboards, m_mice.size());
 
   // Grab all keyboard/mouse input.
   if (m_num_keyboards > 0)
diff --git a/src/util/window_info.cpp b/src/util/window_info.cpp
index bbc51807b..cefa2c4fc 100644
--- a/src/util/window_info.cpp
+++ b/src/util/window_info.cpp
@@ -37,7 +37,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
   const HMONITOR monitor = MonitorFromWindow(hwnd, 0);
   if (!monitor) [[unlikely]]
   {
-    Log_ErrorFmt("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription());
+    ERROR_LOG("{}() failed: {}", "MonitorFromWindow", Error::CreateWin32(GetLastError()).GetDescription());
     return std::nullopt;
   }
 
@@ -45,7 +45,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
   mi.cbSize = sizeof(mi);
   if (!GetMonitorInfoW(monitor, &mi))
   {
-    Log_ErrorFmt("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription());
+    ERROR_LOG("{}() failed: {}", "GetMonitorInfoW", Error::CreateWin32(GetLastError()).GetDescription());
     return std::nullopt;
   }
 
@@ -59,7 +59,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
     LONG res = GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &path_size, &mode_size);
     if (res != ERROR_SUCCESS)
     {
-      Log_ErrorFmt("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription());
+      ERROR_LOG("{}() failed: {}", "GetDisplayConfigBufferSizes", Error::CreateWin32(res).GetDescription());
       return std::nullopt;
     }
 
@@ -71,7 +71,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
       break;
     if (res != ERROR_INSUFFICIENT_BUFFER)
     {
-      Log_ErrorFmt("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription());
+      ERROR_LOG("{}() failed: {}", "QueryDisplayConfig", Error::CreateWin32(res).GetDescription());
       return std::nullopt;
     }
   }
@@ -85,7 +85,7 @@ static std::optional<float> GetRefreshRateFromDisplayConfig(HWND hwnd)
     LONG res = DisplayConfigGetDeviceInfo(&sdn.header);
     if (res != ERROR_SUCCESS)
     {
-      Log_ErrorFmt("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription());
+      ERROR_LOG("{}() failed: {}", "DisplayConfigGetDeviceInfo", Error::CreateWin32(res).GetDescription());
       continue;
     }
 
@@ -198,8 +198,8 @@ private:
   {
     char error_string[256] = {};
     XGetErrorText(display, ee->error_code, error_string, sizeof(error_string));
-    Log_WarningFmt("X11 Error: {} (Error {} Minor {} Request {})", error_string, ee->error_code, ee->minor_code,
-                   ee->request_code);
+    WARNING_LOG("X11 Error: {} (Error {} Minor {} Request {})", error_string, ee->error_code, ee->minor_code,
+                ee->request_code);
 
     s_current_error_inhibiter->m_had_error = true;
     return 0;
@@ -222,7 +222,7 @@ static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
   XRRScreenResources* res = XRRGetScreenResources(display, window);
   if (!res)
   {
-    Log_ErrorPrint("XRRGetScreenResources() failed");
+    ERROR_LOG("XRRGetScreenResources() failed");
     return std::nullopt;
   }
 
@@ -232,29 +232,29 @@ static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
   XRRMonitorInfo* mi = XRRGetMonitors(display, window, True, &num_monitors);
   if (num_monitors < 0)
   {
-    Log_ErrorPrint("XRRGetMonitors() failed");
+    ERROR_LOG("XRRGetMonitors() failed");
     return std::nullopt;
   }
   else if (num_monitors > 1)
   {
-    Log_WarningFmt("XRRGetMonitors() returned {} monitors, using first", num_monitors);
+    WARNING_LOG("XRRGetMonitors() returned {} monitors, using first", num_monitors);
   }
 
   ScopedGuard mi_guard([mi]() { XRRFreeMonitors(mi); });
   if (mi->noutput <= 0)
   {
-    Log_ErrorPrint("Monitor has no outputs");
+    ERROR_LOG("Monitor has no outputs");
     return std::nullopt;
   }
   else if (mi->noutput > 1)
   {
-    Log_WarningFmt("Monitor has {} outputs, using first", mi->noutput);
+    WARNING_LOG("Monitor has {} outputs, using first", mi->noutput);
   }
 
   XRROutputInfo* oi = XRRGetOutputInfo(display, res, mi->outputs[0]);
   if (!oi)
   {
-    Log_ErrorPrint("XRRGetOutputInfo() failed");
+    ERROR_LOG("XRRGetOutputInfo() failed");
     return std::nullopt;
   }
 
@@ -263,7 +263,7 @@ static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
   XRRCrtcInfo* ci = XRRGetCrtcInfo(display, res, oi->crtc);
   if (!ci)
   {
-    Log_ErrorPrint("XRRGetCrtcInfo() failed");
+    ERROR_LOG("XRRGetCrtcInfo() failed");
     return std::nullopt;
   }
 
@@ -280,13 +280,13 @@ static std::optional<float> GetRefreshRateFromXRandR(const WindowInfo& wi)
   }
   if (!mode)
   {
-    Log_ErrorFmt("Failed to look up mode {} (of {})", static_cast<int>(ci->mode), res->nmode);
+    ERROR_LOG("Failed to look up mode {} (of {})", static_cast<int>(ci->mode), res->nmode);
     return std::nullopt;
   }
 
   if (mode->dotClock == 0 || mode->hTotal == 0 || mode->vTotal == 0)
   {
-    Log_ErrorFmt("Modeline is invalid: {}/{}/{}", mode->dotClock, mode->hTotal, mode->vTotal);
+    ERROR_LOG("Modeline is invalid: {}/{}/{}", mode->dotClock, mode->hTotal, mode->vTotal);
     return std::nullopt;
   }
 
diff --git a/src/util/xinput_source.cpp b/src/util/xinput_source.cpp
index 8c9365a93..08f03343f 100644
--- a/src/util/xinput_source.cpp
+++ b/src/util/xinput_source.cpp
@@ -127,7 +127,7 @@ bool XInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
   }
   if (!m_xinput_module)
   {
-    Log_ErrorPrint("Failed to load XInput module.");
+    ERROR_LOG("Failed to load XInput module.");
     return false;
   }
 
@@ -146,7 +146,7 @@ bool XInputSource::Initialize(SettingsInterface& si, std::unique_lock<std::mutex
 
   if (!m_xinput_get_state || !m_xinput_set_state || !m_xinput_get_capabilities)
   {
-    Log_ErrorPrint("Failed to get XInput function pointers.");
+    ERROR_LOG("Failed to get XInput function pointers.");
     return false;
   }
 
@@ -227,7 +227,7 @@ void XInputSource::PollEvents()
     else
     {
       if (result != ERROR_DEVICE_NOT_CONNECTED)
-        Log_WarningFmt("XInputGetState({}) failed: 0x{:08X} / 0x{:08X}", i, result, GetLastError());
+        WARNING_LOG("XInputGetState({}) failed: 0x{:08X} / 0x{:08X}", i, result, GetLastError());
 
       if (was_connected)
         HandleControllerDisconnection(i);
@@ -424,11 +424,11 @@ bool XInputSource::GetGenericBindingMapping(std::string_view device, GenericInpu
 
 void XInputSource::HandleControllerConnection(u32 index)
 {
-  Log_InfoFmt("XInput controller {} connected.", index);
+  INFO_LOG("XInput controller {} connected.", index);
 
   XINPUT_CAPABILITIES caps = {};
   if (m_xinput_get_capabilities(index, 0, &caps) != ERROR_SUCCESS)
-    Log_WarningFmt("Failed to get XInput capabilities for controller {}", index);
+    WARNING_LOG("Failed to get XInput capabilities for controller {}", index);
 
   ControllerData& cd = m_controllers[index];
   cd.connected = true;
@@ -441,7 +441,7 @@ void XInputSource::HandleControllerConnection(u32 index)
 
 void XInputSource::HandleControllerDisconnection(u32 index)
 {
-  Log_InfoFmt("XInput controller {} disconnected.", index);
+  INFO_LOG("XInput controller {} disconnected.", index);
 
   InputManager::OnInputDeviceDisconnected({{
                                             .source_type = InputSourceType::XInput,
diff --git a/src/util/zstd_byte_stream.cpp b/src/util/zstd_byte_stream.cpp
index 715859478..f5beaff3e 100644
--- a/src/util/zstd_byte_stream.cpp
+++ b/src/util/zstd_byte_stream.cpp
@@ -105,8 +105,8 @@ private:
       const size_t ret = ZSTD_compressStream2(m_cstream, &outbuf, &inbuf, action);
       if (ZSTD_isError(ret))
       {
-        Log_ErrorFmt("ZSTD_compressStream2() failed: {} ({})", static_cast<unsigned>(ZSTD_getErrorCode(ret)),
-                     ZSTD_getErrorString(ZSTD_getErrorCode(ret)));
+        ERROR_LOG("ZSTD_compressStream2() failed: {} ({})", static_cast<unsigned>(ZSTD_getErrorCode(ret)),
+                  ZSTD_getErrorString(ZSTD_getErrorCode(ret)));
         SetErrorState();
         return false;
       }
@@ -284,8 +284,8 @@ private:
       size_t ret = ZSTD_decompressStream(m_cstream, &outbuf, &m_in_buffer);
       if (ZSTD_isError(ret))
       {
-        Log_ErrorFmt("ZSTD_decompressStream() failed: {} ({})", static_cast<unsigned>(ZSTD_getErrorCode(ret)),
-                     ZSTD_getErrorString(ZSTD_getErrorCode(ret)));
+        ERROR_LOG("ZSTD_decompressStream() failed: {} ({})", static_cast<unsigned>(ZSTD_getErrorCode(ret)),
+                  ZSTD_getErrorString(ZSTD_getErrorCode(ret)));
         m_in_buffer.pos = m_in_buffer.size;
         m_output_buffer_rpos = 0;
         m_output_buffer_wpos = 0;