Repository: godbus/dbus (go)

Issue:
Add some form of debug option
I have a program that exposes a DBus service. While implementing the service, I've messed up types and signatures a few times, and that was hard to debug.

To properly debug what was going on, I had to modify the dbus library to add debug prints, to see what calls were being made, which objects were being looked up, what responses were being sent out, et cetera.

It would be great if the library had some built-in support for debugging or extra logging like this, so I don't have to modify it each time.

The simplest form would be a `ConnOption` which turns on debugging, and then all those details get printed out via the `log` package.

A perhaps more versatile API would be a series of `ConnOption`s which set up callback functions for each kind of activity. For example:

```
conn, err := dbus.SessionBusPrivate(dbus.WithLookupInspector(func(v interface{}) {
    log.Println("lookup:", v)
}))
```

The name and `v interface{}` are just made up for the sake of the example.

Pull request description:
Add in/out message interceptors
Closes #160 

Patch:
diff --git a/conn.go b/conn.go
index b31eb56..0255fda 100644
--- a/conn.go
+++ b/conn.go
@@ -5,7 +5,6 @@ import (
 	"errors"
 	"io"
 	"os"
-	"reflect"
 	"strings"
 	"sync"
 )
@@ -38,6 +37,8 @@ type Conn struct {
 	handler       Handler
 	signalHandler SignalHandler
 	serialGen     SerialGenerator
+	inInt         Interceptor
+	outInt        Interceptor
 
 	names      *nameTracker
 	calls      *callTracker
@@ -190,6 +191,25 @@ func WithSerialGenerator(gen SerialGenerator) ConnOption {
 	}
 }
 
+// Interceptor intercepts incoming and outgoing messages.
+type Interceptor func(msg *Message)
+
+// WithIncomingInterceptor sets the given interceptor for incoming messages.
+func WithIncomingInterceptor(interceptor Interceptor) ConnOption {
+	return func(conn *Conn) error {
+		conn.inInt = interceptor
+		return nil
+	}
+}
+
+// WithOutgoingInterceptor sets the given interceptor for outgoing messages.
+func WithOutgoingInterceptor(interceptor Interceptor) ConnOption {
+	return func(conn *Conn) error {
+		conn.outInt = interceptor
+		return nil
+	}
+}
+
 // NewConn creates a new private *Conn from an already established connection.
 func NewConn(conn io.ReadWriteCloser, opts ...ConnOption) (*Conn, error) {
 	return newConn(genericTransport{conn}, opts...)
@@ -323,6 +343,10 @@ func (conn *Conn) inWorker() {
 			// Ignore it.
 			continue
 		}
+
+		if conn.inInt != nil {
+			conn.inInt(msg)
+		}
 		switch msg.Type {
 		case TypeError:
 			conn.serialGen.RetireSerial(conn.calls.handleDBusError(msg))
@@ -383,11 +407,10 @@ func (conn *Conn) Object(dest string, path ObjectPath) BusObject {
 	return &Object{conn, dest, path}
 }
 
-func (conn *Conn) sendMessage(msg *Message) {
-	conn.sendMessageAndIfClosed(msg, func() {})
-}
-
 func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) {
+	if conn.outInt != nil {
+		conn.outInt(msg)
+	}
 	err := conn.outHandler.sendAndIfClosed(msg, ifClosed)
 	conn.calls.handleSendError(msg, err)
 	if err != nil {
@@ -483,7 +506,7 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) {
 	if len(e.Body) > 0 {
 		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(e.Body...))
 	}
-	conn.sendMessage(msg)
+	conn.sendMessageAndIfClosed(msg, nil)
 }
 
 // sendReply creates a method reply message corresponding to the parameters and
@@ -501,7 +524,7 @@ func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) {
 	if len(values) > 0 {
 		msg.Headers[FieldSignature] = MakeVariant(SignatureOf(values...))
 	}
-	conn.sendMessage(msg)
+	conn.sendMessageAndIfClosed(msg, nil)
 }
 
 // AddMatchSignal registers the given match rule to receive broadcast
@@ -635,18 +658,6 @@ func getTransport(address string) (transport, error) {
 	return nil, err
 }
 
-// dereferenceAll returns a slice that, assuming that vs is a slice of pointers
-// of arbitrary types, contains the values that are obtained from dereferencing
-// all elements in vs.
-func dereferenceAll(vs []interface{}) []interface{} {
-	for i := range vs {
-		v := reflect.ValueOf(vs[i])
-		v = v.Elem()
-		vs[i] = v.Interface()
-	}
-	return vs
-}
-
 // getKey gets a key from a the list of keys. Returns "" on error / not found...
 func getKey(s, key string) string {
 	for _, keyEqualsValue := range strings.Split(s, ",") {
@@ -671,7 +682,9 @@ func (h *outputHandler) sendAndIfClosed(msg *Message, ifClosed func()) error {
 	h.closed.lck.RLock()
 	defer h.closed.lck.RUnlock()
 	if h.closed.isClosed {
-		ifClosed()
+		if ifClosed != nil {
+			ifClosed()
+		}
 		return nil
 	}
 	h.sendLck.Lock()
diff --git a/export.go b/export.go
index 95d0e29..c277ab1 100644
--- a/export.go
+++ b/export.go
@@ -171,7 +171,7 @@ func (conn *Conn) handleCall(msg *Message) {
 		}
 		reply.Headers[FieldSignature] = MakeVariant(SignatureOf(reply.Body...))
 
-		conn.sendMessage(reply)
+		conn.sendMessageAndIfClosed(reply, nil)
 	}
 }
 


Test patch:
diff --git a/conn_test.go b/conn_test.go
index d6366c7..13c15e5 100644
--- a/conn_test.go
+++ b/conn_test.go
@@ -4,6 +4,7 @@ import (
 	"encoding/binary"
 	"io"
 	"io/ioutil"
+	"log"
 	"testing"
 	"time"
 )
@@ -430,3 +431,25 @@ func TestGetKey(t *testing.T) {
 		t.Error(`Expected "ipv4", got`, family)
 	}
 }
+
+func TestInterceptors(t *testing.T) {
+	conn, err := SessionBusPrivate(
+		WithIncomingInterceptor(func(msg *Message) {
+			log.Println("<", msg)
+		}),
+		WithOutgoingInterceptor(func(msg *Message) {
+			log.Println(">", msg)
+		}),
+	)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer conn.Close()
+
+	if err = conn.Auth(nil); err != nil {
+		t.Fatal(err)
+	}
+	if err = conn.Hello(); err != nil {
+		t.Fatal(err)
+	}
+}