sentry-manager.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. const Serializers = require('./serializers')
  2. const RATE_LIMIT_MAX_ERRORS = 5
  3. const RATE_LIMIT_INTERVAL_MS = 60000
  4. class SentryManager {
  5. constructor(dsn, options) {
  6. this.Sentry = require('@sentry/node')
  7. this.Sentry.init({ dsn, ...options })
  8. // for rate limiting on sentry reporting
  9. this.lastErrorTimeStamp = 0
  10. this.lastErrorCount = 0
  11. }
  12. captureExceptionRateLimited(attributes, message) {
  13. const now = Date.now()
  14. // have we recently reported an error?
  15. const recentSentryReport =
  16. now - this.lastErrorTimeStamp < RATE_LIMIT_INTERVAL_MS
  17. // if so, increment the error count
  18. if (recentSentryReport) {
  19. this.lastErrorCount++
  20. } else {
  21. this.lastErrorCount = 0
  22. this.lastErrorTimeStamp = now
  23. }
  24. // only report 5 errors every minute to avoid overload
  25. if (this.lastErrorCount < RATE_LIMIT_MAX_ERRORS) {
  26. // add a note if the rate limit has been hit
  27. const note =
  28. this.lastErrorCount + 1 === RATE_LIMIT_MAX_ERRORS
  29. ? '(rate limited)'
  30. : ''
  31. // report the exception
  32. this.captureException(attributes, message, `error${note}`)
  33. }
  34. }
  35. captureException(attributes, message, level) {
  36. // handle case of logger.error "message"
  37. if (typeof attributes === 'string') {
  38. attributes = { err: new Error(attributes) }
  39. }
  40. // extract any error object
  41. let error = Serializers.err(attributes.err || attributes.error)
  42. // avoid reporting errors twice
  43. for (const key in attributes) {
  44. const value = attributes[key]
  45. if (value instanceof Error && value.reportedToSentry) {
  46. return
  47. }
  48. }
  49. // include our log message in the error report
  50. if (error == null) {
  51. if (typeof message === 'string') {
  52. error = { message }
  53. }
  54. } else if (message != null) {
  55. attributes.description = message
  56. }
  57. // report the error
  58. if (error != null) {
  59. // capture attributes and use *_id objects as tags
  60. const tags = {}
  61. const extra = {}
  62. for (const key in attributes) {
  63. let value = attributes[key]
  64. if (Serializers[key]) {
  65. value = Serializers[key](value)
  66. }
  67. if (key.match(/_id/) && typeof value === 'string') {
  68. tags[key] = value
  69. }
  70. extra[key] = value
  71. }
  72. // OError integration
  73. extra.info = error.info
  74. delete error.info
  75. try {
  76. // send the error to sentry
  77. this.Sentry.captureException(error, { tags, extra, level })
  78. // put a flag on the errors to avoid reporting them multiple times
  79. for (const key in attributes) {
  80. const value = attributes[key]
  81. if (value instanceof Error) {
  82. value.reportedToSentry = true
  83. }
  84. }
  85. } catch (err) {
  86. // ignore Sentry errors
  87. }
  88. }
  89. }
  90. }
  91. module.exports = SentryManager