Class: Webhookdb::Organization::ErrorHandler
- Inherits:
-
Object
- Object
- Webhookdb::Organization::ErrorHandler
- Includes:
- Dbutil
- Defined in:
- lib/webhookdb/organization/error_handler.rb
Constant Summary collapse
- DOCS_URL =
"https://docs.webhookdb.com/docs/integrating/error-handlers.html"
- MAX_SENTRY_TAG_CHARS =
200
Constants included from Dbutil
Instance Method Summary collapse
-
#_handle_sentry(payload) ⇒ Object
See develop.sentry.dev/sdk/data-model/envelopes/ for directly posting to Sentry.
-
#before_create ⇒ Object
:Sequel Hooks:.
- #dispatch(payload) ⇒ Object
- #payload_for_template(tmpl) ⇒ Object
- #sentry? ⇒ Boolean
Methods included from Dbutil
borrow_conn, configured_connection_options, conn_opts, displaysafe_url, reduce_expr, take_conn
Instance Method Details
#_handle_sentry(payload) ⇒ Object
See develop.sentry.dev/sdk/data-model/envelopes/ for directly posting to Sentry. We do NOT want to use the SDK here, since we do not want to leak anything, and anyway, the runtime information is not important.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/webhookdb/organization/error_handler.rb', line 63 def _handle_sentry(payload) payload = payload.deep_symbolize_keys now = Time.now.utc # We can assume the url is the Sentry DSN u = URI(self.url) key = u.user project_id = u.path.delete("/") # Give some valid value for this, though it's not accurate. client = "sentry-ruby/5.22.1" ts = now.to_i # Auth headers are done by capturing an actual request. The docs aren't clear about their format. # It's possible using the DSN auth would also work but let's use this. headers = { "Content-Type" => "application/x-sentry-envelope", "X-Sentry-Auth" => "Sentry sentry_version=7, sentry_key=#{key}, sentry_client=#{client}, sentry_timestamp=#{ts}", } event_id = Uuidx.v4 # The first line will be used as the title. = "WebhookDB Error in #{payload.fetch(:service_integration_name)}\n\n#{payload.fetch(:message)}" # Let the caller set the level through query params level = URI.decode_www_form(u.query || "").to_h.fetch("level", "warning") # Split structured data into 'extra' (cannot be searched on, just shows in the UI) # and 'tags' (can be searched/faceted on, shows in the right bar). = Webhookdb::Message::Template.new.liquid_drops.keys.to_set , extra = payload.fetch(:details).partition do |k, v| # Non-strings are always tags next true unless v.is_a?(String) # Never tag on basic stuff that doesn't change ever next false if .include?(k) # Unstructured strings may include spaces or braces, and are not tags next false if v.include?(" ") || v.include?("{") # If it's a small string, treat it as a tag. v.size < MAX_SENTRY_TAG_CHARS end # Envelope structure is a multiline JSON file, I guess jsonl format envelopes = [ {event_id:, sent_at: now.iso8601}, {type: "event", content_type: "application/json"}, { event_id:, timestamp: now.iso8601, platform: "ruby", level:, transaction: payload.fetch(:service_integration_table), release: "webhookdb@#{Webhookdb::RELEASE}", environment: Webhookdb::RACK_ENV, tags: .to_h, extra: extra.to_h, # We should use the same grouping for these messages as we would for emails fingerprint: [payload.fetch(:signature)], message: , }, ] body = envelopes.map(&:to_json).join("\n") store_url = URI(self.url) store_url.scheme = "https" if store_url.scheme == "sentry" store_url.user = nil store_url.password = nil store_url.path = "/api/#{project_id}/envelope/" store_url.query = "" Webhookdb::Http.post( store_url.to_s, body, headers:, timeout: Webhookdb::Organization::Alerting.error_handler_timeout, logger: self.logger, ) end |
#before_create ⇒ Object
:Sequel Hooks:
138 139 140 |
# File 'lib/webhookdb/organization/error_handler.rb', line 138 def before_create self[:opaque_id] ||= Webhookdb::Id.new_opaque_id("oeh") end |
#dispatch(payload) ⇒ Object
39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/webhookdb/organization/error_handler.rb', line 39 def dispatch(payload) if self.sentry? self._handle_sentry(payload) return end Webhookdb::Http.post( self.url, payload, timeout: Webhookdb::Organization::Alerting.error_handler_timeout, logger: self.logger, ) end |
#payload_for_template(tmpl) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/webhookdb/organization/error_handler.rb', line 16 def payload_for_template(tmpl) params = { error_type: tmpl.class.name.split("::").last.underscore, details: tmpl.liquid_drops.to_h, signature: tmpl.signature, organization_key: self.organization.key, service_integration_id: tmpl.service_integration.opaque_id, service_integration_name: tmpl.service_integration.service_name, service_integration_table: tmpl.service_integration.table_name, } recipient = Webhookdb::Message::Transport.for(:email).recipient(Webhookdb.support_email) = Webhookdb::Message.render(tmpl, :email, recipient) = .to_s.strip = Premailer.new( , with_html_string: true, warn_level: Premailer::Warnings::SAFE, ) = .to_plain_text params[:message] = return params end |
#sentry? ⇒ Boolean
53 54 55 56 |
# File 'lib/webhookdb/organization/error_handler.rb', line 53 def sentry? u = URI(self.url) return u.scheme == "sentry" || u.host&.end_with?("sentry.io") end |