Merge pull request #849 from BrianBland/ng-error-reporting

Adds bugsnag and newrelic metrics and error reporting
This commit is contained in:
Stephen Day 2014-12-12 18:29:25 -08:00
commit 8fd47c1c18
3 changed files with 117 additions and 1 deletions

View file

@ -11,6 +11,9 @@ import (
log "github.com/Sirupsen/logrus" log "github.com/Sirupsen/logrus"
"github.com/bugsnag/bugsnag-go"
"github.com/yvasiyarov/gorelic"
"github.com/docker/docker-registry" "github.com/docker/docker-registry"
"github.com/docker/docker-registry/configuration" "github.com/docker/docker-registry/configuration"
_ "github.com/docker/docker-registry/storagedriver/filesystem" _ "github.com/docker/docker-registry/storagedriver/filesystem"
@ -27,7 +30,8 @@ func main() {
} }
app := registry.NewApp(*config) app := registry.NewApp(*config)
handler := handlers.CombinedLoggingHandler(os.Stdout, app) handler := configureReporting(app)
handler = handlers.CombinedLoggingHandler(os.Stdout, handler)
log.SetLevel(logLevel(config.Loglevel)) log.SetLevel(logLevel(config.Loglevel))
log.Infof("listening on %v", config.HTTP.Addr) log.Infof("listening on %v", config.HTTP.Addr)
@ -82,3 +86,39 @@ func logLevel(level configuration.Loglevel) log.Level {
return l return l
} }
func configureReporting(app *registry.App) http.Handler {
var handler http.Handler = app
if app.Config.Reporting.Bugsnag.APIKey != "" {
bugsnagConfig := bugsnag.Configuration{
APIKey: app.Config.Reporting.Bugsnag.APIKey,
// TODO(brianbland): provide the registry version here
// AppVersion: "2.0",
}
if app.Config.Reporting.Bugsnag.ReleaseStage != "" {
bugsnagConfig.ReleaseStage = app.Config.Reporting.Bugsnag.ReleaseStage
}
if app.Config.Reporting.Bugsnag.Endpoint != "" {
bugsnagConfig.Endpoint = app.Config.Reporting.Bugsnag.Endpoint
}
bugsnag.Configure(bugsnagConfig)
handler = bugsnag.Handler(handler)
}
if app.Config.Reporting.NewRelic.LicenseKey != "" {
agent := gorelic.NewAgent()
agent.NewrelicLicense = app.Config.Reporting.NewRelic.LicenseKey
if app.Config.Reporting.NewRelic.Name != "" {
agent.NewrelicName = app.Config.Reporting.NewRelic.Name
}
agent.CollectHTTPStat = true
agent.Verbose = true
agent.Run()
handler = agent.WrapHTTPHandler(handler)
}
return handler
}

View file

@ -24,6 +24,9 @@ type Configuration struct {
// Storage is the configuration for the registry's storage driver // Storage is the configuration for the registry's storage driver
Storage Storage `yaml:"storage"` Storage Storage `yaml:"storage"`
// Reporting is the configuration for error reporting
Reporting Reporting `yaml:"reporting"`
// HTTP contains configuration parameters for the registry's http // HTTP contains configuration parameters for the registry's http
// interface. // interface.
HTTP struct { HTTP struct {
@ -180,6 +183,33 @@ func (storage Storage) MarshalYAML() (interface{}, error) {
// Parameters defines a key-value parameters mapping // Parameters defines a key-value parameters mapping
type Parameters map[string]string type Parameters map[string]string
// Reporting defines error reporting methods.
type Reporting struct {
// Bugsnag configures error reporting for Bugsnag (bugsnag.com).
Bugsnag BugsnagReporting `yaml:"bugsnag"`
// NewRelic configures error reporting for NewRelic (newrelic.com)
NewRelic NewRelicReporting `yaml:"newrelic"`
}
// BugsnagReporting configures error reporting for Bugsnag (bugsnag.com).
type BugsnagReporting struct {
// APIKey is the Bugsnag api key.
APIKey string `yaml:"apikey"`
// ReleaseStage tracks where the registry is deployed.
// Examples: production, staging, development
ReleaseStage string `yaml:"releasestage"`
// Endpoint is used for specifying an enterprise Bugsnag endpoint.
Endpoint string `yaml:"endpoint"`
}
// NewRelicReporting configures error reporting for NewRelic (newrelic.com)
type NewRelicReporting struct {
// LicenseKey is the NewRelic user license key
LicenseKey string `yaml:"licensekey"`
// AppName is the component name of the registry in NewRelic
Name string `yaml:"name"`
}
// Parse parses an input configuration yaml document into a Configuration struct // Parse parses an input configuration yaml document into a Configuration struct
// This should generally be capable of handling old configuration format versions // This should generally be capable of handling old configuration format versions
// //
@ -264,6 +294,23 @@ func parseV0_1Registry(in []byte) (*Configuration, error) {
} }
} }
if bugsnagAPIKey, ok := envMap["REGISTRY_REPORTING_BUGSNAG_APIKEY"]; ok {
config.Reporting.Bugsnag.APIKey = bugsnagAPIKey
}
if bugsnagReleaseStage, ok := envMap["REGISTRY_REPORTING_BUGSNAG_RELEASESTAGE"]; ok {
config.Reporting.Bugsnag.ReleaseStage = bugsnagReleaseStage
}
if bugsnagEndpoint, ok := envMap["REGISTRY_REPORTING_BUGSNAG_ENDPOINT"]; ok {
config.Reporting.Bugsnag.Endpoint = bugsnagEndpoint
}
if newRelicLicenseKey, ok := envMap["REGISTRY_REPORTING_NEWRELIC_LICENSEKEY"]; ok {
config.Reporting.NewRelic.LicenseKey = newRelicLicenseKey
}
if newRelicName, ok := envMap["REGISTRY_REPORTING_NEWRELIC_NAME"]; ok {
config.Reporting.NewRelic.Name = newRelicName
}
return (*Configuration)(&config), nil return (*Configuration)(&config), nil
} }

View file

@ -29,6 +29,11 @@ var configStruct = Configuration{
"port": "", "port": "",
}, },
}, },
Reporting: Reporting{
Bugsnag: BugsnagReporting{
APIKey: "BugsnagApiKey",
},
},
} }
// configYamlV0_1 is a Version 0.1 yaml document representing configStruct // configYamlV0_1 is a Version 0.1 yaml document representing configStruct
@ -46,6 +51,9 @@ storage:
secretkey: SUPERSECRET secretkey: SUPERSECRET
host: ~ host: ~
port: ~ port: ~
reporting:
bugsnag:
apikey: BugsnagApiKey
` `
// inmemoryConfigYamlV0_1 is a Version 0.1 yaml document specifying an inmemory storage driver with // inmemoryConfigYamlV0_1 is a Version 0.1 yaml document specifying an inmemory storage driver with
@ -88,6 +96,7 @@ func (suite *ConfigSuite) TestParseSimple(c *C) {
// parsed into a Configuration struct with no storage parameters // parsed into a Configuration struct with no storage parameters
func (suite *ConfigSuite) TestParseInmemory(c *C) { func (suite *ConfigSuite) TestParseInmemory(c *C) {
suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}} suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}}
suite.expectedConfig.Reporting = Reporting{}
config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1))) config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1)))
c.Assert(err, IsNil) c.Assert(err, IsNil)
@ -171,6 +180,22 @@ func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel(c *C) {
c.Assert(config, DeepEquals, suite.expectedConfig) c.Assert(config, DeepEquals, suite.expectedConfig)
} }
func (suite *ConfigSuite) TestParseWithDifferentEnvReporting(c *C) {
suite.expectedConfig.Reporting.Bugsnag.APIKey = "anotherBugsnagApiKey"
suite.expectedConfig.Reporting.Bugsnag.Endpoint = "localhost:8080"
suite.expectedConfig.Reporting.NewRelic.LicenseKey = "NewRelicLicenseKey"
suite.expectedConfig.Reporting.NewRelic.Name = "some NewRelic NAME"
os.Setenv("REGISTRY_REPORTING_BUGSNAG_APIKEY", "anotherBugsnagApiKey")
os.Setenv("REGISTRY_REPORTING_BUGSNAG_ENDPOINT", "localhost:8080")
os.Setenv("REGISTRY_REPORTING_NEWRELIC_LICENSEKEY", "NewRelicLicenseKey")
os.Setenv("REGISTRY_REPORTING_NEWRELIC_NAME", "some NewRelic NAME")
config, err := Parse(bytes.NewReader([]byte(configYamlV0_1)))
c.Assert(err, IsNil)
c.Assert(config, DeepEquals, suite.expectedConfig)
}
// TestParseInvalidVersion validates that the parser will fail to parse a newer configuration // TestParseInvalidVersion validates that the parser will fail to parse a newer configuration
// version than the CurrentVersion // version than the CurrentVersion
func (suite *ConfigSuite) TestParseInvalidVersion(c *C) { func (suite *ConfigSuite) TestParseInvalidVersion(c *C) {
@ -190,6 +215,10 @@ func copyConfig(config Configuration) *Configuration {
for k, v := range config.Storage.Parameters() { for k, v := range config.Storage.Parameters() {
configCopy.Storage.setParameter(k, v) configCopy.Storage.setParameter(k, v)
} }
configCopy.Reporting = Reporting{
Bugsnag: BugsnagReporting{config.Reporting.Bugsnag.APIKey, config.Reporting.Bugsnag.ReleaseStage, config.Reporting.Bugsnag.Endpoint},
NewRelic: NewRelicReporting{config.Reporting.NewRelic.LicenseKey, config.Reporting.NewRelic.Name},
}
return configCopy return configCopy
} }