Repository: rollbar/rollbar-go
Pull request #11 (2018-01-30 18:00:34)

Heroku fixes
Heroku made the following updates to their fork which I hadn't picked up yet. This PR implements these changes.

https://github.com/heroku/rollbar/commit/723a5e3c83536c8dea2c5160852376b2ad6543db
https://github.com/heroku/rollbar/commit/12877eb78310fc8eef58d44a34fdfba3f94febf2
https://github.com/heroku/rollbar/commit/6ca93fdfd7b07d115499e1b2e15685d2e3551de1


Patch:
diff --git a/async_transport.go b/async_transport.go
index adc3cfb..c7df913 100644
--- a/async_transport.go
+++ b/async_transport.go
@@ -5,8 +5,16 @@ import (
 )
 
 type AsyncTransport struct {
-	Token       string
-	Endpoint    string
+	// Rollbar access token used by this transport for communication with the Rollbar API.
+	Token string
+	// Endpoint to post items to.
+	Endpoint string
+	// Logger used to report errors when sending data to Rollbar, e.g.
+	// when the Rollbar API returns 409 Too Many Requests response.
+	// If not set, the client will use the standard log.Printf by default.
+	Logger ClientLogger
+	// Buffer is the size of the channel used for queueing asynchronous payloads for sending to
+	// Rollbar.
 	Buffer      int
 	bodyChannel chan map[string]interface{}
 	waitGroup   sync.WaitGroup
@@ -34,8 +42,8 @@ func (t *AsyncTransport) Send(body map[string]interface{}) error {
 		t.waitGroup.Add(1)
 		t.bodyChannel <- body
 	} else {
-		var err = ErrBufferFull{}
-		rollbarError(err.Error())
+		err := ErrBufferFull{}
+		rollbarError(t.Logger, err.Error())
 		return err
 	}
 	return nil
@@ -58,6 +66,10 @@ func (t *AsyncTransport) SetEndpoint(endpoint string) {
 	t.Endpoint = endpoint
 }
 
+func (t *AsyncTransport) SetLogger(logger ClientLogger) {
+	t.Logger = logger
+}
+
 func (t *AsyncTransport) post(body map[string]interface{}) error {
-	return clientPost(t.Token, t.Endpoint, body)
+	return clientPost(t.Token, t.Endpoint, body, t.Logger)
 }
diff --git a/client.go b/client.go
index 2ff4819..bab7676 100644
--- a/client.go
+++ b/client.go
@@ -19,6 +19,8 @@ import (
 // type.
 type Client struct {
 	io.Closer
+	// Transport used to send data to the Rollbar API. By default an asyncronous
+	// implementation of the Transport interface is used.
 	Transport     Transport
 	configuration configuration
 }
@@ -116,6 +118,11 @@ func (c *Client) SetFingerprint(fingerprint bool) {
 	c.configuration.fingerprint = fingerprint
 }
 
+// Set the logger on the underlying transport
+func (c *Client) SetLogger(logger ClientLogger) {
+	c.Transport.SetLogger(logger)
+}
+
 // Regular expression used to match headers for scrubbing
 // The default value is regexp.MustCompile("Authorization")
 func (c *Client) SetScrubHeaders(headers *regexp.Regexp) {
@@ -421,27 +428,27 @@ func createConfiguration(token, environment, codeVersion, serverHost, serverRoot
 	}
 }
 
-func clientPost(token, endpoint string, body map[string]interface{}) error {
+func clientPost(token, endpoint string, body map[string]interface{}, logger ClientLogger) error {
 	if len(token) == 0 {
-		rollbarError("empty token")
+		rollbarError(logger, "empty token")
 		return nil
 	}
 
 	jsonBody, err := json.Marshal(body)
 	if err != nil {
-		rollbarError("failed to encode payload: %s", err.Error())
+		rollbarError(logger, "failed to encode payload: %s", err.Error())
 		return err
 	}
 
 	resp, err := http.Post(endpoint, "application/json", bytes.NewReader(jsonBody))
 	if err != nil {
-		rollbarError("POST failed: %s", err.Error())
+		rollbarError(logger, "POST failed: %s", err.Error())
 		return err
 	}
 	defer resp.Body.Close()
 
 	if resp.StatusCode != 200 {
-		rollbarError("received response: %s", resp.Status)
+		rollbarError(logger, "received response: %s", resp.Status)
 		return ErrHTTPError(resp.StatusCode)
 	}
 
diff --git a/rollbar.go b/rollbar.go
index 06fc2bb..8e56810 100644
--- a/rollbar.go
+++ b/rollbar.go
@@ -1,7 +1,6 @@
 package rollbar
 
 import (
-	"log"
 	"net/http"
 	"os"
 	"regexp"
@@ -114,6 +113,11 @@ func SetFingerprint(fingerprint bool) {
 	std.SetFingerprint(fingerprint)
 }
 
+// Set an alternative logger to be used by the underlying transport layer.
+func SetLogger(logger ClientLogger) {
+	std.SetLogger(logger)
+}
+
 // -- Getters
 
 // Rollbar access token.
@@ -271,7 +275,7 @@ func Log(level string, interfaces ...interface{}) {
 		case map[string]interface{}:
 			extras = val
 		default:
-			rollbarError("Unknown input type: %T", val)
+			rollbarError(std.Transport.(*AsyncTransport).Logger, "Unknown input type: %T", val)
 		}
 	}
 	if err != nil {
@@ -393,10 +397,3 @@ type CauseStacker interface {
 	Cause() error
 	Stack() Stack
 }
-
-// -- rollbarError
-
-func rollbarError(format string, args ...interface{}) {
-	format = "Rollbar error: " + format + "\n"
-	log.Printf(format, args...)
-}
diff --git a/sync_transport.go b/sync_transport.go
index 66471d5..40d3154 100644
--- a/sync_transport.go
+++ b/sync_transport.go
@@ -1,8 +1,14 @@
 package rollbar
 
 type SyncTransport struct {
-	Token    string
+	// Rollbar access token used by this transport for communication with the Rollbar API.
+	Token string
+	// Endpoint to post items to.
 	Endpoint string
+	// Logger used to report errors when sending data to Rollbar, e.g.
+	// when the Rollbar API returns 409 Too Many Requests response.
+	// If not set, the client will use the standard log.Printf by default.
+	Logger ClientLogger
 }
 
 func NewSyncTransport(token, endpoint string) *SyncTransport {
@@ -13,7 +19,7 @@ func NewSyncTransport(token, endpoint string) *SyncTransport {
 }
 
 func (t *SyncTransport) Send(body map[string]interface{}) error {
-	return clientPost(t.Token, t.Endpoint, body)
+	return clientPost(t.Token, t.Endpoint, body, t.Logger)
 }
 
 func (t *SyncTransport) Wait() {}
@@ -29,3 +35,7 @@ func (t *SyncTransport) SetToken(token string) {
 func (t *SyncTransport) SetEndpoint(endpoint string) {
 	t.Endpoint = endpoint
 }
+
+func (t *SyncTransport) SetLogger(logger ClientLogger) {
+	t.Logger = logger
+}
diff --git a/transforms.go b/transforms.go
index a06792b..372c48a 100644
--- a/transforms.go
+++ b/transforms.go
@@ -16,14 +16,6 @@ import (
 func buildBody(configuration configuration, level, title string, extras map[string]interface{}) map[string]interface{} {
 	timestamp := time.Now().Unix()
 
-	custom := configuration.custom
-	if extras != nil && custom == nil {
-		custom = make(map[string]interface{})
-	}
-	for k, v := range extras {
-		custom[k] = v
-	}
-
 	data := map[string]interface{}{
 		"environment":  configuration.environment,
 		"title":        title,
@@ -42,6 +34,7 @@ func buildBody(configuration configuration, level, title string, extras map[stri
 		},
 	}
 
+	custom := buildCustom(configuration.custom, extras)
 	if custom != nil {
 		data["custom"] = custom
 	}
@@ -61,6 +54,20 @@ func buildBody(configuration configuration, level, title string, extras map[stri
 	}
 }
 
+func buildCustom(custom map[string]interface{}, extras map[string]interface{}) map[string]interface{} {
+	if custom == nil && extras == nil {
+		return nil
+	}
+	m := map[string]interface{}{}
+	for k, v := range custom {
+		m[k] = v
+	}
+	for k, v := range extras {
+		m[k] = v
+	}
+	return m
+}
+
 func addErrorToBody(configuration configuration, body map[string]interface{}, err error, skip int) map[string]interface{} {
 	data := body["data"].(map[string]interface{})
 	errBody, fingerprint := errorBody(configuration, err, skip)
diff --git a/transport.go b/transport.go
index 96755bd..6eb99fb 100644
--- a/transport.go
+++ b/transport.go
@@ -2,20 +2,49 @@ package rollbar
 
 import (
 	"io"
+	"log"
 )
 
 const (
+	// We use a buffered channel for queueing items to send to Rollbar in the asynchronous
+	// implementation of Transport. By default the channel has a capacity of this size.
 	DEFAULT_BUFFER = 1000
 )
 
+// Transport represents an object used for communicating with the Rollbar API.
 type Transport interface {
 	io.Closer
+	// Send the body to the API, returning an error if the send fails. If the implementation to
+	// asyncronous, then a failure can still occur when this method returns no error. In that case
+	// this error represents a failure (or not) of enqueuing the payload.
 	Send(body map[string]interface{}) error
+	// Wait blocks until all messages currently waiting to be processed have been sent.
 	Wait()
+	// Set the access token to use for sending items with this transport.
 	SetToken(token string)
+	// Set the endpoint to send items to.
 	SetEndpoint(endpoint string)
+	// Set the logger to use instead of the standard log.Printf
+	SetLogger(logger ClientLogger)
 }
 
+// ClientLogger is the interface used by the rollbar Client/Transport to report problems.
+type ClientLogger interface {
+	Printf(format string, args ...interface{})
+}
+
+/// NewTransport creates a transport that sends items to the Rollbar API asyncronously.
 func NewTransport(token, endpoint string) Transport {
 	return NewAsyncTransport(token, endpoint, DEFAULT_BUFFER)
 }
+
+// -- rollbarError
+
+func rollbarError(logger ClientLogger, format string, args ...interface{}) {
+	format = "Rollbar error: " + format + "\n"
+	if logger != nil {
+		logger.Printf(format, args...)
+	} else {
+		log.Printf(format, args...)
+	}
+}


Test patch:
diff --git a/client_test.go b/client_test.go
index 7b56e8b..ed165d0 100644
--- a/client_test.go
+++ b/client_test.go
@@ -19,8 +19,9 @@ func (t *TestTransport) Wait() {
 	t.WaitCalled = true
 }
 
-func (t *TestTransport) SetToken(_t string)    {}
-func (t *TestTransport) SetEndpoint(_e string) {}
+func (t *TestTransport) SetToken(_t string)                {}
+func (t *TestTransport) SetEndpoint(_e string)             {}
+func (t *TestTransport) SetLogger(_l rollbar.ClientLogger) {}
 func (t *TestTransport) Send(body map[string]interface{}) error {
 	t.Body = body
 	return nil
diff --git a/rollbar_test.go b/rollbar_test.go
index 665aca2..ac44fb5 100644
--- a/rollbar_test.go
+++ b/rollbar_test.go
@@ -146,6 +146,9 @@ func TestBuildBody(t *testing.T) {
 	if custom["OVERRIDDEN_CUSTOM_KEY"] != "EXTRA" {
 		t.Error("extra custom should overwrite base custom where keys match")
 	}
+	if Custom()["EXTRA_CUSTOM_KEY"] != nil {
+		t.Error("adding extra modified the client custom data config")
+	}
 }
 
 func TestBuildBodyNoBaseCustom(t *testing.T) {