Module: ActionReporter

Defined in:
lib/action_reporter.rb,
lib/action_reporter/base.rb,
lib/action_reporter/error.rb,
lib/action_reporter/utils.rb,
lib/action_reporter/current.rb,
lib/action_reporter/version.rb,
lib/action_reporter/rails_reporter.rb,
lib/action_reporter/sentry_reporter.rb,
lib/action_reporter/audited_reporter.rb,
lib/action_reporter/plugin_discovery.rb,
lib/action_reporter/scout_apm_reporter.rb,
lib/action_reporter/honeybadger_reporter.rb,
lib/action_reporter/paper_trail_reporter.rb,
lib/action_reporter/active_version_reporter.rb

Defined Under Namespace

Modules: PluginDiscovery, Utils Classes: ActiveVersionReporter, AuditedReporter, Base, ConfigurationError, Current, Error, HoneybadgerReporter, PaperTrailReporter, RailsReporter, ReporterError, ScoutApmReporter, SentryReporter

Constant Summary collapse

VERSION =
"3.2.1"

Class Method Summary collapse

Class Method Details

.available_reportersArray<Class>

Get available reporters (auto-discovered + registered) This is lazy-loaded and does not block application boot

Returns:

  • (Array<Class>)

    Array of reporter classes



21
22
23
# File 'lib/action_reporter.rb', line 21

def available_reporters
  PluginDiscovery.available_reporters
end

.check_in(identifier) ⇒ Object



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/action_reporter.rb', line 196

def check_in(identifier)
  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:check_in)

    begin
      reporter.check_in(identifier)
    rescue => e
      handle_reporter_error(reporter, e, "check_in")
    end
  end
end

.context(args) ⇒ Object

Raises:

  • (ArgumentError)


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/action_reporter.rb', line 108

def context(args)
  raise ArgumentError, "context must be a Hash" unless args.is_a?(Hash)

  warn_if_no_reporters(:context)

  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:context)

    begin
      reporter.context(args)
    rescue => e
      handle_reporter_error(reporter, e, "context")
    end
  end
end

.current_remote_addrObject



179
180
181
# File 'lib/action_reporter.rb', line 179

def current_remote_addr
  Current.current_remote_addr
end

.current_remote_addr=(remote_addr) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/action_reporter.rb', line 183

def current_remote_addr=(remote_addr)
  Current.current_remote_addr = remote_addr
  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:current_remote_addr=)

    begin
      reporter.current_remote_addr = remote_addr
    rescue => e
      handle_reporter_error(reporter, e, "current_remote_addr=")
    end
  end
end

.current_request_uuidObject



162
163
164
# File 'lib/action_reporter.rb', line 162

def current_request_uuid
  Current.current_request_uuid
end

.current_request_uuid=(request_uuid) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/action_reporter.rb', line 166

def current_request_uuid=(request_uuid)
  Current.current_request_uuid = request_uuid
  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:current_request_uuid=)

    begin
      reporter.current_request_uuid = request_uuid
    rescue => e
      handle_reporter_error(reporter, e, "current_request_uuid=")
    end
  end
end

.current_userObject



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

def current_user
  Current.current_user
end

.current_user=(user) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/action_reporter.rb', line 149

def current_user=(user)
  Current.current_user = user
  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:current_user=)

    begin
      reporter.current_user = user
    rescue => e
      handle_reporter_error(reporter, e, "current_user=")
    end
  end
end

.enabled_reportersObject



46
47
48
# File 'lib/action_reporter.rb', line 46

def enabled_reporters
  @enabled_reporters || []
end

.enabled_reporters=(reporters) ⇒ Object



42
43
44
# File 'lib/action_reporter.rb', line 42

def enabled_reporters=(reporters)
  @enabled_reporters = reporters || []
end

.error_handlerObject



58
59
60
# File 'lib/action_reporter.rb', line 58

def error_handler
  @error_handler
end

.error_handler=(handler) ⇒ Object



62
63
64
# File 'lib/action_reporter.rb', line 62

def error_handler=(handler)
  @error_handler = handler
end

.handle_reporter_error(reporter, error, method_name) ⇒ Object



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/action_reporter.rb', line 78

def handle_reporter_error(reporter, error, method_name)
  error_message = "ActionReporter: #{reporter.class}##{method_name} failed: #{error.class} - #{error.message}"

  if logger
    logger.error(error_message)
    logger.debug(error.backtrace.join("\n")) if error.backtrace
  end

  if error_handler&.respond_to?(:call)
    error_handler.call(error, reporter, method_name)
  end
rescue => e
  # If error handling itself fails, log to stderr as last resort
  warn "ActionReporter: Error handler failed: #{e.message}"
end

.loggerObject



50
51
52
# File 'lib/action_reporter.rb', line 50

def logger
  @logger || ((defined?(Rails) && Rails.respond_to?(:logger)) ? Rails.logger : nil)
end

.logger=(logger) ⇒ Object



54
55
56
# File 'lib/action_reporter.rb', line 54

def logger=(logger)
  @logger = logger
end

.notify(error, context: {}) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/action_reporter.rb', line 94

def notify(error, context: {})
  warn_if_no_reporters(:notify)

  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:notify)

    begin
      reporter.notify(error, context: context)
    rescue => e
      handle_reporter_error(reporter, e, "notify")
    end
  end
end

.register_reporter(name, class_name:, require_path:) ⇒ Object

Register a custom reporter This allows third-party gems or custom code to register reporters

Examples:

ActionReporter.register_reporter(:custom, class_name: "MyApp::CustomReporter", require_path: "my_app/custom_reporter")

Parameters:

  • name (Symbol)

    Reporter name

  • class_name (String)

    Fully qualified class name

  • require_path (String)

    Path to require



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

def register_reporter(name, class_name:, require_path:)
  PluginDiscovery.register(name, class_name: class_name, require_path: require_path)
end

.reset_contextObject



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/action_reporter.rb', line 124

def reset_context
  Current.current_user = nil
  Current.current_request_uuid = nil
  Current.current_remote_addr = nil
  Current.transaction_id = nil
  Current.transaction_name = nil

  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:reset_context)

    begin
      reporter.reset_context
    rescue => e
      handle_reporter_error(reporter, e, "reset_context")
    end
  end

  # Reset Current attributes if supported
  Current.reset if Current.respond_to?(:reset)
end

.transaction(name: nil, id: nil, **context_options, &block) ⇒ Object

Raises:

  • (ArgumentError)


246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/action_reporter.rb', line 246

def transaction(name: nil, id: nil, **context_options, &block)
  raise ArgumentError, "transaction requires a block" unless block_given?

  previous_name = transaction_name
  previous_id = transaction_id

  begin
    self.transaction_name = name if name
    self.transaction_id = id if id

    context(context_options) if context_options.any?

    block.call
  ensure
    self.transaction_name = previous_name if name
    self.transaction_id = previous_id if id
    if context_options.any?
      context(context_options.transform_values { nil })
    end
  end
end

.transaction_idObject



208
209
210
# File 'lib/action_reporter.rb', line 208

def transaction_id
  Current.transaction_id
end

.transaction_id=(transaction_id) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/action_reporter.rb', line 212

def transaction_id=(transaction_id)
  Current.transaction_id = transaction_id
  context(transaction_id: transaction_id)

  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:transaction_id=)

    begin
      reporter.transaction_id = transaction_id
    rescue => e
      handle_reporter_error(reporter, e, "transaction_id=")
    end
  end
end

.transaction_nameObject



227
228
229
# File 'lib/action_reporter.rb', line 227

def transaction_name
  Current.transaction_name
end

.transaction_name=(transaction_name) ⇒ Object



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/action_reporter.rb', line 231

def transaction_name=(transaction_name)
  Current.transaction_name = transaction_name
  context(transaction_name: transaction_name)

  enabled_reporters.each do |reporter|
    next unless reporter.respond_to?(:transaction_name=)

    begin
      reporter.transaction_name = transaction_name
    rescue => e
      handle_reporter_error(reporter, e, "transaction_name=")
    end
  end
end

.user_id_resolverObject

Optional proc to turn current_user into a string id for reporters (Sentry, Scout, etc.). Receives the same object passed to ActionReporter.current_user= and must return a String (or nil to skip setting an id, if the reporter supports it). When unset, ActionReporter::Base#resolve_user_id uses built-in defaults.



70
71
72
# File 'lib/action_reporter.rb', line 70

def user_id_resolver
  @user_id_resolver
end

.user_id_resolver=(resolver) ⇒ Object



74
75
76
# File 'lib/action_reporter.rb', line 74

def user_id_resolver=(resolver)
  @user_id_resolver = resolver
end

.warn_if_no_reporters(method_name) ⇒ Object



268
269
270
271
272
273
274
# File 'lib/action_reporter.rb', line 268

def warn_if_no_reporters(method_name)
  return if @no_reporters_warned
  return unless enabled_reporters.empty?

  @no_reporters_warned = true
  warn "[action_reporter] #{method_name} called but enabled_reporters is empty"
end