diff --git a/cmd/registry/main.go b/cmd/registry/main.go index 29fa24c15..ba859eecc 100644 --- a/cmd/registry/main.go +++ b/cmd/registry/main.go @@ -11,6 +11,9 @@ import ( log "github.com/Sirupsen/logrus" + "github.com/bugsnag/bugsnag-go" + "github.com/yvasiyarov/gorelic" + "github.com/docker/docker-registry" "github.com/docker/docker-registry/configuration" _ "github.com/docker/docker-registry/storagedriver/filesystem" @@ -27,7 +30,8 @@ func main() { } 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.Infof("listening on %v", config.HTTP.Addr) @@ -82,3 +86,39 @@ func logLevel(level configuration.Loglevel) log.Level { 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 +} diff --git a/configuration/configuration.go b/configuration/configuration.go index 2d7e476be..ab962a90e 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -24,6 +24,9 @@ type Configuration struct { // Storage is the configuration for the registry's storage driver Storage Storage `yaml:"storage"` + // Reporting is the configuration for error reporting + Reporting Reporting `yaml:"reporting"` + // HTTP contains configuration parameters for the registry's http // interface. HTTP struct { @@ -180,6 +183,33 @@ func (storage Storage) MarshalYAML() (interface{}, error) { // Parameters defines a key-value parameters mapping 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 // 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 } diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 0e2276536..5c9ec9e79 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -29,6 +29,11 @@ var configStruct = Configuration{ "port": "", }, }, + Reporting: Reporting{ + Bugsnag: BugsnagReporting{ + APIKey: "BugsnagApiKey", + }, + }, } // configYamlV0_1 is a Version 0.1 yaml document representing configStruct @@ -46,6 +51,9 @@ storage: secretkey: SUPERSECRET host: ~ port: ~ +reporting: + bugsnag: + apikey: BugsnagApiKey ` // 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 func (suite *ConfigSuite) TestParseInmemory(c *C) { suite.expectedConfig.Storage = Storage{"inmemory": Parameters{}} + suite.expectedConfig.Reporting = Reporting{} config, err := Parse(bytes.NewReader([]byte(inmemoryConfigYamlV0_1))) c.Assert(err, IsNil) @@ -171,6 +180,22 @@ func (suite *ConfigSuite) TestParseWithDifferentEnvLoglevel(c *C) { 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 // version than the CurrentVersion func (suite *ConfigSuite) TestParseInvalidVersion(c *C) { @@ -190,6 +215,10 @@ func copyConfig(config Configuration) *Configuration { for k, v := range config.Storage.Parameters() { 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 }