Skip to content

Runtime versions #122

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ jobs:
script: make ci
go: "1.7"
- script: make ci
env: GO_VERSION=1.8
go: "1.8"
- script: make ci
env: GO_VERSION=1.9
go: "1.9"
- script: make ci
go: "1.10"
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ updatedeps:
test: alldeps
@#TODO: 2018-09-20 Not testing the 'errors' package as it relies on some very runtime-specific implementation details.
@# The testing of 'errors' needs to be revisited
@# Additionally skipping Gin if the Go version is 1.7, as the latest version of Gin has dropped support.
@if [ "$(GO_VERSION)" = "1.7" ]; then \
@# Additionally skipping Gin if the Go version is lower than 1.9, as the latest version of Gin has dropped support for these versions.
@if [ "$(GO_VERSION)" = "1.7" ] || [ "$(GO_VERSION)" = "1.8" ] || [ "$(GO_VERSION)" = "1.9" ]; then \
go test . ./martini ./negroni ./sessions ./headers; \
else \
go test . ./gin ./martini ./negroni ./sessions ./headers; \
Expand Down
50 changes: 50 additions & 0 deletions device/runtimeversions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package device

import (
"runtime"
)

// Cached runtime versions that can be updated globally by framework
// integrations through AddVersion.
var versions *RuntimeVersions

// RuntimeVersions define the various versions of Go and any framework that may
// be in use.
// As a user of the notifier you're unlikely to need to modify this struct.
// As such, the authors reserve the right to introduce breaking changes to the
// properties in this struct. In particular the framework versions are liable
// to change in new versions of the notifier in minor/patch versions.
type RuntimeVersions struct {
Go string `json:"go"`

Gin string `json:"gin,omitempty"`
Martini string `json:"martini,omitempty"`
Negroni string `json:"negroni,omitempty"`
Revel string `json:"revel,omitempty"`
}

// GetRuntimeVersions retrieves the recorded runtime versions in a goroutine-safe manner.
func GetRuntimeVersions() *RuntimeVersions {
if versions == nil {
versions = &RuntimeVersions{Go: runtime.Version()}
}
return versions
}

// AddVersion permits a framework to register its version, assuming it's one of
// the officially supported frameworks.
func AddVersion(framework, version string) {
if versions == nil {
versions = &RuntimeVersions{Go: runtime.Version()}
}
switch framework {
case "Martini":
versions.Martini = version
case "Gin":
versions.Gin = version
case "Negroni":
versions.Negroni = version
case "Revel":
versions.Revel = version
}
}
43 changes: 43 additions & 0 deletions device/runtimeversions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package device

import (
"runtime"
"testing"
)

func TestPristineRuntimeVersions(t *testing.T) {
versions = nil // reset global variable
rv := GetRuntimeVersions()
for _, tc := range []struct{ name, got, exp string }{
{name: "Go", got: rv.Go, exp: runtime.Version()},
{name: "Gin", got: rv.Gin, exp: ""},
{name: "Martini", got: rv.Martini, exp: ""},
{name: "Negroni", got: rv.Negroni, exp: ""},
{name: "Revel", got: rv.Revel, exp: ""},
} {
if tc.got != tc.exp {
t.Errorf("expected pristine '%s' runtime version to be '%s' but was '%s'", tc.name, tc.exp, tc.got)
}
}
}

func TestModifiedRuntimeVersions(t *testing.T) {
versions = nil // reset global variable
rv := GetRuntimeVersions()
AddVersion("Gin", "1.2.1")
AddVersion("Martini", "1.0.0")
AddVersion("Negroni", "1.0.2")
AddVersion("Revel", "0.20.1")
for _, tc := range []struct{ name, got, exp string }{
{name: "Go", got: rv.Go, exp: runtime.Version()},
{name: "Gin", got: rv.Gin, exp: "1.2.1"},
{name: "Martini", got: rv.Martini, exp: "1.0.0"},
{name: "Negroni", got: rv.Negroni, exp: "1.0.2"},
{name: "Revel", got: rv.Revel, exp: "0.20.1"},
} {
if tc.got != tc.exp {
t.Errorf("expected modified '%s' runtime version to be '%s' but was '%s'", tc.name, tc.exp, tc.got)
}
}

}
2 changes: 1 addition & 1 deletion examples/revelapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The key files for integrating Bugsnag are:

## Run the example

1. Change the API key in `conf.app` to a project you've created in [Bugsnag](https://app.bugsnag.com).
1. Change the API key in `app.conf` to a project you've created in [Bugsnag](https://app.bugsnag.com).
1. Inside `bugsnag-go/examples/revelapp` do:
```bash
revel run
Expand Down
2 changes: 2 additions & 0 deletions gin/bugsnaggin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bugsnaggin

import (
"github.com/bugsnag/bugsnag-go"
"github.com/bugsnag/bugsnag-go/device"
"github.com/gin-gonic/gin"
)

Expand All @@ -20,6 +21,7 @@ func AutoNotify(rawData ...interface{}) gin.HandlerFunc {
}
}

device.AddVersion(FrameworkName, gin.Version)
state := bugsnag.HandledState{
SeverityReason: bugsnag.SeverityReasonUnhandledMiddlewareError,
OriginalSeverity: bugsnag.SeverityError,
Expand Down
2 changes: 2 additions & 0 deletions martini/bugsnagmiddleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"net/http"

"github.com/bugsnag/bugsnag-go"
"github.com/bugsnag/bugsnag-go/device"
"github.com/go-martini/martini"
)

Expand All @@ -47,6 +48,7 @@ const FrameworkName string = "Martini"
func AutoNotify(rawData ...interface{}) martini.Handler {
updateGlobalConfig(rawData...)

device.AddVersion(FrameworkName, "v1.0") // The latest martini release from 2014
state := bugsnag.HandledState{
SeverityReason: bugsnag.SeverityReasonUnhandledMiddlewareError,
OriginalSeverity: bugsnag.SeverityError,
Expand Down
2 changes: 2 additions & 0 deletions negroni/bugsnagnegroni.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"github.com/bugsnag/bugsnag-go"
"github.com/bugsnag/bugsnag-go/device"
"github.com/urfave/negroni"
)

Expand All @@ -17,6 +18,7 @@ type handler struct {
// AutoNotify sends any panics to bugsnag, and then re-raises them.
func AutoNotify(rawData ...interface{}) negroni.Handler {
updateGlobalConfig(rawData...)
device.AddVersion(FrameworkName, "unknown") // Negroni exposes no version prop.
state := bugsnag.HandledState{
SeverityReason: bugsnag.SeverityReasonUnhandledMiddlewareError,
OriginalSeverity: bugsnag.SeverityError,
Expand Down
8 changes: 7 additions & 1 deletion payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"encoding/json"
"fmt"
"net/http"
"runtime"
"sync"
"time"

"github.com/bugsnag/bugsnag-go/device"
"github.com/bugsnag/bugsnag-go/headers"
"github.com/bugsnag/bugsnag-go/sessions"
)
Expand Down Expand Up @@ -69,7 +71,11 @@ func (p *payload) MarshalJSON() ([]byte, error) {
Version: p.AppVersion,
},
Context: p.Context,
Device: &deviceJSON{Hostname: p.Hostname},
Device: &deviceJSON{
Hostname: p.Hostname,
OsName: runtime.GOOS,
RuntimeVersions: device.GetRuntimeVersions(),
},
Request: p.Request,
Exceptions: []exceptionJSON{
exceptionJSON{
Expand Down
17 changes: 11 additions & 6 deletions payload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,40 @@ package bugsnag

import (
"context"
"fmt"
"runtime"
"strings"
"testing"

"github.com/bugsnag/bugsnag-go/errors"
"github.com/bugsnag/bugsnag-go/sessions"
)

const expSmall = `{"apiKey":"","events":[{"app":{"releaseStage":""},"device":{},"exceptions":[{"errorClass":"","message":"","stacktrace":null}],"metaData":{},"payloadVersion":"4","severity":"","unhandled":false}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.5.2"}}`
const expSmall = `{"apiKey":"","events":[{"app":{"releaseStage":""},"device":{"osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"","message":"","stacktrace":null}],"metaData":{},"payloadVersion":"4","severity":"","unhandled":false}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.5.2"}}`

// The large payload has a timestamp in it which makes it awkward to assert against.
// Instead, assert that the timestamp property exist, along with the rest of the expected payload
const expLargePre = `{"apiKey":"166f5ad3590596f9aa8d601ea89af845","events":[{"app":{"releaseStage":"mega-production","type":"gin","version":"1.5.2"},"context":"/api/v2/albums","device":{"hostname":"super.duper.site"},"exceptions":[{"errorClass":"error class","message":"error message goes here","stacktrace":[{"method":"doA","file":"a.go","lineNumber":65},{"method":"fetchB","file":"b.go","lineNumber":99,"inProject":true},{"method":"incrementI","file":"i.go","lineNumber":651}]}],"groupingHash":"custom grouping hash","metaData":{"custom tab":{"my key":"my value"}},"payloadVersion":"4","session":{"startedAt":"`
const expLargePre = `{"apiKey":"166f5ad3590596f9aa8d601ea89af845","events":[{"app":{"releaseStage":"mega-production","type":"gin","version":"1.5.2"},"context":"/api/v2/albums","device":{"hostname":"super.duper.site","osName":"%s","runtimeVersions":{"go":"%s"}},"exceptions":[{"errorClass":"error class","message":"error message goes here","stacktrace":[{"method":"doA","file":"a.go","lineNumber":65},{"method":"fetchB","file":"b.go","lineNumber":99,"inProject":true},{"method":"incrementI","file":"i.go","lineNumber":651}]}],"groupingHash":"custom grouping hash","metaData":{"custom tab":{"my key":"my value"}},"payloadVersion":"4","session":{"startedAt":"`
const expLargePost = `,"severity":"info","severityReason":{"type":"unhandledError"},"unhandled":true,"user":{"id":"1234baerg134","name":"Kool Kidz on da bus","email":"[email protected]"}}],"notifier":{"name":"Bugsnag Go","url":"https://github.com/bugsnag/bugsnag-go","version":"1.5.2"}}`

func TestMarshalEmptyPayload(t *testing.T) {
sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
p := payload{&Event{Ctx: context.Background()}, &Configuration{}}
bytes, _ := p.MarshalJSON()
if got := string(bytes[:]); got != expSmall {
t.Errorf("Payload different to what was expected. \nGot: %s\nExp: %s", got, expSmall)
exp := fmt.Sprintf(expSmall, runtime.GOOS, runtime.Version())
if got := string(bytes[:]); got != exp {
t.Errorf("Payload different to what was expected. \nGot: %s\nExp: %s", got, exp)
}
}

func TestMarshalLargePayload(t *testing.T) {
payload := makeLargePayload()
bytes, _ := payload.MarshalJSON()
got := string(bytes[:])
if !strings.Contains(got, expLargePre) {
t.Errorf("Expected large payload to contain\n'%s'\n but was\n'%s'", expLargePre, got)
expPre := fmt.Sprintf(expLargePre, runtime.GOOS, runtime.Version())
if !strings.Contains(got, expPre) {
t.Errorf("Expected large payload to contain\n'%s'\n but was\n'%s'", expPre, got)

}
if !strings.Contains(got, expLargePost) {
t.Errorf("Expected large payload to contain\n'%s'\n but was\n'%s'", expLargePost, got)
Expand Down
4 changes: 4 additions & 0 deletions report.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bugsnag

import (
"github.com/bugsnag/bugsnag-go/device"
"github.com/bugsnag/bugsnag-go/sessions"
uuid "github.com/gofrs/uuid"
)
Expand Down Expand Up @@ -57,6 +58,9 @@ type severityReasonJSON struct {

type deviceJSON struct {
Hostname string `json:"hostname,omitempty"`
OsName string `json:"osName,omitempty"`

RuntimeVersions *device.RuntimeVersions `json:"runtimeVersions,omitempty"`
}

// RequestJSON is the request information that populates the Request tab in the dashboard.
Expand Down
2 changes: 2 additions & 0 deletions revel/bugsnagrevel.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

"github.com/bugsnag/bugsnag-go"
"github.com/bugsnag/bugsnag-go/device"
"github.com/revel/revel"
)

Expand Down Expand Up @@ -109,6 +110,7 @@ func init() {
Logger: new(bugsnagRevelLogger),
Synchronous: c.BoolDefault("bugsnag.synchronous", config.Synchronous),
})
device.AddVersion(FrameworkName, revel.Version)
}, order)
}

Expand Down
25 changes: 15 additions & 10 deletions sessions/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,19 +65,24 @@ func TestStartSession(t *testing.T) {
tt := []struct {
prop string
exp interface{}
got interface{}
}{
{got: getString(json, "notifier.name"), prop: "notifier.name", exp: "Bugsnag Go"},
{got: getString(json, "notifier.url"), prop: "notifier.url", exp: "https://github.com/bugsnag/bugsnag-go"},
{got: getString(json, "notifier.version"), prop: "notifier.version", exp: bugsnag.VERSION},
{got: getString(json, "app.releaseStage"), prop: "app.releaseStage", exp: "production"},
{got: getString(json, "app.version"), prop: "app.version", exp: ""},
{got: getString(json, "device.osName"), prop: "device.osName", exp: runtime.GOOS},
{got: getString(json, "device.hostname"), prop: "device.hostname", exp: hostname},
{prop: "notifier.name", exp: "Bugsnag Go"},
{prop: "notifier.url", exp: "https://github.com/bugsnag/bugsnag-go"},
{prop: "notifier.version", exp: bugsnag.VERSION},
{prop: "app.releaseStage", exp: "production"},
{prop: "app.version", exp: ""},
{prop: "device.osName", exp: runtime.GOOS},
{prop: "device.hostname", exp: hostname},
{prop: "device.runtimeVersions.go", exp: runtime.Version()},
{prop: "device.runtimeVersions.gin", exp: ""},
{prop: "device.runtimeVersions.martini", exp: ""},
{prop: "device.runtimeVersions.negroni", exp: ""},
{prop: "device.runtimeVersions.revel", exp: ""},
}
for _, tc := range tt {
if tc.got != tc.exp {
t.Errorf("Expected '%s' to be '%s' but was %s", tc.prop, tc.exp, tc.got)
got := getString(json, tc.prop)
if got != tc.exp {
t.Errorf("Expected '%s' to be '%s' but was '%s'", tc.prop, tc.exp, got)
}
}
sessionCounts := getIndex(json, "sessionCounts", 0)
Expand Down
7 changes: 5 additions & 2 deletions sessions/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ type appPayload struct {
type devicePayload struct {
OsName string `json:"osName,omitempty"`
Hostname string `json:"hostname,omitempty"`

RuntimeVersions *device.RuntimeVersions `json:"runtimeVersions"`
}

// sessionCountsPayload defines the .sessionCounts subobject of the payload
Expand Down Expand Up @@ -64,8 +66,9 @@ func makeSessionPayload(sessions []*Session, config *SessionTrackingConfiguratio
ReleaseStage: releaseStage,
},
Device: &devicePayload{
OsName: runtime.GOOS,
Hostname: hostname,
OsName: runtime.GOOS,
Hostname: hostname,
RuntimeVersions: device.GetRuntimeVersions(),
},
SessionCounts: []sessionCountsPayload{
{
Expand Down