diff --git a/Makefile.am b/Makefile.am
index 2596ee36e..4afe48dd8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -129,6 +129,7 @@ src_mpd_SOURCES = \
 	src/FilterPlugin.cxx src/FilterPlugin.hxx \
 	src/FilterInternal.hxx \
 	src/FilterRegistry.cxx src/FilterRegistry.hxx \
+	src/UpdateDomain.cxx src/UpdateDomain.hxx \
 	src/UpdateGlue.cxx src/UpdateGlue.hxx \
 	src/UpdateQueue.cxx src/UpdateQueue.hxx \
 	src/UpdateIO.cxx src/UpdateIO.hxx \
@@ -154,7 +155,8 @@ src_mpd_SOURCES = \
 	src/ClientSubscribe.cxx src/ClientSubscribe.hxx \
 	src/ClientFile.cxx src/ClientFile.hxx \
 	src/Listen.cxx src/Listen.hxx \
-	src/Log.cxx src/Log.hxx \
+	src/LogInit.cxx src/LogInit.hxx \
+	src/Log.cxx src/Log.hxx src/LogV.hxx \
 	src/ls.cxx src/ls.hxx \
 	src/IOThread.cxx src/IOThread.hxx \
 	src/Main.cxx src/Main.hxx \
@@ -233,6 +235,7 @@ endif
 
 if ENABLE_INOTIFY
 src_mpd_SOURCES += \
+	src/InotifyDomain.cxx src/InotifyDomain.hxx \
 	src/InotifySource.cxx src/InotifySource.hxx \
 	src/InotifyQueue.cxx src/InotifyQueue.hxx \
 	src/InotifyUpdate.cxx src/InotifyUpdate.hxx
@@ -523,6 +526,7 @@ libdecoder_plugins_a_SOURCES += \
 	src/decoder/OggUtil.hxx \
 	src/decoder/OggSyncState.hxx \
 	src/decoder/OggFind.cxx src/decoder/OggFind.hxx \
+	src/decoder/OpusDomain.cxx src/decoder/OpusDomain.hxx \
 	src/decoder/OpusReader.hxx \
 	src/decoder/OpusHead.hxx \
 	src/decoder/OpusHead.cxx \
@@ -557,6 +561,7 @@ endif
 
 if ENABLE_VORBIS_DECODER
 libdecoder_plugins_a_SOURCES += \
+	src/decoder/VorbisDomain.cxx src/decoder/VorbisDomain.hxx \
 	src/decoder/VorbisComments.cxx src/decoder/VorbisComments.hxx \
 	src/decoder/VorbisDecoderPlugin.cxx src/decoder/VorbisDecoderPlugin.h
 endif
@@ -567,6 +572,7 @@ libdecoder_plugins_a_SOURCES += \
 	src/decoder/FlacIOHandle.cxx src/decoder/FlacIOHandle.hxx \
 	src/decoder/FlacMetadata.cxx src/decoder/FlacMetadata.hxx \
 	src/decoder/FlacPcm.cxx src/decoder/FlacPcm.hxx \
+	src/decoder/FlacDomain.cxx src/decoder/FlacDomain.hxx \
 	src/decoder/FlacCommon.cxx src/decoder/FlacCommon.hxx \
 	src/decoder/FlacDecoderPlugin.cxx \
 	src/decoder/FlacDecoderPlugin.h
@@ -1062,13 +1068,17 @@ test_read_conf_LDADD = \
 	libsystem.a \
 	libfs.a \
 	$(GLIB_LIBS)
-test_read_conf_SOURCES = test/read_conf.cxx
+test_read_conf_SOURCES = \
+	src/Log.cxx \
+	test/read_conf.cxx
 
 test_run_resolver_LDADD = \
 	libsystem.a \
 	libutil.a \
 	$(GLIB_LIBS)
-test_run_resolver_SOURCES = test/run_resolver.cxx
+test_run_resolver_SOURCES = \
+	src/Log.cxx \
+	test/run_resolver.cxx
 
 test_DumpDatabase_LDADD = \
 	$(DB_LIBS) \
@@ -1079,6 +1089,7 @@ test_DumpDatabase_LDADD = \
 	libfs.a \
 	$(GLIB_LIBS)
 test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
+	src/Log.cxx \
 	src/DatabaseError.cxx \
 	src/DatabaseRegistry.cxx \
 	src/DatabaseSelection.cxx \
@@ -1102,6 +1113,7 @@ test_run_input_LDADD = \
 	$(GLIB_LIBS)
 test_run_input_SOURCES = test/run_input.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/TagSave.cxx
 
@@ -1118,6 +1130,7 @@ test_visit_archive_LDADD = \
 	libfs.a \
 	$(GLIB_LIBS)
 test_visit_archive_SOURCES = test/visit_archive.cxx \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/InputStream.cxx
 
@@ -1139,6 +1152,7 @@ test_dump_text_file_LDADD = \
 	$(GLIB_LIBS)
 test_dump_text_file_SOURCES = test/dump_text_file.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/TextInputStream.cxx
 
@@ -1158,6 +1172,7 @@ test_dump_playlist_LDADD = \
 	$(GLIB_LIBS)
 test_dump_playlist_SOURCES = test/dump_playlist.cxx \
 	$(DECODER_SRC) \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/Song.cxx src/TagSave.cxx \
 	src/TagFile.cxx \
@@ -1185,6 +1200,7 @@ test_run_decoder_LDADD = \
 	$(GLIB_LIBS)
 test_run_decoder_SOURCES = test/run_decoder.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/ReplayGainInfo.cxx \
 	src/AudioFormat.cxx src/CheckAudioFormat.cxx \
@@ -1206,6 +1222,7 @@ test_read_tags_LDADD = \
 	libutil.a \
 	$(GLIB_LIBS)
 test_read_tags_SOURCES = test/read_tags.cxx \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/ReplayGainInfo.cxx \
 	src/CheckAudioFormat.cxx \
@@ -1216,7 +1233,9 @@ test_dump_rva2_LDADD = \
 	$(TAG_LIBS) \
 	libutil.a \
 	$(GLIB_LIBS)
-test_dump_rva2_SOURCES = test/dump_rva2.cxx
+test_dump_rva2_SOURCES = \
+	src/Log.cxx \
+	test/dump_rva2.cxx
 endif
 
 test_run_filter_LDADD = \
@@ -1229,6 +1248,7 @@ test_run_filter_LDADD = \
 test_run_filter_SOURCES = test/run_filter.cxx \
 	test/FakeReplayGainConfig.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/FilterPlugin.cxx src/FilterRegistry.cxx \
 	src/CheckAudioFormat.cxx \
 	src/AudioFormat.cxx \
@@ -1248,6 +1268,7 @@ if ENABLE_ENCODER
 noinst_PROGRAMS += test/run_encoder
 test_run_encoder_SOURCES = test/run_encoder.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/CheckAudioFormat.cxx \
 	src/AudioFormat.cxx \
 	src/AudioParser.cxx
@@ -1266,6 +1287,7 @@ if ENABLE_VORBIS_ENCODER
 noinst_PROGRAMS += test/test_vorbis_encoder
 test_test_vorbis_encoder_SOURCES = test/test_vorbis_encoder.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/CheckAudioFormat.cxx \
 	src/AudioFormat.cxx \
 	src/AudioParser.cxx \
@@ -1302,6 +1324,7 @@ test_run_normalize_LDADD = \
 	$(GLIB_LIBS)
 
 test_run_convert_SOURCES = test/run_convert.cxx \
+	src/Log.cxx \
 	src/AudioFormat.cxx \
 	src/CheckAudioFormat.cxx \
 	src/AudioParser.cxx
@@ -1326,6 +1349,7 @@ test_run_output_LDADD = $(MPD_LIBS) \
 test_run_output_SOURCES = test/run_output.cxx \
 	test/FakeReplayGainConfig.cxx \
 	test/stdbin.h \
+	src/Log.cxx \
 	src/IOThread.cxx \
 	src/CheckAudioFormat.cxx \
 	src/AudioFormat.cxx \
@@ -1353,6 +1377,7 @@ test_read_mixer_LDADD = \
 	libfs.a \
 	$(GLIB_LIBS)
 test_read_mixer_SOURCES = test/read_mixer.cxx \
+	src/Log.cxx \
 	src/MixerControl.cxx \
 	src/FilterPlugin.cxx \
 	src/filter/VolumeFilterPlugin.cxx
@@ -1372,6 +1397,8 @@ endif
 if ENABLE_INOTIFY
 noinst_PROGRAMS += test/run_inotify
 test_run_inotify_SOURCES = test/run_inotify.cxx \
+	src/Log.cxx \
+	src/InotifyDomain.cxx \
 	src/InotifySource.cxx
 test_run_inotify_LDADD = \
 	libevent.a \
diff --git a/doc/developer.xml b/doc/developer.xml
index 010b85064..eb318fa5a 100644
--- a/doc/developer.xml
+++ b/doc/developer.xml
@@ -61,7 +61,7 @@
 foo(const char *abc, int xyz)
 {
         if (abc == NULL) {
-                g_warning("Foo happened!\n");
+                LogWarning("Foo happened!");
                 return -1;
         }
 
diff --git a/src/ArchiveLookup.cxx b/src/ArchiveLookup.cxx
index 747f5c7e5..c781fcdaf 100644
--- a/src/ArchiveLookup.cxx
+++ b/src/ArchiveLookup.cxx
@@ -19,15 +19,17 @@
 
 #include "config.h" /* must be first for large file support */
 #include "ArchiveLookup.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
-#include <stdio.h>
+#include <glib.h>
 
 #include <string.h>
-#include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <errno.h>
-#include <glib.h>
+
+static constexpr Domain archive_domain("archive");
 
 /**
  *
@@ -65,7 +67,8 @@ bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix
 		//try to stat if its real directory
 		if (stat(pathdupe, &st_info) == -1) {
 			if (errno != ENOTDIR) {
-				g_warning("stat %s failed (errno=%d)\n", pathdupe, errno);
+				FormatErrno(archive_domain,
+					    "Failed to stat %s", pathdupe);
 				break;
 			}
 		} else {
@@ -92,7 +95,9 @@ bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix
 				}
 				break;
 			} else {
-				g_warning("not a regular file %s\n", pathdupe);
+				FormatError(archive_domain,
+					    "Not a regular file: %s",
+					    pathdupe);
 				break;
 			}
 		}
diff --git a/src/Client.cxx b/src/Client.cxx
index be121dfe8..e7da3d1da 100644
--- a/src/Client.cxx
+++ b/src/Client.cxx
@@ -19,6 +19,9 @@
 
 #include "config.h"
 #include "ClientInternal.hxx"
+#include "util/Domain.hxx"
+
+const Domain client_domain("client");
 
 int client_get_uid(const Client *client)
 {
diff --git a/src/ClientEvent.cxx b/src/ClientEvent.cxx
index 90b4a7499..6c86057a1 100644
--- a/src/ClientEvent.cxx
+++ b/src/ClientEvent.cxx
@@ -20,13 +20,12 @@
 #include "config.h"
 #include "ClientInternal.hxx"
 #include "util/Error.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 void
 Client::OnSocketError(Error &&error)
 {
-	g_warning("error on client %d: %s", num, error.GetMessage());
+	FormatError(error, "error on client %d", num);
 
 	SetExpired();
 }
diff --git a/src/ClientExpire.cxx b/src/ClientExpire.cxx
index e87390ddb..ea842cfd9 100644
--- a/src/ClientExpire.cxx
+++ b/src/ClientExpire.cxx
@@ -19,6 +19,7 @@
 
 #include "config.h"
 #include "ClientInternal.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -37,7 +38,7 @@ Client::OnTimeout()
 {
 	if (!IsExpired()) {
 		assert(!idle_waiting);
-		g_debug("[%u] timeout", num);
+		FormatDebug(client_domain, "[%u] timeout", num);
 	}
 
 	Close();
diff --git a/src/ClientInternal.hxx b/src/ClientInternal.hxx
index ca2669db0..e4c2525b4 100644
--- a/src/ClientInternal.hxx
+++ b/src/ClientInternal.hxx
@@ -32,9 +32,6 @@
 #include <string>
 #include <list>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "client"
-
 enum {
 	CLIENT_MAX_SUBSCRIPTIONS = 16,
 	CLIENT_MAX_MESSAGES = 64,
@@ -123,6 +120,8 @@ private:
 	virtual void OnTimeout() override;
 };
 
+extern const class Domain client_domain;
+
 extern int client_timeout;
 extern size_t client_max_command_list_size;
 extern size_t client_max_output_buffer_size;
diff --git a/src/ClientNew.cxx b/src/ClientNew.cxx
index bc12bbf6a..e7bb6e1c2 100644
--- a/src/ClientNew.cxx
+++ b/src/ClientNew.cxx
@@ -26,6 +26,7 @@
 #include "system/Resolver.hxx"
 #include "Permission.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 #include <sys/types.h>
@@ -40,9 +41,6 @@
 #include <tcpd.h>
 #endif
 
-
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
 static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
 
 Client::Client(EventLoop &_loop, Partition &_partition,
@@ -82,9 +80,9 @@ client_new(EventLoop &loop, Partition &partition,
 
 		if (!hosts_access(&req)) {
 			/* tcp wrappers says no */
-			g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
-			      "libwrap refused connection (libwrap=%s) from %s",
-			      progname, hostaddr);
+			FormatWarning(client_domain,
+				      "libwrap refused connection (libwrap=%s) from %s",
+				      progname, hostaddr);
 
 			g_free(hostaddr);
 			close_socket(fd);
@@ -97,7 +95,7 @@ client_new(EventLoop &loop, Partition &partition,
 
 	ClientList &client_list = *partition.instance.client_list;
 	if (client_list.IsFull()) {
-		g_warning("Max Connections Reached!");
+		LogWarning(client_domain, "Max connections reached");
 		close_socket(fd);
 		return;
 	}
@@ -110,8 +108,7 @@ client_new(EventLoop &loop, Partition &partition,
 	client_list.Add(*client);
 
 	remote = sockaddr_to_string(sa, sa_length, IgnoreError());
-	g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
-	      "[%u] opened from %s", client->num, remote);
+	FormatInfo(client_domain, "[%u] opened from %s", client->num, remote);
 	g_free(remote);
 }
 
@@ -122,6 +119,6 @@ Client::Close()
 
 	SetExpired();
 
-	g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE, "[%u] closed", num);
+	FormatInfo(client_domain, "[%u] closed", num);
 	delete this;
 }
diff --git a/src/ClientProcess.cxx b/src/ClientProcess.cxx
index caaef487b..7da1664ca 100644
--- a/src/ClientProcess.cxx
+++ b/src/ClientProcess.cxx
@@ -21,6 +21,7 @@
 #include "ClientInternal.hxx"
 #include "protocol/Result.hxx"
 #include "AllCommands.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -40,10 +41,9 @@ client_process_command_list(Client *client, bool list_ok,
 	for (auto &&i : list) {
 		char *cmd = &*i.begin();
 
-		g_debug("command_process_list: process command \"%s\"",
-			cmd);
+		FormatDebug(client_domain, "process command \"%s\"", cmd);
 		ret = command_process(client, num++, cmd);
-		g_debug("command_process_list: command returned %i", ret);
+		FormatDebug(client_domain, "command returned %i", ret);
 		if (ret != COMMAND_RETURN_OK || client->IsExpired())
 			break;
 		else if (list_ok)
@@ -73,23 +73,26 @@ client_process_line(Client *client, char *line)
 	} else if (client->idle_waiting) {
 		/* during idle mode, clients must not send anything
 		   except "noidle" */
-		g_warning("[%u] command \"%s\" during idle",
-			  client->num, line);
+		FormatWarning(client_domain,
+			      "[%u] command \"%s\" during idle",
+			      client->num, line);
 		return COMMAND_RETURN_CLOSE;
 	}
 
 	if (client->cmd_list.IsActive()) {
 		if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
-			g_debug("[%u] process command list",
-				client->num);
+			FormatDebug(client_domain,
+				    "[%u] process command list",
+				    client->num);
 
 			auto &&cmd_list = client->cmd_list.Commit();
 
 			ret = client_process_command_list(client,
 							  client->cmd_list.IsOKMode(),
 							  std::move(cmd_list));
-			g_debug("[%u] process command "
-				"list returned %i", client->num, ret);
+			FormatDebug(client_domain,
+				    "[%u] process command "
+				    "list returned %i", client->num, ret);
 
 			if (ret == COMMAND_RETURN_CLOSE ||
 			    client->IsExpired())
@@ -101,10 +104,11 @@ client_process_line(Client *client, char *line)
 			client->cmd_list.Reset();
 		} else {
 			if (!client->cmd_list.Add(line)) {
-				g_warning("[%u] command list size "
-					  "is larger than the max (%lu)",
-					  client->num,
-					  (unsigned long)client_max_command_list_size);
+				FormatWarning(client_domain,
+					      "[%u] command list size "
+					      "is larger than the max (%lu)",
+					      client->num,
+					      (unsigned long)client_max_command_list_size);
 				return COMMAND_RETURN_CLOSE;
 			}
 
@@ -118,11 +122,13 @@ client_process_line(Client *client, char *line)
 			client->cmd_list.Begin(true);
 			ret = COMMAND_RETURN_OK;
 		} else {
-			g_debug("[%u] process command \"%s\"",
-				client->num, line);
+			FormatDebug(client_domain,
+				    "[%u] process command \"%s\"",
+				    client->num, line);
 			ret = command_process(client, 0, line);
-			g_debug("[%u] command returned %i",
-				client->num, ret);
+			FormatDebug(client_domain,
+				    "[%u] command returned %i",
+				    client->num, ret);
 
 			if (ret == COMMAND_RETURN_CLOSE ||
 			    client->IsExpired())
diff --git a/src/CommandError.cxx b/src/CommandError.cxx
index c9b6c2deb..30c739128 100644
--- a/src/CommandError.cxx
+++ b/src/CommandError.cxx
@@ -22,6 +22,7 @@
 #include "DatabaseError.hxx"
 #include "protocol/Result.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -93,7 +94,7 @@ print_error(Client *client, const Error &error)
 	assert(client != NULL);
 	assert(error.IsDefined());
 
-	g_warning("%s", error.GetMessage());
+	LogError(error);
 
 	if (error.IsDomain(playlist_domain)) {
 		return print_playlist_result(client,
diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx
index ad5a20441..85c929420 100644
--- a/src/CommandLine.cxx
+++ b/src/CommandLine.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "CommandLine.hxx"
 #include "ls.hxx"
+#include "LogInit.hxx"
 #include "Log.hxx"
 #include "ConfigGlobal.hxx"
 #include "DecoderList.hxx"
@@ -197,7 +198,8 @@ parse_cmdline(int argc, char **argv, struct options *options,
 	options->daemon = !option_no_daemon;
 
 	if (option_no_config) {
-		g_debug("Ignoring config, using daemon defaults\n");
+		LogDebug(cmdline_domain,
+			 "Ignoring config, using daemon defaults");
 		return true;
 	} else if (argc <= 1) {
 		/* default configuration file path */
diff --git a/src/ConfigFile.cxx b/src/ConfigFile.cxx
index b28c8904b..7d2b4e7f7 100644
--- a/src/ConfigFile.cxx
+++ b/src/ConfigFile.cxx
@@ -28,17 +28,13 @@
 #include "util/Domain.hxx"
 #include "fs/Path.hxx"
 #include "fs/FileSystem.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 #include <stdio.h>
 #include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "config"
-
 #define MAX_STRING_SIZE	MPD_PATH_MAX+80
 
 #define CONF_COMMENT		'#'
@@ -261,7 +257,7 @@ ReadConfigFile(ConfigData &config_data, const Path &path, Error &error)
 	assert(!path.IsNull());
 	const std::string path_utf8 = path.ToUTF8();
 
-	g_debug("loading file %s", path_utf8.c_str());
+	FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
 
 	FILE *fp = FOpen(path, FOpenMode::ReadText);
 	if (fp == nullptr) {
diff --git a/src/ConfigGlobal.cxx b/src/ConfigGlobal.cxx
index bd1440a5e..b68a34a38 100644
--- a/src/ConfigGlobal.cxx
+++ b/src/ConfigGlobal.cxx
@@ -23,18 +23,15 @@
 #include "ConfigData.hxx"
 #include "ConfigFile.hxx"
 #include "ConfigPath.hxx"
+#include "ConfigError.hxx"
 #include "fs/Path.hxx"
 #include "util/Error.hxx"
 #include "system/FatalError.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "config"
-
 static ConfigData config_data;
 
 void config_global_finish(void)
@@ -64,8 +61,9 @@ Check(const config_param *param)
 
 	for (const auto &i : param->block_params) {
 		if (!i.used)
-			g_warning("option '%s' on line %i was not recognized",
-				  i.name.c_str(), i.line);
+			FormatWarning(config_domain,
+				      "option '%s' on line %i was not recognized",
+				      i.name.c_str(), i.line);
 	}
 }
 
diff --git a/src/CrossFade.cxx b/src/CrossFade.cxx
index db335651a..420276970 100644
--- a/src/CrossFade.cxx
+++ b/src/CrossFade.cxx
@@ -21,23 +21,25 @@
 #include "CrossFade.hxx"
 #include "MusicChunk.hxx"
 #include "AudioFormat.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <cmath>
 
 #include <assert.h>
 #include <string.h>
 #include <stdlib.h>
-#include <glib.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "crossfade"
+static constexpr Domain cross_fade_domain("cross_fade");
+
+#ifdef WIN32
 
-#ifdef G_OS_WIN32
 static char *
 strtok_r(char *str, const char *delim, gcc_unused char **saveptr)
 {
 	return strtok(str, delim);
 }
+
 #endif
 
 static float mixramp_interpolate(char *ramp_list, float required_db)
@@ -123,14 +125,16 @@ unsigned cross_fade_calc(float duration, float total_time,
 		if (!std::isnan(mixramp_overlap) &&
 		    mixramp_delay <= mixramp_overlap) {
 			chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
-			g_debug("will overlap %d chunks, %fs", chunks,
-				mixramp_overlap - mixramp_delay);
+			FormatDebug(cross_fade_domain,
+				    "will overlap %d chunks, %fs", chunks,
+				    mixramp_overlap - mixramp_delay);
 		}
 	}
 
 	if (chunks > max_chunks) {
 		chunks = max_chunks;
-		g_warning("audio_buffer_size too small for computed MixRamp overlap");
+		LogWarning(cross_fade_domain,
+			   "audio_buffer_size too small for computed MixRamp overlap");
 	}
 
 	return chunks;
diff --git a/src/Daemon.cxx b/src/Daemon.cxx
index 5599926ad..8e22ed131 100644
--- a/src/Daemon.cxx
+++ b/src/Daemon.cxx
@@ -22,6 +22,8 @@
 #include "system/FatalError.hxx"
 #include "fs/Path.hxx"
 #include "fs/FileSystem.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -39,8 +41,7 @@
 #include <grp.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "daemon"
+static constexpr Domain daemon_domain("daemon");
 
 #ifndef WIN32
 
@@ -170,7 +171,7 @@ daemonize_detach(void)
 	FatalError("no support for daemonizing");
 #endif
 
-	g_debug("daemonized!");
+	LogDebug(daemon_domain, "daemonized");
 }
 
 void
@@ -181,7 +182,7 @@ daemonize(bool detach)
 	if (!pidfile.IsNull()) {
 		/* do this before daemon'izing so we can fail gracefully if we can't
 		 * write to the pid file */
-		g_debug("opening pid file");
+		LogDebug(daemon_domain, "opening pid file");
 		fp = FOpen(pidfile, "w+");
 		if (!fp) {
 			const std::string utf8 = pidfile.ToUTF8();
@@ -194,7 +195,7 @@ daemonize(bool detach)
 		daemonize_detach();
 
 	if (!pidfile.IsNull()) {
-		g_debug("writing pid file");
+		LogDebug(daemon_domain, "writing pid file");
 		fprintf(fp, "%lu\n", (unsigned long)getpid());
 		fclose(fp);
 	}
diff --git a/src/DatabaseGlue.cxx b/src/DatabaseGlue.cxx
index e0e004bfa..10d936eac 100644
--- a/src/DatabaseGlue.cxx
+++ b/src/DatabaseGlue.cxx
@@ -43,9 +43,6 @@ extern "C" {
 #include <string.h>
 #include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "database"
-
 static Database *db;
 static bool db_is_open;
 static bool is_simple;
diff --git a/src/DatabaseSave.cxx b/src/DatabaseSave.cxx
index 780c404f3..fb63d4969 100644
--- a/src/DatabaseSave.cxx
+++ b/src/DatabaseSave.cxx
@@ -29,15 +29,13 @@
 #include "tag/TagSettings.h"
 #include "fs/Path.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <stdlib.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "database"
-
 #define DIRECTORY_INFO_BEGIN "info_begin"
 #define DIRECTORY_INFO_END "info_end"
 #define DB_FORMAT_PREFIX "format: "
@@ -155,7 +153,7 @@ db_load_internal(TextFile &file, Directory *music_root, Error &error)
 		}
 	}
 
-	g_debug("reading DB");
+	LogDebug(db_domain, "reading DB");
 
 	db_lock();
 	success = directory_load(file, music_root, error);
diff --git a/src/DecoderAPI.cxx b/src/DecoderAPI.cxx
index b5f1edf76..3f38a0b5e 100644
--- a/src/DecoderAPI.cxx
+++ b/src/DecoderAPI.cxx
@@ -19,6 +19,7 @@
 
 #include "config.h"
 #include "DecoderAPI.hxx"
+#include "DecoderError.hxx"
 #include "AudioConfig.hxx"
 #include "replay_gain_config.h"
 #include "MusicChunk.hxx"
@@ -29,16 +30,12 @@
 #include "Song.hxx"
 #include "InputStream.hxx"
 #include "util/Error.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <assert.h>
 #include <stdlib.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "decoder"
-
 void
 decoder_initialized(struct decoder *decoder,
 		    const AudioFormat audio_format,
@@ -67,14 +64,14 @@ decoder_initialized(struct decoder *decoder,
 	dc->client_cond.signal();
 	dc->Unlock();
 
-	g_debug("audio_format=%s, seekable=%s",
-		audio_format_to_string(dc->in_audio_format, &af_string),
-		seekable ? "true" : "false");
+	FormatDebug(decoder_domain, "audio_format=%s, seekable=%s",
+		    audio_format_to_string(dc->in_audio_format, &af_string),
+		    seekable ? "true" : "false");
 
 	if (dc->in_audio_format != dc->out_audio_format)
-		g_debug("converting to %s",
-			audio_format_to_string(dc->out_audio_format,
-					       &af_string));
+		FormatDebug(decoder_domain, "converting to %s",
+			    audio_format_to_string(dc->out_audio_format,
+						   &af_string));
 }
 
 /**
@@ -288,7 +285,7 @@ size_t decoder_read(struct decoder *decoder,
 	assert(nbytes > 0 || error.IsDefined() || is->IsEOF());
 
 	if (gcc_unlikely(nbytes == 0 && error.IsDefined()))
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 
 	is->Unlock();
 
@@ -404,7 +401,7 @@ decoder_data(struct decoder *decoder,
 			/* the PCM conversion has failed - stop
 			   playback, since we have no better way to
 			   bail out */
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 			return DecoderCommand::STOP;
 		}
 	}
diff --git a/src/DecoderControl.cxx b/src/DecoderControl.cxx
index 91ed17ffd..07f6f9fde 100644
--- a/src/DecoderControl.cxx
+++ b/src/DecoderControl.cxx
@@ -26,9 +26,6 @@
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "decoder_control"
-
 decoder_control::decoder_control()
 	:thread(nullptr),
 	 state(DecoderState::STOP),
diff --git a/src/DecoderThread.cxx b/src/DecoderThread.cxx
index 2c07689df..1aa60ce3f 100644
--- a/src/DecoderThread.cxx
+++ b/src/DecoderThread.cxx
@@ -33,15 +33,16 @@
 #include "DecoderList.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
 #include "tag/ApeReplayGain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <unistd.h>
 #include <stdio.h> /* for SEEK_SET */
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "decoder_thread"
+static constexpr Domain decoder_thread_domain("decoder_thread");
 
 /**
  * Marks the current decoder command as "finished" and notifies the
@@ -78,7 +79,7 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri)
 	input_stream *is = input_stream::Open(uri, dc->mutex, dc->cond, error);
 	if (is == NULL) {
 		if (error.IsDefined())
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 
 		return NULL;
 	}
@@ -99,7 +100,7 @@ decoder_input_stream_open(struct decoder_control *dc, const char *uri)
 	if (!is->Check(error)) {
 		dc->Unlock();
 
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return NULL;
 	}
 
@@ -122,7 +123,7 @@ decoder_stream_decode(const struct decoder_plugin *plugin,
 	assert(input_stream->ready);
 	assert(decoder->dc->state == DecoderState::START);
 
-	g_debug("probing plugin %s", plugin->name);
+	FormatDebug(decoder_thread_domain, "probing plugin %s", plugin->name);
 
 	if (decoder->dc->command == DecoderCommand::STOP)
 		return true;
@@ -155,7 +156,7 @@ decoder_file_decode(const struct decoder_plugin *plugin,
 	assert(g_path_is_absolute(path));
 	assert(decoder->dc->state == DecoderState::START);
 
-	g_debug("probing plugin %s", plugin->name);
+	FormatDebug(decoder_thread_domain, "probing plugin %s", plugin->name);
 
 	if (decoder->dc->command == DecoderCommand::STOP)
 		return true;
diff --git a/src/DespotifyUtils.cxx b/src/DespotifyUtils.cxx
index 3747ddbd9..cf95998ad 100644
--- a/src/DespotifyUtils.cxx
+++ b/src/DespotifyUtils.cxx
@@ -21,13 +21,15 @@
 #include "tag/Tag.hxx"
 #include "ConfigGlobal.hxx"
 #include "ConfigOption.hxx"
-
-#include <glib.h>
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 extern "C" {
 #include <despotify.h>
 }
 
+const Domain despotify_domain("despotify");
+
 static struct despotify_session *g_session;
 static void (*registered_callbacks[8])(struct despotify_session *,
 		int, void *, void *);
@@ -121,24 +123,27 @@ struct despotify_session *mpd_despotify_get_session(void)
 	high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true);
 
 	if (user == NULL || passwd == NULL) {
-		g_debug("disabling despotify because account is not configured");
+		LogDebug(despotify_domain,
+			 "disabling despotify because account is not configured");
 		return nullptr;
 	}
 
 	if (!despotify_init()) {
-		g_debug("Can't initialize despotify\n");
+		LogWarning(despotify_domain, "Can't initialize despotify");
 		return nullptr;
 	}
 
 	g_session = despotify_init_client(callback, NULL,
 					  high_bitrate, true);
 	if (!g_session) {
-		g_debug("Can't initialize despotify client\n");
+		LogWarning(despotify_domain,
+			   "Can't initialize despotify client");
 		return nullptr;
 	}
 
 	if (!despotify_authenticate(g_session, user, passwd)) {
-		g_debug("Can't authenticate despotify session\n");
+		LogWarning(despotify_domain,
+			   "Can't authenticate despotify session");
 		despotify_exit(g_session);
 		return nullptr;
 	}
diff --git a/src/DespotifyUtils.hxx b/src/DespotifyUtils.hxx
index 2d78844c0..b0f8f37c4 100644
--- a/src/DespotifyUtils.hxx
+++ b/src/DespotifyUtils.hxx
@@ -24,6 +24,8 @@ struct Tag;
 struct despotify_session;
 struct ds_track;
 
+extern const class Domain despotify_domain;
+
 /**
  * Return the current despotify session.
  *
diff --git a/src/ExcludeList.cxx b/src/ExcludeList.cxx
index ed04b8d38..16bdbf383 100644
--- a/src/ExcludeList.cxx
+++ b/src/ExcludeList.cxx
@@ -1,3 +1,4 @@
+
 /*
  * Copyright (C) 2003-2011 The Music Player Daemon Project
  * http://www.musicpd.org
@@ -26,21 +27,26 @@
 #include "ExcludeList.hxx"
 #include "fs/Path.hxx"
 #include "fs/FileSystem.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 #include <errno.h>
 
+static constexpr Domain exclude_list_domain("exclude_list");
+
 bool
 ExcludeList::LoadFile(const Path &path_fs)
 {
 	FILE *file = FOpen(path_fs, FOpenMode::ReadText);
 	if (file == NULL) {
-		if (errno != ENOENT) {
-			const char *msg = g_strerror(errno);
+		const int e = errno;
+		if (e != ENOENT) {
 			const auto path_utf8 = path_fs.ToUTF8();
-			g_debug("Failed to open %s: %s",
-				path_utf8.c_str(), msg);
+			FormatErrno(exclude_list_domain,
+				    "Failed to open %s",
+				    path_utf8.c_str());
 		}
 
 		return false;
diff --git a/src/GlobalEvents.cxx b/src/GlobalEvents.cxx
index b587f1ff7..2f8569750 100644
--- a/src/GlobalEvents.cxx
+++ b/src/GlobalEvents.cxx
@@ -28,9 +28,6 @@
 #include <assert.h>
 #include <glib.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "global_events"
-
 namespace GlobalEvents {
 	class Monitor final : public DeferredMonitor {
 	public:
diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx
index d00833be1..e4251d526 100644
--- a/src/IcyMetaDataParser.cxx
+++ b/src/IcyMetaDataParser.cxx
@@ -20,14 +20,15 @@
 #include "config.h"
 #include "IcyMetaDataParser.hxx"
 #include "tag/Tag.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "icy_metadata"
+static constexpr Domain icy_metadata_domain("icy_metadata");
 
 void
 IcyMetaDataParser::Reset()
@@ -88,7 +89,8 @@ icy_parse_tag_item(Tag &tag, const char *item)
 		if (strcmp(p[0], "StreamTitle") == 0)
 			icy_add_item(tag, TAG_TITLE, p[1]);
 		else
-			g_debug("unknown icy-tag: '%s'", p[0]);
+			FormatDebug(icy_metadata_domain,
+				    "unknown icy-tag: '%s'", p[0]);
 	}
 
 	g_strfreev(p);
diff --git a/src/IcyMetaDataServer.cxx b/src/IcyMetaDataServer.cxx
index 10d4ec94f..051a240ba 100644
--- a/src/IcyMetaDataServer.cxx
+++ b/src/IcyMetaDataServer.cxx
@@ -27,9 +27,6 @@
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "icy_server"
-
 char*
 icy_server_metadata_header(const char *name,
 			   const char *genre, const char *url,
diff --git a/src/InotifyDomain.cxx b/src/InotifyDomain.cxx
new file mode 100644
index 000000000..1b8e62d38
--- /dev/null
+++ b/src/InotifyDomain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "InotifyDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain inotify_domain("inotify");
diff --git a/src/InotifyDomain.hxx b/src/InotifyDomain.hxx
new file mode 100644
index 000000000..005487804
--- /dev/null
+++ b/src/InotifyDomain.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_INOTIFY_DOMAIN_HXX
+#define MPD_INOTIFY_DOMAIN_HXX
+
+extern const class Domain inotify_domain;
+
+#endif
diff --git a/src/InotifyQueue.cxx b/src/InotifyQueue.cxx
index 419135dae..28b60ebee 100644
--- a/src/InotifyQueue.cxx
+++ b/src/InotifyQueue.cxx
@@ -19,16 +19,13 @@
 
 #include "config.h"
 #include "InotifyQueue.hxx"
+#include "InotifyDomain.hxx"
 #include "UpdateGlue.hxx"
 #include "event/Loop.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "inotify"
-
 enum {
 	/**
 	 * Wait this long after the last change before calling
@@ -53,7 +50,8 @@ InotifyQueue::OnTimeout()
 			return;
 		}
 
-		g_debug("updating '%s' job=%u", uri_utf8, id);
+		FormatDebug(inotify_domain, "updating '%s' job=%u",
+			    uri_utf8, id);
 
 		queue.pop_front();
 	}
diff --git a/src/InotifySource.cxx b/src/InotifySource.cxx
index c6f0ae4db..587625d3d 100644
--- a/src/InotifySource.cxx
+++ b/src/InotifySource.cxx
@@ -19,19 +19,17 @@
 
 #include "config.h"
 #include "InotifySource.hxx"
+#include "InotifyDomain.hxx"
 #include "util/fifo_buffer.h"
 #include "util/Error.hxx"
 #include "system/fd_util.h"
 #include "system/FatalError.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <sys/inotify.h>
 #include <unistd.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "inotify"
 
 bool
 InotifySource::OnSocketReady(gcc_unused unsigned flags)
@@ -120,8 +118,7 @@ InotifySource::Remove(unsigned wd)
 {
 	int ret = inotify_rm_watch(Get(), wd);
 	if (ret < 0 && errno != EINVAL)
-		g_warning("inotify_rm_watch() has failed: %s",
-			  g_strerror(errno));
+		LogErrno(inotify_domain, "inotify_rm_watch() has failed");
 
 	/* EINVAL may happen here when the file has been deleted; the
 	   kernel seems to auto-unregister deleted files */
diff --git a/src/InotifyUpdate.cxx b/src/InotifyUpdate.cxx
index cccb9650c..d65ff31f3 100644
--- a/src/InotifyUpdate.cxx
+++ b/src/InotifyUpdate.cxx
@@ -21,10 +21,12 @@
 #include "InotifyUpdate.hxx"
 #include "InotifySource.hxx"
 #include "InotifyQueue.hxx"
+#include "InotifyDomain.hxx"
 #include "Mapper.hxx"
 #include "Main.hxx"
 #include "fs/Path.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -36,10 +38,6 @@
 #include <sys/stat.h>
 #include <string.h>
 #include <dirent.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "inotify"
 
 enum {
 	IN_MASK = IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_DELETE_SELF
@@ -120,8 +118,9 @@ remove_watch_directory(WatchDirectory *directory)
 	assert(directory != NULL);
 
 	if (directory->parent == NULL) {
-		g_warning("music directory was removed - "
-			  "cannot continue to watch it");
+		LogWarning(inotify_domain,
+			   "music directory was removed - "
+			   "cannot continue to watch it");
 		return;
 	}
 
@@ -178,8 +177,8 @@ recursive_watch_subdirectories(WatchDirectory *directory,
 
 	dir = opendir(path_fs);
 	if (dir == NULL) {
-		g_warning("Failed to open directory %s: %s",
-			  path_fs, g_strerror(errno));
+		FormatErrno(inotify_domain,
+			    "Failed to open directory %s", path_fs);
 		return;
 	}
 
@@ -194,8 +193,9 @@ recursive_watch_subdirectories(WatchDirectory *directory,
 		child_path_fs = g_strconcat(path_fs, "/", ent->d_name, NULL);
 		ret = stat(child_path_fs, &st);
 		if (ret < 0) {
-			g_warning("Failed to stat %s: %s",
-				  child_path_fs, g_strerror(errno));
+			FormatErrno(inotify_domain,
+				    "Failed to stat %s",
+				    child_path_fs);
 			g_free(child_path_fs);
 			continue;
 		}
@@ -207,8 +207,8 @@ recursive_watch_subdirectories(WatchDirectory *directory,
 
 		ret = inotify_source->Add(child_path_fs, IN_MASK, error);
 		if (ret < 0) {
-			g_warning("Failed to register %s: %s",
-				  child_path_fs, error.GetMessage());
+			FormatError(error,
+				    "Failed to register %s", child_path_fs);
 			error.Clear();
 			g_free(child_path_fs);
 			continue;
@@ -253,7 +253,7 @@ mpd_inotify_callback(int wd, unsigned mask,
 	WatchDirectory *directory;
 	char *uri_fs;
 
-	/*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
+	/*FormatDebug(inotify_domain, "wd=%d mask=0x%x name='%s'", wd, mask, name);*/
 
 	directory = tree_find_watch_directory(wd);
 	if (directory == NULL)
@@ -309,11 +309,11 @@ mpd_inotify_callback(int wd, unsigned mask,
 void
 mpd_inotify_init(unsigned max_depth)
 {
-	g_debug("initializing inotify");
+	LogDebug(inotify_domain, "initializing inotify");
 
 	const Path &path = mapper_get_music_directory_fs();
 	if (path.IsNull()) {
-		g_debug("no music directory configured");
+		LogDebug(inotify_domain, "no music directory configured");
 		return;
 	}
 
@@ -322,7 +322,7 @@ mpd_inotify_init(unsigned max_depth)
 					       mpd_inotify_callback, nullptr,
 					       error);
 	if (inotify_source == NULL) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 
@@ -330,7 +330,7 @@ mpd_inotify_init(unsigned max_depth)
 
 	int descriptor = inotify_source->Add(path.c_str(), IN_MASK, error);
 	if (descriptor < 0) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		delete inotify_source;
 		inotify_source = NULL;
 		return;
@@ -344,7 +344,7 @@ mpd_inotify_init(unsigned max_depth)
 
 	inotify_queue = new InotifyQueue(*main_loop);
 
-	g_debug("watching music directory");
+	LogDebug(inotify_domain, "watching music directory");
 }
 
 void
diff --git a/src/Listen.cxx b/src/Listen.cxx
index 1c240353d..6438d3ed4 100644
--- a/src/Listen.cxx
+++ b/src/Listen.cxx
@@ -27,7 +27,9 @@
 #include "ConfigOption.hxx"
 #include "event/ServerSocket.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
 #include "fs/Path.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -38,8 +40,7 @@
 #include <systemd/sd-daemon.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "listen"
+static constexpr Domain listen_domain("listen");
 
 #define DEFAULT_PORT	6600
 
@@ -82,8 +83,8 @@ listen_systemd_activation(Error &error_r)
 	int n = sd_listen_fds(true);
 	if (n <= 0) {
 		if (n < 0)
-			g_warning("sd_listen_fds() failed: %s",
-				  g_strerror(-n));
+			FormatErrno(listen_domain, -n,
+				    "sd_listen_fds() failed");
 		return false;
 	}
 
@@ -155,7 +156,7 @@ listen_global_init(Error &error)
 
 void listen_global_finish(void)
 {
-	g_debug("listen_global_finish called");
+	LogDebug(listen_domain, "listen_global_finish called");
 
 	assert(listen_socket != NULL);
 
diff --git a/src/Log.cxx b/src/Log.cxx
index 869327d25..f36e68133 100644
--- a/src/Log.cxx
+++ b/src/Log.cxx
@@ -18,7 +18,7 @@
  */
 
 #include "config.h"
-#include "Log.hxx"
+#include "LogV.hxx"
 #include "ConfigData.hxx"
 #include "ConfigGlobal.hxx"
 #include "ConfigOption.hxx"
@@ -30,322 +30,157 @@
 #include "util/Domain.hxx"
 #include "system/FatalError.hxx"
 
+#include <glib.h>
+
 #include <assert.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <string.h>
-#include <stdarg.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <time.h>
 #include <unistd.h>
 #include <errno.h>
-#include <glib.h>
 
-#ifdef HAVE_SYSLOG
-#include <syslog.h>
-#endif
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "log"
-
-#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
-
-#define LOG_DATE_BUF_SIZE 16
-#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
-
-static constexpr Domain log_domain("log");
-
-static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE;
-
-static const char *log_charset;
-
-static bool stdout_mode = true;
-static int out_fd;
-static Path out_path = Path::Null();
-
-static void redirect_logs(int fd)
+static GLogLevelFlags
+ToGLib(LogLevel level)
 {
-	assert(fd >= 0);
-	if (dup2(fd, STDOUT_FILENO) < 0)
-		FatalSystemError("Failed to dup2 stdout");
-	if (dup2(fd, STDERR_FILENO) < 0)
-		FatalSystemError("Failed to dup2 stderr");
-}
-
-static const char *log_date(void)
-{
-	static char buf[LOG_DATE_BUF_SIZE];
-	time_t t = time(NULL);
-	strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t));
-	return buf;
-}
-
-/**
- * Determines the length of the string excluding trailing whitespace
- * characters.
- */
-static int
-chomp_length(const char *p)
-{
-	size_t length = strlen(p);
-
-	while (length > 0 && g_ascii_isspace(p[length - 1]))
-		--length;
-
-	return (int)length;
-}
-
-static void
-file_log_func(const gchar *domain,
-	      GLogLevelFlags log_level,
-	      const gchar *message, gcc_unused gpointer user_data)
-{
-	char *converted;
-
-	if (log_level > log_threshold)
-		return;
-
-	if (log_charset != NULL) {
-		converted = g_convert_with_fallback(message, -1,
-						    log_charset, "utf-8",
-						    NULL, NULL, NULL, NULL);
-		if (converted != NULL)
-			message = converted;
-	} else
-		converted = NULL;
-
-	if (domain == nullptr)
-		domain = "";
-
-	fprintf(stderr, "%s%s%s%.*s\n",
-		stdout_mode ? "" : log_date(),
-		domain, *domain == 0 ? "" : ": ",
-		chomp_length(message), message);
-
-	g_free(converted);
-}
-
-static void
-log_init_stdout(void)
-{
-	g_log_set_default_handler(file_log_func, NULL);
-}
-
-static int
-open_log_file(void)
-{
-	assert(!out_path.IsNull());
-
-	return OpenFile(out_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
-}
-
-static bool
-log_init_file(unsigned line, Error &error)
-{
-	assert(!out_path.IsNull());
-
-	out_fd = open_log_file();
-	if (out_fd < 0) {
-		const std::string out_path_utf8 = out_path.ToUTF8();
-		error.FormatErrno("failed to open log file \"%s\" (config line %u)",
-				  out_path_utf8.c_str(), line);
-		return false;
-	}
-
-	g_log_set_default_handler(file_log_func, NULL);
-	return true;
-}
-
-#ifdef HAVE_SYSLOG
-
-static int
-glib_to_syslog_level(GLogLevelFlags log_level)
-{
-	switch (log_level & G_LOG_LEVEL_MASK) {
-	case G_LOG_LEVEL_ERROR:
-	case G_LOG_LEVEL_CRITICAL:
-		return LOG_ERR;
-
-	case G_LOG_LEVEL_WARNING:
-		return LOG_WARNING;
-
-	case G_LOG_LEVEL_MESSAGE:
-		return LOG_NOTICE;
-
-	case G_LOG_LEVEL_INFO:
-		return LOG_INFO;
-
-	case G_LOG_LEVEL_DEBUG:
-		return LOG_DEBUG;
-
-	default:
-		return LOG_NOTICE;
-	}
-}
-
-static void
-syslog_log_func(const gchar *domain,
-		GLogLevelFlags log_level, const gchar *message,
-		gcc_unused gpointer user_data)
-{
-	if (stdout_mode) {
-		/* fall back to the file log function during
-		   startup */
-		file_log_func(domain, log_level,
-			      message, user_data);
-		return;
-	}
-
-	if (log_level > log_threshold)
-		return;
-
-	if (domain == nullptr)
-		domain = "";
-
-	syslog(glib_to_syslog_level(log_level), "%s%s%.*s",
-	       domain, *domain == 0 ? "" : ": ",
-	       chomp_length(message), message);
-}
-
-static void
-log_init_syslog(void)
-{
-	assert(out_path.IsNull());
-
-	openlog(PACKAGE, 0, LOG_DAEMON);
-	g_log_set_default_handler(syslog_log_func, NULL);
-}
-
-#endif
-
-static inline GLogLevelFlags
-parse_log_level(const char *value, unsigned line)
-{
-	if (0 == strcmp(value, "default"))
-		return G_LOG_LEVEL_MESSAGE;
-	if (0 == strcmp(value, "secure"))
-		return LOG_LEVEL_SECURE;
-	else if (0 == strcmp(value, "verbose"))
+	switch (level) {
+	case LogLevel::DEBUG:
 		return G_LOG_LEVEL_DEBUG;
-	else {
-		FormatFatalError("unknown log level \"%s\" at line %u",
-				 value, line);
+
+	case LogLevel::INFO:
 		return G_LOG_LEVEL_MESSAGE;
+
+	case LogLevel::WARNING:
+	case LogLevel::ERROR:
+		return G_LOG_LEVEL_WARNING;
 	}
+
+	assert(false);
+	gcc_unreachable();
 }
 
 void
-log_early_init(bool verbose)
+Log(const Domain &domain, LogLevel level, const char *msg)
 {
-	if (verbose)
-		log_threshold = G_LOG_LEVEL_DEBUG;
-
-	log_init_stdout();
+	g_log(domain.GetName(), ToGLib(level), "%s", msg);
 }
 
-bool
-log_init(bool verbose, bool use_stdout, Error &error)
+void
+LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap)
 {
-	const struct config_param *param;
+	g_logv(domain.GetName(), ToGLib(level), fmt, ap);
+}
 
-	g_get_charset(&log_charset);
+void
+LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	LogFormatV(domain, level, fmt, ap);
+	va_end(ap);
+}
 
-	if (verbose)
-		log_threshold = G_LOG_LEVEL_DEBUG;
-	else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL)
-		log_threshold = parse_log_level(param->value, param->line);
+void
+FormatDebug(const Domain &domain, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	LogFormatV(domain, LogLevel::DEBUG, fmt, ap);
+	va_end(ap);
+}
 
-	if (use_stdout) {
-		log_init_stdout();
-		return true;
-	} else {
-		param = config_get_param(CONF_LOG_FILE);
-		if (param == NULL) {
-#ifdef HAVE_SYSLOG
-			/* no configuration: default to syslog (if
-			   available) */
-			log_init_syslog();
-			return true;
-#else
-			error.Set(log_domain,
-				  "config parameter 'log_file' not found");
-			return false;
-#endif
-#ifdef HAVE_SYSLOG
-		} else if (strcmp(param->value, "syslog") == 0) {
-			log_init_syslog();
-			return true;
-#endif
-		} else {
-			out_path = config_get_path(CONF_LOG_FILE, error);
-			return !out_path.IsNull() &&
-				log_init_file(param->line, error);
-		}
-	}
+void
+FormatInfo(const Domain &domain, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	LogFormatV(domain, LogLevel::INFO, fmt, ap);
+	va_end(ap);
+}
+
+void
+FormatWarning(const Domain &domain, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	LogFormatV(domain, LogLevel::WARNING, fmt, ap);
+	va_end(ap);
+}
+
+void
+FormatError(const Domain &domain, const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	LogFormatV(domain, LogLevel::ERROR, fmt, ap);
+	va_end(ap);
+}
+
+void
+LogError(const Error &error)
+{
+	Log(error.GetDomain(), LogLevel::ERROR, error.GetMessage());
+}
+
+void
+LogError(const Error &error, const char *msg)
+{
+	LogFormat(error.GetDomain(), LogLevel::ERROR, "%s: %s",
+		  msg, error.GetMessage());
+}
+
+void
+FormatError(const Error &error, const char *fmt, ...)
+{
+	char msg[1024];
+	va_list ap;
+	va_start(ap, fmt);
+	vsnprintf(msg, sizeof(msg), fmt, ap);
+	va_end(ap);
+
+	LogError(error, msg);
+}
+
+void
+LogErrno(const Domain &domain, int e, const char *msg)
+{
+	LogFormat(domain, LogLevel::ERROR, "%s: %s", msg, g_strerror(e));
+}
+
+void
+LogErrno(const Domain &domain, const char *msg)
+{
+	LogErrno(domain, errno, msg);
 }
 
 static void
-close_log_files(void)
+FormatErrnoV(const Domain &domain, int e, const char *fmt, va_list ap)
 {
-	if (stdout_mode)
-		return;
+	char msg[1024];
+	vsnprintf(msg, sizeof(msg), fmt, ap);
 
-#ifdef HAVE_SYSLOG
-	if (out_path.IsNull())
-		closelog();
-#endif
+	LogErrno(domain, e, msg);
 }
 
 void
-log_deinit(void)
+FormatErrno(const Domain &domain, int e, const char *fmt, ...)
 {
-	close_log_files();
-	out_path = Path::Null();
+	va_list ap;
+	va_start(ap, fmt);
+	FormatErrnoV(domain, e, fmt, ap);
+	va_end(ap);
 }
 
-
-void setup_log_output(bool use_stdout)
+void
+FormatErrno(const Domain &domain, const char *fmt, ...)
 {
-	fflush(NULL);
-	if (!use_stdout) {
-#ifndef WIN32
-		if (out_path.IsNull())
-			out_fd = open("/dev/null", O_WRONLY);
-#endif
+	const int e = errno;
 
-		if (out_fd >= 0) {
-			redirect_logs(out_fd);
-			close(out_fd);
-		}
-
-		stdout_mode = false;
-		log_charset = NULL;
-	}
-}
-
-int cycle_log_files(void)
-{
-	int fd;
-
-	if (stdout_mode || out_path.IsNull())
-		return 0;
-
-	assert(!out_path.IsNull());
-
-	g_debug("Cycling log files...\n");
-	close_log_files();
-
-	fd = open_log_file();
-	if (fd < 0) {
-		const std::string out_path_utf8 = out_path.ToUTF8();
-		g_warning("error re-opening log file: %s",
-			  out_path_utf8.c_str());
-		return -1;
-	}
-
-	redirect_logs(fd);
-	g_debug("Done cycling log files\n");
-	return 0;
+	va_list ap;
+	va_start(ap, fmt);
+	FormatErrnoV(domain, e, fmt, ap);
+	va_end(ap);
 }
diff --git a/src/Log.hxx b/src/Log.hxx
index 24cbe5c8d..719f1c448 100644
--- a/src/Log.hxx
+++ b/src/Log.hxx
@@ -20,27 +20,95 @@
 #ifndef MPD_LOG_HXX
 #define MPD_LOG_HXX
 
+#include "gcc.h"
+
+#ifdef WIN32
+#include <windows.h>
+/* damn you, windows.h! */
+#ifdef ERROR
+#undef ERROR
+#endif
+#endif
+
 class Error;
+class Domain;
 
-/**
- * Configure a logging destination for daemon startup, before the
- * configuration file is read.  This allows the daemon to use the
- * logging library (and the command line verbose level) before it's
- * daemonized.
- *
- * @param verbose true when the program is started with --verbose
- */
-void
-log_early_init(bool verbose);
-
-bool
-log_init(bool verbose, bool use_stdout, Error &error);
+enum class LogLevel {
+	DEBUG,
+	INFO,
+	WARNING,
+	ERROR,
+};
 
 void
-log_deinit(void);
+Log(const Domain &domain, LogLevel level, const char *msg);
 
-void setup_log_output(bool use_stdout);
+gcc_fprintf_
+void
+LogFormat(const Domain &domain, LogLevel level, const char *fmt, ...);
 
-int cycle_log_files(void);
+static inline void
+LogDebug(const Domain &domain, const char *msg)
+{
+	Log(domain, LogLevel::DEBUG, msg);
+}
+
+gcc_fprintf
+void
+FormatDebug(const Domain &domain, const char *fmt, ...);
+
+static inline void
+LogInfo(const Domain &domain, const char *msg)
+{
+	Log(domain, LogLevel::INFO, msg);
+}
+
+gcc_fprintf
+void
+FormatInfo(const Domain &domain, const char *fmt, ...);
+
+static inline void
+LogWarning(const Domain &domain, const char *msg)
+{
+	Log(domain, LogLevel::WARNING, msg);
+}
+
+gcc_fprintf
+void
+FormatWarning(const Domain &domain, const char *fmt, ...);
+
+static inline void
+LogError(const Domain &domain, const char *msg)
+{
+	Log(domain, LogLevel::ERROR, msg);
+}
+
+gcc_fprintf
+void
+FormatError(const Domain &domain, const char *fmt, ...);
+
+void
+LogError(const Error &error);
+
+void
+LogError(const Error &error, const char *msg);
+
+gcc_fprintf
+void
+FormatError(const Error &error, const char *fmt, ...);
+
+void
+LogErrno(const Domain &domain, int e, const char *msg);
+
+void
+LogErrno(const Domain &domain, const char *msg);
+
+gcc_fprintf_
+void
+FormatErrno(const Domain &domain, int e, const char *fmt, ...);
+
+gcc_fprintf
+void
+FormatErrno(const Domain &domain, const char *fmt, ...);
 
 #endif /* LOG_H */
diff --git a/src/LogInit.cxx b/src/LogInit.cxx
new file mode 100644
index 000000000..d5e5c1d1f
--- /dev/null
+++ b/src/LogInit.cxx
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "config.h"
+#include "LogInit.hxx"
+#include "Log.hxx"
+#include "ConfigData.hxx"
+#include "ConfigGlobal.hxx"
+#include "ConfigOption.hxx"
+#include "system/fd_util.h"
+#include "system/FatalError.hxx"
+#include "fs/Path.hxx"
+#include "fs/FileSystem.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/FatalError.hxx"
+
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <errno.h>
+#include <glib.h>
+
+#ifdef HAVE_SYSLOG
+#include <syslog.h>
+#endif
+
+#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
+
+#define LOG_DATE_BUF_SIZE 16
+#define LOG_DATE_LEN (LOG_DATE_BUF_SIZE - 1)
+
+static constexpr Domain log_domain("log");
+
+static GLogLevelFlags log_threshold = G_LOG_LEVEL_MESSAGE;
+
+static const char *log_charset;
+
+static bool stdout_mode = true;
+static int out_fd;
+static Path out_path = Path::Null();
+
+static void redirect_logs(int fd)
+{
+	assert(fd >= 0);
+	if (dup2(fd, STDOUT_FILENO) < 0)
+		FatalSystemError("Failed to dup2 stdout");
+	if (dup2(fd, STDERR_FILENO) < 0)
+		FatalSystemError("Failed to dup2 stderr");
+}
+
+static const char *log_date(void)
+{
+	static char buf[LOG_DATE_BUF_SIZE];
+	time_t t = time(NULL);
+	strftime(buf, LOG_DATE_BUF_SIZE, "%b %d %H:%M : ", localtime(&t));
+	return buf;
+}
+
+/**
+ * Determines the length of the string excluding trailing whitespace
+ * characters.
+ */
+static int
+chomp_length(const char *p)
+{
+	size_t length = strlen(p);
+
+	while (length > 0 && g_ascii_isspace(p[length - 1]))
+		--length;
+
+	return (int)length;
+}
+
+static void
+file_log_func(const gchar *domain,
+	      GLogLevelFlags log_level,
+	      const gchar *message, gcc_unused gpointer user_data)
+{
+	char *converted;
+
+	if (log_level > log_threshold)
+		return;
+
+	if (log_charset != NULL) {
+		converted = g_convert_with_fallback(message, -1,
+						    log_charset, "utf-8",
+						    NULL, NULL, NULL, NULL);
+		if (converted != NULL)
+			message = converted;
+	} else
+		converted = NULL;
+
+	if (domain == nullptr)
+		domain = "";
+
+	fprintf(stderr, "%s%s%s%.*s\n",
+		stdout_mode ? "" : log_date(),
+		domain, *domain == 0 ? "" : ": ",
+		chomp_length(message), message);
+
+	g_free(converted);
+}
+
+static void
+log_init_stdout(void)
+{
+	g_log_set_default_handler(file_log_func, NULL);
+}
+
+static int
+open_log_file(void)
+{
+	assert(!out_path.IsNull());
+
+	return OpenFile(out_path, O_CREAT | O_WRONLY | O_APPEND, 0666);
+}
+
+static bool
+log_init_file(unsigned line, Error &error)
+{
+	assert(!out_path.IsNull());
+
+	out_fd = open_log_file();
+	if (out_fd < 0) {
+		const std::string out_path_utf8 = out_path.ToUTF8();
+		error.FormatErrno("failed to open log file \"%s\" (config line %u)",
+				  out_path_utf8.c_str(), line);
+		return false;
+	}
+
+	g_log_set_default_handler(file_log_func, NULL);
+	return true;
+}
+
+#ifdef HAVE_SYSLOG
+
+static int
+glib_to_syslog_level(GLogLevelFlags log_level)
+{
+	switch (log_level & G_LOG_LEVEL_MASK) {
+	case G_LOG_LEVEL_ERROR:
+	case G_LOG_LEVEL_CRITICAL:
+		return LOG_ERR;
+
+	case G_LOG_LEVEL_WARNING:
+		return LOG_WARNING;
+
+	case G_LOG_LEVEL_MESSAGE:
+		return LOG_NOTICE;
+
+	case G_LOG_LEVEL_INFO:
+		return LOG_INFO;
+
+	case G_LOG_LEVEL_DEBUG:
+		return LOG_DEBUG;
+
+	default:
+		return LOG_NOTICE;
+	}
+}
+
+static void
+syslog_log_func(const gchar *domain,
+		GLogLevelFlags log_level, const gchar *message,
+		gcc_unused gpointer user_data)
+{
+	if (stdout_mode) {
+		/* fall back to the file log function during
+		   startup */
+		file_log_func(domain, log_level,
+			      message, user_data);
+		return;
+	}
+
+	if (log_level > log_threshold)
+		return;
+
+	if (domain == nullptr)
+		domain = "";
+
+	syslog(glib_to_syslog_level(log_level), "%s%s%.*s",
+	       domain, *domain == 0 ? "" : ": ",
+	       chomp_length(message), message);
+}
+
+static void
+log_init_syslog(void)
+{
+	assert(out_path.IsNull());
+
+	openlog(PACKAGE, 0, LOG_DAEMON);
+	g_log_set_default_handler(syslog_log_func, NULL);
+}
+
+#endif
+
+static inline GLogLevelFlags
+parse_log_level(const char *value, unsigned line)
+{
+	if (0 == strcmp(value, "default"))
+		return G_LOG_LEVEL_MESSAGE;
+	if (0 == strcmp(value, "secure"))
+		return LOG_LEVEL_SECURE;
+	else if (0 == strcmp(value, "verbose"))
+		return G_LOG_LEVEL_DEBUG;
+	else {
+		FormatFatalError("unknown log level \"%s\" at line %u",
+				 value, line);
+		return G_LOG_LEVEL_MESSAGE;
+	}
+}
+
+void
+log_early_init(bool verbose)
+{
+	if (verbose)
+		log_threshold = G_LOG_LEVEL_DEBUG;
+
+	log_init_stdout();
+}
+
+bool
+log_init(bool verbose, bool use_stdout, Error &error)
+{
+	const struct config_param *param;
+
+	g_get_charset(&log_charset);
+
+	if (verbose)
+		log_threshold = G_LOG_LEVEL_DEBUG;
+	else if ((param = config_get_param(CONF_LOG_LEVEL)) != NULL)
+		log_threshold = parse_log_level(param->value, param->line);
+
+	if (use_stdout) {
+		log_init_stdout();
+		return true;
+	} else {
+		param = config_get_param(CONF_LOG_FILE);
+		if (param == NULL) {
+#ifdef HAVE_SYSLOG
+			/* no configuration: default to syslog (if
+			   available) */
+			log_init_syslog();
+			return true;
+#else
+			error.Set(log_domain,
+				  "config parameter 'log_file' not found");
+			return false;
+#endif
+#ifdef HAVE_SYSLOG
+		} else if (strcmp(param->value, "syslog") == 0) {
+			log_init_syslog();
+			return true;
+#endif
+		} else {
+			out_path = config_get_path(CONF_LOG_FILE, error);
+			return !out_path.IsNull() &&
+				log_init_file(param->line, error);
+		}
+	}
+}
+
+static void
+close_log_files(void)
+{
+	if (stdout_mode)
+		return;
+
+#ifdef HAVE_SYSLOG
+	if (out_path.IsNull())
+		closelog();
+#endif
+}
+
+void
+log_deinit(void)
+{
+	close_log_files();
+	out_path = Path::Null();
+}
+
+
+void setup_log_output(bool use_stdout)
+{
+	fflush(NULL);
+	if (!use_stdout) {
+#ifndef WIN32
+		if (out_path.IsNull())
+			out_fd = open("/dev/null", O_WRONLY);
+#endif
+
+		if (out_fd >= 0) {
+			redirect_logs(out_fd);
+			close(out_fd);
+		}
+
+		stdout_mode = false;
+		log_charset = NULL;
+	}
+}
+
+int cycle_log_files(void)
+{
+	int fd;
+
+	if (stdout_mode || out_path.IsNull())
+		return 0;
+
+	assert(!out_path.IsNull());
+
+	FormatDebug(log_domain, "Cycling log files");
+	close_log_files();
+
+	fd = open_log_file();
+	if (fd < 0) {
+		const std::string out_path_utf8 = out_path.ToUTF8();
+		FormatError(log_domain,
+			    "error re-opening log file: %s",
+			    out_path_utf8.c_str());
+		return -1;
+	}
+
+	redirect_logs(fd);
+	FormatDebug(log_domain, "Done cycling log files");
+	return 0;
+}
diff --git a/src/LogInit.hxx b/src/LogInit.hxx
new file mode 100644
index 000000000..2369fcdca
--- /dev/null
+++ b/src/LogInit.hxx
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_LOG_INIT_HXX
+#define MPD_LOG_INIT_HXX
+
+class Error;
+
+/**
+ * Configure a logging destination for daemon startup, before the
+ * configuration file is read.  This allows the daemon to use the
+ * logging library (and the command line verbose level) before it's
+ * daemonized.
+ *
+ * @param verbose true when the program is started with --verbose
+ */
+void
+log_early_init(bool verbose);
+
+bool
+log_init(bool verbose, bool use_stdout, Error &error);
+
+void
+log_deinit(void);
+
+void setup_log_output(bool use_stdout);
+
+int cycle_log_files(void);
+
+#endif /* LOG_H */
diff --git a/src/LogV.hxx b/src/LogV.hxx
new file mode 100644
index 000000000..4bd4f801d
--- /dev/null
+++ b/src/LogV.hxx
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_LOGV_HXX
+#define MPD_LOGV_HXX
+
+#include "Log.hxx"
+
+#include <stdarg.h>
+
+void
+LogFormatV(const Domain &domain, LogLevel level, const char *fmt, va_list ap);
+
+#endif /* LOG_H */
diff --git a/src/Main.cxx b/src/Main.cxx
index 9216b49cc..360204155 100644
--- a/src/Main.cxx
+++ b/src/Main.cxx
@@ -43,6 +43,7 @@
 #include "Idle.hxx"
 #include "SignalHandlers.hxx"
 #include "Log.hxx"
+#include "LogInit.hxx"
 #include "GlobalEvents.hxx"
 #include "InputInit.hxx"
 #include "event/Loop.hxx"
@@ -56,6 +57,7 @@
 #include "Daemon.hxx"
 #include "system/FatalError.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
 #include "ConfigGlobal.hxx"
 #include "ConfigData.hxx"
 #include "ConfigDefaults.hxx"
@@ -98,6 +100,8 @@ enum {
 	DEFAULT_BUFFER_BEFORE_PLAY = 10,
 };
 
+static constexpr Domain main_domain("main");
+
 GThread *main_task;
 EventLoop *main_loop;
 
@@ -105,11 +109,6 @@ Instance *instance;
 
 static StateFile *state_file;
 
-static inline GQuark main_quark()
-{
-  return g_quark_from_static_string ("main");
-}
-
 static bool
 glue_daemonize_init(const struct options *options, Error &error)
 {
@@ -161,15 +160,18 @@ glue_db_init_and_load(void)
 	const struct config_param *path = config_get_param(CONF_DB_FILE);
 
 	if (param != nullptr && path != nullptr)
-		g_message("Found both 'database' and 'db_file' setting - ignoring the latter");
+		LogInfo(main_domain,
+			"Found both 'database' and 'db_file' setting - ignoring the latter");
 
 	if (!mapper_has_music_directory()) {
 		if (param != nullptr)
-			g_message("Found database setting without "
-				  "music_directory - disabling database");
+			LogInfo(main_domain,
+				"Found database setting without "
+				"music_directory - disabling database");
 		if (path != nullptr)
-			g_message("Found db_file setting without "
-				  "music_directory - disabling database");
+			LogInfo(main_domain,
+				"Found db_file setting without "
+				"music_directory - disabling database");
 		return true;
 	}
 
@@ -372,12 +374,12 @@ int mpd_main(int argc, char *argv[])
 
 	success = parse_cmdline(argc, argv, &options, error);
 	if (!success) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
 	if (!glue_daemonize_init(&options, error)) {
-		g_printerr("%s\n", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -385,7 +387,7 @@ int mpd_main(int argc, char *argv[])
 	TagLoadConfig();
 
 	if (!log_init(options.verbose, options.log_stderr, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -399,7 +401,7 @@ int mpd_main(int argc, char *argv[])
 
 	success = listen_global_init(error);
 	if (!success) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -414,7 +416,7 @@ int mpd_main(int argc, char *argv[])
 	Path::GlobalInit();
 
 	if (!glue_mapper_init(error)) {
-		g_printerr("%s\n", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -426,7 +428,7 @@ int mpd_main(int argc, char *argv[])
 #endif
 
 	if (!pcm_resample_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -446,7 +448,7 @@ int mpd_main(int argc, char *argv[])
 	replay_gain_global_init();
 
 	if (!input_stream_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -486,7 +488,8 @@ int mpd_main(int argc, char *argv[])
 						     G_MAXUINT));
 #else
 	if (success)
-		g_warning("inotify: auto_update was disabled. enable during compilation phase");
+		FormatWarning(main_domain,
+			      "inotify: auto_update was disabled. enable during compilation phase");
 #endif
 
 	config_global_check();
@@ -524,8 +527,9 @@ int mpd_main(int argc, char *argv[])
 
 	start = clock();
 	DatabaseGlobalDeinit();
-	g_debug("db_finish took %f seconds",
-		((float)(clock()-start))/CLOCKS_PER_SEC);
+	FormatDebug(main_domain,
+		    "db_finish took %f seconds",
+		    ((float)(clock()-start))/CLOCKS_PER_SEC);
 
 #ifdef ENABLE_SQLITE
 	sticker_global_finish();
diff --git a/src/Mapper.cxx b/src/Mapper.cxx
index 135621e17..e2d082122 100644
--- a/src/Mapper.cxx
+++ b/src/Mapper.cxx
@@ -28,6 +28,8 @@
 #include "fs/Path.hxx"
 #include "fs/FileSystem.hxx"
 #include "fs/DirectoryReader.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -38,6 +40,8 @@
 #include <errno.h>
 #include <dirent.h>
 
+static constexpr Domain mapper_domain("mapper");
+
 /**
  * The absolute path of the music directory encoded in UTF-8.
  */
@@ -57,12 +61,6 @@ static size_t music_dir_fs_length;
  */
 static Path playlist_dir_fs = Path::Null();
 
-static inline GQuark
-mapper_quark()
-{
-  return g_quark_from_static_string ("mapper");
-}
-
 /**
  * Duplicate a string, chop all trailing slashes.
  */
@@ -82,26 +80,30 @@ check_directory(const char *path_utf8, const Path &path_fs)
 {
 	struct stat st;
 	if (!StatFile(path_fs, st)) {
-		g_warning("Failed to stat directory \"%s\": %s",
-			  path_utf8, g_strerror(errno));
+		FormatErrno(mapper_domain,
+			    "Failed to stat directory \"%s\"",
+			    path_utf8);
 		return;
 	}
 
 	if (!S_ISDIR(st.st_mode)) {
-		g_warning("Not a directory: %s", path_utf8);
+		FormatError(mapper_domain,
+			    "Not a directory: %s", path_utf8);
 		return;
 	}
 
 #ifndef WIN32
 	const Path x = Path::Build(path_fs, ".");
 	if (!StatFile(x, st) && errno == EACCES)
-		g_warning("No permission to traverse (\"execute\") directory: %s",
-			  path_utf8);
+		FormatError(mapper_domain,
+			    "No permission to traverse (\"execute\") directory: %s",
+			    path_utf8);
 #endif
 
 	const DirectoryReader reader(path_fs);
 	if (reader.HasFailed() && errno == EACCES)
-		g_warning("No permission to read directory: %s", path_utf8);
+		FormatError(mapper_domain,
+			    "No permission to read directory: %s", path_utf8);
 }
 
 static void
diff --git a/src/MixerAll.cxx b/src/MixerAll.cxx
index 8005655e5..e9994e901 100644
--- a/src/MixerAll.cxx
+++ b/src/MixerAll.cxx
@@ -26,13 +26,12 @@
 #include "pcm/PcmVolume.hxx"
 #include "OutputInternal.hxx"
 #include "util/Error.hxx"
-
-#include <glib.h>
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mixer"
+static constexpr Domain mixer_domain("mixer");
 
 static int
 output_mixer_get_volume(unsigned i)
@@ -53,8 +52,9 @@ output_mixer_get_volume(unsigned i)
 	Error error;
 	volume = mixer_get_volume(mixer, error);
 	if (volume < 0 && error.IsDefined())
-		g_warning("Failed to read mixer for '%s': %s",
-			  output->name, error.GetMessage());
+		FormatError(error,
+			    "Failed to read mixer for '%s'",
+			    output->name);
 
 	return volume;
 }
@@ -99,8 +99,9 @@ output_mixer_set_volume(unsigned i, unsigned volume)
 	Error error;
 	success = mixer_set_volume(mixer, volume, error);
 	if (!success && error.IsDefined())
-		g_warning("Failed to set mixer for '%s': %s",
-			  output->name, error.GetMessage());
+		FormatError(error,
+			    "Failed to set mixer for '%s'",
+			    output->name);
 
 	return success;
 }
diff --git a/src/MixerControl.cxx b/src/MixerControl.cxx
index 86db5dde4..2759f945a 100644
--- a/src/MixerControl.cxx
+++ b/src/MixerControl.cxx
@@ -25,9 +25,6 @@
 #include <assert.h>
 #include <stddef.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mixer"
-
 Mixer *
 mixer_new(const struct mixer_plugin *plugin, void *ao,
 	  const config_param &param,
diff --git a/src/OutputAll.cxx b/src/OutputAll.cxx
index 7f5eef749..18113f860 100644
--- a/src/OutputAll.cxx
+++ b/src/OutputAll.cxx
@@ -36,9 +36,6 @@
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
 static AudioFormat input_audio_format;
 
 static struct audio_output **audio_outputs;
diff --git a/src/OutputControl.cxx b/src/OutputControl.cxx
index a0fd99701..3dc9c470b 100644
--- a/src/OutputControl.cxx
+++ b/src/OutputControl.cxx
@@ -22,12 +22,14 @@
 #include "OutputThread.hxx"
 #include "OutputInternal.hxx"
 #include "OutputPlugin.hxx"
+#include "OutputError.hxx"
 #include "MixerPlugin.hxx"
 #include "MixerControl.hxx"
 #include "notify.hxx"
 #include "filter/ReplayGainFilterPlugin.hxx"
 #include "FilterPlugin.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -191,8 +193,9 @@ audio_output_open(struct audio_output *ao,
 	if (open && ao->mixer != NULL) {
 		Error error;
 		if (!mixer_open(ao->mixer, error))
-			g_warning("Failed to open mixer for '%s': %s",
-				  ao->name, error.GetMessage());
+			FormatWarning(output_domain,
+				      "Failed to open mixer for '%s'",
+				      ao->name);
 	}
 
 	return open;
diff --git a/src/OutputInit.cxx b/src/OutputInit.cxx
index 79d457dad..8a31a9b97 100644
--- a/src/OutputInit.cxx
+++ b/src/OutputInit.cxx
@@ -37,15 +37,11 @@
 #include "ConfigError.hxx"
 #include "ConfigGlobal.hxx"
 #include "util/Error.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
-
 #define AUDIO_OUTPUT_TYPE	"type"
 #define AUDIO_OUTPUT_NAME	"name"
 #define AUDIO_OUTPUT_FORMAT	"format"
@@ -54,14 +50,15 @@
 static const struct audio_output_plugin *
 audio_output_detect(Error &error)
 {
-	g_warning("Attempt to detect audio output device");
+	LogInfo(output_domain, "Attempt to detect audio output device");
 
 	audio_output_plugins_for_each(plugin) {
 		if (plugin->test_default_device == NULL)
 			continue;
 
-		g_warning("Attempting to detect a %s audio device",
-			  plugin->name);
+		FormatInfo(output_domain,
+			   "Attempting to detect a %s audio device",
+			   plugin->name);
 		if (ao_plugin_test_default_device(plugin))
 			return plugin;
 	}
@@ -201,8 +198,9 @@ ao_base_init(struct audio_output *ao,
 	// It's not really fatal - Part of the filter chain has been set up already
 	// and even an empty one will work (if only with unexpected behaviour)
 	if (filter_error.IsDefined())
-		g_warning("Failed to initialize filter chain for '%s': %s",
-			  ao->name, filter_error.GetMessage());
+		FormatError(filter_error,
+			    "Failed to initialize filter chain for '%s'",
+			    ao->name);
 
 	ao->thread = NULL;
 	ao->command = AO_COMMAND_NONE;
@@ -251,8 +249,9 @@ audio_output_setup(struct audio_output *ao, const config_param &param,
 					    ao->plugin->mixer_plugin,
 					    *ao->filter, mixer_error);
 	if (ao->mixer == NULL && mixer_error.IsDefined())
-		g_warning("Failed to initialize hardware mixer for '%s': %s",
-			  ao->name, mixer_error.GetMessage());
+		FormatError(mixer_error,
+			    "Failed to initialize hardware mixer for '%s'",
+			    ao->name);
 
 	/* use the hardware mixer for replay gain? */
 
@@ -261,7 +260,8 @@ audio_output_setup(struct audio_output *ao, const config_param &param,
 			replay_gain_filter_set_mixer(ao->replay_gain_filter,
 						     ao->mixer, 100);
 		else
-			g_warning("No such mixer for output '%s'", ao->name);
+			FormatError(output_domain,
+				    "No such mixer for output '%s'", ao->name);
 	} else if (strcmp(replay_gain_handler, "software") != 0 &&
 		   ao->replay_gain_filter != NULL) {
 		error.Set(config_domain,
@@ -304,14 +304,16 @@ audio_output_new(const config_param &param,
 			return nullptr;
 		}
 	} else {
-		g_warning("No 'audio_output' defined in config file\n");
+		LogWarning(output_domain,
+			   "No 'audio_output' defined in config file");
 
 		plugin = audio_output_detect(error);
 		if (plugin == NULL)
 			return nullptr;
 
-		g_message("Successfully detected a %s audio device",
-			  plugin->name);
+		FormatInfo(output_domain,
+			   "Successfully detected a %s audio device",
+			   plugin->name);
 	}
 
 	struct audio_output *ao = ao_plugin_init(plugin, param, error);
diff --git a/src/OutputState.cxx b/src/OutputState.cxx
index 776cac8f4..a3650413c 100644
--- a/src/OutputState.cxx
+++ b/src/OutputState.cxx
@@ -26,6 +26,8 @@
 #include "OutputState.hxx"
 #include "OutputAll.hxx"
 #include "OutputInternal.hxx"
+#include "OutputError.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -76,7 +78,8 @@ audio_output_state_read(const char *line)
 	name = endptr + 1;
 	ao = audio_output_find(name);
 	if (ao == NULL) {
-		g_debug("Ignoring device state for '%s'", name);
+		FormatDebug(output_domain,
+			    "Ignoring device state for '%s'", name);
 		return true;
 	}
 
diff --git a/src/OutputThread.cxx b/src/OutputThread.cxx
index 5f94e76d8..b1c670afc 100644
--- a/src/OutputThread.cxx
+++ b/src/OutputThread.cxx
@@ -21,6 +21,7 @@
 #include "OutputThread.hxx"
 #include "OutputInternal.hxx"
 #include "OutputAPI.hxx"
+#include "OutputError.hxx"
 #include "pcm/PcmMix.hxx"
 #include "notify.hxx"
 #include "FilterInternal.hxx"
@@ -31,17 +32,13 @@
 #include "MusicChunk.hxx"
 #include "system/FatalError.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 #include "gcc.h"
 
 #include <glib.h>
 
 #include <assert.h>
-#include <stdlib.h>
 #include <string.h>
-#include <errno.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "output"
 
 static void ao_command_finished(struct audio_output *ao)
 {
@@ -66,8 +63,9 @@ ao_enable(struct audio_output *ao)
 	success = ao_plugin_enable(ao, error);
 	ao->mutex.lock();
 	if (!success) {
-		g_warning("Failed to enable \"%s\" [%s]: %s\n",
-			  ao->name, ao->plugin->name, error.GetMessage());
+		FormatError(error,
+			    "Failed to enable \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
 		return false;
 	}
 
@@ -159,8 +157,8 @@ ao_open(struct audio_output *ao)
 	const AudioFormat filter_audio_format =
 		ao_filter_open(ao, ao->in_audio_format, error);
 	if (!filter_audio_format.IsDefined()) {
-		g_warning("Failed to open filter for \"%s\" [%s]: %s",
-			  ao->name, ao->plugin->name, error.GetMessage());
+		FormatError(error, "Failed to open filter for \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
 
 		ao->fail_timer = g_timer_new();
 		return;
@@ -178,8 +176,8 @@ ao_open(struct audio_output *ao)
 	assert(!ao->open);
 
 	if (!success) {
-		g_warning("Failed to open \"%s\" [%s]: %s",
-			  ao->name, ao->plugin->name, error.GetMessage());
+		FormatError(error, "Failed to open \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
 
 		ao_filter_close(ao);
 		ao->fail_timer = g_timer_new();
@@ -190,15 +188,15 @@ ao_open(struct audio_output *ao)
 
 	ao->open = true;
 
-	g_debug("opened plugin=%s name=\"%s\" "
-		"audio_format=%s",
-		ao->plugin->name, ao->name,
-		audio_format_to_string(ao->out_audio_format, &af_string));
+	FormatDebug(output_domain,
+		    "opened plugin=%s name=\"%s\" audio_format=%s",
+		    ao->plugin->name, ao->name,
+		    audio_format_to_string(ao->out_audio_format, &af_string));
 
 	if (ao->in_audio_format != ao->out_audio_format)
-		g_debug("converting from %s",
-			audio_format_to_string(ao->in_audio_format,
-					       &af_string));
+		FormatDebug(output_domain, "converting from %s",
+			    audio_format_to_string(ao->in_audio_format,
+						   &af_string));
 }
 
 static void
@@ -223,7 +221,8 @@ ao_close(struct audio_output *ao, bool drain)
 
 	ao->mutex.lock();
 
-	g_debug("closed plugin=%s name=\"%s\"", ao->plugin->name, ao->name);
+	FormatDebug(output_domain, "closed plugin=%s name=\"%s\"",
+		    ao->plugin->name, ao->name);
 }
 
 static void
@@ -235,8 +234,9 @@ ao_reopen_filter(struct audio_output *ao)
 	const AudioFormat filter_audio_format =
 		ao_filter_open(ao, ao->in_audio_format, error);
 	if (!filter_audio_format.IsDefined()) {
-		g_warning("Failed to open filter for \"%s\" [%s]: %s",
-			  ao->name, ao->plugin->name, error.GetMessage());
+		FormatError(error,
+			    "Failed to open filter for \"%s\" [%s]",
+			    ao->name, ao->plugin->name);
 
 		/* this is a little code duplication fro ao_close(),
 		   but we cannot call this function because we must
@@ -334,8 +334,8 @@ ao_chunk_data(struct audio_output *ao, const struct music_chunk *chunk,
 		data = replay_gain_filter->FilterPCM(data, length,
 						     &length, error);
 		if (data == NULL) {
-			g_warning("\"%s\" [%s] failed to filter: %s",
-				  ao->name, ao->plugin->name, error.GetMessage());
+			FormatError(error, "\"%s\" [%s] failed to filter",
+				    ao->name, ao->plugin->name);
 			return NULL;
 		}
 	}
@@ -390,8 +390,9 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
 		if (!pcm_mix(dest, data, length,
 			     ao->in_audio_format.format,
 			     1.0 - chunk->mix_ratio)) {
-			g_warning("Cannot cross-fade format %s",
-				  sample_format_to_string(ao->in_audio_format.format));
+			FormatError(output_domain,
+				    "Cannot cross-fade format %s",
+				    sample_format_to_string(ao->in_audio_format.format));
 			return NULL;
 		}
 
@@ -404,8 +405,8 @@ ao_filter_chunk(struct audio_output *ao, const struct music_chunk *chunk,
 	Error error;
 	data = ao->filter->FilterPCM(data, length, &length, error);
 	if (data == NULL) {
-		g_warning("\"%s\" [%s] failed to filter: %s",
-			  ao->name, ao->plugin->name, error.GetMessage());
+		FormatError(error, "\"%s\" [%s] failed to filter",
+			    ao->name, ao->plugin->name);
 		return NULL;
 	}
 
@@ -453,9 +454,8 @@ ao_play_chunk(struct audio_output *ao, const struct music_chunk *chunk)
 		ao->mutex.lock();
 		if (nbytes == 0) {
 			/* play()==0 means failure */
-			g_warning("\"%s\" [%s] failed to play: %s",
-				  ao->name, ao->plugin->name,
-				  error.GetMessage());
+			FormatError(error, "\"%s\" [%s] failed to play",
+				    ao->name, ao->plugin->name);
 
 			ao_close(ao, false);
 
diff --git a/src/PlayerThread.cxx b/src/PlayerThread.cxx
index 67cfc1498..9ad37eaee 100644
--- a/src/PlayerThread.cxx
+++ b/src/PlayerThread.cxx
@@ -33,6 +33,8 @@
 #include "tag/Tag.hxx"
 #include "Idle.hxx"
 #include "GlobalEvents.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <cmath>
 
@@ -40,8 +42,7 @@
 
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "player_thread"
+static constexpr Domain player_domain("player");
 
 enum class CrossFadeState : int8_t {
 	DISABLED = -1,
@@ -402,7 +403,7 @@ Player::OpenOutput()
 
 		return true;
 	} else {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 
 		output_open = false;
 
@@ -461,8 +462,9 @@ Player::CheckDecoderStartup()
 
 		if (!paused && !OpenOutput()) {
 			char *uri = dc.song->GetURI();
-			g_warning("problems opening audio device "
-				  "while playing \"%s\"", uri);
+			FormatError(player_domain,
+				    "problems opening audio device "
+				    "while playing \"%s\"", uri);
 			g_free(uri);
 
 			return true;
@@ -487,7 +489,7 @@ Player::SendSilence()
 
 	struct music_chunk *chunk = buffer.Allocate();
 	if (chunk == nullptr) {
-		g_warning("Failed to allocate silence buffer");
+		LogError(player_domain, "Failed to allocate silence buffer");
 		return false;
 	}
 
@@ -506,7 +508,7 @@ Player::SendSilence()
 
 	Error error;
 	if (!audio_output_all_play(chunk, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		buffer.Return(chunk);
 		return false;
 	}
@@ -838,7 +840,7 @@ Player::PlayNextChunk()
 
 	Error error;
 	if (!play_chunk(pc, song, chunk, buffer, play_audio_format, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 
 		buffer.Return(chunk);
 
@@ -877,7 +879,7 @@ Player::SongBorder()
 	xfade_state = CrossFadeState::UNKNOWN;
 
 	char *uri = song->GetURI();
-	g_message("played \"%s\"", uri);
+	FormatInfo(player_domain, "played \"%s\"", uri);
 	g_free(uri);
 
 	ReplacePipe(dc.pipe);
diff --git a/src/Playlist.cxx b/src/Playlist.cxx
index ad910a55d..9f3758c52 100644
--- a/src/Playlist.cxx
+++ b/src/Playlist.cxx
@@ -19,17 +19,16 @@
 
 #include "config.h"
 #include "Playlist.hxx"
+#include "PlaylistError.hxx"
 #include "PlayerControl.hxx"
 #include "Song.hxx"
 #include "Idle.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "playlist"
-
 void
 playlist::FullIncrementVersions()
 {
@@ -65,7 +64,8 @@ playlist_queue_song_order(struct playlist *playlist, struct player_control *pc,
 	Song *song = playlist->queue.GetOrder(order)->DupDetached();
 
 	uri = song->GetURI();
-	g_debug("queue song %i:\"%s\"", playlist->queued, uri);
+	FormatDebug(playlist_domain, "queue song %i:\"%s\"",
+		    playlist->queued, uri);
 	g_free(uri);
 
 	pc->EnqueueSong(song);
@@ -156,7 +156,7 @@ playlist::PlayOrder(player_control &pc, int order)
 	Song *song = queue.GetOrder(order)->DupDetached();
 
 	char *uri = song->GetURI();
-	g_debug("play %i:\"%s\"", order, uri);
+	FormatDebug(playlist_domain, "play %i:\"%s\"", order, uri);
 	g_free(uri);
 
 	pc.Play(song);
diff --git a/src/PlaylistAny.cxx b/src/PlaylistAny.cxx
index f0d044ca6..997c59a4e 100644
--- a/src/PlaylistAny.cxx
+++ b/src/PlaylistAny.cxx
@@ -21,9 +21,11 @@
 #include "PlaylistAny.hxx"
 #include "PlaylistMapper.hxx"
 #include "PlaylistRegistry.hxx"
+#include "PlaylistError.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
 #include "InputStream.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 
@@ -43,8 +45,7 @@ playlist_open_remote(const char *uri, Mutex &mutex, Cond &cond,
 	input_stream *is = input_stream::Open(uri, mutex, cond, error);
 	if (is == nullptr) {
 		if (error.IsDefined())
-			g_warning("Failed to open %s: %s",
-				  uri, error.GetMessage());
+			FormatError(error, "Failed to open %s", uri);
 
 		return nullptr;
 	}
diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx
index f0cf05121..c6b5690bb 100644
--- a/src/PlaylistControl.cxx
+++ b/src/PlaylistControl.cxx
@@ -24,13 +24,10 @@
 
 #include "config.h"
 #include "Playlist.hxx"
+#include "PlaylistError.hxx"
 #include "PlayerControl.hxx"
 #include "Song.hxx"
-
-#include <glib.h>
-
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "playlist"
+#include "Log.hxx"
 
 void
 playlist::Stop(player_control &pc)
@@ -40,7 +37,7 @@ playlist::Stop(player_control &pc)
 
 	assert(current >= 0);
 
-	g_debug("stop");
+	FormatDebug(playlist_domain, "stop");
 	pc.Stop();
 	queued = -1;
 	playing = false;
diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx
index a68c579d1..3cb68a1ab 100644
--- a/src/PlaylistEdit.cxx
+++ b/src/PlaylistEdit.cxx
@@ -25,6 +25,7 @@
 
 #include "config.h"
 #include "Playlist.hxx"
+#include "PlaylistError.hxx"
 #include "PlayerControl.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
@@ -32,6 +33,7 @@
 #include "Idle.hxx"
 #include "DatabaseGlue.hxx"
 #include "DatabasePlugin.hxx"
+#include "Log.hxx"
 
 #include <stdlib.h>
 
@@ -104,7 +106,7 @@ enum playlist_result
 playlist::AppendURI(struct player_control &pc,
 		    const char *uri, unsigned *added_id)
 {
-	g_debug("add to playlist: %s", uri);
+	FormatDebug(playlist_domain, "add to playlist: %s", uri);
 
 	const Database *db = nullptr;
 	Song *song;
diff --git a/src/PlaylistRegistry.cxx b/src/PlaylistRegistry.cxx
index 028a93231..fa33596f0 100644
--- a/src/PlaylistRegistry.cxx
+++ b/src/PlaylistRegistry.cxx
@@ -37,6 +37,7 @@
 #include "ConfigGlobal.hxx"
 #include "ConfigData.hxx"
 #include "system/FatalError.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
@@ -321,7 +322,7 @@ playlist_list_open_path(const char *path_fs, Mutex &mutex, Cond &cond,
 	input_stream *is = input_stream::Open(path_fs, mutex, cond, error);
 	if (is == nullptr) {
 		if (error.IsDefined())
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 
 		return nullptr;
 	}
diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx
index 1a3a96a23..12c82dc73 100644
--- a/src/PlaylistSave.cxx
+++ b/src/PlaylistSave.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "PlaylistSave.hxx"
 #include "PlaylistFile.hxx"
+#include "PlaylistError.hxx"
 #include "Playlist.hxx"
 #include "Song.hxx"
 #include "Mapper.hxx"
@@ -28,6 +29,7 @@
 #include "fs/FileSystem.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -123,7 +125,8 @@ playlist_load_spl(struct playlist *playlist, struct player_control *pc,
 			}
 
 			if (playlist->AppendURI(*pc, temp2) != PLAYLIST_RESULT_SUCCESS)
-				g_warning("can't add file \"%s\"", temp2);
+				FormatError(playlist_domain,
+					    "can't add file \"%s\"", temp2);
 
 			g_free(temp2);
 		}
diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx
index a0b6972cf..49e9dfb49 100644
--- a/src/PlaylistState.cxx
+++ b/src/PlaylistState.cxx
@@ -24,12 +24,14 @@
 
 #include "config.h"
 #include "PlaylistState.hxx"
+#include "PlaylistError.hxx"
 #include "Playlist.hxx"
 #include "QueueSave.hxx"
 #include "TextFile.hxx"
 #include "PlayerControl.hxx"
 #include "ConfigGlobal.hxx"
 #include "ConfigOption.hxx"
+#include "Log.hxx"
 
 #include <string.h>
 #include <stdlib.h>
@@ -102,7 +104,7 @@ playlist_state_load(TextFile &file, struct playlist *playlist)
 {
 	const char *line = file.ReadLine();
 	if (line == nullptr) {
-		g_warning("No playlist in state file");
+		LogWarning(playlist_domain, "No playlist in state file");
 		return;
 	}
 
@@ -111,8 +113,9 @@ playlist_state_load(TextFile &file, struct playlist *playlist)
 
 		line = file.ReadLine();
 		if (line == nullptr) {
-			g_warning("'" PLAYLIST_STATE_FILE_PLAYLIST_END
-				  "' not found in state file");
+			LogWarning(playlist_domain,
+				   "'" PLAYLIST_STATE_FILE_PLAYLIST_END
+				   "' not found in state file");
 			break;
 		}
 	}
diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx
index 2e48b681f..8f22e312f 100644
--- a/src/QueueSave.cxx
+++ b/src/QueueSave.cxx
@@ -19,7 +19,8 @@
 
 #include "config.h"
 #include "QueueSave.hxx"
-#include "Playlist.hxx"
+#include "Queue.hxx"
+#include "PlaylistError.hxx"
 #include "Song.hxx"
 #include "SongSave.hxx"
 #include "DatabasePlugin.hxx"
@@ -27,6 +28,7 @@
 #include "TextFile.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -96,14 +98,15 @@ queue_load_song(TextFile &file, const char *line, queue *queue)
 		Error error;
 		song = song_load(file, NULL, uri, error);
 		if (song == NULL) {
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 			return;
 		}
 	} else {
 		char *endptr;
 		long ret = strtol(line, &endptr, 10);
 		if (ret < 0 || *endptr != ':' || endptr[1] == 0) {
-			g_warning("Malformed playlist line in state file");
+			LogError(playlist_domain,
+				 "Malformed playlist line in state file");
 			return;
 		}
 
diff --git a/src/SignalHandlers.cxx b/src/SignalHandlers.cxx
index 9d43dbee1..c884b97fd 100644
--- a/src/SignalHandlers.cxx
+++ b/src/SignalHandlers.cxx
@@ -24,15 +24,17 @@
 #ifndef WIN32
 
 #include "Log.hxx"
+#include "LogInit.hxx"
 #include "Main.hxx"
 #include "event/Loop.hxx"
 #include "GlobalEvents.hxx"
 #include "system/FatalError.hxx"
-
-#include <glib.h>
+#include "util/Domain.hxx"
 
 #include <signal.h>
 
+static constexpr Domain signal_handlers_domain("signal_handlers");
+
 static EventLoop *shutdown_loop;
 
 static void
@@ -51,7 +53,7 @@ x_sigaction(int signum, const struct sigaction *act)
 static void
 handle_reload_event(void)
 {
-	g_debug("got SIGHUP, reopening log files");
+	LogDebug(signal_handlers_domain, "got SIGHUP, reopening log files");
 	cycle_log_files();
 }
 
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index 1ed9906c4..8005885eb 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -29,13 +29,8 @@
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
 
-#include <glib.h>
-
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "song"
-
 #define SONG_MTIME "mtime"
 #define SONG_END "song_end"
 
diff --git a/src/StateFile.cxx b/src/StateFile.cxx
index 901ebca39..ce812076c 100644
--- a/src/StateFile.cxx
+++ b/src/StateFile.cxx
@@ -26,14 +26,12 @@
 #include "Volume.hxx"
 #include "event/Loop.hxx"
 #include "fs/FileSystem.hxx"
-
-#include <glib.h>
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <string.h>
-#include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "state_file"
+static constexpr Domain state_file_domain("state_file");
 
 StateFile::StateFile(Path &&_path,
 		     Partition &_partition, EventLoop &_loop)
@@ -66,12 +64,13 @@ StateFile::IsModified() const
 void
 StateFile::Write()
 {
-	g_debug("Saving state file %s", path_utf8.c_str());
+	FormatDebug(state_file_domain,
+		    "Saving state file %s", path_utf8.c_str());
 
 	FILE *fp = FOpen(path, FOpenMode::WriteText);
-	if (G_UNLIKELY(!fp)) {
-		g_warning("failed to create %s: %s",
-			  path_utf8.c_str(), g_strerror(errno));
+	if (gcc_unlikely(!fp)) {
+		FormatErrno(state_file_domain, "failed to create %s",
+			    path_utf8.c_str());
 		return;
 	}
 
@@ -89,12 +88,12 @@ StateFile::Read()
 {
 	bool success;
 
-	g_debug("Loading state file %s", path_utf8.c_str());
+	FormatDebug(state_file_domain, "Loading state file %s", path_utf8.c_str());
 
 	TextFile file(path);
 	if (file.HasFailed()) {
-		g_warning("failed to open %s: %s",
-			  path_utf8.c_str(), g_strerror(errno));
+		FormatErrno(state_file_domain, "failed to open %s",
+			    path_utf8.c_str());
 		return;
 	}
 
@@ -105,7 +104,9 @@ StateFile::Read()
 			playlist_state_restore(line, file, &partition.playlist,
 					       &partition.pc);
 		if (!success)
-			g_warning("Unrecognized line in state file: %s", line);
+			FormatError(state_file_domain,
+				    "Unrecognized line in state file: %s",
+				    line);
 	}
 
 	RememberVersions();
diff --git a/src/Stats.cxx b/src/Stats.cxx
index 0f9703674..de7091ddc 100644
--- a/src/Stats.cxx
+++ b/src/Stats.cxx
@@ -30,6 +30,7 @@ extern "C" {
 #include "DatabasePlugin.hxx"
 #include "DatabaseSimple.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 struct stats stats;
 
@@ -56,7 +57,7 @@ void stats_update(void)
 		stats.artist_count = stats2.artist_count;
 		stats.album_count = stats2.album_count;
 	} else {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 
 		stats.song_count = 0;
 		stats.song_duration = 0;
diff --git a/src/StickerDatabase.cxx b/src/StickerDatabase.cxx
index 8ed82c770..1cd47a605 100644
--- a/src/StickerDatabase.cxx
+++ b/src/StickerDatabase.cxx
@@ -23,6 +23,7 @@
 #include "Idle.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <string>
 #include <map>
@@ -31,9 +32,6 @@
 #include <sqlite3.h>
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "sticker"
-
 #if SQLITE_VERSION_NUMBER < 3003009
 #define sqlite3_prepare_v2 sqlite3_prepare
 #endif
@@ -85,6 +83,12 @@ static sqlite3_stmt *sticker_stmt[G_N_ELEMENTS(sticker_sql)];
 
 static constexpr Domain sticker_domain("sticker");
 
+static void
+LogError(sqlite3 *db, const char *msg)
+{
+	FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db));
+}
+
 static sqlite3_stmt *
 sticker_prepare(const char *sql, Error &error)
 {
@@ -186,22 +190,19 @@ sticker_load_value(const char *type, const char *uri, const char *name)
 
 	ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return NULL;
 	}
 
 	ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return NULL;
 	}
 
 	ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return NULL;
 	}
 
@@ -217,8 +218,7 @@ sticker_load_value(const char *type, const char *uri, const char *name)
 		value = NULL;
 	} else {
 		/* error */
-		g_warning("sqlite3_step() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_step() failed");
 		return NULL;
 	}
 
@@ -243,15 +243,13 @@ sticker_list_values(std::map<std::string, std::string> &table,
 
 	ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -272,8 +270,7 @@ sticker_list_values(std::map<std::string, std::string> &table,
 			/* no op */
 			break;
 		default:
-			g_warning("sqlite3_step() failed: %s",
-				  sqlite3_errmsg(sticker_db));
+			LogError(sticker_db, "sqlite3_step() failed");
 			return false;
 		}
 	} while (ret != SQLITE_DONE);
@@ -303,29 +300,25 @@ sticker_update_value(const char *type, const char *uri,
 
 	ret = sqlite3_bind_text(stmt, 1, value, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 2, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 3, uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 4, name, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -334,8 +327,7 @@ sticker_update_value(const char *type, const char *uri,
 	} while (ret == SQLITE_BUSY);
 
 	if (ret != SQLITE_DONE) {
-		g_warning("sqlite3_step() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_step() failed");
 		return false;
 	}
 
@@ -367,29 +359,25 @@ sticker_insert_value(const char *type, const char *uri,
 
 	ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 4, value, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -398,8 +386,7 @@ sticker_insert_value(const char *type, const char *uri,
 	} while (ret == SQLITE_BUSY);
 
 	if (ret != SQLITE_DONE) {
-		g_warning("sqlite3_step() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_step() failed");
 		return false;
 	}
 
@@ -442,15 +429,13 @@ sticker_delete(const char *type, const char *uri)
 
 	ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -459,8 +444,7 @@ sticker_delete(const char *type, const char *uri)
 	} while (ret == SQLITE_BUSY);
 
 	if (ret != SQLITE_DONE) {
-		g_warning("sqlite3_step() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_step() failed");
 		return false;
 	}
 
@@ -485,22 +469,19 @@ sticker_delete_value(const char *type, const char *uri, const char *name)
 
 	ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 2, uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -509,8 +490,7 @@ sticker_delete_value(const char *type, const char *uri, const char *name)
 	} while (ret == SQLITE_BUSY);
 
 	if (ret != SQLITE_DONE) {
-		g_warning("sqlite3_step() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_step() failed");
 		return false;
 	}
 
@@ -582,8 +562,7 @@ sticker_find(const char *type, const char *base_uri, const char *name,
 
 	ret = sqlite3_bind_text(stmt, 1, type, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -592,15 +571,13 @@ sticker_find(const char *type, const char *base_uri, const char *name,
 
 	ret = sqlite3_bind_text(stmt, 2, base_uri, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
 	ret = sqlite3_bind_text(stmt, 3, name, -1, NULL);
 	if (ret != SQLITE_OK) {
-		g_warning("sqlite3_bind_text() failed: %s",
-			  sqlite3_errmsg(sticker_db));
+		LogError(sticker_db, "sqlite3_bind_text() failed");
 		return false;
 	}
 
@@ -618,8 +595,7 @@ sticker_find(const char *type, const char *base_uri, const char *name,
 			/* no op */
 			break;
 		default:
-			g_warning("sqlite3_step() failed: %s",
-				  sqlite3_errmsg(sticker_db));
+			LogError(sticker_db, "sqlite3_step() failed");
 			return false;
 		}
 	} while (ret != SQLITE_DONE);
diff --git a/src/TextInputStream.cxx b/src/TextInputStream.cxx
index cbdcd2290..d58a2fbee 100644
--- a/src/TextInputStream.cxx
+++ b/src/TextInputStream.cxx
@@ -22,6 +22,7 @@
 #include "InputStream.hxx"
 #include "util/fifo_buffer.h"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -58,7 +59,7 @@ bool TextInputStream::ReadLine(std::string &line)
 			if (nbytes > 0)
 				fifo_buffer_append(buffer, nbytes);
 			else if (error.IsDefined()) {
-				g_warning("%s", error.GetMessage());
+				LogError(error);
 				return false;
 			}
 		} else
diff --git a/src/UpdateArchive.cxx b/src/UpdateArchive.cxx
index 2e0755244..b920b8303 100644
--- a/src/UpdateArchive.cxx
+++ b/src/UpdateArchive.cxx
@@ -20,6 +20,7 @@
 #include "config.h" /* must be first for large file support */
 #include "UpdateArchive.hxx"
 #include "UpdateInternal.hxx"
+#include "UpdateDomain.hxx"
 #include "DatabaseLock.hxx"
 #include "Directory.hxx"
 #include "Song.hxx"
@@ -30,6 +31,7 @@
 #include "ArchiveFile.hxx"
 #include "ArchiveVisitor.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -53,7 +55,8 @@ update_archive_tree(Directory *directory, const char *name)
 		update_archive_tree(subdir, tmp+1);
 	} else {
 		if (strlen(name) == 0) {
-			g_warning("archive returned directory only");
+			LogWarning(update_domain,
+				   "archive returned directory only");
 			return;
 		}
 
@@ -69,8 +72,8 @@ update_archive_tree(Directory *directory, const char *name)
 				db_unlock();
 
 				modified = true;
-				g_message("added %s/%s",
-					  directory->GetPath(), name);
+				FormatInfo(update_domain, "added %s/%s",
+					   directory->GetPath(), name);
 			}
 		}
 	}
@@ -105,14 +108,15 @@ update_archive_file2(Directory *parent, const char *name,
 	Error error;
 	ArchiveFile *file = archive_file_open(plugin, path_fs.c_str(), error);
 	if (file == NULL) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 
-	g_debug("archive %s opened", path_fs.c_str());
+	FormatDebug(update_domain, "archive %s opened", path_fs.c_str());
 
 	if (directory == NULL) {
-		g_debug("creating archive directory: %s", name);
+		FormatDebug(update_domain,
+			    "creating archive directory: %s", name);
 		db_lock();
 		directory = parent->CreateChild(name);
 		/* mark this directory as archive (we use device for
@@ -131,7 +135,8 @@ update_archive_file2(Directory *parent, const char *name,
 			:directory(_directory) {}
 
 		virtual void VisitArchiveEntry(const char *path_utf8) override {
-			g_debug("adding archive file: %s", path_utf8);
+			FormatDebug(update_domain,
+				    "adding archive file: %s", path_utf8);
 			update_archive_tree(directory, path_utf8);
 		}
 	};
diff --git a/src/UpdateContainer.cxx b/src/UpdateContainer.cxx
index 572f8cc67..082e34ffe 100644
--- a/src/UpdateContainer.cxx
+++ b/src/UpdateContainer.cxx
@@ -21,6 +21,7 @@
 #include "UpdateContainer.hxx"
 #include "UpdateInternal.hxx"
 #include "UpdateDatabase.hxx"
+#include "UpdateDomain.hxx"
 #include "DatabaseLock.hxx"
 #include "Directory.hxx"
 #include "Song.hxx"
@@ -29,6 +30,7 @@
 #include "fs/Path.hxx"
 #include "tag/TagHandler.hxx"
 #include "tag/TagBuilder.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -110,7 +112,8 @@ update_container_file(Directory *directory,
 
 		modified = true;
 
-		g_message("added %s/%s", directory->GetPath(), vtrack);
+		FormatInfo(update_domain, "added %s/%s",
+			   directory->GetPath(), vtrack);
 		g_free(vtrack);
 	}
 
diff --git a/src/UpdateDomain.cxx b/src/UpdateDomain.cxx
new file mode 100644
index 000000000..a2bbd5b70
--- /dev/null
+++ b/src/UpdateDomain.cxx
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "UpdateDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain update_domain("update");
diff --git a/src/UpdateDomain.hxx b/src/UpdateDomain.hxx
new file mode 100644
index 000000000..e7528a57e
--- /dev/null
+++ b/src/UpdateDomain.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_UPDATE_DOMAIN_HXX
+#define MPD_UPDATE_DOMAIN_HXX
+
+extern const class Domain update_domain;
+
+#endif
diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx
index 972842bb1..a4a47d468 100644
--- a/src/UpdateGlue.cxx
+++ b/src/UpdateGlue.cxx
@@ -22,11 +22,13 @@
 #include "UpdateQueue.hxx"
 #include "UpdateWalk.hxx"
 #include "UpdateRemove.hxx"
+#include "UpdateDomain.hxx"
 #include "Mapper.hxx"
 #include "DatabaseSimple.hxx"
 #include "Idle.hxx"
 #include "GlobalEvents.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 extern "C" {
 #include "stats.h"
@@ -40,9 +42,6 @@ extern "C" {
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
-
 static enum update_progress {
 	UPDATE_PROGRESS_IDLE = 0,
 	UPDATE_PROGRESS_RUNNING = 1,
@@ -71,23 +70,22 @@ static void * update_task(void *_path)
 	const char *path = (const char *)_path;
 
 	if (path != NULL && *path != 0)
-		g_debug("starting: %s", path);
+		FormatDebug(update_domain, "starting: %s", path);
 	else
-		g_debug("starting");
+		LogDebug(update_domain, "starting");
 
 	modified = update_walk(path, discard);
 
 	if (modified || !db_exists()) {
 		Error error;
 		if (!db_save(error))
-			g_warning("Failed to save database: %s",
-				  error.GetMessage());
+			LogError(error, "Failed to save database");
 	}
 
 	if (path != NULL && *path != 0)
-		g_debug("finished: %s", path);
+		FormatDebug(update_domain, "finished: %s", path);
 	else
-		g_debug("finished");
+		LogDebug(update_domain, "finished");
 	g_free(_path);
 
 	progress = UPDATE_PROGRESS_DONE;
@@ -104,7 +102,7 @@ spawn_update_task(const char *path)
 	modified = false;
 
 #if GLIB_CHECK_VERSION(2,32,0)
-	update_thr = g_thread_new("updadte", update_task, g_strdup(path));
+	update_thr = g_thread_new("update", update_task, g_strdup(path));
 #else
 	GError *e = NULL;
 	update_thr = g_thread_create(update_task, g_strdup(path), TRUE, &e);
@@ -114,7 +112,8 @@ spawn_update_task(const char *path)
 
 	if (++update_task_id > update_task_id_max)
 		update_task_id = 1;
-	g_debug("spawned thread for update job id %i", update_task_id);
+	FormatDebug(update_domain,
+		    "spawned thread for update job id %i", update_task_id);
 }
 
 unsigned
diff --git a/src/UpdateIO.cxx b/src/UpdateIO.cxx
index 7e262555c..ba4fcb7cf 100644
--- a/src/UpdateIO.cxx
+++ b/src/UpdateIO.cxx
@@ -19,12 +19,12 @@
 
 #include "config.h" /* must be first for large file support */
 #include "UpdateIO.hxx"
+#include "src/UpdateDomain.hxx"
 #include "Directory.hxx"
 #include "Mapper.hxx"
 #include "fs/Path.hxx"
 #include "fs/FileSystem.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <errno.h>
 #include <unistd.h>
@@ -39,8 +39,8 @@ stat_directory(const Directory *directory, struct stat *st)
 	if (!StatFile(path_fs, *st)) {
 		int error = errno;
 		const std::string path_utf8 = path_fs.ToUTF8();
-		g_warning("Failed to stat %s: %s",
-			  path_utf8.c_str(), g_strerror(error));
+		FormatErrno(update_domain, error,
+			    "Failed to stat %s", path_utf8.c_str());
 		return -1;
 	}
 
@@ -58,8 +58,8 @@ stat_directory_child(const Directory *parent, const char *name,
 	if (!StatFile(path_fs, *st)) {
 		int error = errno;
 		const std::string path_utf8 = path_fs.ToUTF8();
-		g_warning("Failed to stat %s: %s",
-			  path_utf8.c_str(), g_strerror(error));
+		FormatErrno(update_domain, error,
+			    "Failed to stat %s", path_utf8.c_str());
 		return -1;
 	}
 
diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx
index f9f6994b0..d43ad92c7 100644
--- a/src/UpdateRemove.cxx
+++ b/src/UpdateRemove.cxx
@@ -19,14 +19,15 @@
 
 #include "config.h" /* must be first for large file support */
 #include "UpdateRemove.hxx"
+#include "UpdateDomain.hxx"
 #include "Playlist.hxx"
 #include "GlobalEvents.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
-
 #include "Song.hxx"
 #include "Main.hxx"
 #include "Instance.hxx"
+#include "Log.hxx"
 
 #ifdef ENABLE_SQLITE
 #include "StickerDatabase.hxx"
@@ -54,7 +55,7 @@ song_remove_event(void)
 	assert(removed_song != NULL);
 
 	uri = removed_song->GetURI();
-	g_message("removing %s", uri);
+	FormatInfo(update_domain, "removing %s", uri);
 	g_free(uri);
 
 #ifdef ENABLE_SQLITE
diff --git a/src/UpdateSong.cxx b/src/UpdateSong.cxx
index b1f8cac4d..3aef2b560 100644
--- a/src/UpdateSong.cxx
+++ b/src/UpdateSong.cxx
@@ -23,13 +23,13 @@
 #include "UpdateIO.hxx"
 #include "UpdateDatabase.hxx"
 #include "UpdateContainer.hxx"
+#include "UpdateDomain.hxx"
 #include "DatabaseLock.hxx"
 #include "Directory.hxx"
 #include "Song.hxx"
 #include "DecoderPlugin.hxx"
 #include "DecoderList.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <unistd.h>
 
@@ -43,8 +43,9 @@ update_song_file2(Directory *directory,
 	db_unlock();
 
 	if (!directory_child_access(directory, name, R_OK)) {
-		g_warning("no read permissions on %s/%s",
-			  directory->GetPath(), name);
+		FormatError(update_domain,
+			    "no read permissions on %s/%s",
+			    directory->GetPath(), name);
 		if (song != NULL) {
 			db_lock();
 			delete_song(directory, song);
@@ -67,11 +68,13 @@ update_song_file2(Directory *directory,
 	}
 
 	if (song == NULL) {
-		g_debug("reading %s/%s", directory->GetPath(), name);
+		FormatDebug(update_domain, "reading %s/%s",
+			    directory->GetPath(), name);
 		song = Song::LoadFile(name, directory);
 		if (song == NULL) {
-			g_debug("ignoring unrecognized file %s/%s",
-				directory->GetPath(), name);
+			FormatDebug(update_domain,
+				    "ignoring unrecognized file %s/%s",
+				    directory->GetPath(), name);
 			return;
 		}
 
@@ -80,14 +83,15 @@ update_song_file2(Directory *directory,
 		db_unlock();
 
 		modified = true;
-		g_message("added %s/%s",
-			  directory->GetPath(), name);
+		FormatInfo(update_domain, "added %s/%s",
+			   directory->GetPath(), name);
 	} else if (st->st_mtime != song->mtime || walk_discard) {
-		g_message("updating %s/%s",
-			  directory->GetPath(), name);
+		FormatInfo(update_domain, "updating %s/%s",
+			   directory->GetPath(), name);
 		if (!song->UpdateFile()) {
-			g_debug("deleting unrecognized file %s/%s",
-				directory->GetPath(), name);
+			FormatDebug(update_domain,
+				    "deleting unrecognized file %s/%s",
+				    directory->GetPath(), name);
 			db_lock();
 			delete_song(directory, song);
 			db_unlock();
diff --git a/src/UpdateWalk.cxx b/src/UpdateWalk.cxx
index bd104c564..cfa68dcf4 100644
--- a/src/UpdateWalk.cxx
+++ b/src/UpdateWalk.cxx
@@ -23,6 +23,7 @@
 #include "UpdateDatabase.hxx"
 #include "UpdateSong.hxx"
 #include "UpdateArchive.hxx"
+#include "UpdateDomain.hxx"
 #include "DatabaseLock.hxx"
 #include "DatabaseSimple.hxx"
 #include "Directory.hxx"
@@ -37,6 +38,7 @@
 #include "fs/FileSystem.hxx"
 #include "fs/DirectoryReader.hxx"
 #include "util/UriUtil.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -49,9 +51,6 @@
 #include <stdlib.h>
 #include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "update"
-
 bool walk_discard;
 bool modified;
 
@@ -185,7 +184,7 @@ find_inode_ancestor(Directory *parent, ino_t inode, dev_t device)
 			return -1;
 
 		if (parent->inode == inode && parent->device == device) {
-			g_debug("recursive directory found");
+			LogDebug(update_domain, "recursive directory found");
 			return 1;
 		}
 
@@ -257,7 +256,8 @@ update_directory_child(Directory *directory,
 			db_unlock();
 		}
 	} else {
-		g_debug("update: %s is not a directory, archive or music", name);
+		FormatDebug(update_domain,
+			    "%s is not a directory, archive or music", name);
 	}
 }
 
@@ -352,8 +352,9 @@ update_directory(Directory *directory, const struct stat *st)
 	if (reader.HasFailed()) {
 		int error = errno;
 		const auto path_utf8 = path_fs.ToUTF8();
-		g_warning("Failed to open directory %s: %s",
-			  path_utf8.c_str(), g_strerror(error));
+		FormatErrno(update_domain, error,
+			    "Failed to open directory %s",
+			    path_utf8.c_str());
 		return false;
 	}
 
diff --git a/src/Volume.cxx b/src/Volume.cxx
index 116f4aa18..b23b9688f 100644
--- a/src/Volume.cxx
+++ b/src/Volume.cxx
@@ -22,17 +22,18 @@
 #include "MixerAll.hxx"
 #include "Idle.hxx"
 #include "GlobalEvents.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <stdlib.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "volume"
-
 #define SW_VOLUME_STATE                         "sw_volume: "
 
+static constexpr Domain volume_domain("volume");
+
 static unsigned volume_software_set = 100;
 
 /** the cached hardware mixer value; invalid if negative */
@@ -122,7 +123,8 @@ read_sw_volume_state(const char *line)
 	if (*end == 0 && sv >= 0 && sv <= 100)
 		software_volume_change(sv);
 	else
-		g_warning("Can't parse software volume: %s\n", line);
+		FormatWarning(volume_domain,
+			      "Can't parse software volume: %s", line);
 	return true;
 }
 
diff --git a/src/ZeroconfAvahi.cxx b/src/ZeroconfAvahi.cxx
index 8d26c75f6..9714b13b1 100644
--- a/src/ZeroconfAvahi.cxx
+++ b/src/ZeroconfAvahi.cxx
@@ -23,6 +23,8 @@
 #include "Listen.hxx"
 #include "event/Loop.hxx"
 #include "system/FatalError.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -36,8 +38,7 @@
 
 #include <avahi-glib/glib-watch.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "avahi"
+static constexpr Domain avahi_domain("avahi");
 
 static char *avahiName;
 static int avahiRunning;
@@ -58,13 +59,15 @@ static void avahiGroupCallback(AvahiEntryGroup * g,
 	char *n;
 	assert(g);
 
-	g_debug("Service group changed to state %d", state);
+	FormatDebug(avahi_domain,
+		    "Service group changed to state %d", state);
 
 	switch (state) {
 	case AVAHI_ENTRY_GROUP_ESTABLISHED:
 		/* The entry group has been established successfully */
-		g_message("Service '%s' successfully established.",
-			  avahiName);
+		FormatInfo(avahi_domain,
+			   "Service '%s' successfully established.",
+			   avahiName);
 		break;
 
 	case AVAHI_ENTRY_GROUP_COLLISION:
@@ -73,43 +76,48 @@ static void avahiGroupCallback(AvahiEntryGroup * g,
 		avahi_free(avahiName);
 		avahiName = n;
 
-		g_message("Service name collision, renaming service to '%s'",
-			  avahiName);
+		FormatInfo(avahi_domain,
+			   "Service name collision, renaming service to '%s'",
+			   avahiName);
 
 		/* And recreate the services */
 		avahiRegisterService(avahi_entry_group_get_client(g));
 		break;
 
 	case AVAHI_ENTRY_GROUP_FAILURE:
-		g_warning("Entry group failure: %s",
-			  avahi_strerror(avahi_client_errno
-					 (avahi_entry_group_get_client(g))));
+		FormatError(avahi_domain,
+			    "Entry group failure: %s",
+			    avahi_strerror(avahi_client_errno
+					   (avahi_entry_group_get_client(g))));
 		/* Some kind of failure happened while we were registering our services */
 		avahiRunning = 0;
 		break;
 
 	case AVAHI_ENTRY_GROUP_UNCOMMITED:
-		g_debug("Service group is UNCOMMITED");
+		LogDebug(avahi_domain, "Service group is UNCOMMITED");
 		break;
 	case AVAHI_ENTRY_GROUP_REGISTERING:
-		g_debug("Service group is REGISTERING");
+		LogDebug(avahi_domain, "Service group is REGISTERING");
 	}
 }
 
 /* Registers a new service with avahi */
 static void avahiRegisterService(AvahiClient * c)
 {
+	FormatDebug(avahi_domain, "Registering service %s/%s",
+		    SERVICE_TYPE, avahiName);
+
 	int ret;
 	assert(c);
-	g_debug("Registering service %s/%s", SERVICE_TYPE, avahiName);
 
 	/* If this is the first time we're called,
 	 * let's create a new entry group */
 	if (!avahiGroup) {
 		avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL);
 		if (!avahiGroup) {
-			g_warning("Failed to create avahi EntryGroup: %s",
-				  avahi_strerror(avahi_client_errno(c)));
+			FormatError(avahi_domain,
+				    "Failed to create avahi EntryGroup: %s",
+				    avahi_strerror(avahi_client_errno(c)));
 			goto fail;
 		}
 	}
@@ -124,16 +132,16 @@ static void avahiRegisterService(AvahiClient * c)
 					    avahiName, SERVICE_TYPE, NULL,
 					    NULL, listen_port, NULL);
 	if (ret < 0) {
-		g_warning("Failed to add service %s: %s", SERVICE_TYPE,
-			  avahi_strerror(ret));
+		FormatError(avahi_domain, "Failed to add service %s: %s",
+			    SERVICE_TYPE, avahi_strerror(ret));
 		goto fail;
 	}
 
 	/* Tell the server to register the service group */
 	ret = avahi_entry_group_commit(avahiGroup);
 	if (ret < 0) {
-		g_warning("Failed to commit service group: %s",
-			  avahi_strerror(ret));
+		FormatError(avahi_domain, "Failed to commit service group: %s",
+			    avahi_strerror(ret));
 		goto fail;
 	}
 	return;
@@ -150,11 +158,11 @@ static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
 	assert(c);
 
 	/* Called whenever the client or server state changes */
-	g_debug("Client changed to state %d", state);
+	FormatDebug(avahi_domain, "Client changed to state %d", state);
 
 	switch (state) {
 	case AVAHI_CLIENT_S_RUNNING:
-		g_debug("Client is RUNNING");
+		LogDebug(avahi_domain, "Client is RUNNING");
 
 		/* The server has startup successfully and registered its host
 		 * name on the network, so it's time to create our services */
@@ -165,7 +173,8 @@ static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
 	case AVAHI_CLIENT_FAILURE:
 		reason = avahi_client_errno(c);
 		if (reason == AVAHI_ERR_DISCONNECTED) {
-			g_message("Client Disconnected, will reconnect shortly");
+			LogInfo(avahi_domain,
+				"Client Disconnected, will reconnect shortly");
 			if (avahiGroup) {
 				avahi_entry_group_free(avahiGroup);
 				avahiGroup = NULL;
@@ -178,51 +187,57 @@ static void avahiClientCallback(AvahiClient * c, AvahiClientState state,
 					     avahiClientCallback, NULL,
 					     &reason);
 			if (!avahiClient) {
-				g_warning("Could not reconnect: %s",
-					  avahi_strerror(reason));
+				FormatWarning(avahi_domain,
+					      "Could not reconnect: %s",
+					      avahi_strerror(reason));
 				avahiRunning = 0;
 			}
 		} else {
-			g_warning("Client failure: %s (terminal)",
-				  avahi_strerror(reason));
+			FormatWarning(avahi_domain,
+				      "Client failure: %s (terminal)",
+				      avahi_strerror(reason));
 			avahiRunning = 0;
 		}
 		break;
 
 	case AVAHI_CLIENT_S_COLLISION:
-		g_debug("Client is COLLISION");
+		LogDebug(avahi_domain, "Client is COLLISION");
+
 		/* Let's drop our registered services. When the server is back
 		 * in AVAHI_SERVER_RUNNING state we will register them
 		 * again with the new host name. */
 		if (avahiGroup) {
-			g_debug("Resetting group");
+			LogDebug(avahi_domain, "Resetting group");
 			avahi_entry_group_reset(avahiGroup);
 		}
 
+		break;
+
 	case AVAHI_CLIENT_S_REGISTERING:
-		g_debug("Client is REGISTERING");
+		LogDebug(avahi_domain, "Client is REGISTERING");
+
 		/* The server records are now being established. This
 		 * might be caused by a host name change. We need to wait
 		 * for our own records to register until the host name is
 		 * properly esatblished. */
 
 		if (avahiGroup) {
-			g_debug("Resetting group");
+			LogDebug(avahi_domain, "Resetting group");
 			avahi_entry_group_reset(avahiGroup);
 		}
 
 		break;
 
 	case AVAHI_CLIENT_CONNECTING:
-		g_debug("Client is CONNECTING");
+		LogDebug(avahi_domain, "Client is CONNECTING");
+		break;
 	}
 }
 
 void
 AvahiInit(EventLoop &loop, const char *serviceName)
 {
-	int error;
-	g_debug("Initializing interface");
+	LogDebug(avahi_domain, "Initializing interface");
 
 	if (!avahi_is_valid_service_name(serviceName))
 		FormatFatalError("Invalid zeroconf_name \"%s\"", serviceName);
@@ -241,12 +256,13 @@ AvahiInit(EventLoop &loop, const char *serviceName)
 	avahi_poll = avahi_glib_poll_get(avahi_glib_poll);
 #endif
 
+	int error;
 	avahiClient = avahi_client_new(avahi_poll, AVAHI_CLIENT_NO_FAIL,
 				       avahiClientCallback, NULL, &error);
 
 	if (!avahiClient) {
-		g_warning("Failed to create client: %s",
-			  avahi_strerror(error));
+		FormatError(avahi_domain, "Failed to create client: %s",
+			    avahi_strerror(error));
 		AvahiDeinit();
 	}
 }
@@ -254,7 +270,7 @@ AvahiInit(EventLoop &loop, const char *serviceName)
 void
 AvahiDeinit(void)
 {
-	g_debug("Shutting down interface");
+	LogDebug(avahi_domain, "Shutting down interface");
 
 	if (avahiGroup) {
 		avahi_entry_group_free(avahiGroup);
diff --git a/src/ZeroconfBonjour.cxx b/src/ZeroconfBonjour.cxx
index b77154e1b..215391b76 100644
--- a/src/ZeroconfBonjour.cxx
+++ b/src/ZeroconfBonjour.cxx
@@ -22,14 +22,15 @@
 #include "ZeroconfInternal.hxx"
 #include "Listen.hxx"
 #include "event/SocketMonitor.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 #include "gcc.h"
 
 #include <glib.h>
 
 #include <dns_sd.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "bonjour"
+static constexpr Domain bonjour_domain("bonjour");
 
 class BonjourMonitor final : public SocketMonitor {
 	DNSServiceRef service_ref;
@@ -64,11 +65,14 @@ dnsRegisterCallback(gcc_unused DNSServiceRef sdRef,
 		    gcc_unused void *context)
 {
 	if (errorCode != kDNSServiceErr_NoError) {
-		g_warning("Failed to register zeroconf service.");
+		LogError(bonjour_domain,
+			 "Failed to register zeroconf service");
 
 		bonjour_monitor->Cancel();
 	} else {
-		g_debug("Registered zeroconf service with name '%s'", name);
+		FormatDebug(bonjour_domain,
+			    "Registered zeroconf service with name '%s'",
+			    name);
 	}
 }
 
@@ -85,7 +89,8 @@ BonjourInit(EventLoop &loop, const char *service_name)
 						       NULL);
 
 	if (error != kDNSServiceErr_NoError) {
-		g_warning("Failed to register zeroconf service.");
+		LogError(bonjour_domain,
+			 "Failed to register zeroconf service");
 
 		if (dnsReference) {
 			DNSServiceRefDeallocate(dnsReference);
diff --git a/src/ZeroconfGlue.cxx b/src/ZeroconfGlue.cxx
index 6e50d07ef..44c257872 100644
--- a/src/ZeroconfGlue.cxx
+++ b/src/ZeroconfGlue.cxx
@@ -24,9 +24,11 @@
 #include "ConfigGlobal.hxx"
 #include "ConfigOption.hxx"
 #include "Listen.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 #include "gcc.h"
 
-#include <glib.h>
+static constexpr Domain zeroconf_domain("zeroconf");
 
 /* The default service name to publish
  * (overridden by 'zeroconf_name' config parameter)
@@ -48,7 +50,8 @@ ZeroconfInit(gcc_unused EventLoop &loop)
 		return;
 
 	if (listen_port <= 0) {
-		g_warning("No global port, disabling zeroconf");
+		LogInfo(zeroconf_domain,
+			"No global port, disabling zeroconf");
 		zeroconfEnabled = false;
 		return;
 	}
diff --git a/src/db/SimpleDatabasePlugin.cxx b/src/db/SimpleDatabasePlugin.cxx
index 2e30086fc..2cb820827 100644
--- a/src/db/SimpleDatabasePlugin.cxx
+++ b/src/db/SimpleDatabasePlugin.cxx
@@ -31,6 +31,7 @@
 #include "fs/FileSystem.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -169,7 +170,7 @@ SimpleDatabase::Open(Error &error)
 	if (!Load(error)) {
 		root->Free();
 
-		g_warning("Failed to load database: %s", error.GetMessage());
+		LogError(error);
 		error.Clear();
 
 		if (!Check(error))
@@ -284,15 +285,15 @@ SimpleDatabase::Save(Error &error)
 {
 	db_lock();
 
-	g_debug("removing empty directories from DB");
+	LogDebug(simple_db_domain, "removing empty directories from DB");
 	root->PruneEmpty();
 
-	g_debug("sorting DB");
+	LogDebug(simple_db_domain, "sorting DB");
 	root->Sort();
 
 	db_unlock();
 
-	g_debug("writing DB");
+	LogDebug(simple_db_domain, "writing DB");
 
 	FILE *fp = FOpen(path, FOpenMode::WriteText);
 	if (!fp) {
diff --git a/src/decoder/AdPlugDecoderPlugin.cxx b/src/decoder/AdPlugDecoderPlugin.cxx
index 5c04e116d..3ec48cb23 100644
--- a/src/decoder/AdPlugDecoderPlugin.cxx
+++ b/src/decoder/AdPlugDecoderPlugin.cxx
@@ -23,6 +23,7 @@
 #include "DecoderAPI.hxx"
 #include "CheckAudioFormat.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <adplug/adplug.h>
 #include <adplug/emuopl.h>
@@ -31,9 +32,6 @@
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "adplug"
-
 static unsigned sample_rate;
 
 static bool
@@ -43,7 +41,7 @@ adplug_init(const config_param &param)
 
 	sample_rate = param.GetBlockValue("sample_rate", 48000u);
 	if (!audio_check_sample_rate(sample_rate, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return false;
 	}
 
diff --git a/src/decoder/AudiofileDecoderPlugin.cxx b/src/decoder/AudiofileDecoderPlugin.cxx
index 1ee57de4a..c7b1b1016 100644
--- a/src/decoder/AudiofileDecoderPlugin.cxx
+++ b/src/decoder/AudiofileDecoderPlugin.cxx
@@ -24,19 +24,19 @@
 #include "CheckAudioFormat.hxx"
 #include "tag/TagHandler.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <audiofile.h>
 #include <af_vfs.h>
-#include <assert.h>
-#include <glib.h>
-#include <stdio.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "audiofile"
+#include <assert.h>
 
 /* pick 1020 since its devisible for 8,16,24, and 32-bit audio */
 #define CHUNK_SIZE		1020
 
+static constexpr Domain audiofile_domain("audiofile");
+
 static int audiofile_get_duration(const char *file)
 {
 	int total_time;
@@ -59,7 +59,7 @@ audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
 	Error error;
 	size_t nbytes = is->LockRead(data, length, error);
 	if (nbytes == 0 && error.IsDefined()) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return -1;
 	}
 
@@ -143,8 +143,9 @@ audiofile_setup_sample_format(AFfilehandle af_fp)
 
 	afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
 	if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
-		g_debug("input file has %d bit samples, converting to 16",
-			bits);
+		FormatDebug(audiofile_domain,
+			    "input file has %d bit samples, converting to 16",
+			    bits);
 		bits = 16;
 	}
 
@@ -168,7 +169,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
 	char chunk[CHUNK_SIZE];
 
 	if (!is->IsSeekable()) {
-		g_warning("not seekable");
+		LogWarning(audiofile_domain, "not seekable");
 		return;
 	}
 
@@ -176,7 +177,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
 
 	af_fp = afOpenVirtualFile(vf, "r", nullptr);
 	if (af_fp == AF_NULL_FILEHANDLE) {
-		g_warning("failed to input stream\n");
+		LogWarning(audiofile_domain, "failed to input stream");
 		return;
 	}
 
@@ -186,7 +187,7 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
 				       audiofile_setup_sample_format(af_fp),
 				       afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
 				       error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		afCloseFile(af_fp);
 		return;
 	}
@@ -232,8 +233,9 @@ audiofile_scan_file(const char *file,
 	int total_time = audiofile_get_duration(file);
 
 	if (total_time < 0) {
-		g_debug("Failed to get total song time from: %s\n",
-			file);
+		FormatWarning(audiofile_domain,
+			      "Failed to get total song time from: %s",
+			      file);
 		return false;
 	}
 
diff --git a/src/decoder/DsdiffDecoderPlugin.cxx b/src/decoder/DsdiffDecoderPlugin.cxx
index 80b88a2c2..43002768a 100644
--- a/src/decoder/DsdiffDecoderPlugin.cxx
+++ b/src/decoder/DsdiffDecoderPlugin.cxx
@@ -35,13 +35,11 @@
 #include "util/Error.hxx"
 #include "tag/TagHandler.hxx"
 #include "DsdLib.hxx"
+#include "Log.hxx"
 
 #include <unistd.h>
 #include <stdio.h> /* for SEEK_SET, SEEK_CUR */
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "dsdiff"
-
 struct DsdiffHeader {
 	struct dsdlib_id id;
 	uint32_t size_high, size_low;
@@ -437,7 +435,7 @@ dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
 	if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
 				       SampleFormat::DSD,
 				       metadata.channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 
diff --git a/src/decoder/DsfDecoderPlugin.cxx b/src/decoder/DsfDecoderPlugin.cxx
index b327fc9dc..4c4a66aaa 100644
--- a/src/decoder/DsfDecoderPlugin.cxx
+++ b/src/decoder/DsfDecoderPlugin.cxx
@@ -36,13 +36,11 @@
 #include "util/Error.hxx"
 #include "DsdLib.hxx"
 #include "tag/TagHandler.hxx"
+#include "Log.hxx"
 
 #include <unistd.h>
 #include <stdio.h> /* for SEEK_SET, SEEK_CUR */
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "dsf"
-
 struct DsfMetaData {
 	unsigned sample_rate, channels;
 	bool bitreverse;
@@ -290,7 +288,7 @@ dsf_stream_decode(struct decoder *decoder, struct input_stream *is)
 	if (!audio_format_init_checked(audio_format, metadata.sample_rate / 8,
 				       SampleFormat::DSD,
 				       metadata.channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 	/* Calculate song time from DSD chunk size and sample frequency */
diff --git a/src/decoder/FaadDecoderPlugin.cxx b/src/decoder/FaadDecoderPlugin.cxx
index f026a6216..de846c61a 100644
--- a/src/decoder/FaadDecoderPlugin.cxx
+++ b/src/decoder/FaadDecoderPlugin.cxx
@@ -26,18 +26,14 @@
 #include "tag/TagHandler.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <neaacdec.h>
 
-#include <glib.h>
-
 #include <assert.h>
 #include <string.h>
 #include <unistd.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "faad"
-
 #define AAC_MAX_CHANNELS	6
 
 static const unsigned adts_sample_rates[] =
@@ -395,7 +391,7 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
 	Error error;
 	ret = faad_decoder_init(decoder, buffer, audio_format, error);
 	if (!ret) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		NeAACDecClose(decoder);
 		return;
 	}
@@ -424,21 +420,24 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
 		decoded = faad_decoder_decode(decoder, buffer, &frame_info);
 
 		if (frame_info.error > 0) {
-			g_warning("error decoding AAC stream: %s\n",
-				  NeAACDecGetErrorMessage(frame_info.error));
+			FormatWarning(faad_decoder_domain,
+				      "error decoding AAC stream: %s",
+				      NeAACDecGetErrorMessage(frame_info.error));
 			break;
 		}
 
 		if (frame_info.channels != audio_format.channels) {
-			g_warning("channel count changed from %u to %u",
-				  audio_format.channels, frame_info.channels);
+			FormatInfo(faad_decoder_domain,
+				   "channel count changed from %u to %u",
+				   audio_format.channels, frame_info.channels);
 			break;
 		}
 
 		if (frame_info.samplerate != audio_format.sample_rate) {
-			g_warning("sample rate changed from %u to %lu",
-				  audio_format.sample_rate,
-				  (unsigned long)frame_info.samplerate);
+			FormatInfo(faad_decoder_domain,
+				   "sample rate changed from %u to %lu",
+				   audio_format.sample_rate,
+				   (unsigned long)frame_info.samplerate);
 			break;
 		}
 
diff --git a/src/decoder/FfmpegDecoderPlugin.cxx b/src/decoder/FfmpegDecoderPlugin.cxx
index a725e1f7d..646b8f974 100644
--- a/src/decoder/FfmpegDecoderPlugin.cxx
+++ b/src/decoder/FfmpegDecoderPlugin.cxx
@@ -28,6 +28,8 @@
 #include "InputStream.hxx"
 #include "CheckAudioFormat.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "LogV.hxx"
 
 #include <glib.h>
 
@@ -50,27 +52,26 @@ extern "C" {
 #include <libavutil/dict.h>
 }
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ffmpeg"
+static constexpr Domain ffmpeg_domain("ffmpeg");
 
 /* suppress the ffmpeg compatibility macro */
 #ifdef SampleFormat
 #undef SampleFormat
 #endif
 
-static GLogLevelFlags
-level_ffmpeg_to_glib(int level)
+static LogLevel
+import_ffmpeg_level(int level)
 {
 	if (level <= AV_LOG_FATAL)
-		return G_LOG_LEVEL_CRITICAL;
+		return LogLevel::ERROR;
 
-	if (level <= AV_LOG_ERROR)
-		return G_LOG_LEVEL_WARNING;
+	if (level <= AV_LOG_WARNING)
+		return LogLevel::WARNING;
 
 	if (level <= AV_LOG_INFO)
-		return G_LOG_LEVEL_MESSAGE;
+		return LogLevel::INFO;
 
-	return G_LOG_LEVEL_DEBUG;
+	return LogLevel::DEBUG;
 }
 
 static void
@@ -83,8 +84,9 @@ mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level,
 		cls = *(const AVClass *const*)ptr;
 
 	if (cls != NULL) {
-		char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL);
-		g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl);
+		char *domain = g_strconcat(ffmpeg_domain.GetName(), "/", cls->item_name(ptr), NULL);
+		const Domain d(domain);
+		LogFormatV(d, import_ffmpeg_level(level), fmt, vl);
 		g_free(domain);
 	}
 }
@@ -287,7 +289,8 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
 
 		if (len < 0) {
 			/* if error, we skip the frame */
-			g_message("decoding failed, frame skipped\n");
+			LogInfo(ffmpeg_domain,
+				"decoding failed, frame skipped");
 			break;
 		}
 
@@ -328,11 +331,13 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt)
 	const char *name = av_get_sample_fmt_string(buffer, sizeof(buffer),
 						    sample_fmt);
 	if (name != NULL)
-		g_warning("Unsupported libavcodec SampleFormat value: %s (%d)",
-			  name, sample_fmt);
+		FormatError(ffmpeg_domain,
+			    "Unsupported libavcodec SampleFormat value: %s (%d)",
+			    name, sample_fmt);
 	else
-		g_warning("Unsupported libavcodec SampleFormat value: %d",
-			  sample_fmt);
+		FormatError(ffmpeg_domain,
+			    "Unsupported libavcodec SampleFormat value: %d",
+			    sample_fmt);
 	return SampleFormat::UNDEFINED;
 }
 
@@ -377,12 +382,12 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 	if (input_format == NULL)
 		return;
 
-	g_debug("detected input format '%s' (%s)",
-		input_format->name, input_format->long_name);
+	FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)",
+		    input_format->name, input_format->long_name);
 
 	AvioStream stream(decoder, input);
 	if (!stream.Open()) {
-		g_warning("Failed to open stream");
+		LogError(ffmpeg_domain, "Failed to open stream");
 		return;
 	}
 
@@ -391,21 +396,21 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 	if (mpd_ffmpeg_open_input(&format_context, stream.io,
 				  input->uri.c_str(),
 				  input_format) != 0) {
-		g_warning("Open failed\n");
+		LogError(ffmpeg_domain, "Open failed");
 		return;
 	}
 
 	const int find_result =
 		avformat_find_stream_info(format_context, NULL);
 	if (find_result < 0) {
-		g_warning("Couldn't find stream info\n");
+		LogError(ffmpeg_domain, "Couldn't find stream info");
 		avformat_close_input(&format_context);
 		return;
 	}
 
 	int audio_stream = ffmpeg_find_audio_stream(format_context);
 	if (audio_stream == -1) {
-		g_warning("No audio stream inside\n");
+		LogError(ffmpeg_domain, "No audio stream inside");
 		avformat_close_input(&format_context);
 		return;
 	}
@@ -414,12 +419,13 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 
 	AVCodecContext *codec_context = av_stream->codec;
 	if (codec_context->codec_name[0] != 0)
-		g_debug("codec '%s'", codec_context->codec_name);
+		FormatDebug(ffmpeg_domain, "codec '%s'",
+			    codec_context->codec_name);
 
 	AVCodec *codec = avcodec_find_decoder(codec_context->codec_id);
 
 	if (!codec) {
-		g_warning("Unsupported audio codec\n");
+		LogError(ffmpeg_domain, "Unsupported audio codec");
 		avformat_close_input(&format_context);
 		return;
 	}
@@ -435,7 +441,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 				       codec_context->sample_rate,
 				       sample_format,
 				       codec_context->channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		avformat_close_input(&format_context);
 		return;
 	}
@@ -447,7 +453,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 
 	const int open_result = avcodec_open2(codec_context, codec, NULL);
 	if (open_result < 0) {
-		g_warning("Could not open codec\n");
+		LogError(ffmpeg_domain, "Could not open codec");
 		avformat_close_input(&format_context);
 		return;
 	}
@@ -461,7 +467,7 @@ ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
 
 	AVFrame *frame = avcodec_alloc_frame();
 	if (!frame) {
-		g_warning("Could not allocate frame\n");
+		LogError(ffmpeg_domain, "Could not allocate frame");
 		avformat_close_input(&format_context);
 		return;
 	}
diff --git a/src/decoder/FfmpegMetaData.cxx b/src/decoder/FfmpegMetaData.cxx
index f4b7386ef..9965e4d28 100644
--- a/src/decoder/FfmpegMetaData.cxx
+++ b/src/decoder/FfmpegMetaData.cxx
@@ -25,9 +25,6 @@
 #include "tag/TagTable.hxx"
 #include "tag/TagHandler.hxx"
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ffmpeg"
-
 static const struct tag_table ffmpeg_tags[] = {
 	{ "year", TAG_DATE },
 	{ "author-sort", TAG_ARTIST_SORT },
diff --git a/src/decoder/FlacCommon.cxx b/src/decoder/FlacCommon.cxx
index 9f5d81f85..6bafeb9c2 100644
--- a/src/decoder/FlacCommon.cxx
+++ b/src/decoder/FlacCommon.cxx
@@ -27,8 +27,8 @@
 #include "FlacPcm.hxx"
 #include "CheckAudioFormat.hxx"
 #include "util/Error.hxx"
-
-#include <glib.h>
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 
@@ -74,7 +74,7 @@ flac_got_stream_info(struct flac_data *data,
 				       stream_info->sample_rate,
 				       flac_sample_format(stream_info->bits_per_sample),
 				       stream_info->channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		data->unsupported = true;
 		return;
 	}
@@ -136,7 +136,7 @@ flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
 				       header->sample_rate,
 				       flac_sample_format(header->bits_per_sample),
 				       header->channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		data->unsupported = true;
 		return false;
 	}
diff --git a/src/decoder/FlacCommon.hxx b/src/decoder/FlacCommon.hxx
index f9fade6fc..726e9de92 100644
--- a/src/decoder/FlacCommon.hxx
+++ b/src/decoder/FlacCommon.hxx
@@ -31,9 +31,6 @@
 #include <FLAC/stream_decoder.h>
 #include <FLAC/metadata.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "flac"
-
 struct flac_data : public FlacInput {
 	PcmBuffer buffer;
 
diff --git a/src/decoder/FlacDecoderPlugin.cxx b/src/decoder/FlacDecoderPlugin.cxx
index a6b10fbe2..7ce44febd 100644
--- a/src/decoder/FlacDecoderPlugin.cxx
+++ b/src/decoder/FlacDecoderPlugin.cxx
@@ -19,10 +19,12 @@
 
 #include "config.h" /* must be first for large file support */
 #include "FlacDecoderPlugin.h"
+#include "FlacDomain.hxx"
 #include "FlacCommon.hxx"
 #include "FlacMetadata.hxx"
 #include "OggCodec.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -54,7 +56,7 @@ static void flacPrintErroredState(FLAC__StreamDecoderState state)
 		break;
 	}
 
-	g_warning("%s\n", FLAC__StreamDecoderStateString[state]);
+	LogError(flac_domain, FLAC__StreamDecoderStateString[state]);
 }
 
 static void flacMetadata(gcc_unused const FLAC__StreamDecoder * dec,
@@ -90,8 +92,9 @@ flac_scan_file(const char *file,
 {
 	FlacMetadataChain chain;
 	if (!chain.Read(file)) {
-		g_debug("Failed to read FLAC tags: %s",
-			chain.GetStatusString());
+		FormatDebug(flac_domain,
+			    "Failed to read FLAC tags: %s",
+			    chain.GetStatusString());
 		return false;
 	}
 
@@ -105,8 +108,9 @@ flac_scan_stream(struct input_stream *is,
 {
 	FlacMetadataChain chain;
 	if (!chain.Read(is)) {
-		g_debug("Failed to read FLAC tags: %s",
-			chain.GetStatusString());
+		FormatDebug(flac_domain,
+			    "Failed to read FLAC tags: %s",
+			    chain.GetStatusString());
 		return false;
 	}
 
@@ -122,12 +126,14 @@ flac_decoder_new(void)
 {
 	FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
 	if (sd == nullptr) {
-		g_warning("FLAC__stream_decoder_new() failed");
+		LogError(flac_domain,
+			 "FLAC__stream_decoder_new() failed");
 		return nullptr;
 	}
 
 	if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
-		g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
+		LogDebug(flac_domain,
+			 "FLAC__stream_decoder_set_metadata_respond() has failed");
 
 	return sd;
 }
@@ -139,7 +145,7 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
 	data->total_frames = duration;
 
 	if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
-		g_warning("problem reading metadata");
+		LogWarning(flac_domain, "problem reading metadata");
 		return false;
 	}
 
@@ -265,7 +271,8 @@ flac_decode_internal(struct decoder * decoder,
 		stream_init(flac_dec, &data, is_ogg);
 	if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
 		FLAC__stream_decoder_delete(flac_dec);
-		g_warning("%s", FLAC__StreamDecoderInitStatusString[status]);
+		LogWarning(flac_domain,
+			   FLAC__StreamDecoderInitStatusString[status]);
 		return;
 	}
 
@@ -299,8 +306,9 @@ oggflac_scan_file(const char *file,
 {
 	FlacMetadataChain chain;
 	if (!chain.ReadOgg(file)) {
-		g_debug("Failed to read OggFLAC tags: %s",
-			chain.GetStatusString());
+		FormatDebug(flac_domain,
+			    "Failed to read OggFLAC tags: %s",
+			    chain.GetStatusString());
 		return false;
 	}
 
@@ -314,8 +322,9 @@ oggflac_scan_stream(struct input_stream *is,
 {
 	FlacMetadataChain chain;
 	if (!chain.ReadOgg(is)) {
-		g_debug("Failed to read OggFLAC tags: %s",
-			chain.GetStatusString());
+		FormatDebug(flac_domain,
+			    "Failed to read OggFLAC tags: %s",
+			    chain.GetStatusString());
 		return false;
 	}
 
diff --git a/src/decoder/FlacDomain.cxx b/src/decoder/FlacDomain.cxx
new file mode 100644
index 000000000..2bc91079e
--- /dev/null
+++ b/src/decoder/FlacDomain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "config.h"
+#include "FlacDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain flac_domain("flac");
diff --git a/src/decoder/FlacDomain.hxx b/src/decoder/FlacDomain.hxx
new file mode 100644
index 000000000..8d5b825ed
--- /dev/null
+++ b/src/decoder/FlacDomain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_FLAC_DOMAIN_HXX
+#define MPD_FLAC_DOMAIN_HXX
+
+#include "check.h"
+
+extern const class Domain flac_domain;
+
+#endif
diff --git a/src/decoder/FlacInput.cxx b/src/decoder/FlacInput.cxx
index 19abfca81..88b942971 100644
--- a/src/decoder/FlacInput.cxx
+++ b/src/decoder/FlacInput.cxx
@@ -19,9 +19,11 @@
 
 #include "config.h"
 #include "FlacInput.hxx"
+#include "FlacDomain.hxx"
 #include "DecoderAPI.hxx"
 #include "InputStream.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 #include "gcc.h"
 
 FLAC__StreamDecoderReadStatus
@@ -49,8 +51,10 @@ FlacInput::Seek(FLAC__uint64 absolute_byte_offset)
 		return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
 
 	::Error error;
-	if (!input_stream->LockSeek(absolute_byte_offset, SEEK_SET, error))
+	if (!input_stream->LockSeek(absolute_byte_offset, SEEK_SET, error)) {
+		LogError(error);
 		return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
+	}
 
 	return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
 }
@@ -89,7 +93,8 @@ FlacInput::Error(FLAC__StreamDecoderErrorStatus status)
 {
 	if (decoder == nullptr ||
 	    decoder_get_command(decoder) != DecoderCommand::STOP)
-		g_warning("%s", FLAC__StreamDecoderErrorStatusString[status]);
+		LogWarning(flac_domain,
+			   FLAC__StreamDecoderErrorStatusString[status]);
 }
 
 FLAC__StreamDecoderReadStatus
diff --git a/src/decoder/FluidsynthDecoderPlugin.cxx b/src/decoder/FluidsynthDecoderPlugin.cxx
index 4db4f1618..99f1598c8 100644
--- a/src/decoder/FluidsynthDecoderPlugin.cxx
+++ b/src/decoder/FluidsynthDecoderPlugin.cxx
@@ -22,13 +22,14 @@
 #include "DecoderAPI.hxx"
 #include "CheckAudioFormat.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <fluidsynth.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "fluidsynth"
+static constexpr Domain fluidsynth_domain("fluidsynth");
 
 static unsigned sample_rate;
 static const char *soundfont_path;
@@ -36,27 +37,27 @@ static const char *soundfont_path;
 /**
  * Convert a fluidsynth log level to a GLib log level.
  */
-static GLogLevelFlags
-fluidsynth_level_to_glib(enum fluid_log_level level)
+static LogLevel
+fluidsynth_level_to_mpd(enum fluid_log_level level)
 {
 	switch (level) {
 	case FLUID_PANIC:
 	case FLUID_ERR:
-		return G_LOG_LEVEL_CRITICAL;
+		return LogLevel::ERROR;
 
 	case FLUID_WARN:
-		return G_LOG_LEVEL_WARNING;
+		return LogLevel::WARNING;
 
 	case FLUID_INFO:
-		return G_LOG_LEVEL_INFO;
+		return LogLevel::INFO;
 
 	case FLUID_DBG:
 	case LAST_LOG_LEVEL:
-		return G_LOG_LEVEL_DEBUG;
+		return LogLevel::DEBUG;
 	}
 
 	/* invalid fluidsynth log level */
-	return G_LOG_LEVEL_MESSAGE;
+	return LogLevel::INFO;
 }
 
 /**
@@ -66,8 +67,9 @@ fluidsynth_level_to_glib(enum fluid_log_level level)
 static void
 fluidsynth_mpd_log_function(int level, char *message, gcc_unused void *data)
 {
-	g_log(G_LOG_DOMAIN, fluidsynth_level_to_glib(fluid_log_level(level)),
-	      "%s", message);
+	Log(fluidsynth_domain,
+	    fluidsynth_level_to_mpd(fluid_log_level(level)),
+	    message);
 }
 
 static bool
@@ -77,7 +79,7 @@ fluidsynth_init(const config_param &param)
 
 	sample_rate = param.GetBlockValue("sample_rate", 48000u);
 	if (!audio_check_sample_rate(sample_rate, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return false;
 	}
 
@@ -125,7 +127,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
 
 	ret = fluid_synth_sfload(synth, soundfont_path, true);
 	if (ret < 0) {
-		g_warning("fluid_synth_sfload() failed");
+		LogWarning(fluidsynth_domain, "fluid_synth_sfload() failed");
 		delete_fluid_synth(synth);
 		delete_fluid_settings(settings);
 		return;
@@ -142,7 +144,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
 
 	ret = fluid_player_add(player, path_fs);
 	if (ret != 0) {
-		g_warning("fluid_player_add() failed");
+		LogWarning(fluidsynth_domain, "fluid_player_add() failed");
 		delete_fluid_player(player);
 		delete_fluid_synth(synth);
 		delete_fluid_settings(settings);
@@ -153,7 +155,7 @@ fluidsynth_file_decode(struct decoder *decoder, const char *path_fs)
 
 	ret = fluid_player_play(player);
 	if (ret != 0) {
-		g_warning("fluid_player_play() failed");
+		LogWarning(fluidsynth_domain, "fluid_player_play() failed");
 		delete_fluid_player(player);
 		delete_fluid_synth(synth);
 		delete_fluid_settings(settings);
diff --git a/src/decoder/GmeDecoderPlugin.cxx b/src/decoder/GmeDecoderPlugin.cxx
index dbe1d000f..bbcc9618a 100644
--- a/src/decoder/GmeDecoderPlugin.cxx
+++ b/src/decoder/GmeDecoderPlugin.cxx
@@ -24,6 +24,8 @@
 #include "tag/TagHandler.hxx"
 #include "util/UriUtil.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 #include <assert.h>
@@ -32,11 +34,10 @@
 
 #include <gme/gme.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "gme"
-
 #define SUBTUNE_PREFIX "tune_"
 
+static constexpr Domain gme_domain("gme");
+
 static constexpr unsigned GME_SAMPLE_RATE = 44100;
 static constexpr unsigned GME_CHANNELS = 2;
 static constexpr unsigned GME_BUFFER_FRAMES = 2048;
@@ -106,7 +107,7 @@ gme_container_scan(const char *path_fs, const unsigned int tnum)
 	Music_Emu *emu;
 	const char *gme_err = gme_open_file(path_fs, &emu, GME_SAMPLE_RATE);
 	if (gme_err != nullptr) {
-		g_warning("%s", gme_err);
+		LogWarning(gme_domain, gme_err);
 		return nullptr;
 	}
 
@@ -134,7 +135,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 		gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
 	g_free(path_container);
 	if (gme_err != nullptr) {
-		g_warning("%s", gme_err);
+		LogWarning(gme_domain, gme_err);
 		return;
 	}
 
@@ -142,7 +143,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 	const int song_num = get_song_num(path_fs);
 	gme_err = gme_track_info(emu, &ti, song_num);
 	if (gme_err != nullptr) {
-		g_warning("%s", gme_err);
+		LogWarning(gme_domain, gme_err);
 		gme_delete(emu);
 		return;
 	}
@@ -158,7 +159,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 	if (!audio_format_init_checked(audio_format, GME_SAMPLE_RATE,
 				       SampleFormat::S16, GME_CHANNELS,
 				       error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		gme_free_info(ti);
 		gme_delete(emu);
 		return;
@@ -168,7 +169,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 
 	gme_err = gme_start_track(emu, song_num);
 	if (gme_err != nullptr)
-		g_warning("%s", gme_err);
+		LogWarning(gme_domain, gme_err);
 
 	if (ti->length > 0)
 		gme_set_fade(emu, ti->length);
@@ -179,7 +180,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 		short buf[GME_BUFFER_SAMPLES];
 		gme_err = gme_play(emu, GME_BUFFER_SAMPLES, buf);
 		if (gme_err != nullptr) {
-			g_warning("%s", gme_err);
+			LogWarning(gme_domain, gme_err);
 			return;
 		}
 
@@ -188,7 +189,7 @@ gme_file_decode(struct decoder *decoder, const char *path_fs)
 			float where = decoder_seek_where(decoder);
 			gme_err = gme_seek(emu, int(where * 1000));
 			if (gme_err != nullptr)
-				g_warning("%s", gme_err);
+				LogWarning(gme_domain, gme_err);
 			decoder_command_finished(decoder);
 		}
 
@@ -211,7 +212,7 @@ gme_scan_file(const char *path_fs,
 		gme_open_file(path_container, &emu, GME_SAMPLE_RATE);
 	g_free(path_container);
 	if (gme_err != nullptr) {
-		g_warning("%s", gme_err);
+		LogWarning(gme_domain, gme_err);
 		return false;
 	}
 
@@ -220,7 +221,7 @@ gme_scan_file(const char *path_fs,
 	gme_info_t *ti;
 	gme_err = gme_track_info(emu, &ti, song_num);
 	if (gme_err != nullptr) {
-		g_warning("%s", gme_err);
+		LogWarning(gme_domain, gme_err);
 		gme_delete(emu);
 		return false;
 	}
diff --git a/src/decoder/MadDecoderPlugin.cxx b/src/decoder/MadDecoderPlugin.cxx
index b7d90892b..1cce24f31 100644
--- a/src/decoder/MadDecoderPlugin.cxx
+++ b/src/decoder/MadDecoderPlugin.cxx
@@ -27,6 +27,8 @@
 #include "tag/TagHandler.hxx"
 #include "CheckAudioFormat.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 #include <unistd.h>
@@ -40,9 +42,6 @@
 #include <id3tag.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mad"
-
 #define FRAMES_CUSHION    2000
 
 #define READ_BUFFER_SIZE  40960
@@ -65,6 +64,8 @@ enum muteframe {
 
 #define DEFAULT_GAPLESS_MP3_PLAYBACK true
 
+static constexpr Domain mad_domain("mad");
+
 static bool gapless_playback;
 
 static inline int32_t
@@ -367,7 +368,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag)
 		}
 
 		if (count != tagsize) {
-			g_debug("error parsing ID3 tag");
+			LogDebug(mad_domain, "error parsing ID3 tag");
 			g_free(allocated);
 			return;
 		}
@@ -482,9 +483,9 @@ MadDecoder::DecodeNextFrameHeader(Tag **tag)
 			if (stream.error == MAD_ERROR_BUFLEN)
 				return DECODE_CONT;
 			else {
-				g_warning("unrecoverable frame level error "
-					  "(%s).\n",
-					  mad_stream_errorstr(&stream));
+				FormatWarning(mad_domain,
+					      "unrecoverable frame level error: %s",
+					      mad_stream_errorstr(&stream));
 				return DECODE_BREAK;
 			}
 		}
@@ -529,9 +530,9 @@ MadDecoder::DecodeNextFrame()
 			if (stream.error == MAD_ERROR_BUFLEN)
 				return DECODE_CONT;
 			else {
-				g_warning("unrecoverable frame level error "
-					  "(%s).\n",
-					  mad_stream_errorstr(&stream));
+				FormatWarning(mad_domain,
+					      "unrecoverable frame level error: %s",
+					      mad_stream_errorstr(&stream));
 				return DECODE_BREAK;
 			}
 		}
@@ -702,8 +703,8 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
 	           &lame->version.major, &lame->version.minor) != 2)
 		return false;
 
-	g_debug("detected LAME version %i.%i (\"%s\")\n",
-		lame->version.major, lame->version.minor, lame->encoder);
+	FormatDebug(mad_domain, "detected LAME version %i.%i (\"%s\")",
+		    lame->version.major, lame->version.minor, lame->encoder);
 
 	/* The reference volume was changed from the 83dB used in the
 	 * ReplayGain spec to 89dB in lame 3.95.1.  Bump the gain for older
@@ -719,7 +720,7 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
 	mad_bit_read(ptr, 16);
 
 	lame->peak = mad_f_todouble(mad_bit_read(ptr, 32) << 5); /* peak */
-	g_debug("LAME peak found: %f\n", lame->peak);
+	FormatDebug(mad_domain, "LAME peak found: %f", lame->peak);
 
 	lame->track_gain = 0;
 	name = mad_bit_read(ptr, 3); /* gain name */
@@ -728,7 +729,8 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
 	gain = mad_bit_read(ptr, 9); /* gain*10 */
 	if (gain && name == 1 && orig != 0) {
 		lame->track_gain = ((sign ? -gain : gain) / 10.0) + adj;
-		g_debug("LAME track gain found: %f\n", lame->track_gain);
+		FormatDebug(mad_domain, "LAME track gain found: %f",
+			    lame->track_gain);
 	}
 
 	/* tmz reports that this isn't currently written by any version of lame
@@ -743,7 +745,8 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
 	gain = mad_bit_read(ptr, 9); /* gain*10 */
 	if (gain && name == 2 && orig != 0) {
 		lame->album_gain = ((sign ? -gain : gain) / 10.0) + adj;
-		g_debug("LAME album gain found: %f\n", lame->track_gain);
+		FormatDebug(mad_domain, "LAME album gain found: %f",
+			    lame->track_gain);
 	}
 #else
 	mad_bit_read(ptr, 16);
@@ -754,8 +757,8 @@ parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen)
 	lame->encoder_delay = mad_bit_read(ptr, 12);
 	lame->encoder_padding = mad_bit_read(ptr, 12);
 
-	g_debug("encoder delay is %i, encoder padding is %i\n",
-	      lame->encoder_delay, lame->encoder_padding);
+	FormatDebug(mad_domain, "encoder delay is %i, encoder padding is %i",
+		    lame->encoder_delay, lame->encoder_padding);
 
 	mad_bit_read(ptr, 80);
 
@@ -880,8 +883,9 @@ MadDecoder::DecodeFirstFrame(Tag **tag)
 		return false;
 
 	if (max_frames > 8 * 1024 * 1024) {
-		g_warning("mp3 file header indicates too many frames: %lu\n",
-			  max_frames);
+		FormatWarning(mad_domain,
+			      "mp3 file header indicates too many frames: %lu",
+			      max_frames);
 		return false;
 	}
 
@@ -1120,8 +1124,8 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
 		delete tag;
 
 		if (decoder_get_command(decoder) == DecoderCommand::NONE)
-			g_warning
-			    ("Input does not appear to be a mp3 bit stream.\n");
+			LogError(mad_domain,
+				 "Input does not appear to be a mp3 bit stream");
 		return;
 	}
 
@@ -1132,7 +1136,7 @@ mp3_decode(struct decoder *decoder, struct input_stream *input_stream)
 				       SampleFormat::S24_P32,
 				       MAD_NCHANNELS(&data.frame.header),
 				       error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		delete tag;
 		return;
 	}
diff --git a/src/decoder/MikmodDecoderPlugin.cxx b/src/decoder/MikmodDecoderPlugin.cxx
index 78a26891a..fb82eb732 100644
--- a/src/decoder/MikmodDecoderPlugin.cxx
+++ b/src/decoder/MikmodDecoderPlugin.cxx
@@ -22,13 +22,14 @@
 #include "DecoderAPI.hxx"
 #include "tag/TagHandler.hxx"
 #include "system/FatalError.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 #include <mikmod.h>
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mikmod"
+static constexpr Domain mikmod_domain("mikmod");
 
 /* this is largely copied from alsaplayer */
 
@@ -127,8 +128,9 @@ mikmod_decoder_init(const config_param &param)
 		   DMODE_16BITS);
 
 	if (MikMod_Init(params)) {
-		g_warning("Could not init MikMod: %s\n",
-			  MikMod_strerror(MikMod_errno));
+		FormatError(mikmod_domain,
+			    "Could not init MikMod: %s",
+			    MikMod_strerror(MikMod_errno));
 		return false;
 	}
 
@@ -154,7 +156,8 @@ mikmod_decoder_file_decode(struct decoder *decoder, const char *path_fs)
 	g_free(path2);
 
 	if (handle == nullptr) {
-		g_warning("failed to open mod: %s", path_fs);
+		FormatError(mikmod_domain,
+			    "failed to open mod: %s", path_fs);
 		return;
 	}
 
@@ -187,7 +190,8 @@ mikmod_decoder_scan_file(const char *path_fs,
 
 	if (handle == nullptr) {
 		g_free(path2);
-		g_debug("Failed to open file: %s", path_fs);
+		FormatDebug(mikmod_domain,
+			    "Failed to open file: %s", path_fs);
 		return false;
 
 	}
diff --git a/src/decoder/ModplugDecoderPlugin.cxx b/src/decoder/ModplugDecoderPlugin.cxx
index 9cbf44b15..39c366492 100644
--- a/src/decoder/ModplugDecoderPlugin.cxx
+++ b/src/decoder/ModplugDecoderPlugin.cxx
@@ -22,6 +22,8 @@
 #include "DecoderAPI.hxx"
 #include "InputStream.hxx"
 #include "tag/TagHandler.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <libmodplug/modplug.h>
 
@@ -29,8 +31,7 @@
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "modplug"
+static constexpr Domain modplug_domain("modplug");
 
 static constexpr size_t MODPLUG_FRAME_SIZE = 4096;
 static constexpr size_t MODPLUG_PREALLOC_BLOCK = 256 * 1024;
@@ -43,12 +44,12 @@ mod_loadfile(struct decoder *decoder, struct input_stream *is)
 	const goffset size = is->GetSize();
 
 	if (size == 0) {
-		g_warning("file is empty");
+		LogWarning(modplug_domain, "file is empty");
 		return nullptr;
 	}
 
 	if (size > MODPLUG_FILE_LIMIT) {
-		g_warning("file too large");
+		LogWarning(modplug_domain, "file too large");
 		return nullptr;
 	}
 
@@ -77,7 +78,7 @@ mod_loadfile(struct decoder *decoder, struct input_stream *is)
 		}
 
 		if (goffset(bdatas->len + ret) > MODPLUG_FILE_LIMIT) {
-			g_warning("stream too large\n");
+			LogWarning(modplug_domain, "stream too large");
 			g_free(data);
 			g_byte_array_free(bdatas, TRUE);
 			return nullptr;
@@ -103,7 +104,7 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
 	bdatas = mod_loadfile(decoder, is);
 
 	if (!bdatas) {
-		g_warning("could not load stream\n");
+		LogWarning(modplug_domain, "could not load stream");
 		return;
 	}
 
@@ -119,7 +120,7 @@ mod_decode(struct decoder *decoder, struct input_stream *is)
 	f = ModPlug_Load(bdatas->data, bdatas->len);
 	g_byte_array_free(bdatas, TRUE);
 	if (!f) {
-		g_warning("could not decode stream\n");
+		LogWarning(modplug_domain, "could not decode stream");
 		return;
 	}
 
diff --git a/src/decoder/MpcdecDecoderPlugin.cxx b/src/decoder/MpcdecDecoderPlugin.cxx
index 252fe92e6..c0785accc 100644
--- a/src/decoder/MpcdecDecoderPlugin.cxx
+++ b/src/decoder/MpcdecDecoderPlugin.cxx
@@ -24,6 +24,8 @@
 #include "CheckAudioFormat.hxx"
 #include "tag/TagHandler.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <mpc/mpcdec.h>
 
@@ -32,14 +34,13 @@
 #include <unistd.h>
 #include <math.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mpcdec"
-
 struct mpc_decoder_data {
 	struct input_stream *is;
 	struct decoder *decoder;
 };
 
+static constexpr Domain mpcdec_domain("mpcdec");
+
 static mpc_int32_t
 mpc_read_cb(mpc_reader *reader, void *ptr, mpc_int32_t size)
 {
@@ -148,7 +149,8 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
 	mpc_demux *demux = mpc_demux_init(&reader);
 	if (demux == nullptr) {
 		if (decoder_get_command(mpd_decoder) != DecoderCommand::STOP)
-			g_warning("Not a valid musepack stream");
+			LogWarning(mpcdec_domain,
+				   "Not a valid musepack stream");
 		return;
 	}
 
@@ -160,7 +162,7 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
 	if (!audio_format_init_checked(audio_format, info.sample_freq,
 				       SampleFormat::S24_P32,
 				       info.channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		mpc_demux_exit(demux);
 		return;
 	}
@@ -199,7 +201,8 @@ mpcdec_decode(struct decoder *mpd_decoder, struct input_stream *is)
 		frame.buffer = (MPC_SAMPLE_FORMAT *)sample_buffer;
 		mpc_status status = mpc_demux_decode(demux, &frame);
 		if (status != MPC_STATUS_OK) {
-			g_warning("Failed to decode sample");
+			LogWarning(mpcdec_domain,
+				   "Failed to decode sample");
 			break;
 		}
 
diff --git a/src/decoder/Mpg123DecoderPlugin.cxx b/src/decoder/Mpg123DecoderPlugin.cxx
index 3100a0f1c..928af39e6 100644
--- a/src/decoder/Mpg123DecoderPlugin.cxx
+++ b/src/decoder/Mpg123DecoderPlugin.cxx
@@ -23,14 +23,15 @@
 #include "CheckAudioFormat.hxx"
 #include "tag/TagHandler.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <mpg123.h>
 #include <stdio.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "mpg123"
+static constexpr Domain mpg123_domain("mpg123");
 
 static bool
 mpd_mpg123_init(gcc_unused const config_param &param)
@@ -70,8 +71,9 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
 	error = mpg123_open(handle, path_dup);
 	g_free(path_dup);
 	if (error != MPG123_OK) {
-		g_warning("libmpg123 failed to open %s: %s",
-			  path_fs, mpg123_plain_strerror(error));
+		FormatWarning(mpg123_domain,
+			      "libmpg123 failed to open %s: %s",
+			      path_fs, mpg123_plain_strerror(error));
 		return false;
 	}
 
@@ -79,21 +81,24 @@ mpd_mpg123_open(mpg123_handle *handle, const char *path_fs,
 
 	error = mpg123_getformat(handle, &rate, &channels, &encoding);
 	if (error != MPG123_OK) {
-		g_warning("mpg123_getformat() failed: %s",
-			  mpg123_plain_strerror(error));
+		FormatWarning(mpg123_domain,
+			      "mpg123_getformat() failed: %s",
+			      mpg123_plain_strerror(error));
 		return false;
 	}
 
 	if (encoding != MPG123_ENC_SIGNED_16) {
 		/* other formats not yet implemented */
-		g_warning("expected MPG123_ENC_SIGNED_16, got %d", encoding);
+		FormatWarning(mpg123_domain,
+			      "expected MPG123_ENC_SIGNED_16, got %d",
+			      encoding);
 		return false;
 	}
 
 	Error error2;
 	if (!audio_format_init_checked(audio_format, rate, SampleFormat::S16,
 				       channels, error2)) {
-		g_warning("%s", error2.GetMessage());
+		LogError(error2);
 		return false;
 	}
 
@@ -112,8 +117,9 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
 
 	handle = mpg123_new(nullptr, &error);
 	if (handle == nullptr) {
-		g_warning("mpg123_new() failed: %s",
-			  mpg123_plain_strerror(error));
+		FormatError(mpg123_domain,
+			    "mpg123_new() failed: %s",
+			    mpg123_plain_strerror(error));
 		return;
 	}
 
@@ -158,8 +164,9 @@ mpd_mpg123_file_decode(struct decoder *decoder, const char *path_fs)
 		error = mpg123_read(handle, buffer, sizeof(buffer), &nbytes);
 		if (error != MPG123_OK) {
 			if (error != MPG123_DONE)
-				g_warning("mpg123_read() failed: %s",
-					  mpg123_plain_strerror(error));
+				FormatWarning(mpg123_domain,
+					      "mpg123_read() failed: %s",
+					      mpg123_plain_strerror(error));
 			break;
 		}
 
@@ -204,8 +211,9 @@ mpd_mpg123_scan_file(const char *path_fs,
 
 	handle = mpg123_new(nullptr, &error);
 	if (handle == nullptr) {
-		g_warning("mpg123_new() failed: %s",
-			  mpg123_plain_strerror(error));
+		FormatError(mpg123_domain,
+			    "mpg123_new() failed: %s",
+			    mpg123_plain_strerror(error));
 		return false;
 	}
 
diff --git a/src/decoder/OpusDecoderPlugin.cxx b/src/decoder/OpusDecoderPlugin.cxx
index ea1d6660c..96c52a083 100644
--- a/src/decoder/OpusDecoderPlugin.cxx
+++ b/src/decoder/OpusDecoderPlugin.cxx
@@ -19,6 +19,7 @@
 
 #include "config.h" /* must be first for large file support */
 #include "OpusDecoderPlugin.h"
+#include "OpusDomain.hxx"
 #include "OpusHead.hxx"
 #include "OpusTags.hxx"
 #include "OggUtil.hxx"
@@ -31,6 +32,7 @@
 #include "tag/TagBuilder.hxx"
 #include "InputStream.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <opus.h>
 #include <ogg/ogg.h>
@@ -40,9 +42,6 @@
 #include <stdio.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "opus"
-
 static const opus_int32 opus_sample_rate = 48000;
 
 gcc_pure
@@ -62,7 +61,7 @@ IsOpusTags(const ogg_packet &packet)
 static bool
 mpd_opus_init(gcc_unused const config_param &param)
 {
-	g_debug("%s", opus_get_version_string());
+	LogDebug(opus_domain, opus_get_version_string());
 
 	return true;
 }
@@ -199,8 +198,8 @@ MPDOpusDecoder::HandleBOS(const ogg_packet &packet)
 	opus_decoder = opus_decoder_create(opus_sample_rate, channels,
 					   &opus_error);
 	if (opus_decoder == nullptr) {
-		g_warning("libopus error: %s",
-			  opus_strerror(opus_error));
+		FormatError(opus_domain, "libopus error: %s",
+			    opus_strerror(opus_error));
 		return DecoderCommand::STOP;
 	}
 
@@ -249,7 +248,7 @@ MPDOpusDecoder::HandleAudio(const ogg_packet &packet)
 				  output_buffer, output_size,
 				  0);
 	if (nframes < 0) {
-		g_warning("%s", opus_strerror(nframes));
+		LogError(opus_domain, opus_strerror(nframes));
 		return DecoderCommand::STOP;
 	}
 
diff --git a/src/decoder/OpusDomain.cxx b/src/decoder/OpusDomain.cxx
new file mode 100644
index 000000000..2b8bb1bba
--- /dev/null
+++ b/src/decoder/OpusDomain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "config.h"
+#include "OpusDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain opus_domain("opus");
diff --git a/src/decoder/OpusDomain.hxx b/src/decoder/OpusDomain.hxx
new file mode 100644
index 000000000..488eca27d
--- /dev/null
+++ b/src/decoder/OpusDomain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_OPUS_DOMAIN_HXX
+#define MPD_OPUS_DOMAIN_HXX
+
+#include "check.h"
+
+extern const class Domain opus_domain;
+
+#endif
diff --git a/src/decoder/PcmDecoderPlugin.cxx b/src/decoder/PcmDecoderPlugin.cxx
index 94867f01d..6996b583a 100644
--- a/src/decoder/PcmDecoderPlugin.cxx
+++ b/src/decoder/PcmDecoderPlugin.cxx
@@ -22,6 +22,7 @@
 #include "DecoderAPI.hxx"
 #include "InputStream.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 extern "C" {
 #include "util/byte_reverse.h"
@@ -32,9 +33,6 @@ extern "C" {
 #include <string.h>
 #include <stdio.h> /* for SEEK_SET */
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
 static void
 pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
 {
@@ -86,7 +84,7 @@ pcm_stream_decode(struct decoder *decoder, struct input_stream *is)
 			if (is->LockSeek(offset, SEEK_SET, error)) {
 				decoder_command_finished(decoder);
 			} else {
-				g_warning("seeking failed: %s", error.GetMessage());
+				LogError(error);
 				decoder_seek_error(decoder);
 			}
 
diff --git a/src/decoder/SndfileDecoderPlugin.cxx b/src/decoder/SndfileDecoderPlugin.cxx
index 56853958c..5c7efe230 100644
--- a/src/decoder/SndfileDecoderPlugin.cxx
+++ b/src/decoder/SndfileDecoderPlugin.cxx
@@ -24,11 +24,12 @@
 #include "CheckAudioFormat.hxx"
 #include "tag/TagHandler.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <sndfile.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "sndfile"
+static constexpr Domain sndfile_domain("sndfile");
 
 static sf_count_t
 sndfile_vio_get_filelen(void *user_data)
@@ -57,7 +58,7 @@ sndfile_vio_read(void *ptr, sf_count_t count, void *user_data)
 	Error error;
 	size_t nbytes = is->LockRead(ptr, count, error);
 	if (nbytes == 0 && error.IsDefined()) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return -1;
 	}
 
@@ -124,7 +125,7 @@ sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
 
 	sf = sf_open_virtual(&vio, SFM_READ, &info, is);
 	if (sf == nullptr) {
-		g_warning("sf_open_virtual() failed");
+		LogWarning(sndfile_domain, "sf_open_virtual() failed");
 		return;
 	}
 
@@ -136,7 +137,7 @@ sndfile_stream_decode(struct decoder *decoder, struct input_stream *is)
 	if (!audio_format_init_checked(audio_format, info.samplerate,
 				       SampleFormat::S32,
 				       info.channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 
@@ -187,7 +188,8 @@ sndfile_scan_file(const char *path_fs,
 
 	if (!audio_valid_sample_rate(info.samplerate)) {
 		sf_close(sf);
-		g_warning("Invalid sample rate in %s\n", path_fs);
+		FormatWarning(sndfile_domain,
+			      "Invalid sample rate in %s", path_fs);
 		return false;
 	}
 
diff --git a/src/decoder/VorbisDecoderPlugin.cxx b/src/decoder/VorbisDecoderPlugin.cxx
index a4a938aa8..55ce943e8 100644
--- a/src/decoder/VorbisDecoderPlugin.cxx
+++ b/src/decoder/VorbisDecoderPlugin.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "VorbisDecoderPlugin.h"
 #include "VorbisComments.hxx"
+#include "VorbisDomain.hxx"
 #include "DecoderAPI.hxx"
 #include "InputStream.hxx"
 #include "OggCodec.hxx"
@@ -27,6 +28,7 @@
 #include "util/UriUtil.hxx"
 #include "CheckAudioFormat.hxx"
 #include "tag/TagHandler.hxx"
+#include "Log.hxx"
 
 #ifndef HAVE_TREMOR
 #define OV_EXCLUDE_STATIC_CALLBACKS
@@ -50,9 +52,6 @@
 #include <errno.h>
 #include <unistd.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "vorbis"
-
 #if G_BYTE_ORDER == G_BIG_ENDIAN
 #define VORBIS_BIG_ENDIAN true
 #else
@@ -144,8 +143,9 @@ vorbis_is_open(struct vorbis_input_stream *vis, OggVorbis_File *vf,
 	if (ret < 0) {
 		if (decoder == NULL ||
 		    decoder_get_command(decoder) == DecoderCommand::NONE)
-			g_warning("Failed to open Ogg Vorbis stream: %s",
-				  vorbis_strerror(ret));
+			FormatWarning(vorbis_domain,
+				      "Failed to open Ogg Vorbis stream: %s",
+				      vorbis_strerror(ret));
 		return false;
 	}
 
@@ -198,7 +198,7 @@ vorbis_stream_decode(struct decoder *decoder,
 
 	const vorbis_info *vi = ov_info(&vf, -1);
 	if (vi == NULL) {
-		g_warning("ov_info() has failed");
+		LogWarning(vorbis_domain, "ov_info() has failed");
 		return;
 	}
 
@@ -211,7 +211,7 @@ vorbis_stream_decode(struct decoder *decoder,
 				       SampleFormat::FLOAT,
 #endif
 				       vi->channels, error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 
@@ -272,7 +272,8 @@ vorbis_stream_decode(struct decoder *decoder,
 		if (current_section != prev_section) {
 			vi = ov_info(&vf, -1);
 			if (vi == NULL) {
-				g_warning("ov_info() has failed");
+				LogWarning(vorbis_domain,
+					   "ov_info() has failed");
 				break;
 			}
 
@@ -280,7 +281,8 @@ vorbis_stream_decode(struct decoder *decoder,
 			    vi->channels != (int)audio_format.channels) {
 				/* we don't support audio format
 				   change yet */
-				g_warning("audio format change, stopping here");
+				LogWarning(vorbis_domain,
+					   "audio format change, stopping here");
 				break;
 			}
 
diff --git a/src/decoder/VorbisDomain.cxx b/src/decoder/VorbisDomain.cxx
new file mode 100644
index 000000000..d7c70a641
--- /dev/null
+++ b/src/decoder/VorbisDomain.cxx
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#include "config.h"
+#include "VorbisDomain.hxx"
+#include "util/Domain.hxx"
+
+const Domain vorbis_domain("vorbis");
diff --git a/src/decoder/VorbisDomain.hxx b/src/decoder/VorbisDomain.hxx
new file mode 100644
index 000000000..69e2e11cb
--- /dev/null
+++ b/src/decoder/VorbisDomain.hxx
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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.
+ */
+
+#ifndef MPD_VORBIS_DOMAIN_HXX
+#define MPD_VORBIS_DOMAIN_HXX
+
+#include "check.h"
+
+extern const class Domain vorbis_domain;
+
+#endif
diff --git a/src/decoder/WavpackDecoderPlugin.cxx b/src/decoder/WavpackDecoderPlugin.cxx
index ecabafefe..8ee898e30 100644
--- a/src/decoder/WavpackDecoderPlugin.cxx
+++ b/src/decoder/WavpackDecoderPlugin.cxx
@@ -25,6 +25,8 @@
 #include "tag/TagHandler.hxx"
 #include "tag/ApeTag.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <wavpack/wavpack.h>
 #include <glib.h>
@@ -33,11 +35,10 @@
 #include <stdio.h>
 #include <stdlib.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "wavpack"
-
 #define ERRORLEN 80
 
+static constexpr Domain wavpack_domain("wavpack");
+
 /** A pointer type for format converter function. */
 typedef void (*format_samples_t)(
 	int bytes_per_sample,
@@ -155,7 +156,7 @@ wavpack_decode(struct decoder *decoder, WavpackContext *wpc, bool can_seek)
 				       WavpackGetSampleRate(wpc),
 				       sample_format,
 				       WavpackGetNumChannels(wpc), error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return;
 	}
 
@@ -294,10 +295,9 @@ wavpack_scan_file(const char *fname,
 
 	wpc = WavpackOpenFileInput(fname, error, OPEN_TAGS, 0);
 	if (wpc == NULL) {
-		g_warning(
-			"failed to open WavPack file \"%s\": %s\n",
-			fname, error
-		);
+		FormatError(wavpack_domain,
+			    "failed to open WavPack file \"%s\": %s",
+			    fname, error);
 		return false;
 	}
 
@@ -532,7 +532,8 @@ wavpack_streamdecode(struct decoder * decoder, struct input_stream *is)
 	);
 
 	if (wpc == NULL) {
-		g_warning("failed to open WavPack stream: %s\n", error);
+		FormatError(wavpack_domain,
+			    "failed to open WavPack stream: %s", error);
 		return;
 	}
 
@@ -558,10 +559,9 @@ wavpack_filedecode(struct decoder *decoder, const char *fname)
 		OPEN_TAGS | OPEN_WVC | OPEN_NORMALIZE, 23
 	);
 	if (wpc == NULL) {
-		g_warning(
-			"failed to open WavPack file \"%s\": %s\n",
-			fname, error
-		);
+		FormatWarning(wavpack_domain,
+			      "failed to open WavPack file \"%s\": %s",
+			      fname, error);
 		return;
 	}
 
diff --git a/src/decoder/WildmidiDecoderPlugin.cxx b/src/decoder/WildmidiDecoderPlugin.cxx
index 3a057ca2c..1a390706f 100644
--- a/src/decoder/WildmidiDecoderPlugin.cxx
+++ b/src/decoder/WildmidiDecoderPlugin.cxx
@@ -22,18 +22,17 @@
 #include "DecoderAPI.hxx"
 #include "tag/TagHandler.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
 #include "fs/Path.hxx"
 #include "fs/FileSystem.hxx"
 #include "system/FatalError.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 extern "C" {
 #include <wildmidi_lib.h>
 }
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "wildmidi"
+static constexpr Domain wildmidi_domain("wildmidi");
 
 static constexpr unsigned WILDMIDI_SAMPLE_RATE = 48000;
 
@@ -49,7 +48,9 @@ wildmidi_init(const config_param &param)
 
 	if (!FileExists(path)) {
 		const auto utf8 = path.ToUTF8();
-		g_debug("configuration file does not exist: %s", utf8.c_str());
+		FormatDebug(wildmidi_domain,
+			    "configuration file does not exist: %s",
+			    utf8.c_str());
 		return false;
 	}
 
diff --git a/src/decoder/sidplay_decoder_plugin.cxx b/src/decoder/sidplay_decoder_plugin.cxx
index fed0476ec..486dd816f 100644
--- a/src/decoder/sidplay_decoder_plugin.cxx
+++ b/src/decoder/sidplay_decoder_plugin.cxx
@@ -20,6 +20,8 @@
 #include "config.h"
 #include "../DecoderAPI.hxx"
 #include "tag/TagHandler.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <errno.h>
 #include <stdlib.h>
@@ -30,11 +32,10 @@
 #include <sidplay/builders/resid.h>
 #include <sidplay/utils/SidTuneMod.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "sidplay"
-
 #define SUBTUNE_PREFIX "tune_"
 
+static constexpr Domain sidplay_domain("sidplay");
+
 static GPatternSpec *path_with_subtune;
 static const char *songlength_file;
 static GKeyFile *songlength_database;
@@ -52,8 +53,9 @@ sidplay_load_songlength_db(const char *path)
 	gsize size;
 
 	if (!g_file_get_contents(path, &data, &size, &error)) {
-		g_warning("unable to read songlengths file %s: %s",
-			  path, error->message);
+		FormatError(sidplay_domain,
+			    "unable to read songlengths file %s: %s",
+			    path, error->message);
 		g_error_free(error);
 		return NULL;
 	}
@@ -68,8 +70,9 @@ sidplay_load_songlength_db(const char *path)
 						 G_KEY_FILE_NONE, &error);
 	g_free(data);
 	if (!success) {
-		g_warning("unable to parse songlengths file %s: %s",
-			  path, error->message);
+		FormatError(sidplay_domain,
+			    "unable to parse songlengths file %s: %s",
+			    path, error->message);
 		g_error_free(error);
 		g_key_file_free(db);
 		return NULL;
@@ -162,7 +165,8 @@ get_song_length(const char *path_fs)
 	SidTuneMod tune(sid_file);
 	g_free(sid_file);
 	if(!tune) {
-		g_warning("failed to load file for calculating md5 sum");
+		LogWarning(sidplay_domain,
+			   "failed to load file for calculating md5 sum");
 		return -1;
 	}
 	char md5sum[SIDTUNE_MD5_LENGTH+1];
@@ -205,7 +209,7 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 	SidTune tune(path_container, NULL, true);
 	g_free(path_container);
 	if (!tune) {
-		g_warning("failed to load file");
+		LogWarning(sidplay_domain, "failed to load file");
 		return;
 	}
 
@@ -220,7 +224,8 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 	sidplay2 player;
 	int iret = player.load(&tune);
 	if (iret != 0) {
-		g_warning("sidplay2.load() failed: %s", player.error());
+		FormatWarning(sidplay_domain,
+			      "sidplay2.load() failed: %s", player.error());
 		return;
 	}
 
@@ -228,19 +233,20 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 
 	ReSIDBuilder builder("ReSID");
 	if (!builder) {
-		g_warning("failed to initialize ReSIDBuilder");
+		LogWarning(sidplay_domain,
+			   "failed to initialize ReSIDBuilder");
 		return;
 	}
 
 	builder.create(player.info().maxsids);
 	if (!builder) {
-		g_warning("ReSIDBuilder.create() failed");
+		LogWarning(sidplay_domain, "ReSIDBuilder.create() failed");
 		return;
 	}
 
 	builder.filter(filter_setting);
 	if (!builder) {
-		g_warning("ReSIDBuilder.filter() failed");
+		LogWarning(sidplay_domain, "ReSIDBuilder.filter() failed");
 		return;
 	}
 
@@ -274,7 +280,8 @@ sidplay_file_decode(struct decoder *decoder, const char *path_fs)
 
 	iret = player.config(config);
 	if (iret != 0) {
-		g_warning("sidplay2.config() failed: %s", player.error());
+		FormatWarning(sidplay_domain,
+			      "sidplay2.config() failed: %s", player.error());
 		return;
 	}
 
diff --git a/src/encoder/OpusEncoderPlugin.cxx b/src/encoder/OpusEncoderPlugin.cxx
index c6e523ec4..f3803e2ec 100644
--- a/src/encoder/OpusEncoderPlugin.cxx
+++ b/src/encoder/OpusEncoderPlugin.cxx
@@ -33,9 +33,6 @@
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "opus_encoder"
-
 struct opus_encoder {
 	/** the base class */
 	Encoder encoder;
diff --git a/src/encoder/TwolameEncoderPlugin.cxx b/src/encoder/TwolameEncoderPlugin.cxx
index 7931ef413..6862173f7 100644
--- a/src/encoder/TwolameEncoderPlugin.cxx
+++ b/src/encoder/TwolameEncoderPlugin.cxx
@@ -24,6 +24,7 @@
 #include "ConfigError.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <twolame.h>
 
@@ -109,7 +110,8 @@ TwolameEncoder::Configure(const config_param &param, Error &error)
 static Encoder *
 twolame_encoder_init(const config_param &param, Error &error_r)
 {
-	g_debug("libtwolame version %s", get_twolame_version());
+	FormatDebug(twolame_encoder_domain,
+		    "libtwolame version %s", get_twolame_version());
 
 	TwolameEncoder *encoder = new TwolameEncoder();
 
diff --git a/src/encoder/VorbisEncoderPlugin.cxx b/src/encoder/VorbisEncoderPlugin.cxx
index 8cc1d480c..84b4cac28 100644
--- a/src/encoder/VorbisEncoderPlugin.cxx
+++ b/src/encoder/VorbisEncoderPlugin.cxx
@@ -33,9 +33,6 @@
 
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "vorbis_encoder"
-
 struct vorbis_encoder {
 	/** the base class */
 	Encoder encoder;
diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx
index 342acc04e..c3dbbad3d 100644
--- a/src/event/ServerSocket.cxx
+++ b/src/event/ServerSocket.cxx
@@ -31,6 +31,7 @@
 #include "system/fd_util.h"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -52,9 +53,6 @@
 #include <netdb.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "listen"
-
 #define DEFAULT_PORT	6600
 
 class OneServerSocket final : private SocketMonitor {
@@ -167,14 +165,16 @@ OneServerSocket::Accept()
 					&peer_address_length);
 	if (peer_fd < 0) {
 		const SocketErrorMessage msg;
-		g_warning("accept() failed: %s", (const char *)msg);
+		FormatError(server_socket_domain,
+			    "accept() failed: %s", (const char *)msg);
 		return;
 	}
 
 	if (socket_keepalive(peer_fd)) {
 		const SocketErrorMessage msg;
-		g_warning("Could not set TCP keepalive option: %s",
-			  (const char *)msg);
+		FormatError(server_socket_domain,
+			    "Could not set TCP keepalive option: %s",
+			    (const char *)msg);
 	}
 
 	parent.OnAccept(peer_fd,
@@ -241,11 +241,12 @@ ServerSocket::Open(Error &error)
 			if (good != nullptr && good->GetSerial() == i.GetSerial()) {
 				char *address_string = i.ToString();
 				char *good_string = good->ToString();
-				g_warning("bind to '%s' failed: %s "
-					  "(continuing anyway, because "
-					  "binding to '%s' succeeded)",
-					  address_string, error2.GetMessage(),
-					  good_string);
+				FormatWarning(server_socket_domain,
+					      "bind to '%s' failed: %s "
+					      "(continuing anyway, because "
+					      "binding to '%s' succeeded)",
+					      address_string, error2.GetMessage(),
+					      good_string);
 				g_free(address_string);
 				g_free(good_string);
 			} else if (bad == nullptr) {
diff --git a/src/filter/ReplayGainFilterPlugin.cxx b/src/filter/ReplayGainFilterPlugin.cxx
index 0dd8905cb..d22a5232e 100644
--- a/src/filter/ReplayGainFilterPlugin.cxx
+++ b/src/filter/ReplayGainFilterPlugin.cxx
@@ -30,14 +30,12 @@
 #include "pcm/PcmBuffer.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
-
-#include <glib.h>
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "replay_gain"
+static constexpr Domain replay_gain_domain("replay_gain");
 
 class ReplayGainFilter final : public Filter {
 	/**
@@ -105,7 +103,9 @@ public:
 			/* no change */
 			return;
 
-		g_debug("replay gain mode has changed %d->%d\n", mode, _mode);
+		FormatDebug(replay_gain_domain,
+			    "replay gain mode has changed %d->%d\n",
+			    mode, _mode);
 
 		mode = _mode;
 		Update();
@@ -122,15 +122,14 @@ public:
 				      size_t *dest_size_r, Error &error);
 };
 
-static constexpr Domain replay_gain_domain("replay_gain");
-
 void
 ReplayGainFilter::Update()
 {
 	if (mode != REPLAY_GAIN_OFF) {
 		float scale = replay_gain_tuple_scale(&info.tuples[mode],
 		    replay_gain_preamp, replay_gain_missing_preamp, replay_gain_limit);
-		g_debug("scale=%f\n", (double)scale);
+		FormatDebug(replay_gain_domain,
+			    "scale=%f\n", (double)scale);
 
 		volume = pcm_float_to_volume(scale);
 	} else
@@ -145,8 +144,7 @@ ReplayGainFilter::Update()
 
 		Error error;
 		if (!mixer_set_volume(mixer, _volume, error))
-			g_warning("Failed to update hardware mixer: %s",
-				  error.GetMessage());
+			LogError(error, "Failed to update hardware mixer");
 	}
 }
 
diff --git a/src/fs/Path.cxx b/src/fs/Path.cxx
index 9fdb78d35..09616c9f4 100644
--- a/src/fs/Path.cxx
+++ b/src/fs/Path.cxx
@@ -23,6 +23,7 @@
 #include "system/FatalError.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 #include "gcc.h"
 
 #include <glib.h>
@@ -35,9 +36,6 @@
 #include <stdio.h> // for sprintf()
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "path"
-
 /**
  * Maximal number of bytes required to represent path name in UTF-8
  * (including nul-terminator).
@@ -145,7 +143,8 @@ SetFSCharset(const char *charset)
 
 	fs_charset = charset;
 
-	g_debug("SetFSCharset: fs charset is: %s", fs_charset.c_str());
+	FormatDebug(path_domain,
+		    "SetFSCharset: fs charset is: %s", fs_charset.c_str());
 }
 
 const std::string &Path::GetFSCharset()
@@ -180,7 +179,8 @@ void Path::GlobalInit()
 	if (charset) {
 		SetFSCharset(charset);
 	} else {
-		g_message("setting filesystem charset to ISO-8859-1");
+		LogDebug(path_domain,
+			 "setting filesystem charset to ISO-8859-1");
 		SetFSCharset("ISO-8859-1");
 	}
 }
diff --git a/src/input/ArchiveInputPlugin.cxx b/src/input/ArchiveInputPlugin.cxx
index 025b25fef..08e1cabfe 100644
--- a/src/input/ArchiveInputPlugin.cxx
+++ b/src/input/ArchiveInputPlugin.cxx
@@ -25,9 +25,13 @@
 #include "ArchiveFile.hxx"
 #include "InputPlugin.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
+static constexpr Domain archive_domain("archive");
+
 /**
  * select correct archive plugin to handle the input stream
  * may allow stacking of archive plugins. for example for handling
@@ -51,7 +55,8 @@ input_archive_open(const char *pathname,
 	pname = g_strdup(pathname);
 	// archive_lookup will modify pname when true is returned
 	if (!archive_lookup(pname, &archive, &filename, &suffix)) {
-		g_debug("not an archive, lookup %s failed\n", pname);
+		FormatDebug(archive_domain,
+			    "not an archive, lookup %s failed", pname);
 		g_free(pname);
 		return NULL;
 	}
@@ -59,7 +64,8 @@ input_archive_open(const char *pathname,
 	//check which archive plugin to use (by ext)
 	arplug = archive_plugin_from_suffix(suffix);
 	if (!arplug) {
-		g_warning("can't handle archive %s\n",archive);
+		FormatWarning(archive_domain,
+			      "can't handle archive %s", archive);
 		g_free(pname);
 		return NULL;
 	}
diff --git a/src/input/CdioParanoiaInputPlugin.cxx b/src/input/CdioParanoiaInputPlugin.cxx
index 660c157e8..65db12760 100644
--- a/src/input/CdioParanoiaInputPlugin.cxx
+++ b/src/input/CdioParanoiaInputPlugin.cxx
@@ -28,6 +28,7 @@
 #include "InputPlugin.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <stdio.h>
 #include <stdint.h>
@@ -195,17 +196,20 @@ input_cdio_open(const char *uri,
 	bool reverse_endian;
 	switch (data_bigendianp(i->drv)) {
 	case -1:
-		g_debug("cdda: drive returns unknown audio data");
+		LogDebug(cdio_domain, "drive returns unknown audio data");
 		reverse_endian = false;
 		break;
+
 	case 0:
-		g_debug("cdda: drive returns audio data Little Endian.");
+		LogDebug(cdio_domain, "drive returns audio data Little Endian");
 		reverse_endian = G_BYTE_ORDER == G_BIG_ENDIAN;
 		break;
+
 	case 1:
-		g_debug("cdda: drive returns audio data Big Endian.");
+		LogDebug(cdio_domain, "drive returns audio data Big Endian");
 		reverse_endian = G_BYTE_ORDER == G_LITTLE_ENDIAN;
 		break;
+
 	default:
 		error.Format(cdio_domain, "Drive returns unknown data type %d",
 			     data_bigendianp(i->drv));
@@ -305,7 +309,8 @@ input_cdio_read(struct input_stream *is, void *ptr, size_t length,
 
 			s_err = cdda_errors(cis->drv);
 			if (s_err) {
-				g_warning("paranoia_read: %s", s_err );
+				FormatError(cdio_domain,
+					    "paranoia_read: %s", s_err);
 				free(s_err);
 			}
 			s_mess = cdda_messages(cis->drv);
diff --git a/src/input/CurlInputPlugin.cxx b/src/input/CurlInputPlugin.cxx
index 2c99f5c38..b707da2f0 100644
--- a/src/input/CurlInputPlugin.cxx
+++ b/src/input/CurlInputPlugin.cxx
@@ -31,6 +31,7 @@
 #include "IOThread.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 
@@ -53,9 +54,6 @@
 #error libcurl is too old
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_curl"
-
 /**
  * Do not buffer more than this number of bytes.  It should be a
  * reasonable limit that doesn't make low-end machines suffer too
@@ -297,8 +295,9 @@ CurlSockets::UpdateSockets()
 	CURLMcode mcode = curl_multi_fdset(curl.multi, &rfds, &wfds,
 					   &efds, &max_fd);
 	if (mcode != CURLM_OK) {
-		g_warning("curl_multi_fdset() failed: %s\n",
-			  curl_multi_strerror(mcode));
+		FormatError(curlm_domain,
+			    "curl_multi_fdset() failed: %s",
+			    curl_multi_strerror(mcode));
 		return;
 	}
 
@@ -537,8 +536,9 @@ CurlSockets::PrepareSockets()
 
 		return timeout2;
 	} else {
-		g_warning("curl_multi_timeout() failed: %s\n",
-			  curl_multi_strerror(mcode));
+		FormatWarning(curlm_domain,
+			      "curl_multi_timeout() failed: %s",
+			      curl_multi_strerror(mcode));
 		return -1;
 	}
 }
@@ -880,7 +880,7 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
 		buffer[end - value] = 0;
 
 		icy_metaint = g_ascii_strtoull(buffer, NULL, 10);
-		g_debug("icy-metaint=%zu", icy_metaint);
+		FormatDebug(curl_domain, "icy-metaint=%zu", icy_metaint);
 
 		if (icy_metaint > 0) {
 			c->icy.Start(icy_metaint);
diff --git a/src/input/DespotifyInputPlugin.cxx b/src/input/DespotifyInputPlugin.cxx
index 4e441da3c..b0665e659 100644
--- a/src/input/DespotifyInputPlugin.cxx
+++ b/src/input/DespotifyInputPlugin.cxx
@@ -24,6 +24,7 @@
 #include "InputStream.hxx"
 #include "InputPlugin.hxx"
 #include "tag/Tag.hxx"
+#include "Log.hxx"
 
 extern "C" {
 #include <despotify.h>
@@ -85,7 +86,7 @@ refill_buffer(DespotifyInputStream *ctx)
 			break;
 
 		if (rc < 0) {
-			g_debug("despotify_get_pcm error\n");
+			LogDebug(despotify_domain, "despotify_get_pcm error");
 			ctx->eof = true;
 			break;
 		}
@@ -108,14 +109,14 @@ static void callback(gcc_unused struct despotify_session* ds,
 		break;
 
 	case DESPOTIFY_TRACK_PLAY_ERROR:
-		g_debug("Track play error\n");
+		LogWarning(despotify_domain, "Track play error");
 		ctx->eof = true;
 		ctx->len_available = 0;
 		break;
 
 	case DESPOTIFY_END_OF_PLAYLIST:
 		ctx->eof = true;
-		g_debug("End of playlist: %d\n", ctx->eof);
+		FormatDebug(despotify_domain, "End of playlist: %d", ctx->eof);
 		break;
 	}
 }
@@ -139,7 +140,7 @@ input_despotify_open(const char *url,
 
 	ds_link = despotify_link_from_uri(url + 6);
 	if (!ds_link) {
-		g_debug("Can't find %s\n", url);
+		FormatDebug(despotify_domain, "Can't find %s", url);
 		return NULL;
 	}
 	if (ds_link->type != LINK_TYPE_TRACK) {
diff --git a/src/input/FfmpegInputPlugin.cxx b/src/input/FfmpegInputPlugin.cxx
index 1837303bb..f8b948c43 100644
--- a/src/input/FfmpegInputPlugin.cxx
+++ b/src/input/FfmpegInputPlugin.cxx
@@ -34,9 +34,6 @@ extern "C" {
 #include <libavformat/avformat.h>
 }
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_ffmpeg"
-
 struct FfmpegInputStream {
 	struct input_stream base;
 
diff --git a/src/input/FileInputPlugin.cxx b/src/input/FileInputPlugin.cxx
index 7d1fc0a7f..75103f711 100644
--- a/src/input/FileInputPlugin.cxx
+++ b/src/input/FileInputPlugin.cxx
@@ -33,9 +33,6 @@
 #include <string.h>
 #include <glib.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_file"
-
 static constexpr Domain file_domain("file");
 
 struct FileInputStream {
diff --git a/src/input/MmsInputPlugin.cxx b/src/input/MmsInputPlugin.cxx
index 02810bb22..15c6ac377 100644
--- a/src/input/MmsInputPlugin.cxx
+++ b/src/input/MmsInputPlugin.cxx
@@ -31,9 +31,6 @@
 #include <string.h>
 #include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_mms"
-
 struct MmsInputStream {
 	struct input_stream base;
 
diff --git a/src/input/RewindInputPlugin.cxx b/src/input/RewindInputPlugin.cxx
index 4bf5421db..a49cf7663 100644
--- a/src/input/RewindInputPlugin.cxx
+++ b/src/input/RewindInputPlugin.cxx
@@ -28,9 +28,6 @@
 #include <string.h>
 #include <stdio.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "input_rewind"
-
 extern const struct input_plugin rewind_input_plugin;
 
 struct RewindInputStream {
diff --git a/src/mixer/AlsaMixerPlugin.cxx b/src/mixer/AlsaMixerPlugin.cxx
index 13e3283f0..05d70c4c0 100644
--- a/src/mixer/AlsaMixerPlugin.cxx
+++ b/src/mixer/AlsaMixerPlugin.cxx
@@ -27,6 +27,7 @@
 #include "util/ReusableArray.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <algorithm>
 
@@ -124,8 +125,9 @@ AlsaMixerMonitor::DispatchSockets()
 
 	int err = snd_mixer_handle_events(mixer);
 	if (err < 0) {
-		g_warning("snd_mixer_handle_events() failed: %s",
-			  snd_strerror(err));
+		FormatError(alsa_mixer_domain,
+			    "snd_mixer_handle_events() failed: %s",
+			    snd_strerror(err));
 
 		if (err == -ENODEV) {
 			/* the sound device was unplugged; disable
diff --git a/src/mixer/OssMixerPlugin.cxx b/src/mixer/OssMixerPlugin.cxx
index 84cd223e6..5b533470b 100644
--- a/src/mixer/OssMixerPlugin.cxx
+++ b/src/mixer/OssMixerPlugin.cxx
@@ -23,6 +23,7 @@
 #include "system/fd_util.h"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -190,8 +191,9 @@ OssMixer::GetVolume(Error &error)
 	right = (level & 0xff00) >> 8;
 
 	if (left != right) {
-		g_warning("volume for left and right is not the same, \"%i\" and "
-			  "\"%i\"\n", left, right);
+		FormatWarning(oss_mixer_domain,
+			      "volume for left and right is not the same, \"%i\" and "
+			      "\"%i\"\n", left, right);
 	}
 
 	return left;
diff --git a/src/mixer/PulseMixerPlugin.cxx b/src/mixer/PulseMixerPlugin.cxx
index 2a5b48f3d..f651c34b0 100644
--- a/src/mixer/PulseMixerPlugin.cxx
+++ b/src/mixer/PulseMixerPlugin.cxx
@@ -24,6 +24,7 @@
 #include "GlobalEvents.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -37,9 +38,6 @@
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pulse_mixer"
-
 struct PulseMixer final : public Mixer {
 	PulseOutput *output;
 
@@ -104,8 +102,9 @@ pulse_mixer_update(PulseMixer *pm,
 					   pa_stream_get_index(stream),
 					   pulse_mixer_volume_cb, pm);
 	if (o == NULL) {
-		g_warning("pa_context_get_sink_input_info() failed: %s",
-			  pa_strerror(pa_context_errno(context)));
+		FormatError(pulse_mixer_domain,
+			    "pa_context_get_sink_input_info() failed: %s",
+			    pa_strerror(pa_context_errno(context)));
 		pulse_mixer_offline(pm);
 		return;
 	}
@@ -125,8 +124,9 @@ pulse_mixer_on_connect(gcc_unused PulseMixer *pm,
 				 (pa_subscription_mask_t)PA_SUBSCRIPTION_MASK_SINK_INPUT,
 				 NULL, NULL);
 	if (o == NULL) {
-		g_warning("pa_context_subscribe() failed: %s",
-			  pa_strerror(pa_context_errno(context)));
+		FormatError(pulse_mixer_domain,
+			    "pa_context_subscribe() failed: %s",
+			    pa_strerror(pa_context_errno(context)));
 		return;
 	}
 
diff --git a/src/mixer/WinmmMixerPlugin.cxx b/src/mixer/WinmmMixerPlugin.cxx
index ae25b532c..dbb43dce4 100644
--- a/src/mixer/WinmmMixerPlugin.cxx
+++ b/src/mixer/WinmmMixerPlugin.cxx
@@ -30,9 +30,6 @@
 #include <math.h>
 #include <windows.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "winmm_mixer"
-
 struct WinmmMixer final : public Mixer {
 	WinmmOutput *output;
 
diff --git a/src/output/AlsaOutputPlugin.cxx b/src/output/AlsaOutputPlugin.cxx
index c9bf01909..79b81282b 100644
--- a/src/output/AlsaOutputPlugin.cxx
+++ b/src/output/AlsaOutputPlugin.cxx
@@ -25,15 +25,13 @@
 #include "util/Manual.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 #include <alsa/asoundlib.h>
 
 #include <string>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "alsa"
-
 #define ALSA_PCM_NEW_HW_PARAMS_API
 #define ALSA_PCM_NEW_SW_PARAMS_API
 
@@ -216,8 +214,9 @@ alsa_test_default_device(void)
 	int ret = snd_pcm_open(&handle, default_device,
 	                       SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
 	if (ret) {
-		g_message("Error opening default ALSA device: %s\n",
-			  snd_strerror(-ret));
+		FormatError(alsa_output_domain,
+			    "Error opening default ALSA device: %s",
+			    snd_strerror(-ret));
 		return false;
 	} else
 		snd_pcm_close(handle);
@@ -413,9 +412,11 @@ configure_hw:
 		err = snd_pcm_hw_params_set_access(ad->pcm, hwparams,
 						   SND_PCM_ACCESS_MMAP_INTERLEAVED);
 		if (err < 0) {
-			g_warning("Cannot set mmap'ed mode on ALSA device \"%s\":  %s\n",
-				  alsa_device(ad), snd_strerror(-err));
-			g_warning("Falling back to direct write mode\n");
+			FormatWarning(alsa_output_domain,
+				      "Cannot set mmap'ed mode on ALSA device \"%s\": %s",
+				      alsa_device(ad), snd_strerror(-err));
+			LogWarning(alsa_output_domain,
+				   "Falling back to direct write mode");
 			ad->use_mmap = false;
 		} else
 			ad->writei = snd_pcm_mmap_writei;
@@ -443,8 +444,9 @@ configure_hw:
 
 	snd_pcm_format_t format;
 	if (snd_pcm_hw_params_get_format(hwparams, &format) == 0)
-		g_debug("format=%s (%s)", snd_pcm_format_name(format),
-			snd_pcm_format_description(format));
+		FormatDebug(alsa_output_domain,
+			    "format=%s (%s)", snd_pcm_format_name(format),
+			    snd_pcm_format_description(format));
 
 	err = snd_pcm_hw_params_set_channels_near(ad->pcm, hwparams,
 						  &channels);
@@ -473,9 +475,9 @@ configure_hw:
 	unsigned buffer_time_min, buffer_time_max;
 	snd_pcm_hw_params_get_buffer_time_min(hwparams, &buffer_time_min, 0);
 	snd_pcm_hw_params_get_buffer_time_max(hwparams, &buffer_time_max, 0);
-	g_debug("buffer: size=%u..%u time=%u..%u",
-		(unsigned)buffer_size_min, (unsigned)buffer_size_max,
-		buffer_time_min, buffer_time_max);
+	FormatDebug(alsa_output_domain, "buffer: size=%u..%u time=%u..%u",
+		    (unsigned)buffer_size_min, (unsigned)buffer_size_max,
+		    buffer_time_min, buffer_time_max);
 
 	snd_pcm_uframes_t period_size_min, period_size_max;
 	snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, 0);
@@ -483,9 +485,9 @@ configure_hw:
 	unsigned period_time_min, period_time_max;
 	snd_pcm_hw_params_get_period_time_min(hwparams, &period_time_min, 0);
 	snd_pcm_hw_params_get_period_time_max(hwparams, &period_time_max, 0);
-	g_debug("period: size=%u..%u time=%u..%u",
-		(unsigned)period_size_min, (unsigned)period_size_max,
-		period_time_min, period_time_max);
+	FormatDebug(alsa_output_domain, "period: size=%u..%u time=%u..%u",
+		    (unsigned)period_size_min, (unsigned)period_size_max,
+		    period_time_min, period_time_max);
 
 	if (ad->buffer_time > 0) {
 		buffer_time = ad->buffer_time;
@@ -504,8 +506,9 @@ configure_hw:
 	if (period_time_ro == 0 && buffer_time >= 10000) {
 		period_time_ro = period_time = buffer_time / 4;
 
-		g_debug("default period_time = buffer_time/4 = %u/4 = %u",
-			buffer_time, period_time);
+		FormatDebug(alsa_output_domain,
+			    "default period_time = buffer_time/4 = %u/4 = %u",
+			    buffer_time, period_time);
 	}
 
 	if (period_time_ro > 0) {
@@ -525,7 +528,8 @@ configure_hw:
 	} else if (err < 0)
 		goto error;
 	if (retry != MPD_ALSA_RETRY_NR)
-		g_debug("ALSA period_time set to %d\n", period_time);
+		FormatDebug(alsa_output_domain,
+			    "ALSA period_time set to %d", period_time);
 
 	snd_pcm_uframes_t alsa_buffer_size;
 	cmd = "snd_pcm_hw_params_get_buffer_size";
@@ -567,8 +571,8 @@ configure_hw:
 	if (err < 0)
 		goto error;
 
-	g_debug("buffer_size=%u period_size=%u",
-		(unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
+	FormatDebug(alsa_output_domain, "buffer_size=%u period_size=%u",
+		    (unsigned)alsa_buffer_size, (unsigned)alsa_period_size);
 
 	if (alsa_period_size == 0)
 		/* this works around a SIGFPE bug that occurred when
@@ -673,8 +677,9 @@ alsa_open(struct audio_output *ao, AudioFormat &audio_format, Error &error)
 		return false;
 	}
 
-	g_debug("opened %s type=%s", snd_pcm_name(ad->pcm),
-		snd_pcm_type_name(snd_pcm_type(ad->pcm)));
+	FormatDebug(alsa_output_domain, "opened %s type=%s",
+		    snd_pcm_name(ad->pcm),
+		    snd_pcm_type_name(snd_pcm_type(ad->pcm)));
 
 	if (!alsa_setup_or_dsd(ad, audio_format, error)) {
 		snd_pcm_close(ad->pcm);
@@ -700,9 +705,12 @@ static int
 alsa_recover(AlsaOutput *ad, int err)
 {
 	if (err == -EPIPE) {
-		g_debug("Underrun on ALSA device \"%s\"\n", alsa_device(ad));
+		FormatDebug(alsa_output_domain,
+			    "Underrun on ALSA device \"%s\"", alsa_device(ad));
 	} else if (err == -ESTRPIPE) {
-		g_debug("ALSA device \"%s\" was suspended\n", alsa_device(ad));
+		FormatDebug(alsa_output_domain,
+			    "ALSA device \"%s\" was suspended",
+			    alsa_device(ad));
 	}
 
 	switch (snd_pcm_state(ad->pcm)) {
diff --git a/src/output/AoOutputPlugin.cxx b/src/output/AoOutputPlugin.cxx
index a54611511..e66969e20 100644
--- a/src/output/AoOutputPlugin.cxx
+++ b/src/output/AoOutputPlugin.cxx
@@ -22,15 +22,13 @@
 #include "OutputAPI.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <ao/ao.h>
 #include <glib.h>
 
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "ao"
-
 /* An ao_sample_format, with all fields set to zero: */
 static ao_sample_format OUR_AO_FORMAT_INITIALIZER;
 
@@ -125,8 +123,8 @@ AoOutput::Configure(const config_param &param, Error &error)
 		return false;
 	}
 
-	g_debug("using ao driver \"%s\" for \"%s\"\n", ai->short_name,
-		param.GetBlockValue("name", nullptr));
+	FormatDebug(ao_output_domain, "using ao driver \"%s\" for \"%s\"\n",
+		    ai->short_name, param.GetBlockValue("name", nullptr));
 
 	value = param.GetBlockValue("options", nullptr);
 	if (value != nullptr) {
diff --git a/src/output/FifoOutputPlugin.cxx b/src/output/FifoOutputPlugin.cxx
index 34199bff9..babda5f9e 100644
--- a/src/output/FifoOutputPlugin.cxx
+++ b/src/output/FifoOutputPlugin.cxx
@@ -27,19 +27,15 @@
 #include "fs/FileSystem.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 #include "open.h"
 
-#include <glib.h>
-
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "fifo"
-
 #define FIFO_BUFFER_SIZE 65536 /* pipe capacity on Linux >= 2.6.11 */
 
 struct FifoOutput {
@@ -78,11 +74,13 @@ static constexpr Domain fifo_output_domain("fifo_output");
 inline void
 FifoOutput::Delete()
 {
-	g_debug("Removing FIFO \"%s\"", path_utf8.c_str());
+	FormatDebug(fifo_output_domain,
+		    "Removing FIFO \"%s\"", path_utf8.c_str());
 
 	if (!RemoveFile(path)) {
-		g_warning("Could not remove FIFO \"%s\": %s",
-			  path_utf8.c_str(), g_strerror(errno));
+		FormatErrno(fifo_output_domain,
+			    "Could not remove FIFO \"%s\"",
+			    path_utf8.c_str());
 		return;
 	}
 
@@ -249,8 +247,9 @@ fifo_output_cancel(struct audio_output *ao)
 		bytes = read(fd->input, buf, FIFO_BUFFER_SIZE);
 
 	if (bytes < 0 && errno != EAGAIN) {
-		g_warning("Flush of FIFO \"%s\" failed: %s",
-			  fd->path_utf8.c_str(), g_strerror(errno));
+		FormatErrno(fifo_output_domain,
+			    "Flush of FIFO \"%s\" failed",
+			    fd->path_utf8.c_str());
 	}
 }
 
diff --git a/src/output/HttpdClient.cxx b/src/output/HttpdClient.cxx
index e776d526b..cbf396088 100644
--- a/src/output/HttpdClient.cxx
+++ b/src/output/HttpdClient.cxx
@@ -24,13 +24,11 @@
 #include "Page.hxx"
 #include "IcyMetaDataServer.hxx"
 #include "system/SocketError.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
 HttpdClient::~HttpdClient()
 {
 	if (state == RESPONSE) {
@@ -80,7 +78,8 @@ HttpdClient::HandleLine(const char *line)
 	if (state == REQUEST) {
 		if (strncmp(line, "GET /", 5) != 0) {
 			/* only GET is supported */
-			g_warning("malformed request line from client");
+			LogWarning(httpd_output_domain,
+				   "malformed request line from client");
 			return false;
 		}
 
@@ -171,7 +170,9 @@ HttpdClient::SendResponse()
 	ssize_t nbytes = SocketMonitor::Write(buffer, strlen(buffer));
 	if (gcc_unlikely(nbytes < 0)) {
 		const SocketErrorMessage msg;
-		g_warning("failed to write to client: %s", (const char *)msg);
+		FormatWarning(httpd_output_domain,
+			      "failed to write to client: %s",
+			      (const char *)msg);
 		Close();
 		return false;
 	}
@@ -278,8 +279,9 @@ HttpdClient::TryWrite()
 
 				if (!IsSocketErrorClosed(e)) {
 					SocketErrorMessage msg(e);
-					g_warning("failed to write to client: %s",
-						  (const char *)msg);
+					FormatWarning(httpd_output_domain,
+						      "failed to write to client: %s",
+						      (const char *)msg);
 				}
 
 				Close();
@@ -304,8 +306,9 @@ HttpdClient::TryWrite()
 
 				if (!IsSocketErrorClosed(e)) {
 					SocketErrorMessage msg(e);
-					g_warning("failed to write to client: %s",
-						  (const char *)msg);
+					FormatWarning(httpd_output_domain,
+						      "failed to write to client: %s",
+						      (const char *)msg);
 				}
 
 				Close();
@@ -326,8 +329,9 @@ HttpdClient::TryWrite()
 
 			if (!IsSocketErrorClosed(e)) {
 				SocketErrorMessage msg(e);
-				g_warning("failed to write to client: %s",
-					  (const char *)msg);
+				FormatWarning(httpd_output_domain,
+					      "failed to write to client: %s",
+					      (const char *)msg);
 			}
 
 			Close();
@@ -399,7 +403,8 @@ BufferedSocket::InputResult
 HttpdClient::OnSocketInput(const void *data, size_t length)
 {
 	if (state == RESPONSE) {
-		g_warning("unexpected input from client");
+		LogWarning(httpd_output_domain,
+			   "unexpected input from client");
 		LockClose();
 		return InputResult::CLOSED;
 	}
@@ -433,7 +438,7 @@ HttpdClient::OnSocketInput(const void *data, size_t length)
 void
 HttpdClient::OnSocketError(Error &&error)
 {
-	g_warning("error on HTTP client: %s", error.GetMessage());
+	LogError(error);
 }
 
 void
diff --git a/src/output/HttpdInternal.hxx b/src/output/HttpdInternal.hxx
index 925116b61..9437cfedf 100644
--- a/src/output/HttpdInternal.hxx
+++ b/src/output/HttpdInternal.hxx
@@ -205,4 +205,6 @@ private:
 			      size_t address_length, int uid) override;
 };
 
+extern const class Domain httpd_output_domain;
+
 #endif
diff --git a/src/output/HttpdOutputPlugin.cxx b/src/output/HttpdOutputPlugin.cxx
index db395069e..f7c83002e 100644
--- a/src/output/HttpdOutputPlugin.cxx
+++ b/src/output/HttpdOutputPlugin.cxx
@@ -31,6 +31,7 @@
 #include "Main.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 
@@ -44,10 +45,7 @@
 #include <tcpd.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "httpd_output"
-
-static constexpr Domain httpd_output_domain("httpd_output");
+const Domain httpd_output_domain("httpd_output");
 
 inline
 HttpdOutput::HttpdOutput(EventLoop &_loop)
@@ -210,8 +208,9 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
 
 		if (!hosts_access(&req)) {
 			/* tcp wrappers says no */
-			g_warning("libwrap refused connection (libwrap=%s) from %s",
-			      progname, hostaddr);
+			FormatWarning(httpd_output_domain,
+				      "libwrap refused connection (libwrap=%s) from %s",
+				      progname, hostaddr);
 			g_free(hostaddr);
 			close_socket(fd);
 			return;
@@ -233,7 +232,7 @@ HttpdOutput::OnAccept(int fd, const sockaddr &address,
 		else
 			close_socket(fd);
 	} else if (fd < 0 && errno != EINTR) {
-		g_warning("accept() failed: %s", g_strerror(errno));
+		LogErrno(httpd_output_domain, "accept() failed");
 	}
 }
 
@@ -420,7 +419,8 @@ HttpdOutput::BroadcastFromEncoder()
 	mutex.lock();
 	for (auto &client : clients) {
 		if (client.GetQueueSize() > 256 * 1024) {
-			g_debug("client is too slow, flushing its queue");
+			FormatDebug(httpd_output_domain,
+				    "client is too slow, flushing its queue");
 			client.CancelQueue();
 		}
 	}
diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx
index 727fba13e..75c7daf81 100644
--- a/src/output/JackOutputPlugin.cxx
+++ b/src/output/JackOutputPlugin.cxx
@@ -23,6 +23,7 @@
 #include "ConfigError.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <assert.h>
 
@@ -38,9 +39,6 @@
 #include <unistd.h>
 #include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "jack"
-
 enum {
 	MAX_PORTS = 16,
 };
@@ -216,14 +214,14 @@ set_audioformat(JackOutput *jd, AudioFormat &audio_format)
 static void
 mpd_jack_error(const char *msg)
 {
-	g_warning("%s", msg);
+	LogError(jack_output_domain, msg);
 }
 
 #ifdef HAVE_JACK_SET_INFO_FUNCTION
 static void
 mpd_jack_info(const char *msg)
 {
-	g_message("%s", msg);
+	LogInfo(jack_output_domain, msg);
 }
 #endif
 
@@ -360,8 +358,9 @@ mpd_jack_init(const config_param &param, Error &error)
 		/* compatibility with MPD < 0.16 */
 		value = param.GetBlockValue("ports", nullptr);
 		if (value != nullptr)
-			g_warning("deprecated option 'ports' in line %d",
-				  param.line);
+			FormatWarning(jack_output_domain,
+				      "deprecated option 'ports' in line %d",
+				      param.line);
 	}
 
 	if (value != nullptr) {
@@ -376,10 +375,11 @@ mpd_jack_init(const config_param &param, Error &error)
 
 	if (jd->num_destination_ports > 0 &&
 	    jd->num_destination_ports != jd->num_source_ports)
-		g_warning("number of source ports (%u) mismatches the "
-			  "number of destination ports (%u) in line %d",
-			  jd->num_source_ports, jd->num_destination_ports,
-			  param.line);
+		FormatWarning(jack_output_domain,
+			      "number of source ports (%u) mismatches the "
+			      "number of destination ports (%u) in line %d",
+			      jd->num_source_ports, jd->num_destination_ports,
+			      param.line);
 
 	jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u);
 
@@ -500,9 +500,10 @@ mpd_jack_start(JackOutput *jd, Error &error)
 		     num_destination_ports < MAX_PORTS &&
 			     jports[num_destination_ports] != nullptr;
 		     ++num_destination_ports) {
-			g_debug("destination_port[%u] = '%s'\n",
-				num_destination_ports,
-				jports[num_destination_ports]);
+			FormatDebug(jack_output_domain,
+				    "destination_port[%u] = '%s'\n",
+				    num_destination_ports,
+				    jports[num_destination_ports]);
 			destination_ports[num_destination_ports] =
 				jports[num_destination_ports];
 		}
diff --git a/src/output/OSXOutputPlugin.cxx b/src/output/OSXOutputPlugin.cxx
index 8445a8028..eee215b32 100644
--- a/src/output/OSXOutputPlugin.cxx
+++ b/src/output/OSXOutputPlugin.cxx
@@ -25,15 +25,13 @@
 #include "util/Domain.hxx"
 #include "thread/Mutex.hxx"
 #include "thread/Cond.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 #include <CoreAudio/AudioHardware.h>
 #include <AudioUnit/AudioUnit.h>
 #include <CoreServices/CoreServices.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "osx"
-
 struct OSXOutput {
 	struct audio_output base;
 
@@ -156,15 +154,17 @@ osx_output_set_device(OSXOutput *oo, Error &error)
 			goto done;
 		}
 		if (strcmp(oo->device_name, name) == 0) {
-			g_debug("found matching device: ID=%u, name=%s",
-				(unsigned int) deviceids[i], name);
+			FormatDebug(osx_output_domain,
+				    "found matching device: ID=%u, name=%s",
+				    (unsigned)deviceids[i], name);
 			break;
 		}
 	}
 	if (i == numdevices) {
-		g_warning("Found no audio device with name '%s' "
-			  "(will use default audio device)",
-			  oo->device_name);
+		FormatWarning(osx_output_domain,
+			      "Found no audio device with name '%s' "
+			      "(will use default audio device)",
+			      oo->device_name);
 		goto done;
 	}
 
@@ -181,8 +181,10 @@ osx_output_set_device(OSXOutput *oo, Error &error)
 		ret = false;
 		goto done;
 	}
-	g_debug("set OS X audio output device ID=%u, name=%s",
-		(unsigned int) deviceids[i], name);
+
+	FormatDebug(osx_output_domain,
+		    "set OS X audio output device ID=%u, name=%s",
+		    (unsigned)deviceids[i], name);
 
 done:
 	delete[] deviceids;
diff --git a/src/output/OpenALOutputPlugin.cxx b/src/output/OpenALOutputPlugin.cxx
index f8b1f5417..e753b206f 100644
--- a/src/output/OpenALOutputPlugin.cxx
+++ b/src/output/OpenALOutputPlugin.cxx
@@ -33,9 +33,6 @@
 #include <OpenAL/alc.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "openal"
-
 /* should be enough for buffer size = 2048 */
 #define NUM_BUFFERS 16
 
diff --git a/src/output/OssOutputPlugin.cxx b/src/output/OssOutputPlugin.cxx
index 7bf741397..781e2bf43 100644
--- a/src/output/OssOutputPlugin.cxx
+++ b/src/output/OssOutputPlugin.cxx
@@ -24,6 +24,7 @@
 #include "system/fd_util.h"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -35,9 +36,6 @@
 #include <unistd.h>
 #include <assert.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "oss"
-
 #if defined(__OpenBSD__) || defined(__NetBSD__)
 # include <soundcard.h>
 #else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
@@ -142,8 +140,10 @@ oss_output_test_default_device(void)
 			close(fd);
 			return true;
 		}
-		g_warning("Error opening OSS device \"%s\": %s\n",
-			  default_devices[i], g_strerror(errno));
+
+		FormatErrno(oss_output_domain,
+			    "Error opening OSS device \"%s\"",
+			    default_devices[i]);
 	}
 
 	return false;
@@ -177,17 +177,20 @@ oss_open_default(Error &error)
 			/* never reached */
 			break;
 		case OSS_STAT_DOESN_T_EXIST:
-			g_warning("%s not found\n", dev);
+			FormatWarning(oss_output_domain,
+				      "%s not found", dev);
 			break;
 		case OSS_STAT_NOT_CHAR_DEV:
-			g_warning("%s is not a character device\n", dev);
+			FormatWarning(oss_output_domain,
+				      "%s is not a character device", dev);
 			break;
 		case OSS_STAT_NO_PERMS:
-			g_warning("%s: permission denied\n", dev);
+			FormatWarning(oss_output_domain,
+				      "%s: permission denied", dev);
 			break;
 		case OSS_STAT_OTHER:
-			g_warning("Error accessing %s: %s\n",
-				  dev, g_strerror(err[i]));
+			FormatErrno(oss_output_domain, err[i],
+				    "Error accessing %s", dev);
 		}
 	}
 
diff --git a/src/output/PulseOutputPlugin.cxx b/src/output/PulseOutputPlugin.cxx
index d64e0af16..ab797387d 100644
--- a/src/output/PulseOutputPlugin.cxx
+++ b/src/output/PulseOutputPlugin.cxx
@@ -24,6 +24,7 @@
 #include "mixer/PulseMixerPlugin.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -628,8 +629,9 @@ pulse_output_close(struct audio_output *ao)
 		o = pa_stream_drain(po->stream,
 				    pulse_output_stream_success_cb, po);
 		if (o == nullptr) {
-			g_warning("pa_stream_drain() has failed: %s",
-				  pa_strerror(pa_context_errno(po->context)));
+			FormatWarning(pulse_output_domain,
+				      "pa_stream_drain() has failed: %s",
+				      pa_strerror(pa_context_errno(po->context)));
 		} else
 			pulse_wait_for_operation(po->mainloop, o);
 	}
@@ -804,8 +806,9 @@ pulse_output_cancel(struct audio_output *ao)
 
 	o = pa_stream_flush(po->stream, pulse_output_stream_success_cb, po);
 	if (o == nullptr) {
-		g_warning("pa_stream_flush() has failed: %s",
-			  pa_strerror(pa_context_errno(po->context)));
+		FormatWarning(pulse_output_domain,
+			      "pa_stream_flush() has failed: %s",
+			      pa_strerror(pa_context_errno(po->context)));
 		pa_threaded_mainloop_unlock(po->mainloop);
 		return;
 	}
@@ -829,7 +832,7 @@ pulse_output_pause(struct audio_output *ao)
 	Error error;
 	if (!pulse_output_wait_stream(po, error)) {
 		pa_threaded_mainloop_unlock(po->mainloop);
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return false;
 	}
 
@@ -840,7 +843,7 @@ pulse_output_pause(struct audio_output *ao)
 	if (!pa_stream_is_corked(po->stream) &&
 	    !pulse_output_stream_pause(po, true, error)) {
 		pa_threaded_mainloop_unlock(po->mainloop);
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return false;
 	}
 
diff --git a/src/output/RecorderOutputPlugin.cxx b/src/output/RecorderOutputPlugin.cxx
index 31c378c56..9a7eba01f 100644
--- a/src/output/RecorderOutputPlugin.cxx
+++ b/src/output/RecorderOutputPlugin.cxx
@@ -34,9 +34,6 @@
 #include <unistd.h>
 #include <errno.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "recorder"
-
 struct RecorderOutput {
 	struct audio_output base;
 
diff --git a/src/output/RoarOutputPlugin.cxx b/src/output/RoarOutputPlugin.cxx
index f6d261372..9d66bb63b 100644
--- a/src/output/RoarOutputPlugin.cxx
+++ b/src/output/RoarOutputPlugin.cxx
@@ -25,6 +25,7 @@
 #include "thread/Mutex.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -34,9 +35,6 @@
 #include <roaraudio.h>
 #undef new
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "roaraudio"
-
 struct RoarOutput {
 	struct audio_output base;
 
@@ -242,7 +240,7 @@ roar_cancel_locked(RoarOutput *self)
 	if (roar_vs_stream(vss, &(self->info), ROAR_DIR_PLAY,
 			   &(self->err)) < 0) {
 		roar_vs_close(vss, ROAR_VS_TRUE, &(self->err));
-		g_warning("Failed to start stream");
+		LogError(roar_output_domain, "Failed to start stream");
 		return;
 	}
 
diff --git a/src/output/ShoutOutputPlugin.cxx b/src/output/ShoutOutputPlugin.cxx
index 836ba4b4c..19f2b61cd 100644
--- a/src/output/ShoutOutputPlugin.cxx
+++ b/src/output/ShoutOutputPlugin.cxx
@@ -26,6 +26,7 @@
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
 #include "system/FatalError.hxx"
+#include "Log.hxx"
 
 #include <shout/shout.h>
 #include <glib.h>
@@ -35,9 +36,6 @@
 #include <string.h>
 #include <stdio.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "shout"
-
 static constexpr unsigned DEFAULT_CONN_TIMEOUT = 2;
 
 struct ShoutOutput final {
@@ -358,8 +356,9 @@ static void close_shout_conn(ShoutOutput * sd)
 
 	if (shout_get_connected(sd->shout_conn) != SHOUTERR_UNCONNECTED &&
 	    shout_close(sd->shout_conn) != SHOUTERR_SUCCESS) {
-		g_warning("problem closing connection to shout server: %s\n",
-			  shout_get_error(sd->shout_conn));
+		FormatWarning(shout_output_domain,
+			      "problem closing connection to shout server: %s",
+			      shout_get_error(sd->shout_conn));
 	}
 }
 
@@ -507,7 +506,7 @@ static void my_shout_set_tag(struct audio_output *ao,
 		if (!encoder_pre_tag(sd->encoder, error) ||
 		    !write_page(sd, error) ||
 		    !encoder_tag(sd->encoder, tag, error)) {
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 			return;
 		}
 	} else {
@@ -518,7 +517,8 @@ static void my_shout_set_tag(struct audio_output *ao,
 		shout_metadata_add(sd->shout_meta, "song", song);
 		if (SHOUTERR_SUCCESS != shout_set_metadata(sd->shout_conn,
 							   sd->shout_meta)) {
-			g_warning("error setting shout metadata\n");
+			LogWarning(shout_output_domain,
+				   "error setting shout metadata");
 		}
 	}
 
diff --git a/src/output/SolarisOutputPlugin.cxx b/src/output/SolarisOutputPlugin.cxx
index e3428cbc2..0836dc2e2 100644
--- a/src/output/SolarisOutputPlugin.cxx
+++ b/src/output/SolarisOutputPlugin.cxx
@@ -49,9 +49,6 @@ struct audio_info {
 
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "solaris_output"
-
 struct SolarisOutput {
 	struct audio_output base;
 
diff --git a/src/output/WinmmOutputPlugin.cxx b/src/output/WinmmOutputPlugin.cxx
index e8b3c3a3a..d3f74dd44 100644
--- a/src/output/WinmmOutputPlugin.cxx
+++ b/src/output/WinmmOutputPlugin.cxx
@@ -30,9 +30,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "winmm_output"
-
 struct WinmmBuffer {
 	PcmBuffer buffer;
 
diff --git a/src/pcm/PcmConvert.cxx b/src/pcm/PcmConvert.cxx
index 38672810e..4260ccb0f 100644
--- a/src/pcm/PcmConvert.cxx
+++ b/src/pcm/PcmConvert.cxx
@@ -31,9 +31,6 @@
 #include <assert.h>
 #include <math.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
 const Domain pcm_convert_domain("pcm_convert");
 
 PcmConvert::PcmConvert()
diff --git a/src/pcm/PcmResampleLibsamplerate.cxx b/src/pcm/PcmResampleLibsamplerate.cxx
index 1ab9b1953..2ffe4b8c4 100644
--- a/src/pcm/PcmResampleLibsamplerate.cxx
+++ b/src/pcm/PcmResampleLibsamplerate.cxx
@@ -21,6 +21,7 @@
 #include "PcmResampleInternal.hxx"
 #include "util/Error.hxx"
 #include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -28,9 +29,6 @@
 #include <stdlib.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm"
-
 static int lsr_converter = SRC_SINC_FASTEST;
 
 static constexpr Domain libsamplerate_domain("libsamplerate");
@@ -74,8 +72,9 @@ pcm_resample_lsr_global_init(const char *converter, Error &error)
 		return false;
 	}
 
-	g_debug("libsamplerate converter '%s'",
-		src_get_name(lsr_converter));
+	FormatDebug(libsamplerate_domain,
+		    "libsamplerate converter '%s'",
+		    src_get_name(lsr_converter));
 
 	return true;
 }
@@ -133,8 +132,9 @@ pcm_resample_set(PcmResampler *state,
 
 	SRC_DATA *data = &state->data;
 	data->src_ratio = (double)dest_rate / (double)src_rate;
-	g_debug("setting samplerate conversion ratio to %.2lf",
-		data->src_ratio);
+	FormatDebug(libsamplerate_domain,
+		    "setting samplerate conversion ratio to %.2lf",
+		    data->src_ratio);
 	src_set_ratio(state->state, data->src_ratio);
 
 	return true;
diff --git a/src/pcm/PcmVolume.cxx b/src/pcm/PcmVolume.cxx
index ff505e29d..05ab73c68 100644
--- a/src/pcm/PcmVolume.cxx
+++ b/src/pcm/PcmVolume.cxx
@@ -27,9 +27,6 @@
 #include <stdint.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "pcm_volume"
-
 static void
 pcm_volume_change_8(int8_t *buffer, const int8_t *end, int volume)
 {
diff --git a/src/playlist/AsxPlaylistPlugin.cxx b/src/playlist/AsxPlaylistPlugin.cxx
index 19fa5a0fb..4dcbf56b9 100644
--- a/src/playlist/AsxPlaylistPlugin.cxx
+++ b/src/playlist/AsxPlaylistPlugin.cxx
@@ -25,14 +25,15 @@
 #include "Song.hxx"
 #include "tag/Tag.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "asx"
+static constexpr Domain asx_domain("asx");
 
 /**
  * This is the state object for the GLib XML parser.
@@ -224,7 +225,7 @@ asx_open_stream(struct input_stream *is)
 		if (nbytes == 0) {
 			if (error2.IsDefined()) {
 				g_markup_parse_context_free(context);
-				g_warning("%s", error2.GetMessage());
+				LogError(error2);
 				return NULL;
 			}
 
@@ -234,7 +235,8 @@ asx_open_stream(struct input_stream *is)
 		success = g_markup_parse_context_parse(context, buffer, nbytes,
 						       &error);
 		if (!success) {
-			g_warning("XML parser failed: %s", error->message);
+			FormatErrno(asx_domain,
+				    "XML parser failed: %s", error->message);
 			g_error_free(error);
 			g_markup_parse_context_free(context);
 			return NULL;
@@ -243,7 +245,8 @@ asx_open_stream(struct input_stream *is)
 
 	success = g_markup_parse_context_end_parse(context, &error);
 	if (!success) {
-		g_warning("XML parser failed: %s", error->message);
+		FormatErrno(asx_domain,
+			    "XML parser failed: %s", error->message);
 		g_error_free(error);
 		g_markup_parse_context_free(context);
 		return NULL;
diff --git a/src/playlist/CuePlaylistPlugin.cxx b/src/playlist/CuePlaylistPlugin.cxx
index 0ec5c3068..5b0b6c6d2 100644
--- a/src/playlist/CuePlaylistPlugin.cxx
+++ b/src/playlist/CuePlaylistPlugin.cxx
@@ -29,9 +29,6 @@
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "cue"
-
 class CuePlaylist final : public SongEnumerator {
 	struct input_stream *is;
 	TextInputStream tis;
diff --git a/src/playlist/DespotifyPlaylistPlugin.cxx b/src/playlist/DespotifyPlaylistPlugin.cxx
index b0394a5da..a1a865c08 100644
--- a/src/playlist/DespotifyPlaylistPlugin.cxx
+++ b/src/playlist/DespotifyPlaylistPlugin.cxx
@@ -24,13 +24,12 @@
 #include "MemorySongEnumerator.hxx"
 #include "tag/Tag.hxx"
 #include "Song.hxx"
+#include "Log.hxx"
 
 extern "C" {
 #include <despotify.h>
 }
 
-#include <glib.h>
-
 #include <string.h>
 #include <stdlib.h>
 
@@ -48,7 +47,8 @@ add_song(std::forward_list<SongPointer> &songs, struct ds_track *track)
 
 	if (despotify_track_to_uri(track, ds_uri) != ds_uri) {
 		/* Should never really fail, but let's be sure */
-		g_debug("Can't add track %s\n", track->title);
+		FormatDebug(despotify_domain,
+			    "Can't add track %s", track->title);
 		return;
 	}
 
@@ -99,7 +99,7 @@ despotify_playlist_open_uri(const char *url,
 	ds_link *link =
 		despotify_link_from_uri(url + strlen(despotify_playlist_plugin.schemes[0]) + 3);
 	if (link == nullptr) {
-		g_debug("Can't find %s\n", url);
+		FormatDebug(despotify_domain, "Can't find %s\n", url);
 		return nullptr;
 	}
 
diff --git a/src/playlist/EmbeddedCuePlaylistPlugin.cxx b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
index 6b497d50c..5fc1f28e0 100644
--- a/src/playlist/EmbeddedCuePlaylistPlugin.cxx
+++ b/src/playlist/EmbeddedCuePlaylistPlugin.cxx
@@ -39,9 +39,6 @@
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "cue"
-
 class EmbeddedCuePlaylist final : public SongEnumerator {
 public:
 	/**
diff --git a/src/playlist/PlsPlaylistPlugin.cxx b/src/playlist/PlsPlaylistPlugin.cxx
index 721f9adab..567add465 100644
--- a/src/playlist/PlsPlaylistPlugin.cxx
+++ b/src/playlist/PlsPlaylistPlugin.cxx
@@ -25,11 +25,15 @@
 #include "Song.hxx"
 #include "tag/Tag.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <string>
 
+static constexpr Domain pls_domain("pls");
+
 static void
 pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
 {
@@ -40,7 +44,8 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
 	int num_entries = g_key_file_get_integer(keyfile, "playlist",
 						 "NumberOfEntries", &error);
 	if (error) {
-		g_debug("Invalid PLS file: '%s'", error->message);
+		FormatError(pls_domain,
+			    "Invalid PLS file: '%s'", error->message);
 		g_error_free(error);
 		error = NULL;
 
@@ -59,7 +64,8 @@ pls_parser(GKeyFile *keyfile, std::forward_list<SongPointer> &songs)
 		value = g_key_file_get_string(keyfile, "playlist", key,
 					      &error);
 		if(error) {
-			g_debug("Invalid PLS entry %s: '%s'",key, error->message);
+			FormatError(pls_domain, "Invalid PLS entry %s: '%s'",
+				    key, error->message);
 			g_error_free(error);
 			g_free(key);
 			return;
@@ -118,7 +124,7 @@ pls_open_stream(struct input_stream *is)
 		nbytes = is->LockRead(buffer, sizeof(buffer), error2);
 		if (nbytes == 0) {
 			if (error2.IsDefined()) {
-				g_warning("%s", error2.GetMessage());
+				LogError(error2);
 				return NULL;
 			}
 
@@ -130,7 +136,7 @@ pls_open_stream(struct input_stream *is)
 	} while (kf_data.length() < 65536);
 
 	if (kf_data.empty()) {
-		g_warning("KeyFile parser failed: No Data");
+		LogWarning(pls_domain, "KeyFile parser failed: No Data");
 		return NULL;
 	}
 
@@ -140,7 +146,8 @@ pls_open_stream(struct input_stream *is)
 					    G_KEY_FILE_NONE, &error);
 
 	if (!success) {
-		g_warning("KeyFile parser failed: %s", error->message);
+		FormatError(pls_domain,
+			    "KeyFile parser failed: %s", error->message);
 		g_error_free(error);
 		g_key_file_free(keyfile);
 		return NULL;
diff --git a/src/playlist/RssPlaylistPlugin.cxx b/src/playlist/RssPlaylistPlugin.cxx
index cd42606d6..c2becc15a 100644
--- a/src/playlist/RssPlaylistPlugin.cxx
+++ b/src/playlist/RssPlaylistPlugin.cxx
@@ -25,14 +25,15 @@
 #include "Song.hxx"
 #include "tag/Tag.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "rss"
+static constexpr Domain rss_domain("rss");
 
 /**
  * This is the state object for the GLib XML parser.
@@ -221,7 +222,7 @@ rss_open_stream(struct input_stream *is)
 		if (nbytes == 0) {
 			if (error2.IsDefined()) {
 				g_markup_parse_context_free(context);
-				g_warning("%s", error2.GetMessage());
+				LogError(error2);
 				return NULL;
 			}
 
@@ -231,7 +232,8 @@ rss_open_stream(struct input_stream *is)
 		success = g_markup_parse_context_parse(context, buffer, nbytes,
 						       &error);
 		if (!success) {
-			g_warning("XML parser failed: %s", error->message);
+			FormatError(rss_domain,
+				    "XML parser failed: %s", error->message);
 			g_error_free(error);
 			g_markup_parse_context_free(context);
 			return NULL;
@@ -240,7 +242,8 @@ rss_open_stream(struct input_stream *is)
 
 	success = g_markup_parse_context_end_parse(context, &error);
 	if (!success) {
-		g_warning("XML parser failed: %s", error->message);
+		FormatError(rss_domain,
+			    "XML parser failed: %s", error->message);
 		g_error_free(error);
 		g_markup_parse_context_free(context);
 		return NULL;
diff --git a/src/playlist/SoundCloudPlaylistPlugin.cxx b/src/playlist/SoundCloudPlaylistPlugin.cxx
index e6dbf5cf9..99bef29e7 100644
--- a/src/playlist/SoundCloudPlaylistPlugin.cxx
+++ b/src/playlist/SoundCloudPlaylistPlugin.cxx
@@ -26,6 +26,8 @@
 #include "Song.hxx"
 #include "tag/Tag.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 #include <yajl/yajl_parse.h>
@@ -36,13 +38,16 @@ static struct {
 	char *apikey;
 } soundcloud_config;
 
+static constexpr Domain soundcloud_domain("soundcloud");
+
 static bool
 soundcloud_init(const config_param &param)
 {
 	soundcloud_config.apikey = param.DupBlockString("apikey");
 	if (soundcloud_config.apikey == NULL) {
-		g_debug("disabling the soundcloud playlist plugin "
-			"because API key is not set");
+		LogDebug(soundcloud_domain,
+			 "disabling the soundcloud playlist plugin "
+			 "because API key is not set");
 		return false;
 	}
 
@@ -254,7 +259,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
 							error);
 	if (input_stream == NULL) {
 		if (error.IsDefined())
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 		return -1;
 	}
 
@@ -269,7 +274,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
 			input_stream->Read(buffer, sizeof(buffer), error);
 		if (nbytes == 0) {
 			if (error.IsDefined())
-				g_warning("%s", error.GetMessage());
+				LogError(error);
 
 			if (input_stream->IsEOF()) {
 				done = true;
@@ -296,7 +301,7 @@ soundcloud_parse_json(const char *url, yajl_handle hand,
 		    )
 		{
 			unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes);
-			g_warning("%s", str);
+			LogError(soundcloud_domain, (const char *)str);
 			yajl_free_error(hand, str);
 			break;
 		}
@@ -341,7 +346,9 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
 	rest = p;
 
 	if (strcmp(scheme, "soundcloud") != 0) {
-		g_warning("incompatible scheme for soundcloud plugin: %s", scheme);
+		FormatWarning(soundcloud_domain,
+			      "incompatible scheme for soundcloud plugin: %s",
+			      scheme);
 		g_free(s);
 		return NULL;
 	}
@@ -361,7 +368,7 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond)
 	g_free(s);
 
 	if (u == NULL) {
-		g_warning("unknown soundcloud URI");
+		LogWarning(soundcloud_domain, "unknown soundcloud URI");
 		return NULL;
 	}
 
diff --git a/src/playlist/XspfPlaylistPlugin.cxx b/src/playlist/XspfPlaylistPlugin.cxx
index 797fd7f3a..9b55f1962 100644
--- a/src/playlist/XspfPlaylistPlugin.cxx
+++ b/src/playlist/XspfPlaylistPlugin.cxx
@@ -24,14 +24,15 @@
 #include "InputStream.hxx"
 #include "tag/Tag.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
 #include <assert.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "xspf"
+static constexpr Domain xspf_domain("xspf");
 
 /**
  * This is the state object for the GLib XML parser.
@@ -240,7 +241,7 @@ xspf_open_stream(struct input_stream *is)
 		if (nbytes == 0) {
 			if (error2.IsDefined()) {
 				g_markup_parse_context_free(context);
-				g_warning("%s", error2.GetMessage());
+				LogError(error2);
 				return NULL;
 			}
 
@@ -250,7 +251,8 @@ xspf_open_stream(struct input_stream *is)
 		success = g_markup_parse_context_parse(context, buffer, nbytes,
 						       &error);
 		if (!success) {
-			g_warning("XML parser failed: %s", error->message);
+			FormatError(xspf_domain,
+				    "XML parser failed: %s", error->message);
 			g_error_free(error);
 			g_markup_parse_context_free(context);
 			return NULL;
@@ -259,7 +261,8 @@ xspf_open_stream(struct input_stream *is)
 
 	success = g_markup_parse_context_end_parse(context, &error);
 	if (!success) {
-		g_warning("XML parser failed: %s", error->message);
+		FormatError(xspf_domain,
+			    "XML parser failed: %s", error->message);
 		g_error_free(error);
 		g_markup_parse_context_free(context);
 		return NULL;
diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx
index 7c0dc942a..1cf4ec851 100644
--- a/src/system/FatalError.cxx
+++ b/src/system/FatalError.cxx
@@ -20,6 +20,8 @@
 #include "config.h"
 #include "FatalError.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "LogV.hxx"
 
 #include <glib.h>
 
@@ -33,13 +35,12 @@
 #include <errno.h>
 #endif
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "FatalError"
+static constexpr Domain fatal_error_domain("fatal_error");
 
 void
 FatalError(const char *msg)
 {
-	g_critical("%s", msg);
+	LogError(fatal_error_domain, msg);
 	exit(EXIT_FAILURE);
 }
 
@@ -48,7 +49,7 @@ FormatFatalError(const char *fmt, ...)
 {
 	va_list ap;
 	va_start(ap, fmt);
-	g_logv(G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, fmt, ap);
+	LogFormatV(fatal_error_domain, LogLevel::ERROR, fmt, ap);
 	va_end(ap);
 
 	exit(EXIT_FAILURE);
@@ -88,7 +89,7 @@ FatalSystemError(const char *msg)
 	system_error = g_strerror(errno);
 #endif
 
-	g_critical("%s: %s", msg, system_error);
+	FormatError(fatal_error_domain, "%s: %s", msg, system_error);
 	exit(EXIT_FAILURE);
 }
 
diff --git a/src/tag/Aiff.cxx b/src/tag/Aiff.cxx
index 09d107d5b..51622fec7 100644
--- a/src/tag/Aiff.cxx
+++ b/src/tag/Aiff.cxx
@@ -19,6 +19,8 @@
 
 #include "config.h" /* must be first for large file support */
 #include "Aiff.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -26,11 +28,9 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "aiff"
+static constexpr Domain aiff_domain("aiff");
 
 struct aiff_header {
 	char id[4];
@@ -50,15 +50,14 @@ aiff_seek_id3(FILE *file)
 
 	struct stat st;
 	if (fstat(fileno(file), &st) < 0) {
-		g_warning("Failed to stat file descriptor: %s",
-			  g_strerror(errno));
+		LogErrno(aiff_domain, "Failed to stat file descriptor");
 		return 0;
 	}
 
 	/* seek to the beginning and read the AIFF header */
 
 	if (fseek(file, 0, SEEK_SET) != 0) {
-		g_warning("Failed to seek: %s", g_strerror(errno));
+		LogErrno(aiff_domain, "Failed to seek");
 		return 0;
 	}
 
diff --git a/src/tag/Riff.cxx b/src/tag/Riff.cxx
index 3d022e661..d756ebc30 100644
--- a/src/tag/Riff.cxx
+++ b/src/tag/Riff.cxx
@@ -19,6 +19,8 @@
 
 #include "config.h" /* must be first for large file support */
 #include "Riff.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -26,11 +28,9 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "riff"
+static constexpr Domain riff_domain("riff");
 
 struct riff_header {
 	char id[4];
@@ -50,15 +50,14 @@ riff_seek_id3(FILE *file)
 
 	struct stat st;
 	if (fstat(fileno(file), &st) < 0) {
-		g_warning("Failed to stat file descriptor: %s",
-			  g_strerror(errno));
+		LogErrno(riff_domain, "Failed to stat file descriptor");
 		return 0;
 	}
 
 	/* seek to the beginning and read the RIFF header */
 
 	if (fseek(file, 0, SEEK_SET) != 0) {
-		g_warning("Failed to seek: %s", g_strerror(errno));
+		LogErrno(riff_domain, "Failed to seek");
 		return 0;
 	}
 
diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx
index 2a217dc47..2f32ef6e5 100644
--- a/src/tag/TagId3.cxx
+++ b/src/tag/TagId3.cxx
@@ -24,6 +24,8 @@
 #include "Tag.hxx"
 #include "TagBuilder.hxx"
 #include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "Log.hxx"
 #include "ConfigGlobal.hxx"
 #include "Riff.hxx"
 #include "Aiff.hxx"
@@ -36,9 +38,6 @@
 #include <errno.h>
 #include <string.h>
 
-#undef G_LOG_DOMAIN
-#define G_LOG_DOMAIN "id3"
-
 #  ifndef ID3_FRAME_COMPOSER
 #    define ID3_FRAME_COMPOSER "TCOM"
 #  endif
@@ -58,6 +57,8 @@
 #define ID3_FRAME_ALBUM_ARTIST "TPE2"
 #endif
 
+static constexpr Domain id3_domain("id3");
+
 static inline bool
 tag_is_id3v1(struct id3_tag *tag)
 {
@@ -104,8 +105,9 @@ import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
 						nullptr, nullptr,
 						nullptr, nullptr);
 		if (utf8 == nullptr) {
-			g_debug("Unable to convert %s string to UTF-8: '%s'",
-				encoding, isostr);
+			FormatWarning(id3_domain,
+				      "Unable to convert %s string to UTF-8: '%s'",
+				      encoding, isostr);
 			g_free(isostr);
 			return nullptr;
 		}
@@ -526,7 +528,7 @@ tag_id3_riff_aiff_load(FILE *file)
 	id3_byte_t *buffer = (id3_byte_t *)g_malloc(size);
 	size_t ret = fread(buffer, size, 1, file);
 	if (ret != 1) {
-		g_warning("Failed to read RIFF chunk");
+		LogWarning(id3_domain, "Failed to read RIFF chunk");
 		g_free(buffer);
 		return nullptr;
 	}
@@ -564,7 +566,7 @@ tag_id3_scan(const char *path_fs,
 	struct id3_tag *tag = tag_id3_load(path_fs, error);
 	if (tag == nullptr) {
 		if (error.IsDefined())
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 
 		return false;
 	}
diff --git a/test/dump_playlist.cxx b/test/dump_playlist.cxx
index af73ed4e7..2f6b3ed2a 100644
--- a/test/dump_playlist.cxx
+++ b/test/dump_playlist.cxx
@@ -32,6 +32,7 @@
 #include "PlaylistPlugin.hxx"
 #include "fs/Path.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -174,7 +175,7 @@ int main(int argc, char **argv)
 	io_thread_start();
 
 	if (!input_stream_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
@@ -193,7 +194,7 @@ int main(int argc, char **argv)
 		is = input_stream::Open(uri, mutex, cond, error);
 		if (is == NULL) {
 			if (error.IsDefined())
-				g_warning("%s", error.GetMessage());
+				LogError(error);
 			else
 				g_printerr("input_stream::Open() failed\n");
 			return 2;
diff --git a/test/dump_text_file.cxx b/test/dump_text_file.cxx
index 286c63c93..685f0fbb9 100644
--- a/test/dump_text_file.cxx
+++ b/test/dump_text_file.cxx
@@ -25,6 +25,7 @@
 #include "stdbin.h"
 #include "TextInputStream.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #ifdef ENABLE_ARCHIVE
 #include "ArchiveList.hxx"
@@ -66,7 +67,7 @@ dump_input_stream(struct input_stream *is)
 	is->WaitReady();
 
 	if (!is->Check(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		is->Unlock();
 		return EXIT_FAILURE;
 	}
@@ -82,7 +83,7 @@ dump_input_stream(struct input_stream *is)
 	is->Lock();
 
 	if (!is->Check(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		is->Unlock();
 		return EXIT_FAILURE;
 	}
@@ -123,7 +124,7 @@ int main(int argc, char **argv)
 
 	Error error;
 	if (!input_stream_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
@@ -138,7 +139,7 @@ int main(int argc, char **argv)
 		is->Close();
 	} else {
 		if (error.IsDefined())
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 		else
 			g_printerr("input_stream::Open() failed\n");
 		ret = 2;
diff --git a/test/read_tags.cxx b/test/read_tags.cxx
index d7f2f38d9..b4e5b2bb0 100644
--- a/test/read_tags.cxx
+++ b/test/read_tags.cxx
@@ -28,6 +28,7 @@
 #include "tag/TagId3.hxx"
 #include "tag/ApeTag.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -167,7 +168,7 @@ int main(int argc, char **argv)
 
 	Error error;
 	if (!input_stream_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx
index 794302b5d..c2aa1d7d6 100644
--- a/test/run_decoder.cxx
+++ b/test/run_decoder.cxx
@@ -25,6 +25,7 @@
 #include "InputStream.hxx"
 #include "AudioFormat.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 #include "stdbin.h"
 
 #include <glib.h>
@@ -167,7 +168,7 @@ int main(int argc, char **argv)
 
 	Error error;
 	if (!input_stream_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
@@ -192,7 +193,7 @@ int main(int argc, char **argv)
 			input_stream::Open(decoder.uri, mutex, cond, error);
 		if (is == NULL) {
 			if (error.IsDefined())
-				g_warning("%s", error.GetMessage());
+				LogError(error);
 			else
 				g_printerr("input_stream::Open() failed\n");
 
diff --git a/test/run_inotify.cxx b/test/run_inotify.cxx
index 459d0c504..6796480e5 100644
--- a/test/run_inotify.cxx
+++ b/test/run_inotify.cxx
@@ -21,6 +21,7 @@
 #include "InotifySource.hxx"
 #include "event/Loop.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -68,14 +69,14 @@ int main(int argc, char **argv)
 						      my_inotify_callback,
 						      nullptr, error);
 	if (source == NULL) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
 	int descriptor = source->Add(path, IN_MASK, error);
 	if (descriptor < 0) {
 		delete source;
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
diff --git a/test/run_input.cxx b/test/run_input.cxx
index 79b57536a..d278676ce 100644
--- a/test/run_input.cxx
+++ b/test/run_input.cxx
@@ -26,6 +26,7 @@
 #include "InputInit.hxx"
 #include "IOThread.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #ifdef ENABLE_ARCHIVE
 #include "ArchiveList.hxx"
@@ -61,7 +62,7 @@ dump_input_stream(struct input_stream *is)
 	is->WaitReady();
 
 	if (!is->Check(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		is->Unlock();
 		return EXIT_FAILURE;
 	}
@@ -84,7 +85,7 @@ dump_input_stream(struct input_stream *is)
 		num_read = is->Read(buffer, sizeof(buffer), error);
 		if (num_read == 0) {
 			if (error.IsDefined())
-				g_warning("%s", error.GetMessage());
+				LogError(error);
 
 			break;
 		}
@@ -95,7 +96,7 @@ dump_input_stream(struct input_stream *is)
 	}
 
 	if (!is->Check(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		is->Unlock();
 		return EXIT_FAILURE;
 	}
@@ -136,7 +137,7 @@ int main(int argc, char **argv)
 #endif
 
 	if (!input_stream_global_init(error)) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return 2;
 	}
 
@@ -151,7 +152,7 @@ int main(int argc, char **argv)
 		is->Close();
 	} else {
 		if (error.IsDefined())
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 		else
 			g_printerr("input_stream::Open() failed\n");
 		ret = 2;
diff --git a/test/run_resolver.cxx b/test/run_resolver.cxx
index 559e12934..7da2fd5b2 100644
--- a/test/run_resolver.cxx
+++ b/test/run_resolver.cxx
@@ -20,6 +20,7 @@
 #include "config.h"
 #include "system/Resolver.hxx"
 #include "util/Error.hxx"
+#include "Log.hxx"
 
 #include <glib.h>
 
@@ -45,7 +46,7 @@ int main(int argc, char **argv)
 		resolve_host_port(argv[1], 80, AI_PASSIVE, SOCK_STREAM,
 				  error);
 	if (ai == NULL) {
-		g_warning("%s", error.GetMessage());
+		LogError(error);
 		return EXIT_FAILURE;
 	}
 
@@ -54,7 +55,7 @@ int main(int argc, char **argv)
 					     error);
 		if (p == NULL) {
 			freeaddrinfo(ai);
-			g_warning("%s", error.GetMessage());
+			LogError(error);
 			return EXIT_FAILURE;
 		}