1
0

configuration.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package bugsnag
  2. import (
  3. "log"
  4. "net/http"
  5. "path/filepath"
  6. "strings"
  7. )
  8. // Endpoints hold the HTTP endpoints of the notifier.
  9. type Endpoints struct {
  10. Sessions string
  11. Notify string
  12. }
  13. // Configuration sets up and customizes communication with the Bugsnag API.
  14. type Configuration struct {
  15. // Your Bugsnag API key, e.g. "c9d60ae4c7e70c4b6c4ebd3e8056d2b8". You can
  16. // find this by clicking Settings on https://bugsnag.com/.
  17. APIKey string
  18. // Deprecated: Use Endpoints (with an 's') instead.
  19. // The Endpoint to notify about crashes. This defaults to
  20. // "https://notify.bugsnag.com/", if you're using Bugsnag Enterprise then
  21. // set it to your internal Bugsnag endpoint.
  22. Endpoint string
  23. // Endpoints define the HTTP endpoints that the notifier should notify
  24. // about crashes and sessions. These default to notify.bugsnag.com for
  25. // error reports and sessions.bugsnag.com for sessions.
  26. // If you are using bugsnag on-premise you will have to set these to your
  27. // Event Server and Session Server endpoints. If the notify endpoint is set
  28. // but the sessions endpoint is not, session tracking will be disabled
  29. // automatically to avoid leaking session information outside of your
  30. // server configuration, and a warning will be logged.
  31. Endpoints Endpoints
  32. // The current release stage. This defaults to "production" and is used to
  33. // filter errors in the Bugsnag dashboard.
  34. ReleaseStage string
  35. // A specialized type of the application, such as the worker queue or web
  36. // framework used, like "rails", "mailman", or "celery"
  37. AppType string
  38. // The currently running version of the app. This is used to filter errors
  39. // in the Bugsnag dasboard. If you set this then Bugsnag will only re-open
  40. // resolved errors if they happen in different app versions.
  41. AppVersion string
  42. // AutoCaptureSessions can be set to false to disable automatic session
  43. // tracking. If you want control over what is deemed a session, you can
  44. // switch off automatic session tracking with this configuration, and call
  45. // bugsnag.StartSession() when appropriate for your application. See the
  46. // official docs for instructions and examples of associating handled
  47. // errors with sessions and ensuring error rate accuracy on the Bugsnag
  48. // dashboard. This will default to true, but is stored as an interface to enable
  49. // us to detect when this option has not been set.
  50. AutoCaptureSessions interface{}
  51. // The hostname of the current server. This defaults to the return value of
  52. // os.Hostname() and is graphed in the Bugsnag dashboard.
  53. Hostname string
  54. // The Release stages to notify in. If you set this then bugsnag-go will
  55. // only send notifications to Bugsnag if the ReleaseStage is listed here.
  56. NotifyReleaseStages []string
  57. // packages that are part of your app. Bugsnag uses this to determine how
  58. // to group errors and how to display them on your dashboard. You should
  59. // include any packages that are part of your app, and exclude libraries
  60. // and helpers. You can list wildcards here, and they'll be expanded using
  61. // filepath.Glob. The default value is []string{"main*"}
  62. ProjectPackages []string
  63. // The SourceRoot is the directory where the application is built, and the
  64. // assumed prefix of lines on the stacktrace originating in the parent
  65. // application. When set, the prefix is trimmed from callstack file names
  66. // before ProjectPackages for better readability and to better group errors
  67. // on the Bugsnag dashboard. The default value is $GOPATH/src or $GOROOT/src
  68. // if $GOPATH is unset. At runtime, $GOROOT is the root used during the Go
  69. // build.
  70. SourceRoot string
  71. // Any meta-data that matches these filters will be marked as [FILTERED]
  72. // before sending a Notification to Bugsnag. It defaults to
  73. // []string{"password", "secret"} so that request parameters like password,
  74. // password_confirmation and auth_secret will not be sent to Bugsnag.
  75. ParamsFilters []string
  76. // The PanicHandler is used by Bugsnag to catch unhandled panics in your
  77. // application. The default panicHandler uses mitchellh's panicwrap library,
  78. // and you can disable this feature by passing an empty: func() {}
  79. PanicHandler func()
  80. // The logger that Bugsnag should log to. Uses the same defaults as go's
  81. // builtin logging package. bugsnag-go logs whenever it notifies Bugsnag
  82. // of an error, and when any error occurs inside the library itself.
  83. Logger interface {
  84. Printf(format string, v ...interface{}) // limited to the functions used
  85. }
  86. // The http Transport to use, defaults to the default http Transport. This
  87. // can be configured if you are in an environment like Google App Engine
  88. // that has stringent conditions on making http requests.
  89. Transport http.RoundTripper
  90. // Whether bugsnag should notify synchronously. This defaults to false which
  91. // causes bugsnag-go to spawn a new goroutine for each notification.
  92. Synchronous bool
  93. // Whether the notifier should send all sessions recorded so far to Bugsnag
  94. // when repanicking to ensure that no session information is lost in a
  95. // fatal crash.
  96. flushSessionsOnRepanic bool
  97. // TODO: remember to update the update() function when modifying this struct
  98. }
  99. func (config *Configuration) update(other *Configuration) *Configuration {
  100. if other.APIKey != "" {
  101. config.APIKey = other.APIKey
  102. }
  103. if other.Hostname != "" {
  104. config.Hostname = other.Hostname
  105. }
  106. if other.AppType != "" {
  107. config.AppType = other.AppType
  108. }
  109. if other.AppVersion != "" {
  110. config.AppVersion = other.AppVersion
  111. }
  112. if other.SourceRoot != "" {
  113. config.SourceRoot = other.SourceRoot
  114. }
  115. if other.ReleaseStage != "" {
  116. config.ReleaseStage = other.ReleaseStage
  117. }
  118. if other.ParamsFilters != nil {
  119. config.ParamsFilters = other.ParamsFilters
  120. }
  121. if other.ProjectPackages != nil {
  122. config.ProjectPackages = other.ProjectPackages
  123. }
  124. if other.Logger != nil {
  125. config.Logger = other.Logger
  126. }
  127. if other.NotifyReleaseStages != nil {
  128. config.NotifyReleaseStages = other.NotifyReleaseStages
  129. }
  130. if other.PanicHandler != nil {
  131. config.PanicHandler = other.PanicHandler
  132. }
  133. if other.Transport != nil {
  134. config.Transport = other.Transport
  135. }
  136. if other.Synchronous {
  137. config.Synchronous = true
  138. }
  139. if other.AutoCaptureSessions != nil {
  140. config.AutoCaptureSessions = other.AutoCaptureSessions
  141. }
  142. config.updateEndpoints(other.Endpoint, &other.Endpoints)
  143. return config
  144. }
  145. // IsAutoCaptureSessions identifies whether or not the notifier should
  146. // automatically capture sessions as requests come in. It's a convenience
  147. // wrapper that allows automatic session capturing to be enabled by default.
  148. func (config *Configuration) IsAutoCaptureSessions() bool {
  149. if config.AutoCaptureSessions == nil {
  150. return true // enabled by default
  151. }
  152. if val, ok := config.AutoCaptureSessions.(bool); ok {
  153. return val
  154. }
  155. // It has been configured to *something* (although not a valid value)
  156. // assume the user wanted to disable this option.
  157. return false
  158. }
  159. func (config *Configuration) updateEndpoints(endpoint string, endpoints *Endpoints) {
  160. if endpoint != "" {
  161. config.Logger.Printf("WARNING: the 'Endpoint' Bugsnag configuration parameter is deprecated in favor of 'Endpoints'")
  162. config.Endpoints.Notify = endpoint
  163. config.Endpoints.Sessions = ""
  164. }
  165. if endpoints.Notify != "" {
  166. config.Endpoints.Notify = endpoints.Notify
  167. if endpoints.Sessions == "" {
  168. config.Logger.Printf("WARNING: Bugsnag notify endpoint configured without also configuring the sessions endpoint. No sessions will be recorded")
  169. config.Endpoints.Sessions = ""
  170. }
  171. }
  172. if endpoints.Sessions != "" {
  173. if endpoints.Notify == "" {
  174. panic("FATAL: Bugsnag sessions endpoint configured without also changing the notify endpoint. Bugsnag cannot identify where to report errors")
  175. }
  176. config.Endpoints.Sessions = endpoints.Sessions
  177. }
  178. }
  179. func (config *Configuration) merge(other *Configuration) *Configuration {
  180. return config.clone().update(other)
  181. }
  182. func (config *Configuration) clone() *Configuration {
  183. clone := *config
  184. return &clone
  185. }
  186. func (config *Configuration) isProjectPackage(pkg string) bool {
  187. for _, p := range config.ProjectPackages {
  188. if d, f := filepath.Split(p); f == "**" {
  189. if strings.HasPrefix(pkg, d) {
  190. return true
  191. }
  192. }
  193. if match, _ := filepath.Match(p, pkg); match {
  194. return true
  195. }
  196. }
  197. return false
  198. }
  199. func (config *Configuration) stripProjectPackages(file string) string {
  200. trimmedFile := file
  201. if strings.HasPrefix(trimmedFile, config.SourceRoot) {
  202. trimmedFile = strings.TrimPrefix(trimmedFile, config.SourceRoot)
  203. }
  204. for _, p := range config.ProjectPackages {
  205. if len(p) > 2 && p[len(p)-2] == '/' && p[len(p)-1] == '*' {
  206. p = p[:len(p)-1]
  207. } else if p[len(p)-1] == '*' && p[len(p)-2] == '*' {
  208. p = p[:len(p)-2]
  209. } else {
  210. p = p + "/"
  211. }
  212. if strings.HasPrefix(trimmedFile, p) {
  213. return strings.TrimPrefix(trimmedFile, p)
  214. }
  215. }
  216. return trimmedFile
  217. }
  218. func (config *Configuration) logf(fmt string, args ...interface{}) {
  219. if config != nil && config.Logger != nil {
  220. config.Logger.Printf(fmt, args...)
  221. } else {
  222. log.Printf(fmt, args...)
  223. }
  224. }
  225. func (config *Configuration) notifyInReleaseStage() bool {
  226. if config.NotifyReleaseStages == nil {
  227. return true
  228. }
  229. if config.ReleaseStage == "" {
  230. return true
  231. }
  232. for _, r := range config.NotifyReleaseStages {
  233. if r == config.ReleaseStage {
  234. return true
  235. }
  236. }
  237. return false
  238. }