diff --git a/gdbus/gdbus.h b/gdbus/gdbus.h
index d7be17661149..24006a0d54d6 100644
--- a/gdbus/gdbus.h
+++ b/gdbus/gdbus.h
@@ -234,6 +234,12 @@ struct GDBusSecurityTable {
 void g_dbus_set_flags(int flags);
 int g_dbus_get_flags(void);
 
+typedef void (*g_dbus_destroy_func_t)(void *user_data);
+typedef void (*g_dbus_debug_func_t)(const char *str, void *user_data);
+
+void g_dbus_set_debug(g_dbus_debug_func_t cb, void *user_data,
+				g_dbus_destroy_func_t destroy);
+
 gboolean g_dbus_register_interface(DBusConnection *connection,
 					const char *path, const char *name,
 					const GDBusMethodTable *methods,
diff --git a/gdbus/object.c b/gdbus/object.c
index 72d2d46e30ef..1c19355cdbfe 100644
--- a/gdbus/object.c
+++ b/gdbus/object.c
@@ -20,6 +20,7 @@
 #include <dbus/dbus.h>
 
 #include "gdbus.h"
+#include "src/shared/util.h"
 
 #define info(fmt...)
 #define error(fmt...)
@@ -72,9 +73,16 @@ struct property_data {
 	DBusMessage *message;
 };
 
+struct debug_data {
+	g_dbus_debug_func_t func;
+	g_dbus_destroy_func_t destroy;
+	void *data;
+};
+
 static int global_flags = 0;
 static struct generic_data *root;
 static GSList *pending = NULL;
+static struct debug_data debug = { NULL, NULL, NULL };
 
 static gboolean process_changes(gpointer user_data);
 static void process_properties_from_interface(struct generic_data *data,
@@ -124,6 +132,20 @@ static bool check_testing(int flags, int flag)
 	return !(global_flags & G_DBUS_FLAG_ENABLE_TESTING);
 }
 
+static void g_dbus_debug(const char *format, ...)
+{
+	va_list va;
+	char str[MAX_INPUT];
+
+	if (!format || !debug.func)
+		return;
+
+	va_start(va, format);
+	vsnprintf(str, sizeof(str), format, va);
+	debug.func(str, debug.data);
+	va_end(va);
+}
+
 static void generate_interface_xml(GString *gstr, struct interface_data *iface)
 {
 	const GDBusMethodTable *method;
@@ -565,6 +587,22 @@ static void append_interface(gpointer data, gpointer user_data)
 	dbus_message_iter_close_container(array, &entry);
 }
 
+static const char *dbus_message_type_string(DBusMessage *msg)
+{
+	return dbus_message_type_to_string(dbus_message_get_type(msg));
+}
+
+static void g_dbus_send_unref(DBusConnection *conn, DBusMessage *msg)
+{
+	g_dbus_debug("[%s] %s.%s",
+			dbus_message_type_string(msg),
+			dbus_message_get_interface(msg),
+			dbus_message_get_member(msg));
+
+	dbus_connection_send(conn, msg, NULL);
+	dbus_message_unref(msg);
+}
+
 static void emit_interfaces_added(struct generic_data *data)
 {
 	DBusMessage *signal;
@@ -605,9 +643,8 @@ static void emit_interfaces_added(struct generic_data *data)
 
 	dbus_message_iter_close_container(&iter, &array);
 
-	/* Use dbus_connection_send to avoid recursive calls to g_dbus_flush */
-	dbus_connection_send(data->conn, signal, NULL);
-	dbus_message_unref(signal);
+	/* Use g_dbus_send_unref to avoid recursive calls to g_dbus_flush */
+	g_dbus_send_unref(data->conn, signal);
 }
 
 static struct interface_data *find_interface(GSList *interfaces,
@@ -1002,9 +1039,8 @@ static void emit_interfaces_removed(struct generic_data *data)
 
 	dbus_message_iter_close_container(&iter, &array);
 
-	/* Use dbus_connection_send to avoid recursive calls to g_dbus_flush */
-	dbus_connection_send(data->conn, signal, NULL);
-	dbus_message_unref(signal);
+	/* Use g_dbus_send_unref to avoid recursive calls to g_dbus_flush */
+	g_dbus_send_unref(data->conn, signal);
 }
 
 static void remove_pending(struct generic_data *data)
@@ -1069,6 +1105,13 @@ static DBusHandlerResult generic_message(DBusConnection *connection,
 	const GDBusMethodTable *method;
 	const char *interface;
 
+	g_dbus_debug("[%s:%s] > %s.%s [#%d]",
+			dbus_message_get_sender(message),
+			dbus_message_type_string(message),
+			dbus_message_get_interface(message),
+			dbus_message_get_member(message),
+			dbus_message_get_serial(message));
+
 	interface = dbus_message_get_interface(message);
 
 	iface = find_interface(data->interfaces, interface);
@@ -1559,6 +1602,35 @@ gboolean g_dbus_send_message(DBusConnection *connection, DBusMessage *message)
 	/* Flush pending signal to guarantee message order */
 	g_dbus_flush(connection);
 
+	switch (dbus_message_get_type(message)) {
+	case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+		g_dbus_debug("[%s:%s] < [#%d]",
+				dbus_message_get_destination(message),
+				dbus_message_type_string(message),
+				dbus_message_get_reply_serial(message));
+		break;
+	case DBUS_MESSAGE_TYPE_ERROR:
+		g_dbus_debug("[%s:%s] < %s [#%d]",
+				dbus_message_get_destination(message),
+				dbus_message_type_string(message),
+				dbus_message_get_error_name(message),
+				dbus_message_get_reply_serial(message));
+		break;
+	case DBUS_MESSAGE_TYPE_SIGNAL:
+		g_dbus_debug("[%s] %s.%s",
+				dbus_message_type_string(message),
+				dbus_message_get_interface(message),
+				dbus_message_get_member(message));
+		break;
+	default:
+		g_dbus_debug("[%s:%s] < %s.%s",
+				dbus_message_get_destination(message),
+				dbus_message_type_string(message),
+				dbus_message_get_interface(message),
+				dbus_message_get_member(message));
+		break;
+	}
+
 	result = dbus_connection_send(connection, message, NULL);
 
 out:
@@ -1584,6 +1656,12 @@ gboolean g_dbus_send_message_with_reply(DBusConnection *connection,
 		return FALSE;
 	}
 
+	g_dbus_debug("[%s:%s] < %s.%s",
+			dbus_message_get_destination(message),
+			dbus_message_type_string(message),
+			dbus_message_get_interface(message),
+			dbus_message_get_member(message));
+
 	return ret;
 }
 
@@ -1756,9 +1834,8 @@ static void process_properties_from_interface(struct generic_data *data,
 	g_slist_free(iface->pending_prop);
 	iface->pending_prop = NULL;
 
-	/* Use dbus_connection_send to avoid recursive calls to g_dbus_flush */
-	dbus_connection_send(data->conn, signal, NULL);
-	dbus_message_unref(signal);
+	/* Use g_dbus_send_unref to avoid recursive calls to g_dbus_flush */
+	g_dbus_send_unref(data->conn, signal);
 }
 
 static void process_property_changes(struct generic_data *data)
@@ -1885,3 +1962,14 @@ int g_dbus_get_flags(void)
 {
 	return global_flags;
 }
+
+void g_dbus_set_debug(g_dbus_debug_func_t cb, void *user_data,
+				g_dbus_destroy_func_t destroy)
+{
+	if (debug.destroy)
+		debug.destroy(debug.data);
+
+	debug.func = cb;
+	debug.destroy = destroy;
+	debug.data = user_data;
+}
