bugwatch-ruby 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57c0dd9d1f88dcfeb7bad5a6440ef0b7b9ccdbb0a0ca53bd777c4520f8257bea
4
- data.tar.gz: 11bfad309a476d9d4501ee6be56ef27d6bb7157d6ef5744bc7401606e019346a
3
+ metadata.gz: 5bfce1e168aea51e8e32c863fbe5cf0c8fb6818b3769beaa8cf3d6258074ca5e
4
+ data.tar.gz: e371b7debb6555bfbad2c82440df057d70a2e69267b2c252bbd8973206721dd0
5
5
  SHA512:
6
- metadata.gz: b81113875823f9826c3cc50c29a675adb53c1fcebe978836411549867a5eba6cdaf16df2fe9fe8d64fd5d30005f844705ede56a9d66fe13e655b995788c51f1f
7
- data.tar.gz: 9990375e5142f344e6f883687250426c39fcf016625bf4140e990c679d8f18a7688ed94735294468aed95a834d039bb9b3cd898c5e6aa45593db4f73f9d7bfeb
6
+ metadata.gz: ff5482ec3e65aecd1309ddc6b68bff66c19cbaef30b86ea936681b4db7e05d60f83016129ef702aace4c74efa72618a313f9d861c9dfb0a6c841be8e6607e463
7
+ data.tar.gz: 62e271e5fa126376f47bce1dd378bda5c3012599613ede8144d475621736f02079da71b9b23bcd6daa768c292dfcd7ffc02174328c6da265aba835a5828eb69d
@@ -6,14 +6,20 @@ module Bugwatch
6
6
  :notify_release_stages,
7
7
  :ignore_classes,
8
8
  :app_version,
9
- :logger
9
+ :logger,
10
+ :enable_performance_tracking,
11
+ :sample_rate,
12
+ :ignore_request_paths
10
13
 
11
14
  def initialize
12
- @endpoint = nil
13
- @release_stage = "production"
14
- @notify_release_stages = ["production"]
15
- @ignore_classes = []
16
- @logger = Logger.new($stdout) if defined?(Logger)
15
+ @endpoint = nil
16
+ @release_stage = "production"
17
+ @notify_release_stages = ["production"]
18
+ @ignore_classes = []
19
+ @logger = Logger.new($stdout) if defined?(Logger)
20
+ @enable_performance_tracking = true
21
+ @sample_rate = 1.0
22
+ @ignore_request_paths = []
17
23
  end
18
24
 
19
25
  def notify_for_release_stage?
@@ -28,5 +34,12 @@ module Bugwatch
28
34
  false
29
35
  end
30
36
  end
37
+
38
+ def track_request?(path)
39
+ return false unless enable_performance_tracking
40
+ return false if ignore_request_paths.any? { |pattern| path.match?(pattern) }
41
+ return false if sample_rate < 1.0 && rand > sample_rate
42
+ true
43
+ end
31
44
  end
32
45
  end
@@ -11,6 +11,7 @@ module Bugwatch
11
11
 
12
12
  begin
13
13
  status, headers, body = @app.call(env)
14
+ record_transaction(env, status, start)
14
15
  [status, headers, body]
15
16
  rescue Exception => e # rubocop:disable Lint/RescueException
16
17
  duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
@@ -21,8 +22,33 @@ module Bugwatch
21
22
  Notification.new(payload).deliver
22
23
  end
23
24
 
25
+ record_transaction(env, 500, start)
24
26
  raise
25
27
  end
26
28
  end
29
+
30
+ private
31
+
32
+ def record_transaction(env, status, start)
33
+ config = Bugwatch.configuration
34
+ req = Rack::Request.new(env)
35
+ return unless config.track_request?(req.path)
36
+
37
+ duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
38
+
39
+ payload = {
40
+ name: "#{req.request_method} #{req.path}",
41
+ request_method: req.request_method,
42
+ request_url: req.path,
43
+ duration_ms: duration_ms,
44
+ status_code: status,
45
+ environment: config.release_stage,
46
+ occurred_at: Time.now.utc.iso8601
47
+ }
48
+
49
+ Bugwatch.transaction_buffer.push(payload)
50
+ rescue StandardError
51
+ # Never let tracking break the app
52
+ end
27
53
  end
28
54
  end
@@ -0,0 +1,61 @@
1
+ module Bugwatch
2
+ class TransactionBuffer
3
+ BATCH_SIZE = 50
4
+ FLUSH_INTERVAL = 10 # seconds
5
+
6
+ def initialize(config: Bugwatch.configuration)
7
+ @config = config
8
+ @sender = TransactionSender.new(config: config)
9
+ @mutex = Mutex.new
10
+ @buffer = []
11
+ start_flusher
12
+ end
13
+
14
+ def push(payload)
15
+ should_flush = false
16
+
17
+ @mutex.synchronize do
18
+ @buffer << payload
19
+ should_flush = @buffer.size >= BATCH_SIZE
20
+ end
21
+
22
+ flush if should_flush
23
+ end
24
+
25
+ def flush
26
+ batch = @mutex.synchronize do
27
+ items = @buffer
28
+ @buffer = []
29
+ items
30
+ end
31
+
32
+ @sender.send_batch(batch) unless batch.empty?
33
+ rescue StandardError
34
+ # Never let flushing break the app
35
+ end
36
+
37
+ def shutdown
38
+ stop_flusher
39
+ flush
40
+ end
41
+
42
+ private
43
+
44
+ def start_flusher
45
+ @flusher = Thread.new do
46
+ loop do
47
+ sleep FLUSH_INTERVAL
48
+ flush
49
+ end
50
+ rescue StandardError
51
+ # Silently handle thread errors
52
+ end
53
+ @flusher.abort_on_exception = false
54
+ @flusher.daemon = true if @flusher.respond_to?(:daemon=)
55
+ end
56
+
57
+ def stop_flusher
58
+ @flusher&.kill
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,48 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+
5
+ module Bugwatch
6
+ class TransactionSender
7
+ TIMEOUT = 3
8
+
9
+ def initialize(config: Bugwatch.configuration)
10
+ @config = config
11
+ end
12
+
13
+ def send_batch(payloads)
14
+ return if payloads.empty?
15
+ return unless @config.api_key
16
+ return unless @config.endpoint
17
+
18
+ Thread.new do
19
+ post_batch(payloads)
20
+ rescue StandardError
21
+ # Fire-and-forget: swallow all errors
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def post_batch(payloads)
28
+ uri = URI.parse("#{@config.endpoint.chomp("/")}/api/v1/transactions/batch")
29
+
30
+ http = Net::HTTP.new(uri.host, uri.port)
31
+ http.use_ssl = uri.scheme == "https"
32
+ http.open_timeout = TIMEOUT
33
+ http.read_timeout = TIMEOUT
34
+ http.write_timeout = TIMEOUT
35
+
36
+ request = Net::HTTP::Post.new(uri.path)
37
+ request["Content-Type"] = "application/json"
38
+ request["X-Api-Key"] = @config.api_key
39
+ request["X-BugWatch-Ruby"] = Bugwatch::VERSION
40
+
41
+ request.body = JSON.generate({ transactions: payloads })
42
+
43
+ http.request(request)
44
+ rescue StandardError
45
+ # Silently discard network errors
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
1
  module Bugwatch
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/bugwatch.rb CHANGED
@@ -9,6 +9,8 @@ require_relative "bugwatch/backtrace_cleaner"
9
9
  require_relative "bugwatch/error_builder"
10
10
  require_relative "bugwatch/report_builder"
11
11
  require_relative "bugwatch/notification"
12
+ require_relative "bugwatch/transaction_sender"
13
+ require_relative "bugwatch/transaction_buffer"
12
14
  require_relative "bugwatch/middleware"
13
15
  require_relative "bugwatch/railtie" if defined?(Rails::Railtie)
14
16
 
@@ -46,5 +48,11 @@ module Bugwatch
46
48
  def leave_breadcrumb(message, type: "manual", metadata: {})
47
49
  BreadcrumbCollector.add(message, type: type, metadata: metadata)
48
50
  end
51
+
52
+ def transaction_buffer
53
+ @transaction_buffer ||= TransactionBuffer.new(config: configuration)
54
+ end
49
55
  end
50
56
  end
57
+
58
+ at_exit { Bugwatch.transaction_buffer.shutdown rescue nil }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugwatch-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BugWatch
@@ -56,6 +56,8 @@ files:
56
56
  - lib/bugwatch/notification.rb
57
57
  - lib/bugwatch/railtie.rb
58
58
  - lib/bugwatch/report_builder.rb
59
+ - lib/bugwatch/transaction_buffer.rb
60
+ - lib/bugwatch/transaction_sender.rb
59
61
  - lib/bugwatch/user_context.rb
60
62
  - lib/bugwatch/version.rb
61
63
  homepage: https://github.com/bugwatch/bugwatch-ruby