Class: PeakFlowUtils::Notifier

Inherits:
Object
  • Object
show all
Defined in:
lib/peak_flow_utils/notifier.rb

Defined Under Namespace

Classes: FailedToReportError, NotConfiguredError, NotifyMessageError

Constant Summary collapse

CAPTURED_PARAMETERS_INSTANCE_VARIABLE =
:@peak_flow_utils_captured_parameters

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(auth_token:) ⇒ Notifier

Returns a new instance of Notifier.



61
62
63
64
65
66
# File 'lib/peak_flow_utils/notifier.rb', line 61

def initialize(auth_token:)
  @auth_token = auth_token
  @mutex = ::Mutex.new
  @on_notify_callbacks = []
  @parameters = ::PeakFlowUtils::InheritedLocalVar.new({})
end

Instance Attribute Details

#auth_tokenObject (readonly)

Returns the value of attribute auth_token.



8
9
10
# File 'lib/peak_flow_utils/notifier.rb', line 8

def auth_token
  @auth_token
end

#mutexObject (readonly)

Returns the value of attribute mutex.



8
9
10
# File 'lib/peak_flow_utils/notifier.rb', line 8

def mutex
  @mutex
end

#parametersObject (readonly)

Returns the value of attribute parameters.



8
9
10
# File 'lib/peak_flow_utils/notifier.rb', line 8

def parameters
  @parameters
end

Class Method Details

.configure(auth_token:) ⇒ Object



10
11
12
# File 'lib/peak_flow_utils/notifier.rb', line 10

def self.configure(auth_token:)
  @current = PeakFlowUtils::Notifier.new(auth_token: auth_token)
end

.currentObject

rubocop:disable Style/TrivialAccessors



14
15
16
# File 'lib/peak_flow_utils/notifier.rb', line 14

def self.current # rubocop:disable Style/TrivialAccessors
  @current
end

.notify(*args, **kwargs) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'lib/peak_flow_utils/notifier.rb', line 18

def self.notify(*args, **kwargs)
  if args.any?
    raise ArgumentError, "unexpected positional arguments" unless args.first.is_a?(Hash) && args.length == 1

    kwargs = args.first.merge(kwargs)
  end

  PeakFlowUtils::Notifier.current&.notify(**kwargs)
end

.notify_message(message) ⇒ Object



28
29
30
# File 'lib/peak_flow_utils/notifier.rb', line 28

def self.notify_message(message, **)
  PeakFlowUtils::Notifier.current&.notify_message(message, **)
end

.reset_parametersObject



32
33
34
# File 'lib/peak_flow_utils/notifier.rb', line 32

def self.reset_parameters
  ::PeakFlowUtils::Notifier.current&.instance_variable_set(:@parameters, ::PeakFlowUtils::InheritedLocalVar.new({}))
end

.with_parameters(parameters) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/peak_flow_utils/notifier.rb', line 36

def self.with_parameters(parameters)
  return yield unless ::PeakFlowUtils::Notifier.current

  random_id = ::SecureRandom.hex(16)

  ::PeakFlowUtils::Notifier.current.mutex.synchronize do
    current_parameters = ::PeakFlowUtils::Notifier.current.parameters.value
    raise "'parameters' was nil?" if current_parameters.nil?

    current_parameters[random_id] = parameters
  end

  begin
    yield
  rescue StandardError => e
    ::PeakFlowUtils::Notifier.current.capture_parameters_for_error(e)
    raise
  ensure
    ::PeakFlowUtils::Notifier.current.mutex.synchronize do
      current_parameters = ::PeakFlowUtils::Notifier.current.parameters.value
      current_parameters.delete(random_id)
    end
  end
end

Instance Method Details

#capture_parameters_for_error(error) ⇒ Object



68
69
70
71
72
# File 'lib/peak_flow_utils/notifier.rb', line 68

def capture_parameters_for_error(error)
  return if error.instance_variable_defined?(CAPTURED_PARAMETERS_INSTANCE_VARIABLE)

  error.instance_variable_set(CAPTURED_PARAMETERS_INSTANCE_VARIABLE, current_parameters)
end

#captured_parameters_for_error(error) ⇒ Object



74
75
76
77
78
# File 'lib/peak_flow_utils/notifier.rb', line 74

def captured_parameters_for_error(error)
  return unless error&.instance_variable_defined?(CAPTURED_PARAMETERS_INSTANCE_VARIABLE)

  error.instance_variable_get(CAPTURED_PARAMETERS_INSTANCE_VARIABLE)
end

#current_parameters(error: nil, parameters: nil) ⇒ Object



80
81
82
83
84
85
86
87
# File 'lib/peak_flow_utils/notifier.rb', line 80

def current_parameters(error: nil, parameters: nil)
  hashes = current_parameters_hashes
  captured_parameters = captured_parameters_for_error(error)
  hashes << captured_parameters if captured_parameters
  hashes << parameters if parameters

  ::PeakFlowUtils::DeepMerger.execute!(hashes: hashes)
end

#current_parameters_hashesObject



89
90
91
# File 'lib/peak_flow_utils/notifier.rb', line 89

def current_parameters_hashes
  parameters.value.values
end

#error_message_from_response(response) ⇒ Object



93
94
95
96
97
98
99
100
101
102
# File 'lib/peak_flow_utils/notifier.rb', line 93

def error_message_from_response(response)
  message = "Couldn't report error to Peakflow (code #{response.code})"

  if response["content-type"]&.starts_with?("application/json")
    response_data = ::JSON.parse(response.body)
    message << ": #{response_data.fetch("errors").join(". ")}" if response_data["errors"]
  end

  message
end

#notify(error:, environment: nil, parameters: nil) ⇒ Object



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
# File 'lib/peak_flow_utils/notifier.rb', line 104

def notify(error:, environment: nil, parameters: nil)
  error = normalize_error(error, fallback_backtrace: caller(2))
  error_parser = ::PeakFlowUtils::NotifierErrorParser.new(
    backtrace: error.backtrace,
    environment: environment,
    error: error
  )

  merged_parameters = current_parameters(error: error, parameters: parameters)

  uri = URI("https://www.peakflow.io/errors/reports")

  @on_notify_callbacks.each do |on_notify_callback|
    on_notify_callback.call(parameters: merged_parameters)
  end

  data = {
    auth_token: auth_token,
    error: {
      backtrace: error.backtrace,
      environment: error_parser.cleaned_environment,
      error_class: error.class.name,
      file_path: error_parser.file_path,
      line_number: error_parser.line_number,
      message: error.message,
      parameters: merged_parameters,
      remote_ip: error_parser.remote_ip,
      url: error_parser.url,
      user_agent: error_parser.user_agent
    }
  }

  send_notify_request(data: PeakFlowUtils::ParseJson.new(data).parse, uri: uri)
end

#notify_message(message) ⇒ Object



139
140
141
142
143
# File 'lib/peak_flow_utils/notifier.rb', line 139

def notify_message(message, **)
  raise NotifyMessageError, message
rescue NotifyMessageError => e
  notify(error: e, **)
end

#on_notify(&blk) ⇒ Object



145
146
147
# File 'lib/peak_flow_utils/notifier.rb', line 145

def on_notify(&blk)
  @on_notify_callbacks << blk
end

#send_notify_request(data:, uri:) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/peak_flow_utils/notifier.rb', line 149

def send_notify_request(data:, uri:)
  https = ::Net::HTTP.new(uri.host, uri.port)
  https.use_ssl = true

  request = ::Net::HTTP::Post.new(uri.path)
  request["Content-Type"] = "application/json"
  request.body = ::JSON.generate(data)

  response = https.request(request)

  raise FailedToReportError, error_message_from_response(response) unless response.code == "200"

  response_data = ::JSON.parse(response.body)

  # Data not always present so dont use fetch
  ::PeakFlowUtils::NotifierResponse.new(
    bug_report_id: response_data["bug_report_id"],
    bug_report_instance_id: response_data["bug_report_instance_id"],
    project_id: response_data["project_id"],
    project_slug: response_data["project_slug"],
    url: response_data["url"]
  )
end