diff --git a/.travis.yml b/.travis.yml index 107e6d6..a7b8ca9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,15 @@ language: go go: + - 1.7.x - 1.8.x - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x - master + +matrix: + allow_failures: + go: 1.7.x diff --git a/client.go b/client.go index 283fca6..189a13f 100644 --- a/client.go +++ b/client.go @@ -26,6 +26,7 @@ type Client struct { // implementation of the Transport interface is used. Transport Transport configuration configuration + diagnostic diagnostic } // New returns the default implementation of a Client. @@ -38,9 +39,11 @@ func New(token, environment, codeVersion, serverHost, serverRoot string) *Client func NewAsync(token, environment, codeVersion, serverHost, serverRoot string) *Client { configuration := createConfiguration(token, environment, codeVersion, serverHost, serverRoot) transport := NewTransport(token, configuration.endpoint) + diagnostic := createDiagnostic() return &Client{ Transport: transport, configuration: configuration, + diagnostic: diagnostic, } } @@ -48,9 +51,11 @@ func NewAsync(token, environment, codeVersion, serverHost, serverRoot string) *C func NewSync(token, environment, codeVersion, serverHost, serverRoot string) *Client { configuration := createConfiguration(token, environment, codeVersion, serverHost, serverRoot) transport := NewSyncTransport(token, configuration.endpoint) + diagnostic := createDiagnostic() return &Client{ Transport: transport, configuration: configuration, + diagnostic: diagnostic, } } @@ -73,33 +78,39 @@ func (c *Client) SetToken(token string) { // SetEnvironment sets the environment under which all errors and messages will be submitted. func (c *Client) SetEnvironment(environment string) { + c.diagnostic.configuredOptions["environment"] = environment c.configuration.environment = environment } // SetEndpoint sets the endpoint to post items to. This also configures the underlying Transport. func (c *Client) SetEndpoint(endpoint string) { + c.diagnostic.configuredOptions["endpoint"] = endpoint c.configuration.endpoint = endpoint c.Transport.SetEndpoint(endpoint) } // SetPlatform sets the platform to be reported for all items. func (c *Client) SetPlatform(platform string) { + c.diagnostic.configuredOptions["platform"] = platform c.configuration.platform = platform } // SetCodeVersion sets the string describing the running code version on the server. func (c *Client) SetCodeVersion(codeVersion string) { + c.diagnostic.configuredOptions["codeVersion"] = codeVersion c.configuration.codeVersion = codeVersion } // SetServerHost sets the hostname sent with each item. This value will be indexed. func (c *Client) SetServerHost(serverHost string) { + c.diagnostic.configuredOptions["serverHost"] = serverHost c.configuration.serverHost = serverHost } // SetServerRoot sets the path to the application code root, not including the final slash. // This is used to collapse non-project code when displaying tracebacks. func (c *Client) SetServerRoot(serverRoot string) { + c.diagnostic.configuredOptions["serverRoot"] = serverRoot c.configuration.serverRoot = serverRoot } @@ -112,22 +123,33 @@ func (c *Client) SetCustom(custom map[string]interface{}) { // any subsequent errors or messages. Only id is required to be // non-empty. func (c *Client) SetPerson(id, username, email string) { - c.configuration.person = Person{ + person := Person{ Id: id, Username: username, Email: email, } + + c.diagnostic.configuredOptions["person"] = map[string]string{ + "Id": id, + "Username": username, + "Email": email, + } + c.configuration.person = person } // ClearPerson clears any previously set person information. See `SetPerson` for more // information. func (c *Client) ClearPerson() { - c.configuration.person = Person{} + person := Person{} + + c.diagnostic.configuredOptions["person"] = map[string]string{} + c.configuration.person = person } // SetFingerprint sets whether or not to use a custom client-side fingerprint. The default value is // false. func (c *Client) SetFingerprint(fingerprint bool) { + c.diagnostic.configuredOptions["fingerprint"] = fingerprint c.configuration.fingerprint = fingerprint } @@ -139,12 +161,14 @@ func (c *Client) SetLogger(logger ClientLogger) { // SetScrubHeaders sets the regular expression used to match headers for scrubbing. // The default value is regexp.MustCompile("Authorization") func (c *Client) SetScrubHeaders(headers *regexp.Regexp) { + c.diagnostic.configuredOptions["scrubHeaders"] = headers c.configuration.scrubHeaders = headers } // SetScrubFields sets the regular expression to match keys in the item payload for scrubbing. // The default vlaue is regexp.MustCompile("password|secret|token"), func (c *Client) SetScrubFields(fields *regexp.Regexp) { + c.diagnostic.configuredOptions["scrubFields"] = fields c.configuration.scrubFields = fields } @@ -160,6 +184,7 @@ func (c *Client) SetScrubFields(fields *regexp.Regexp) { // make before it is finally sent. Be careful with the modifications you make as they could lead to // the payload being malformed from the perspective of the API. func (c *Client) SetTransform(transform func(map[string]interface{})) { + c.diagnostic.configuredOptions["transform"] = functionToString(transform) c.configuration.transform = transform } @@ -171,6 +196,7 @@ func (c *Client) SetTransform(transform func(map[string]interface{})) { // In order to preserve the default unwrapping behavior, callers of SetUnwrapper may wish to include // a call to DefaultUnwrapper in their custom unwrapper function. See the example on the SetUnwrapper function. func (c *Client) SetUnwrapper(unwrapper UnwrapperFunc) { + c.diagnostic.configuredOptions["unwrapper"] = functionToString(unwrapper) c.configuration.unwrapper = unwrapper } @@ -183,6 +209,7 @@ func (c *Client) SetUnwrapper(unwrapper UnwrapperFunc) { // to include a call to DefaultStackTracer in their custom tracing function. See the example // on the SetStackTracer function. func (c *Client) SetStackTracer(stackTracer StackTracerFunc) { + c.diagnostic.configuredOptions["stackTracer"] = functionToString(stackTracer) c.configuration.stackTracer = stackTracer } @@ -193,6 +220,7 @@ func (c *Client) SetStackTracer(stackTracer StackTracerFunc) { // this function is called with the result of calling Error(), otherwise // the string representation of the value is passed to this function. func (c *Client) SetCheckIgnore(checkIgnore func(string) bool) { + c.diagnostic.configuredOptions["checkIgnore"] = functionToString(checkIgnore) c.configuration.checkIgnore = checkIgnore } @@ -201,6 +229,7 @@ func (c *Client) SetCheckIgnore(checkIgnore func(string) bool) { // CaptureIpAnonymize means apply a pseudo-anonymization. // CaptureIpNone means do not capture anything. func (c *Client) SetCaptureIp(captureIp captureIp) { + c.diagnostic.configuredOptions["captureIp"] = captureIp c.configuration.captureIp = captureIp } @@ -560,7 +589,7 @@ func (c *Client) Close() error { } func (c *Client) buildBody(ctx context.Context, level, title string, extras map[string]interface{}) map[string]interface{} { - return buildBody(ctx, c.configuration, level, title, extras) + return buildBody(ctx, c.configuration, c.diagnostic, level, title, extras) } func (c *Client) requestDetails(r *http.Request) map[string]interface{} { @@ -652,6 +681,18 @@ func createConfiguration(token, environment, codeVersion, serverHost, serverRoot } } +type diagnostic struct { + languageVersion string + configuredOptions map[string]interface{} +} + +func createDiagnostic() diagnostic { + return diagnostic{ + languageVersion: runtime.Version(), + configuredOptions: map[string]interface{}{}, + } +} + // clientPost returns an error which indicates the type of error that occured while attempting to // send the body input to the endpoint given, or nil if no error occurred. If error is not nil, the // boolean return parameter indicates whether the error is temporary or not. If this boolean return @@ -716,3 +757,7 @@ func isTemporary(err error) bool { } return false } + +func functionToString(function interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name() +} diff --git a/client_test.go b/client_test.go index 638f4c0..82453ae 100644 --- a/client_test.go +++ b/client_test.go @@ -7,6 +7,7 @@ import ( "context" "reflect" "testing" + "strings" ) type TestTransport struct { @@ -295,16 +296,19 @@ func TestLambdaWrapperWithMessage(t *testing.T) { func TestGettersAndSetters_Default(t *testing.T) { c := testClient() + c.Transport = &TestTransport{} testGettersAndSetters(c, t) } func TestGettersAndSetters_Async(t *testing.T) { c := rollbar.NewAsync("", "", "", "", "") + c.Transport = &TestTransport{} testGettersAndSetters(c, t) } func TestGettersAndSetters_Sync(t *testing.T) { c := rollbar.NewSync("", "", "", "", "") + c.Transport = &TestTransport{} testGettersAndSetters(c, t) } @@ -316,8 +320,10 @@ func testGettersAndSetters(client *rollbar.Client, t *testing.T) { codeVersion := "CodeVersion" host := "SomeHost" root := "////" + fingerprint := true scrubHeaders := regexp.MustCompile("Foo") scrubFields := regexp.MustCompile("squirrel|doggo") + captureIP := rollbar.CaptureIpNone errorIfEqual(token, client.Token(), t) errorIfEqual(environment, client.Environment(), t) @@ -326,6 +332,10 @@ func testGettersAndSetters(client *rollbar.Client, t *testing.T) { errorIfEqual(codeVersion, client.CodeVersion(), t) errorIfEqual(host, client.ServerHost(), t) errorIfEqual(root, client.ServerRoot(), t) + errorIfEqual(fingerprint, client.Fingerprint(), t) + errorIfEqual(captureIP, client.CaptureIp(), t) + errorIfEqual(scrubHeaders, client.ScrubHeaders(), t) + errorIfEqual(scrubFields, client.ScrubFields(), t) if client.Fingerprint() { t.Error("expected fingerprint to default to false") @@ -348,10 +358,11 @@ func testGettersAndSetters(client *rollbar.Client, t *testing.T) { client.SetCodeVersion(codeVersion) client.SetServerHost(host) client.SetServerRoot(root) - client.SetFingerprint(true) + client.SetFingerprint(fingerprint) client.SetLogger(&rollbar.SilentClientLogger{}) client.SetScrubHeaders(scrubHeaders) client.SetScrubFields(scrubFields) + client.SetCaptureIp(captureIP) client.SetEnabled(true) @@ -362,6 +373,10 @@ func testGettersAndSetters(client *rollbar.Client, t *testing.T) { errorIfNotEqual(codeVersion, client.CodeVersion(), t) errorIfNotEqual(host, client.ServerHost(), t) errorIfNotEqual(root, client.ServerRoot(), t) + errorIfNotEqual(fingerprint, client.Fingerprint(), t) + errorIfNotEqual(captureIP, client.CaptureIp(), t) + errorIfNotEqual(scrubHeaders, client.ScrubHeaders(), t) + errorIfNotEqual(scrubFields, client.ScrubFields(), t) if !client.Fingerprint() { t.Error("expected fingerprint to default to false") @@ -374,15 +389,39 @@ func testGettersAndSetters(client *rollbar.Client, t *testing.T) { if !client.ScrubFields().MatchString("squirrel") { t.Error("expected matching scrub field") } + + client.ErrorWithLevel(rollbar.ERR, errors.New("Bork")) + + if transport, ok := client.Transport.(*TestTransport); ok { + body := transport.Body + if body["data"] == nil { + t.Error("body should have data") + } + data := body["data"].(map[string]interface{}) + configuredOptions := configuredOptionsFromData(data) + + errorIfNotEqual(environment, configuredOptions["environment"].(string), t) + errorIfNotEqual(endpoint, configuredOptions["endpoint"].(string), t) + errorIfNotEqual(platform, configuredOptions["platform"].(string), t) + errorIfNotEqual(codeVersion, configuredOptions["codeVersion"].(string), t) + errorIfNotEqual(host, configuredOptions["serverHost"].(string), t) + errorIfNotEqual(root, configuredOptions["serverRoot"].(string), t) + errorIfNotEqual(fingerprint, configuredOptions["fingerprint"].(bool), t) + errorIfNotEqual(scrubHeaders, configuredOptions["scrubHeaders"].(*regexp.Regexp), t) + errorIfNotEqual(scrubFields, configuredOptions["scrubFields"].(*regexp.Regexp), t) + + } else { + t.Fail() + } } -func errorIfEqual(a, b string, t *testing.T) { +func errorIfEqual(a, b interface{}, t *testing.T) { if a == b { t.Error("Expected", a, " != ", b) } } -func errorIfNotEqual(a, b string, t *testing.T) { +func errorIfNotEqual(a, b interface{}, t *testing.T) { if a != b { t.Error("Expected", a, " == ", b) } @@ -408,6 +447,13 @@ func TestSetPerson(t *testing.T) { errorIfNotEqual(id, person["id"], t) errorIfNotEqual(username, person["username"], t) errorIfNotEqual(email, person["email"], t) + + configuredOptions := configuredOptionsFromData(data) + configuredPerson := configuredOptions["person"].(map[string]string) + + errorIfNotEqual(id, configuredPerson["Id"], t) + errorIfNotEqual(username, configuredPerson["Username"], t) + errorIfNotEqual(email, configuredPerson["Email"], t) } else { t.Fail() } @@ -452,6 +498,52 @@ func TestTransform(t *testing.T) { if data["some_custom_field"] != "hello_world" { t.Error("data should have field set by transform") } + configuredOptions := configuredOptionsFromData(data) + if !strings.Contains(configuredOptions["transform"].(string), "TestTransform.func1") { + t.Error("data should have transform in diagnostic object") + } + } else { + t.Fail() + } +} + +func TestSetUnwrapper(t *testing.T) { + client := testClient() + client.SetUnwrapper(rollbar.DefaultUnwrapper) + + client.ErrorWithLevel(rollbar.ERR, errors.New("Bork")) + + if transport, ok := client.Transport.(*TestTransport); ok { + body := transport.Body + if body["data"] == nil { + t.Error("body should have data") + } + data := body["data"].(map[string]interface{}) + configuredOptions := configuredOptionsFromData(data) + if !strings.Contains(configuredOptions["unwrapper"].(string), "func1") { + t.Error("data should have unwrapper in diagnostic object") + } + } else { + t.Fail() + } +} + +func TestSetStackTracer(t *testing.T) { + client := testClient() + client.SetStackTracer(rollbar.DefaultStackTracer) + + client.ErrorWithLevel(rollbar.ERR, errors.New("Bork")) + + if transport, ok := client.Transport.(*TestTransport); ok { + body := transport.Body + if body["data"] == nil { + t.Error("body should have data") + } + data := body["data"].(map[string]interface{}) + configuredOptions := configuredOptionsFromData(data) + if !strings.Contains(configuredOptions["stackTracer"].(string), "func2") { + t.Error("data should have stackTracer in diagnostic object") + } } else { t.Fail() } @@ -484,3 +576,10 @@ func TestEnabled(t *testing.T) { t.Fail() } } + +func configuredOptionsFromData(data map[string]interface{}) map[string]interface{} { + notifier := data["notifier"].(map[string]interface{}) + diagnostic := notifier["diagnostic"].(map[string]interface{}) + configuredOptions := diagnostic["configuredOptions"].(map[string]interface{}) + return configuredOptions +} diff --git a/transforms.go b/transforms.go index f262cbd..d4e066f 100644 --- a/transforms.go +++ b/transforms.go @@ -13,7 +13,9 @@ import ( // Build the main JSON structure that will be sent to Rollbar with the // appropriate metadata. -func buildBody(ctx context.Context, configuration configuration, level, title string, extras map[string]interface{}) map[string]interface{} { +func buildBody(ctx context.Context, configuration configuration, diagnostic diagnostic, + level, title string, extras map[string]interface{}) map[string]interface{} { + timestamp := time.Now().Unix() data := map[string]interface{}{ @@ -31,6 +33,10 @@ func buildBody(ctx context.Context, configuration configuration, level, title st "notifier": map[string]interface{}{ "name": NAME, "version": VERSION, + "diagnostic": map[string]interface{}{ + "languageVersion": diagnostic.languageVersion, + "configuredOptions": diagnostic.configuredOptions, + }, }, }