1
0

bugsnag.go 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package bugsnag
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "net/http"
  7. "os"
  8. "path/filepath"
  9. "runtime"
  10. "sync"
  11. "time"
  12. "github.com/bugsnag/bugsnag-go/device"
  13. "github.com/bugsnag/bugsnag-go/errors"
  14. "github.com/bugsnag/bugsnag-go/sessions"
  15. // Fixes a bug with SHA-384 intermediate certs on some platforms.
  16. // - https://github.com/bugsnag/bugsnag-go/issues/9
  17. _ "crypto/sha512"
  18. )
  19. // VERSION defines the version of this Bugsnag notifier
  20. const VERSION = "1.4.0"
  21. var panicHandlerOnce sync.Once
  22. var sessionTrackerOnce sync.Once
  23. var middleware middlewareStack
  24. // Config is the configuration for the default bugsnag notifier.
  25. var Config Configuration
  26. var sessionTrackingConfig sessions.SessionTrackingConfiguration
  27. // DefaultSessionPublishInterval defines how often sessions should be sent to
  28. // Bugsnag.
  29. // Deprecated: Exposed for developer sanity in testing. Modify at own risk.
  30. var DefaultSessionPublishInterval = 60 * time.Second
  31. var defaultNotifier = Notifier{&Config, nil}
  32. var sessionTracker sessions.SessionTracker
  33. // Configure Bugsnag. The only required setting is the APIKey, which can be
  34. // obtained by clicking on "Settings" in your Bugsnag dashboard. This function
  35. // is also responsible for installing the global panic handler, so it should be
  36. // called as early as possible in your initialization process.
  37. func Configure(config Configuration) {
  38. Config.update(&config)
  39. updateSessionConfig()
  40. // Only do once in case the user overrides the default panichandler, and
  41. // configures multiple times.
  42. panicHandlerOnce.Do(Config.PanicHandler)
  43. }
  44. // StartSession creates new context from the context.Context instance with
  45. // Bugsnag session data attached. Will start the session tracker if not already
  46. // started
  47. func StartSession(ctx context.Context) context.Context {
  48. sessionTrackerOnce.Do(startSessionTracking)
  49. return sessionTracker.StartSession(ctx)
  50. }
  51. // Notify sends an error.Error to Bugsnag along with the current stack trace.
  52. // If at all possible, it is recommended to pass in a context.Context, e.g.
  53. // from a http.Request or bugsnag.StartSession() as Bugsnag will be able to
  54. // extract additional information in some cases. The rawData is used to send
  55. // extra information along with the error. For example you can pass the current
  56. // http.Request to Bugsnag to see information about it in the dashboard, or set
  57. // the severity of the notification. For a detailed list of the information
  58. // that can be extracted, see
  59. // https://docs.bugsnag.com/platforms/go/reporting-handled-errors/
  60. func Notify(err error, rawData ...interface{}) error {
  61. if e := checkForEmptyError(err); e != nil {
  62. return e
  63. }
  64. // Stripping one stackframe to not include this function in the stacktrace
  65. // for a manual notification.
  66. skipFrames := 1
  67. return defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
  68. }
  69. // AutoNotify logs a panic on a goroutine and then repanics.
  70. // It should only be used in places that have existing panic handlers further
  71. // up the stack.
  72. // Although it's not strictly enforced, it's highly recommended to pass a
  73. // context.Context object that has at one-point been returned from
  74. // bugsnag.StartSession. Doing so ensures your stability score remains accurate,
  75. // and future versions of Bugsnag may extract more useful information from this
  76. // context.
  77. // The rawData is used to send extra information along with any
  78. // panics that are handled this way.
  79. // Usage:
  80. // go func() {
  81. // ctx := bugsnag.StartSession(context.Background())
  82. // defer bugsnag.AutoNotify(ctx)
  83. // // (possibly crashy code)
  84. // }()
  85. // See also: bugsnag.Recover()
  86. func AutoNotify(rawData ...interface{}) {
  87. if err := recover(); err != nil {
  88. severity := defaultNotifier.getDefaultSeverity(rawData, SeverityError)
  89. state := HandledState{SeverityReasonHandledPanic, severity, true, ""}
  90. rawData = append([]interface{}{state}, rawData...)
  91. // We strip the following stackframes as they don't add much info
  92. // - runtime/$arch - e.g. runtime/asm_amd64.s#call32
  93. // - runtime/panic.go#gopanic
  94. // Panics have their own stacktrace, so no stripping of the current stack
  95. skipFrames := 2
  96. defaultNotifier.NotifySync(errors.New(err, skipFrames), true, rawData...)
  97. sessionTracker.FlushSessions()
  98. panic(err)
  99. }
  100. }
  101. // Recover logs a panic on a goroutine and then recovers.
  102. // Although it's not strictly enforced, it's highly recommended to pass a
  103. // context.Context object that has at one-point been returned from
  104. // bugsnag.StartSession. Doing so ensures your stability score remains accurate,
  105. // and future versions of Bugsnag may extract more useful information from this
  106. // context.
  107. // The rawData is used to send extra information along with
  108. // any panics that are handled this way
  109. // Usage:
  110. // go func() {
  111. // ctx := bugsnag.StartSession(context.Background())
  112. // defer bugsnag.Recover(ctx)
  113. // // (possibly crashy code)
  114. // }()
  115. // If you wish that any panics caught by the call to Recover shall affect your
  116. // stability score (it does not by default):
  117. // go func() {
  118. // ctx := bugsnag.StartSession(context.Background())
  119. // defer bugsnag.Recover(ctx, bugsnag.HandledState{Unhandled: true})
  120. // // (possibly crashy code)
  121. // }()
  122. // See also: bugsnag.AutoNotify()
  123. func Recover(rawData ...interface{}) {
  124. if err := recover(); err != nil {
  125. severity := defaultNotifier.getDefaultSeverity(rawData, SeverityWarning)
  126. state := HandledState{SeverityReasonHandledPanic, severity, false, ""}
  127. rawData = append([]interface{}{state}, rawData...)
  128. // We strip the following stackframes as they don't add much info
  129. // - runtime/$arch - e.g. runtime/asm_amd64.s#call32
  130. // - runtime/panic.go#gopanic
  131. // Panics have their own stacktrace, so no stripping of the current stack
  132. skipFrames := 2
  133. defaultNotifier.Notify(errors.New(err, skipFrames), rawData...)
  134. }
  135. }
  136. // OnBeforeNotify adds a callback to be run before a notification is sent to
  137. // Bugsnag. It can be used to modify the event or its MetaData. Changes made
  138. // to the configuration are local to notifying about this event. To prevent the
  139. // event from being sent to Bugsnag return an error, this error will be
  140. // returned from bugsnag.Notify() and the event will not be sent.
  141. func OnBeforeNotify(callback func(event *Event, config *Configuration) error) {
  142. middleware.OnBeforeNotify(callback)
  143. }
  144. // Handler creates an http Handler that notifies Bugsnag any panics that
  145. // happen. It then repanics so that the default http Server panic handler can
  146. // handle the panic too. The rawData is used to send extra information along
  147. // with any panics that are handled this way.
  148. func Handler(h http.Handler, rawData ...interface{}) http.Handler {
  149. notifier := New(rawData...)
  150. if h == nil {
  151. h = http.DefaultServeMux
  152. }
  153. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  154. request := r
  155. // Record a session if auto notify session is enabled
  156. ctx := r.Context()
  157. if Config.IsAutoCaptureSessions() {
  158. ctx = StartSession(ctx)
  159. }
  160. ctx = AttachRequestData(ctx, request)
  161. request = r.WithContext(ctx)
  162. defer notifier.AutoNotify(ctx, request)
  163. h.ServeHTTP(w, request)
  164. })
  165. }
  166. // HandlerFunc creates an http HandlerFunc that notifies Bugsnag about any
  167. // panics that happen. It then repanics so that the default http Server panic
  168. // handler can handle the panic too. The rawData is used to send extra
  169. // information along with any panics that are handled this way. If you have
  170. // already wrapped your http server using bugsnag.Handler() you don't also need
  171. // to wrap each HandlerFunc.
  172. func HandlerFunc(h http.HandlerFunc, rawData ...interface{}) http.HandlerFunc {
  173. notifier := New(rawData...)
  174. return func(w http.ResponseWriter, r *http.Request) {
  175. request := r
  176. // Record a session if auto notify session is enabled
  177. ctx := request.Context()
  178. if notifier.Config.IsAutoCaptureSessions() {
  179. ctx = StartSession(ctx)
  180. }
  181. ctx = AttachRequestData(ctx, request)
  182. request = request.WithContext(ctx)
  183. defer notifier.AutoNotify(ctx)
  184. h(w, request)
  185. }
  186. }
  187. // checkForEmptyError checks if the given error (to be reported to Bugsnag) is
  188. // nil. If it is, then log an error message and return another error wrapping
  189. // this error message.
  190. func checkForEmptyError(err error) error {
  191. if err != nil {
  192. return nil
  193. }
  194. msg := "attempted to notify Bugsnag without supplying an error. Bugsnag not notified"
  195. Config.Logger.Printf("ERROR: " + msg)
  196. return fmt.Errorf(msg)
  197. }
  198. func init() {
  199. // Set up builtin middlewarez
  200. OnBeforeNotify(httpRequestMiddleware)
  201. // Default configuration
  202. sourceRoot := ""
  203. if gopath := os.Getenv("GOPATH"); len(gopath) > 0 {
  204. sourceRoot = filepath.Join(gopath, "src") + "/"
  205. } else {
  206. sourceRoot = filepath.Join(runtime.GOROOT(), "src") + "/"
  207. }
  208. Config.update(&Configuration{
  209. APIKey: "",
  210. Endpoints: Endpoints{
  211. Notify: "https://notify.bugsnag.com",
  212. Sessions: "https://sessions.bugsnag.com",
  213. },
  214. Hostname: device.GetHostname(),
  215. AppType: "",
  216. AppVersion: "",
  217. AutoCaptureSessions: true,
  218. ReleaseStage: "",
  219. ParamsFilters: []string{"password", "secret", "authorization", "cookie"},
  220. SourceRoot: sourceRoot,
  221. // * for app-engine
  222. ProjectPackages: []string{"main*"},
  223. NotifyReleaseStages: nil,
  224. Logger: log.New(os.Stdout, log.Prefix(), log.Flags()),
  225. PanicHandler: defaultPanicHandler,
  226. Transport: http.DefaultTransport,
  227. flushSessionsOnRepanic: true,
  228. })
  229. updateSessionConfig()
  230. }
  231. func startSessionTracking() {
  232. if sessionTracker == nil {
  233. updateSessionConfig()
  234. sessionTracker = sessions.NewSessionTracker(&sessionTrackingConfig)
  235. }
  236. }
  237. func updateSessionConfig() {
  238. sessionTrackingConfig.Update(&sessions.SessionTrackingConfiguration{
  239. APIKey: Config.APIKey,
  240. AutoCaptureSessions: Config.AutoCaptureSessions,
  241. Endpoint: Config.Endpoints.Sessions,
  242. Version: VERSION,
  243. PublishInterval: DefaultSessionPublishInterval,
  244. Transport: Config.Transport,
  245. ReleaseStage: Config.ReleaseStage,
  246. Hostname: Config.Hostname,
  247. AppType: Config.AppType,
  248. AppVersion: Config.AppVersion,
  249. NotifyReleaseStages: Config.NotifyReleaseStages,
  250. Logger: Config.Logger,
  251. })
  252. }