File: datadog_notifier.rb

package info (click to toggle)
ruby-exception-notification 5.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 364 kB
  • sloc: ruby: 1,350; makefile: 2
file content (156 lines) | stat: -rw-r--r-- 3,862 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# frozen_string_literal: true

require "action_dispatch"

module ExceptionNotifier
  class DatadogNotifier < BaseNotifier
    attr_reader :client,
      :default_options

    def initialize(options)
      super
      @client = options.fetch(:client)
      @default_options = options
    end

    def call(exception, options = {})
      client.emit_event(
        datadog_event(exception, options)
      )
    end

    def datadog_event(exception, options = {})
      DatadogExceptionEvent.new(
        exception,
        options.reverse_merge(default_options)
      ).event
    end

    class DatadogExceptionEvent
      include ExceptionNotifier::BacktraceCleaner

      MAX_TITLE_LENGTH = 120
      MAX_VALUE_LENGTH = 300
      MAX_BACKTRACE_SIZE = 3
      ALERT_TYPE = "error"

      attr_reader :exception,
        :options

      def initialize(exception, options)
        @exception = exception
        @options = options
      end

      def request
        @request ||= ActionDispatch::Request.new(options[:env]) if options[:env]
      end

      def controller
        @controller ||= options[:env] && options[:env]["action_controller.instance"]
      end

      def backtrace
        @backtrace ||= exception.backtrace ? clean_backtrace(exception) : []
      end

      def tags
        options[:tags] || []
      end

      def title_prefix
        options[:title_prefix] || ""
      end

      def event
        title = formatted_title
        body = formatted_body

        Dogapi::Event.new(
          body,
          msg_title: title,
          alert_type: ALERT_TYPE,
          tags: tags,
          aggregation_key: [title]
        )
      end

      def formatted_title
        title =
          "#{title_prefix}#{controller_subtitle} (#{exception.class}) #{exception.message.inspect}"

        truncate(title, MAX_TITLE_LENGTH)
      end

      def formatted_body
        text = []

        text << "%%%"
        text << formatted_request if request
        text << formatted_session if request
        text << formatted_backtrace
        text << "%%%"

        text.join("\n")
      end

      def formatted_key_value(key, value)
        "**#{key}:** #{value}"
      end

      def formatted_request
        text = []
        text << "### **Request**"
        text << formatted_key_value("URL", request.url)
        text << formatted_key_value("HTTP Method", request.request_method)
        text << formatted_key_value("IP Address", request.remote_ip)
        text << formatted_key_value("Parameters", request.filtered_parameters.inspect)
        text << formatted_key_value("Timestamp", Time.current)
        text << formatted_key_value("Server", Socket.gethostname)
        text << formatted_key_value("Rails root", Rails.root) if defined?(Rails) && Rails.respond_to?(:root)
        text << formatted_key_value("Process", $PROCESS_ID)
        text << "___"
        text.join("\n")
      end

      def formatted_session
        text = []
        text << "### **Session**"
        text << formatted_key_value("Data", request.session.to_hash)
        text << "___"
        text.join("\n")
      end

      def formatted_backtrace
        size = [backtrace.size, MAX_BACKTRACE_SIZE].min

        text = []
        text << "### **Backtrace**"
        text << "````"
        size.times { |i| text << backtrace[i] }
        text << "````"
        text << "___"
        text.join("\n")
      end

      def truncate(string, max)
        (string.length > max) ? "#{string[0...max]}..." : string
      end

      def inspect_object(object)
        case object
        when Hash, Array
          truncate(object.inspect, MAX_VALUE_LENGTH)
        else
          object.to_s
        end
      end

      private

      def controller_subtitle
        "#{controller.controller_name} #{controller.action_name}" if controller
      end
    end
  end
end