diff --git a/go.mod b/go.mod
index e6543b1..6437375 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.12
 require (
 	github.com/arduino/arduino-cli v0.0.0-20201215104024-6a177ebf56f2
 	github.com/arduino/go-paths-helper v1.5.0
+	github.com/fatih/color v1.7.0
 	github.com/pkg/errors v0.9.1
 	github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37
 	github.com/stretchr/testify v1.6.1
diff --git a/handler/builder.go b/handler/builder.go
index e42a4d3..cbdfe17 100644
--- a/handler/builder.go
+++ b/handler/builder.go
@@ -54,6 +54,7 @@ func (handler *InoHandler) rebuildEnvironmentLoop() {
 		// Regenerate preprocessed sketch!
 		done := make(chan bool)
 		go func() {
+			defer streams.CatchAndLogPanic()
 
 			handler.progressHandler.Create("arduinoLanguageServerRebuild")
 			handler.progressHandler.Begin("arduinoLanguageServerRebuild", &lsp.WorkDoneProgressBegin{
diff --git a/handler/handler.go b/handler/handler.go
index aeea175..dcfd574 100644
--- a/handler/handler.go
+++ b/handler/handler.go
@@ -83,7 +83,10 @@ func NewInoHandler(stdio io.ReadWriteCloser, board lsp.Board) *InoHandler {
 	handler.clangdStarted = sync.NewCond(&handler.dataMux)
 	stdStream := jsonrpc2.NewBufferedStream(stdio, jsonrpc2.VSCodeObjectCodec{})
 	var stdHandler jsonrpc2.Handler = jsonrpc2.HandlerWithError(handler.HandleMessageFromIDE)
-	handler.StdioConn = jsonrpc2.NewConn(context.Background(), stdStream, stdHandler)
+	handler.StdioConn = jsonrpc2.NewConn(context.Background(), stdStream, stdHandler,
+		jsonrpc2.OnRecv(streams.JSONRPCConnLogOnRecv("IDE --> LS     CL:")),
+		jsonrpc2.OnSend(streams.JSONRPCConnLogOnSend("IDE <-- LS     CL:")),
+	)
 
 	handler.progressHandler = NewProgressProxy(handler.StdioConn)
 
@@ -176,6 +179,8 @@ func (handler *InoHandler) HandleMessageFromIDE(ctx context.Context, conn *jsonr
 		// method "initialize"
 
 		go func() {
+			defer streams.CatchAndLogPanic()
+
 			// Start clangd asynchronously
 			log.Printf("LS  --- initializing workbench (queued)")
 			handler.dataMux.Lock()
@@ -514,7 +519,9 @@ func (handler *InoHandler) initializeWorkbench(ctx context.Context, params *lsp.
 
 		clangdStream := jsonrpc2.NewBufferedStream(clangdStdio, jsonrpc2.VSCodeObjectCodec{})
 		clangdHandler := AsyncHandler{jsonrpc2.HandlerWithError(handler.FromClangd)}
-		handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler)
+		handler.ClangdConn = jsonrpc2.NewConn(context.Background(), clangdStream, clangdHandler,
+			jsonrpc2.OnRecv(streams.JSONRPCConnLogOnRecv("IDE     LS <-- CL:")),
+			jsonrpc2.OnSend(streams.JSONRPCConnLogOnSend("IDE     LS --> CL:")))
 
 		// Send initialization command to clangd
 		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
@@ -1087,12 +1094,15 @@ func (handler *InoHandler) transformClangdResult(method string, inoURI, cppURI l
 
 		if r.DocumentSymbolArray != nil {
 			// Treat the input as []DocumentSymbol
+			log.Printf("    <-- documentSymbol(%d document symbols)", len(*r.DocumentSymbolArray))
 			return handler.cpp2inoDocumentSymbols(*r.DocumentSymbolArray, inoURI)
 		} else if r.SymbolInformationArray != nil {
 			// Treat the input as []SymbolInformation
+			log.Printf("    <-- documentSymbol(%d symbol information)", len(*r.SymbolInformationArray))
 			return handler.cpp2inoSymbolInformation(*r.SymbolInformationArray)
 		} else {
 			// Treat the input as null
+			log.Printf("    <-- null documentSymbol")
 		}
 
 	case *[]lsp.CommandOrCodeAction:
diff --git a/streams/jsonrpc2.go b/streams/jsonrpc2.go
new file mode 100644
index 0000000..3887399
--- /dev/null
+++ b/streams/jsonrpc2.go
@@ -0,0 +1,52 @@
+package streams
+
+import (
+	"log"
+	"runtime/debug"
+
+	"github.com/fatih/color"
+	"github.com/sourcegraph/jsonrpc2"
+)
+
+var green = color.New(color.FgHiGreen)
+var red = color.New(color.FgHiRed)
+
+// JSONRPCConnLogOnRecv perform logging of the given req and resp
+func JSONRPCConnLogOnRecv(prefix string) func(req *jsonrpc2.Request, resp *jsonrpc2.Response) {
+	return func(req *jsonrpc2.Request, resp *jsonrpc2.Response) {
+		jsonrpcLog(prefix, req, resp, false)
+	}
+}
+
+// JSONRPCConnLogOnSend perform logging of the given req and resp
+func JSONRPCConnLogOnSend(prefix string) func(req *jsonrpc2.Request, resp *jsonrpc2.Response) {
+	return func(req *jsonrpc2.Request, resp *jsonrpc2.Response) {
+		jsonrpcLog(prefix, req, resp, true)
+	}
+}
+
+func jsonrpcLog(prefix string, req *jsonrpc2.Request, resp *jsonrpc2.Response, sending bool) {
+	color.NoColor = false
+	var c *color.Color
+	if sending {
+		c = red
+	} else {
+		c = green
+	}
+	if resp != nil {
+		if req != nil {
+			log.Print(c.Sprintf(prefix+" ANSWER %s %v (%v)", req.Method, req.ID, resp.ID))
+		} else {
+			log.Print(c.Sprintf(prefix+" ANSWER UNBOUND (%v)", resp.ID))
+		}
+	} else if req != nil {
+		if !req.Notif {
+			log.Print(c.Sprintf(prefix+" REQUEST %s %v", req.Method, req.ID))
+		} else {
+			log.Print(c.Sprintf(prefix+" NOTIFICATION %s", req.Method))
+		}
+	} else {
+		log.Print(green.Sprintf(prefix + " NULL MESSAGE"))
+		log.Print(string(debug.Stack()))
+	}
+}