diff --git a/src/client/game.cpp b/src/client/game.cpp
index f1e798a52..76e9194ec 100644
--- a/src/client/game.cpp
+++ b/src/client/game.cpp
@@ -43,6 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "gui/touchcontrols.h"
 #include "itemdef.h"
 #include "log.h"
+#include "log_internal.h"
 #include "filesys.h"
 #include "gameparams.h"
 #include "gettext.h"
diff --git a/src/client/inputhandler.cpp b/src/client/inputhandler.cpp
index 168ef1193..6517ad582 100644
--- a/src/client/inputhandler.cpp
+++ b/src/client/inputhandler.cpp
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "gui/mainmenumanager.h"
 #include "gui/touchcontrols.h"
 #include "hud.h"
+#include "log_internal.h"
 
 void KeyCache::populate_nonchanging()
 {
diff --git a/src/client/sound/ogg_file.cpp b/src/client/sound/ogg_file.cpp
index 11659c706..660dfdf94 100644
--- a/src/client/sound/ogg_file.cpp
+++ b/src/client/sound/ogg_file.cpp
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include <cassert>
 #include <cstring> // memcpy
+#include <memory>
 
 namespace sound {
 
diff --git a/src/client/sound/sound_singleton.h b/src/client/sound/sound_singleton.h
index 32cd2d4f8..10ecc0d96 100644
--- a/src/client/sound/sound_singleton.h
+++ b/src/client/sound/sound_singleton.h
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #pragma once
 
+#include <memory>
 #include "al_helpers.h"
 
 namespace sound {
diff --git a/src/craftdef.cpp b/src/craftdef.cpp
index 72b8e8f9d..611632579 100644
--- a/src/craftdef.cpp
+++ b/src/craftdef.cpp
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <sstream>
 #include <unordered_set>
 #include <algorithm>
+#include <queue>
 #include "gamedef.h"
 #include "inventory.h"
 #include "util/serialize.h"
diff --git a/src/filesys.cpp b/src/filesys.cpp
index 4287c8b05..196aca080 100644
--- a/src/filesys.cpp
+++ b/src/filesys.cpp
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <cerrno>
 #include <fstream>
 #include <atomic>
+#include <memory>
 #include "log.h"
 #include "config.h"
 #include "porting.h"
diff --git a/src/log.cpp b/src/log.cpp
index f7eb691ac..5fac64f5c 100644
--- a/src/log.cpp
+++ b/src/log.cpp
@@ -17,7 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
 
-#include "log.h"
+#include "log_internal.h"
 
 #include "threading/mutex_auto_lock.h"
 #include "debug.h"
@@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "config.h"
 #include "exceptions.h"
 #include "util/numeric.h"
-#include "log.h"
 #include "filesys.h"
 
 #ifdef __ANDROID__
diff --git a/src/log.h b/src/log.h
index 721ce58ed..ccd13acf3 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,198 +1,9 @@
-/*
-Minetest
-Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
-
-This program is free software; you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation; either version 2.1 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License along
-with this program; if not, write to the Free Software Foundation, Inc.,
-51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-*/
+// SPDX-License-Identifier: LGPL-2.1-or-later
 
 #pragma once
 
-#include <atomic>
-#include <map>
-#include <queue>
-#include <string_view>
-#include <fstream>
-#include <thread>
-#include <mutex>
-#include "threading/mutex_auto_lock.h"
 #include "util/basic_macros.h"
 #include "util/stream.h"
-#include "irrlichttypes.h"
-
-class ILogOutput;
-
-enum LogLevel {
-	LL_NONE, // Special level that is always printed
-	LL_ERROR,
-	LL_WARNING,
-	LL_ACTION,  // In-game actions
-	LL_INFO,
-	LL_VERBOSE,
-	LL_TRACE,
-	LL_MAX,
-};
-
-enum LogColor {
-	LOG_COLOR_NEVER,
-	LOG_COLOR_ALWAYS,
-	LOG_COLOR_AUTO,
-};
-
-typedef u8 LogLevelMask;
-#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
-
-class Logger {
-public:
-	void addOutput(ILogOutput *out);
-	void addOutput(ILogOutput *out, LogLevel lev);
-	void addOutputMasked(ILogOutput *out, LogLevelMask mask);
-	void addOutputMaxLevel(ILogOutput *out, LogLevel lev);
-	LogLevelMask removeOutput(ILogOutput *out);
-	void setLevelSilenced(LogLevel lev, bool silenced);
-
-	void registerThread(std::string_view name);
-	void deregisterThread();
-
-	void log(LogLevel lev, std::string_view text);
-	// Logs without a prefix
-	void logRaw(LogLevel lev, std::string_view text);
-
-	static LogLevel stringToLevel(std::string_view name);
-	static const char *getLevelLabel(LogLevel lev);
-
-	bool hasOutput(LogLevel level) {
-		return m_has_outputs[level].load(std::memory_order_relaxed);
-	}
-
-	bool isLevelSilenced(LogLevel level) {
-		return m_silenced_levels[level].load(std::memory_order_relaxed);
-	}
-
-	static LogColor color_mode;
-
-private:
-	void logToOutputsRaw(LogLevel, std::string_view line);
-	void logToOutputs(LogLevel, const std::string &combined,
-		const std::string &time, const std::string &thread_name,
-		std::string_view payload_text);
-
-	const std::string &getThreadName();
-
-	std::vector<ILogOutput *> m_outputs[LL_MAX];
-	std::atomic<bool> m_has_outputs[LL_MAX];
-	std::atomic<bool> m_silenced_levels[LL_MAX];
-	std::map<std::thread::id, std::string> m_thread_names;
-	mutable std::mutex m_mutex;
-};
-
-class ILogOutput {
-public:
-	virtual void logRaw(LogLevel, std::string_view line) = 0;
-	virtual void log(LogLevel, const std::string &combined,
-		const std::string &time, const std::string &thread_name,
-		std::string_view payload_text) = 0;
-};
-
-class ICombinedLogOutput : public ILogOutput {
-public:
-	void log(LogLevel lev, const std::string &combined,
-		const std::string &time, const std::string &thread_name,
-		std::string_view payload_text)
-	{
-		logRaw(lev, combined);
-	}
-};
-
-class StreamLogOutput : public ICombinedLogOutput {
-public:
-	StreamLogOutput(std::ostream &stream);
-
-	void logRaw(LogLevel lev, std::string_view line);
-
-private:
-	std::ostream &m_stream;
-	bool is_tty = false;
-};
-
-class FileLogOutput : public ICombinedLogOutput {
-public:
-	void setFile(const std::string &filename, s64 file_size_max);
-
-	void logRaw(LogLevel lev, std::string_view line)
-	{
-		m_stream << line << std::endl;
-	}
-
-private:
-	std::ofstream m_stream;
-};
-
-class LogOutputBuffer : public ICombinedLogOutput {
-public:
-	LogOutputBuffer(Logger &logger) :
-		m_logger(logger)
-	{
-		updateLogLevel();
-	};
-
-	virtual ~LogOutputBuffer()
-	{
-		m_logger.removeOutput(this);
-	}
-
-	void updateLogLevel();
-
-	void logRaw(LogLevel lev, std::string_view line);
-
-	void clear()
-	{
-		MutexAutoLock lock(m_buffer_mutex);
-		m_buffer = std::queue<std::string>();
-	}
-
-	bool empty() const
-	{
-		MutexAutoLock lock(m_buffer_mutex);
-		return m_buffer.empty();
-	}
-
-	std::string get()
-	{
-		MutexAutoLock lock(m_buffer_mutex);
-		if (m_buffer.empty())
-			return "";
-		std::string s = std::move(m_buffer.front());
-		m_buffer.pop();
-		return s;
-	}
-
-private:
-	// g_logger serializes calls to logRaw() with a mutex, but that
-	// doesn't prevent get() / clear() from being called on top of it.
-	// This mutex prevents that.
-	mutable std::mutex m_buffer_mutex;
-	std::queue<std::string> m_buffer;
-	Logger &m_logger;
-};
-
-#ifdef __ANDROID__
-class AndroidLogOutput : public ICombinedLogOutput {
-public:
-	void logRaw(LogLevel lev, std::string_view line);
-};
-#endif
 
 /*
  * LogTarget
@@ -325,16 +136,6 @@ private:
 
 };
 
-#ifdef __ANDROID__
-extern AndroidLogOutput stdout_output;
-extern AndroidLogOutput stderr_output;
-#else
-extern StreamLogOutput stdout_output;
-extern StreamLogOutput stderr_output;
-#endif
-
-extern Logger g_logger;
-
 /*
  * By making the streams thread_local, each thread has its own
  * private buffer. Two or more threads can write to the same stream
diff --git a/src/log_internal.h b/src/log_internal.h
new file mode 100644
index 000000000..c8bc1b310
--- /dev/null
+++ b/src/log_internal.h
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+
+#pragma once
+
+#include <atomic>
+#include <map>
+#include <queue>
+#include <string_view>
+#include <fstream>
+#include <thread>
+#include <mutex>
+#include "threading/mutex_auto_lock.h"
+#include "util/basic_macros.h"
+#include "util/stream.h"
+#include "irrlichttypes.h"
+#include "log.h"
+
+class ILogOutput;
+
+enum LogLevel {
+	LL_NONE, // Special level that is always printed
+	LL_ERROR,
+	LL_WARNING,
+	LL_ACTION,  // In-game actions
+	LL_INFO,
+	LL_VERBOSE,
+	LL_TRACE,
+	LL_MAX,
+};
+
+enum LogColor {
+	LOG_COLOR_NEVER,
+	LOG_COLOR_ALWAYS,
+	LOG_COLOR_AUTO,
+};
+
+typedef u8 LogLevelMask;
+#define LOGLEVEL_TO_MASKLEVEL(x) (1 << x)
+
+class Logger {
+public:
+	void addOutput(ILogOutput *out);
+	void addOutput(ILogOutput *out, LogLevel lev);
+	void addOutputMasked(ILogOutput *out, LogLevelMask mask);
+	void addOutputMaxLevel(ILogOutput *out, LogLevel lev);
+	LogLevelMask removeOutput(ILogOutput *out);
+	void setLevelSilenced(LogLevel lev, bool silenced);
+
+	void registerThread(std::string_view name);
+	void deregisterThread();
+
+	void log(LogLevel lev, std::string_view text);
+	// Logs without a prefix
+	void logRaw(LogLevel lev, std::string_view text);
+
+	static LogLevel stringToLevel(std::string_view name);
+	static const char *getLevelLabel(LogLevel lev);
+
+	bool hasOutput(LogLevel level) {
+		return m_has_outputs[level].load(std::memory_order_relaxed);
+	}
+
+	bool isLevelSilenced(LogLevel level) {
+		return m_silenced_levels[level].load(std::memory_order_relaxed);
+	}
+
+	static LogColor color_mode;
+
+private:
+	void logToOutputsRaw(LogLevel, std::string_view line);
+	void logToOutputs(LogLevel, const std::string &combined,
+		const std::string &time, const std::string &thread_name,
+		std::string_view payload_text);
+
+	const std::string &getThreadName();
+
+	std::vector<ILogOutput *> m_outputs[LL_MAX];
+	std::atomic<bool> m_has_outputs[LL_MAX];
+	std::atomic<bool> m_silenced_levels[LL_MAX];
+	std::map<std::thread::id, std::string> m_thread_names;
+	mutable std::mutex m_mutex;
+};
+
+class ILogOutput {
+public:
+	virtual void logRaw(LogLevel, std::string_view line) = 0;
+	virtual void log(LogLevel, const std::string &combined,
+		const std::string &time, const std::string &thread_name,
+		std::string_view payload_text) = 0;
+};
+
+class ICombinedLogOutput : public ILogOutput {
+public:
+	void log(LogLevel lev, const std::string &combined,
+		const std::string &time, const std::string &thread_name,
+		std::string_view payload_text)
+	{
+		logRaw(lev, combined);
+	}
+};
+
+class StreamLogOutput : public ICombinedLogOutput {
+public:
+	StreamLogOutput(std::ostream &stream);
+
+	void logRaw(LogLevel lev, std::string_view line);
+
+private:
+	std::ostream &m_stream;
+	bool is_tty = false;
+};
+
+class FileLogOutput : public ICombinedLogOutput {
+public:
+	void setFile(const std::string &filename, s64 file_size_max);
+
+	void logRaw(LogLevel lev, std::string_view line)
+	{
+		m_stream << line << std::endl;
+	}
+
+private:
+	std::ofstream m_stream;
+};
+
+class LogOutputBuffer : public ICombinedLogOutput {
+public:
+	LogOutputBuffer(Logger &logger) :
+		m_logger(logger)
+	{
+		updateLogLevel();
+	};
+
+	virtual ~LogOutputBuffer()
+	{
+		m_logger.removeOutput(this);
+	}
+
+	void updateLogLevel();
+
+	void logRaw(LogLevel lev, std::string_view line);
+
+	void clear()
+	{
+		MutexAutoLock lock(m_buffer_mutex);
+		m_buffer = std::queue<std::string>();
+	}
+
+	bool empty() const
+	{
+		MutexAutoLock lock(m_buffer_mutex);
+		return m_buffer.empty();
+	}
+
+	std::string get()
+	{
+		MutexAutoLock lock(m_buffer_mutex);
+		if (m_buffer.empty())
+			return "";
+		std::string s = std::move(m_buffer.front());
+		m_buffer.pop();
+		return s;
+	}
+
+private:
+	// g_logger serializes calls to logRaw() with a mutex, but that
+	// doesn't prevent get() / clear() from being called on top of it.
+	// This mutex prevents that.
+	mutable std::mutex m_buffer_mutex;
+	std::queue<std::string> m_buffer;
+	Logger &m_logger;
+};
+
+#ifdef __ANDROID__
+class AndroidLogOutput : public ICombinedLogOutput {
+public:
+	void logRaw(LogLevel lev, std::string_view line);
+};
+#endif
+
+#ifdef __ANDROID__
+extern AndroidLogOutput stdout_output;
+extern AndroidLogOutput stderr_output;
+#else
+extern StreamLogOutput stdout_output;
+extern StreamLogOutput stderr_output;
+#endif
+
+extern Logger g_logger;
diff --git a/src/main.cpp b/src/main.cpp
index 1e717bfd1..803f3c6b0 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "migratesettings.h"
 #include "gettext.h"
 #include "log.h"
+#include "log_internal.h"
 #include "util/quicktune.h"
 #include "httpfetch.h"
 #include "gameparams.h"
diff --git a/src/pathfinder.cpp b/src/pathfinder.cpp
index 5420431f5..8b90a139c 100644
--- a/src/pathfinder.cpp
+++ b/src/pathfinder.cpp
@@ -40,6 +40,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	#include <sys/time.h>
 #endif
 
+#include <queue>
+
 /******************************************************************************/
 /* Typedefs and macros                                                        */
 /******************************************************************************/
diff --git a/src/script/lua_api/l_util.cpp b/src/script/lua_api/l_util.cpp
index 45a447cb3..c899e55f4 100644
--- a/src/script/lua_api/l_util.cpp
+++ b/src/script/lua_api/l_util.cpp
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "convert_json.h"
 #include "debug.h"
 #include "log.h"
+#include "log_internal.h"
 #include "tool.h"
 #include "filesys.h"
 #include "settings.h"
diff --git a/src/serialization.cpp b/src/serialization.cpp
index 0319b0159..d35e0f23f 100644
--- a/src/serialization.cpp
+++ b/src/serialization.cpp
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include <zlib.h>
 #include <zstd.h>
+#include <memory>
 
 /* report a zlib or i/o error */
 static void zerr(int ret)
diff --git a/src/terminal_chat_console.h b/src/terminal_chat_console.h
index 1bd226609..7ce2c5c2b 100644
--- a/src/terminal_chat_console.h
+++ b/src/terminal_chat_console.h
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "threading/thread.h"
 #include "util/container.h"
 #include "log.h"
+#include "log_internal.h"
 #include <set>
 #include <sstream>
 
diff --git a/src/texture_override.cpp b/src/texture_override.cpp
index 1b8b4671d..e8386afe2 100644
--- a/src/texture_override.cpp
+++ b/src/texture_override.cpp
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/string.h"
 #include <algorithm>
 #include <fstream>
+#include <map>
 
 #define override_cast static_cast<override_t>
 
diff --git a/src/threading/thread.cpp b/src/threading/thread.cpp
index 21143f231..f9e356ab7 100644
--- a/src/threading/thread.cpp
+++ b/src/threading/thread.cpp
@@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
 
 #include "threading/thread.h"
 #include "threading/mutex_auto_lock.h"
-#include "log.h"
+#include "log_internal.h"
 #include "porting.h"
 
 // for setName
diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp
index 33f8dcbb5..a3b9250a0 100644
--- a/src/unittest/test.cpp
+++ b/src/unittest/test.cpp
@@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "nodedef.h"
 #include "itemdef.h"
 #include "dummygamedef.h"
+#include "log_internal.h"
 #include "modchannels.h"
 #include "util/numeric.h"
 #include "porting.h"
diff --git a/src/util/colorize.cpp b/src/util/colorize.cpp
index 873ec06fc..0814c2d34 100644
--- a/src/util/colorize.cpp
+++ b/src/util/colorize.cpp
@@ -23,6 +23,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #include "log.h"
 #include "string.h"
 #include <sstream>
+#include <memory>
 
 std::string colorize_url(const std::string &url)
 {