diff --git a/src/client/Client.hxx b/src/client/Client.hxx
index 06b5a68b5..2f030b486 100644
--- a/src/client/Client.hxx
+++ b/src/client/Client.hxx
@@ -39,6 +39,8 @@ class Client final
 	friend struct ClientPerPartitionListHook;
 	friend class ClientList;
 
+	const std::string name;
+
 	IntrusiveListHook<> list_siblings, partition_siblings;
 
 	CoarseTimerEvent timeout_event;
@@ -52,8 +54,6 @@ class Client final
 
 	CommandListBuilder cmd_list;
 
-	const unsigned int num;	/* client number */
-
 	/** is this client waiting for an "idle" response? */
 	bool idle_waiting = false;
 
@@ -122,7 +122,7 @@ public:
 	Client(EventLoop &loop, Partition &partition,
 	       UniqueSocketDescriptor fd, int uid,
 	       unsigned _permission,
-	       int num) noexcept;
+	       std::string &&_name) noexcept;
 
 	~Client() noexcept;
 
diff --git a/src/client/Event.cxx b/src/client/Event.cxx
index 52afc9404..870d4eb79 100644
--- a/src/client/Event.cxx
+++ b/src/client/Event.cxx
@@ -9,7 +9,7 @@
 void
 Client::OnSocketError(std::exception_ptr ep) noexcept
 {
-	FmtError(client_domain, "error on client {}: {}", num, ep);
+	FmtError(client_domain, "[{}] error: {}", name, ep);
 
 	SetExpired();
 }
diff --git a/src/client/Expire.cxx b/src/client/Expire.cxx
index b26b1d01b..a775d3947 100644
--- a/src/client/Expire.cxx
+++ b/src/client/Expire.cxx
@@ -28,7 +28,7 @@ Client::OnTimeout() noexcept
 		assert(!idle_waiting);
 		assert(!background_command);
 
-		FmtDebug(client_domain, "[{}] timeout", num);
+		FmtDebug(client_domain, "[{}] timeout", name);
 	}
 
 	Close();
diff --git a/src/client/New.cxx b/src/client/New.cxx
index 36d9f616d..84788b5fd 100644
--- a/src/client/New.cxx
+++ b/src/client/New.cxx
@@ -12,10 +12,13 @@
 #include "net/PeerCredentials.hxx"
 #include "net/UniqueSocketDescriptor.hxx"
 #include "net/SocketAddress.hxx"
+#include "net/ToString.hxx"
 #include "util/SpanCast.hxx"
 #include "Log.hxx"
 #include "Version.h"
 
+#include <fmt/core.h>
+
 #include <cassert>
 
 using std::string_view_literals::operator""sv;
@@ -25,27 +28,41 @@ static constexpr auto GREETING = "OK MPD " PROTOCOL_VERSION "\n"sv;
 Client::Client(EventLoop &_loop, Partition &_partition,
 	       UniqueSocketDescriptor _fd,
 	       int _uid, unsigned _permission,
-	       int _num) noexcept
+	       std::string &&_name) noexcept
 	:FullyBufferedSocket(_fd.Release(), _loop,
 			     16384, client_max_output_buffer_size),
+	 name(std::move(_name)),
 	 timeout_event(_loop, BIND_THIS_METHOD(OnTimeout)),
 	 partition(&_partition),
 	 permission(_permission),
 	 uid(_uid),
-	 num(_num),
 	 last_album_art(_loop)
 {
+	FmtInfo(client_domain, "[{}] client connected", name);
+
 	timeout_event.Schedule(client_timeout);
 }
 
+[[gnu::pure]]
+static std::string
+MakeClientName(SocketAddress address, const SocketPeerCredentials &cred) noexcept
+{
+	if (cred.IsDefined()) {
+		if (cred.GetPid() > 0)
+			return fmt::format("pid={} uid={}", cred.GetPid(), cred.GetUid());
+
+		return fmt::format("uid={}", cred.GetUid());
+	}
+
+	return ToString(address);
+}
+
 void
 client_new(EventLoop &loop, Partition &partition,
 	   UniqueSocketDescriptor fd, SocketAddress remote_address,
 	   SocketPeerCredentials cred,
 	   unsigned permission) noexcept
 {
-	static unsigned int next_client_num;
-
 	assert(fd.IsDefined());
 
 	ClientList &client_list = *partition.instance.client_list;
@@ -58,16 +75,12 @@ client_new(EventLoop &loop, Partition &partition,
 
 	const int uid = cred.IsDefined() ? static_cast<int>(cred.GetUid()) : -1;
 
-	const unsigned num = next_client_num++;
 	auto *client = new Client(loop, partition, std::move(fd), uid,
-				    permission,
-				    num);
+				  permission,
+				  MakeClientName(remote_address, cred));
 
 	client_list.Add(*client);
 	partition.clients.push_back(*client);
-
-	FmtInfo(client_domain, "[{}] opened from {}",
-		num, remote_address);
 }
 
 void
@@ -79,6 +92,6 @@ Client::Close() noexcept
 	if (FullyBufferedSocket::IsDefined())
 		FullyBufferedSocket::Close();
 
-	FmtInfo(client_domain, "[{}] closed", num);
+	FmtInfo(client_domain, "[{}] disconnected", name);
 	delete this;
 }
diff --git a/src/client/Process.cxx b/src/client/Process.cxx
index 6aa5225d1..3956b3d07 100644
--- a/src/client/Process.cxx
+++ b/src/client/Process.cxx
@@ -54,14 +54,14 @@ Client::ProcessLine(char *line) noexcept
 		   request */
 		FmtWarning(client_domain,
 			   "[{}] malformed command {:?}",
-			   num, line);
+			   name, line);
 		return CommandResult::CLOSE;
 	}
 
 	if (cmd_list.IsActive() && IsAsyncCommmand(line)) {
 		FmtWarning(client_domain,
 			   "[{}] not possible in comand list: {:?}",
-			   num, line);
+			   name, line);
 		return CommandResult::CLOSE;
 	}
 
@@ -82,17 +82,15 @@ Client::ProcessLine(char *line) noexcept
 		   except "noidle" */
 		FmtWarning(client_domain,
 			   "[{}] command {:?} during idle",
-			   num, line);
+			   name, line);
 		return CommandResult::CLOSE;
 	}
 
 	if (cmd_list.IsActive()) {
 		if (StringIsEqual(line, CLIENT_LIST_MODE_END)) {
-			const unsigned id = num;
-
 			FmtDebug(client_domain,
 				 "[{}] process command list",
-				 id);
+				 name);
 
 			const bool ok_mode = cmd_list.IsOKMode();
 			auto list = cmd_list.Commit();
@@ -102,7 +100,7 @@ Client::ProcessLine(char *line) noexcept
 						      std::move(list));
 			FmtDebug(client_domain,
 				 "[{}] process command "
-				 "list returned {}", id, unsigned(ret));
+				 "list returned {}", name, unsigned(ret));
 
 			if (ret == CommandResult::OK)
 				WriteOK();
@@ -113,7 +111,7 @@ Client::ProcessLine(char *line) noexcept
 				FmtWarning(client_domain,
 					   "[{}] command list size "
 					   "is larger than the max ({})",
-					   num, client_max_command_list_size);
+					   name, client_max_command_list_size);
 				return CommandResult::CLOSE;
 			}
 
@@ -127,15 +125,13 @@ Client::ProcessLine(char *line) noexcept
 			cmd_list.Begin(true);
 			return CommandResult::OK;
 		} else {
-			const unsigned id = num;
-
 			FmtDebug(client_domain,
 				 "[{}] process command {:?}",
-				 id, line);
+				 name, line);
 			auto ret = command_process(*this, 0, line);
 			FmtDebug(client_domain,
 				 "[{}] command returned {}",
-				 id, unsigned(ret));
+				 name, unsigned(ret));
 
 			if (IsExpired())
 				return CommandResult::CLOSE;