Skip to content

Commit dbe7e3d

Browse files
halfbytejoshuap
andauthored
feat: implements honeybadger.event by synchronous log call (#512)
* Implements Honeybadger.event by sync log call * API changes, add timestamp * fix #event: Use Hash() and reverse merge order. * feat: implement simple debug backend endpoint for events (#513) * Implement simple debug backend endpoint for events This currently is missing a queue and calls the backend directly from the agent. Should I implement an events_worker within this PR or in the PR that adds the server backend? * Refactor signature of events backend to take only one argument * WIP: Add worker * WIP start of worker spec * Worker spec successfully duplicated * Implement timeout mechanism using separate thread Given that the worker relies on the Queue as the main scheduling mechanism I saw no other way than to start a second thread that occasionally throws a message into the queue to check if the timeout is reached. This seems to work in testing. * Remove one timeout check, namespace config * Remove unused code * Add events worker to agent stop/flush commands * Fix debug message in events worker --------- Co-authored-by: Joshua Wood <[email protected]> * Slightly bump sleep values in test to fix jruby tests There seems to be a slight difference in how sleep works in jruby so the timeouts in the tests did not hit predictably. * install sqlite dev package for rails tests * use sudo * Okay, sqlite problem seems to be based on rubygems issue sparklemotion/sqlite3-ruby#411 * I have no idea what I'm doing * feat: http(s) backend implementation for events (#520) * Implement simple debug backend endpoint for events This currently is missing a queue and calls the backend directly from the agent. Should I implement an events_worker within this PR or in the PR that adds the server backend? * Refactor signature of events backend to take only one argument * WIP: Add worker * WIP start of worker spec * Worker spec successfully duplicated * Implement timeout mechanism using separate thread Given that the worker relies on the Queue as the main scheduling mechanism I saw no other way than to start a second thread that occasionally throws a message into the queue to check if the timeout is reached. This seems to work in testing. * Remove one timeout check, namespace config * Remove unused code * Add server back end functionality for events This adds a minimal set of tests to ensure API conformance I've tested the code manually against "the real thing(tm)" * Add events worker to agent stop/flush commands * Fix debug message in events worker --------- Co-authored-by: Joshua Wood <[email protected]> * Support Hash as first argument to Honeybadger#event (#521) This enables both signatures: # With event type as first argument (recommended): Honeybadger.event("user_signed_up", user_id: 123) # With just a payload: Honeybadger.event(event_type: "user_signed_up", user_id: 123) * Don't memoize events config The config is initialized after the agent is created (when the app loads). * Lazy initialize events worker This results in less change for current users—if you aren't using insights, the extra threads don't need to run. We could change this back in the future. --------- Co-authored-by: Joshua Wood <[email protected]>
1 parent 597653d commit dbe7e3d

File tree

18 files changed

+937
-4
lines changed

18 files changed

+937
-4
lines changed

.github/workflows/ruby.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ jobs:
5757
with:
5858
ruby-version: ${{ matrix.ruby }}
5959
bundler-cache: true
60+
rubygems: latest
6061

6162
- name: Build and test regular ruby
6263
run: |

lib/honeybadger/agent.rb

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
require 'honeybadger/plugin'
88
require 'honeybadger/logging'
99
require 'honeybadger/worker'
10+
require 'honeybadger/events_worker'
1011
require 'honeybadger/breadcrumbs'
1112

1213
module Honeybadger
@@ -354,6 +355,7 @@ def flush
354355
yield
355356
ensure
356357
worker.flush
358+
events_worker&.flush
357359
end
358360

359361
# Stops the Honeybadger service.
@@ -362,9 +364,41 @@ def flush
362364
# Honeybadger.stop # => nil
363365
def stop(force = false)
364366
worker.shutdown(force)
367+
events_worker&.shutdown(force)
365368
true
366369
end
367370

371+
# Sends event to events backend
372+
#
373+
# @example
374+
# # With event type as first argument (recommended):
375+
# Honeybadger.event("user_signed_up", user_id: 123)
376+
#
377+
# # With just a payload:
378+
# Honeybadger.event(event_type: "user_signed_up", user_id: 123)
379+
#
380+
# @param event_name [String, Hash] a String describing the event or a Hash
381+
# when the second argument is omitted.
382+
# @param payload [Hash] Additional data to be sent with the event as keyword arguments
383+
#
384+
# @return [void]
385+
def event(event_type, payload = {})
386+
init_events_worker
387+
388+
ts = DateTime.now.new_offset(0).rfc3339
389+
merged = {ts: ts}
390+
391+
if event_type.is_a?(String)
392+
merged.merge!(event_type: event_type)
393+
else
394+
merged.merge!(Hash(event_type))
395+
end
396+
397+
merged.merge!(Hash(payload))
398+
399+
events_worker.push(merged)
400+
end
401+
368402
# @api private
369403
attr_reader :config
370404

@@ -437,7 +471,7 @@ def with_rack_env(rack_env, &block)
437471
end
438472

439473
# @api private
440-
attr_reader :worker
474+
attr_reader :worker, :events_worker
441475

442476
# @api private
443477
# @!method init!(...)
@@ -475,9 +509,15 @@ def send_now(object)
475509
end
476510

477511
def init_worker
512+
return if @worker
478513
@worker = Worker.new(config)
479514
end
480515

516+
def init_events_worker
517+
return if @events_worker
518+
@events_worker = EventsWorker.new(config)
519+
end
520+
481521
def with_error_handling
482522
yield
483523
rescue => ex

lib/honeybadger/backend/base.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ def track_deployment(payload)
109109
notify(:deploys, payload)
110110
end
111111

112+
# Send event
113+
# @example
114+
# backend.event([{event_type: "email_received", ts: "2023-03-04T12:12:00+1:00", subject: 'Re: Aquisition' }})
115+
#
116+
# @param [Array] payload array of event hashes to send
117+
# @raise NotImplementedError
118+
def event(payload)
119+
raise NotImplementedError, "must define #event on subclass"
120+
end
121+
112122
private
113123

114124
attr_reader :config

lib/honeybadger/backend/debug.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ def check_in(id)
1717
return Response.new(ENV['DEBUG_BACKEND_STATUS'].to_i, nil) if ENV['DEBUG_BACKEND_STATUS']
1818
super
1919
end
20+
21+
def event(payload)
22+
logger.unknown("sending event to debug backend with event=#{payload.to_json}")
23+
return Response.new(ENV['DEBUG_BACKEND_STATUS'].to_i, nil) if ENV['DEBUG_BACKEND_STATUS']
24+
super
25+
end
2026
end
2127
end
2228
end

lib/honeybadger/backend/null.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ def notify(feature, payload)
2424
def check_in(id)
2525
StubbedResponse.new
2626
end
27+
28+
def event(payload)
29+
StubbedResponse.new
30+
end
2731
end
2832
end
2933
end

lib/honeybadger/backend/server.rb

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,10 @@ module Backend
1111
class Server < Base
1212
ENDPOINTS = {
1313
notices: '/v1/notices'.freeze,
14-
deploys: '/v1/deploys'.freeze
14+
deploys: '/v1/deploys'.freeze,
1515
}.freeze
16-
1716
CHECK_IN_ENDPOINT = '/v1/check_in'.freeze
18-
17+
EVENTS_ENDPOINT = '/v1/events'.freeze
1918

2019
HTTP_ERRORS = Util::HTTP::ERRORS
2120

@@ -48,6 +47,18 @@ def check_in(id)
4847
Response.new(:error, nil, "HTTP Error: #{e.class}")
4948
end
5049

50+
# Send event
51+
# @example
52+
# backend.event([{event_type: "email_received", ts: "2023-03-04T12:12:00+1:00", subject: 'Re: Aquisition' }})
53+
#
54+
# @param [Array] payload array of event hashes to send
55+
# @return [Response]
56+
def event(payload)
57+
Response.new(@http.post_newline_delimited(EVENTS_ENDPOINT, payload))
58+
rescue *HTTP_ERRORS => e
59+
Response.new(:error, nil, "HTTP Error: #{e.class}")
60+
end
61+
5162
private
5263

5364
def payload_headers(payload)

lib/honeybadger/config.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,14 @@ def max_queue_size
224224
self[:max_queue_size]
225225
end
226226

227+
def events_batch_size
228+
self[:'events.batch_size']
229+
end
230+
231+
def events_timeout
232+
self[:'events.timeout']
233+
end
234+
227235
def params_filters
228236
Array(self[:'request.filter_keys'])
229237
end

lib/honeybadger/config/defaults.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ class Boolean; end
9191
default: 100,
9292
type: Integer
9393
},
94+
:'events.batch_size' => {
95+
description: 'Send events batch if n events have accumulated',
96+
default: 100,
97+
type: Integer
98+
},
99+
:'events.timeout' => {
100+
description: 'Timeout after which the events batch will be sent regardless (in milliseconds)',
101+
default: 30_000,
102+
type: Integer
103+
},
94104
plugins: {
95105
description: 'An optional list of plugins to load. Default is to load all plugins.',
96106
default: nil,

0 commit comments

Comments
 (0)