bugsnag_performance 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31cfcfc45215dec768c1e6040c418f63355ea308cf2d8d660ccad2c3f2de3621
4
- data.tar.gz: e3868f8cd3341f285e0bd868c3db54e640739ed0a755308162c3dac96cf47b76
3
+ metadata.gz: 84d3f11ac05bf81b8de7503e8992327b5152196ed024ee348aaac879fa7b845e
4
+ data.tar.gz: 517ae6ce8cfa46220e1a2d5b3394514cc2096a8139cd7c82492e5d62dbcff615
5
5
  SHA512:
6
- metadata.gz: f394a3a656e3bbd76aa825db67b2e6bd156590d666648ca6484df820fc833f8904e24dce311084bcfc64ff4e064f5bbcd181a92e51a60d27c7ccedf7a029ccd4
7
- data.tar.gz: 9f8ad547b130279f751169c2305ce57faaf34a566782abca3df7fc307efbab8b14873c6ef0946069a0f49f159d40d8b225969eaf5defa5bb236b402e10afd05d
6
+ metadata.gz: 74294e03b1b6bfada4a0043b12fa5d82ea0953dd592d274c36792b49a96445fbf21026b275ae0055d2a5505aad93099bc063332102e25fdfae48a8cd2242e1d9
7
+ data.tar.gz: 8f4c8de90d51d4e613869b7f74b5ed8a88f142819d4e576442df0e457dd3534b6d5a3c959feb7d2557286784211658a17ae387503df2078b819b77c9f4e078d3
@@ -9,7 +9,6 @@ module BugsnagPerformance
9
9
  attr_accessor :app_version
10
10
  attr_accessor :release_stage
11
11
  attr_accessor :enabled_release_stages
12
- attr_accessor :use_managed_quota
13
12
 
14
13
  attr_writer :endpoint
15
14
 
@@ -20,7 +19,6 @@ module BugsnagPerformance
20
19
  @api_key = fetch(errors_configuration, :api_key, env: "BUGSNAG_PERFORMANCE_API_KEY")
21
20
  @app_version = fetch(errors_configuration, :app_version)
22
21
  @release_stage = fetch(errors_configuration, :release_stage, env: "BUGSNAG_PERFORMANCE_RELEASE_STAGE", default: "production")
23
- @use_managed_quota = true
24
22
 
25
23
  @enabled_release_stages = fetch(errors_configuration, :enabled_release_stages, env: "BUGSNAG_PERFORMANCE_ENABLED_RELEASE_STAGES")
26
24
 
@@ -16,7 +16,6 @@ module BugsnagPerformance
16
16
  validate_string(:app_version, optional: true)
17
17
  validate_string(:release_stage, optional: true)
18
18
  validate_array(:enabled_release_stages, "non-empty strings", optional: true, &method(:valid_string?))
19
- validate_boolean(:use_managed_quota, optional: false)
20
19
  valid_endpoint = validate_endpoint
21
20
 
22
21
  # if the endpoint is invalid then we shouldn't attempt to send traces
@@ -72,16 +71,6 @@ module BugsnagPerformance
72
71
  end
73
72
  end
74
73
 
75
- def validate_boolean(name, optional:)
76
- value = @configuration.send(name)
77
-
78
- if (value.nil? && optional) || value == true || value == false
79
- @valid_configuration.send("#{name}=", value)
80
- else
81
- @messages << "#{name} should be a boolean, got #{value.inspect}"
82
- end
83
- end
84
-
85
74
  def validate_array(name, description, optional:, &block)
86
75
  value = @configuration.send(name)
87
76
 
@@ -3,6 +3,8 @@
3
3
  module BugsnagPerformance
4
4
  module Internal
5
5
  class Delivery
6
+ attr_reader :uri
7
+
6
8
  def initialize(configuration)
7
9
  @uri = URI(configuration.endpoint)
8
10
  @common_headers = {
@@ -26,14 +26,9 @@ module BugsnagPerformance
26
26
  :SPAN_STATUS_UNSET,
27
27
  :SPAN_STATUS_ERROR
28
28
 
29
- def initialize(sampler)
30
- @sampler = sampler
31
- end
32
-
33
29
  def encode(span_data)
34
30
  {
35
31
  resourceSpans: span_data
36
- .filter { |span| @sampler.resample_span?(span) }
37
32
  .group_by(&:resource)
38
33
  .map do |resource, scope_spans|
39
34
  {
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BugsnagPerformance
4
+ module Internal
5
+ class ProbabilityAttributeSpanProcessor
6
+ def initialize(probability_manager)
7
+ @probability_manager = probability_manager
8
+ end
9
+
10
+ def on_start(span, parent_context)
11
+ # avoid overwriting the attribute if the sampler has already set it
12
+ if span.attributes.nil? || span.attributes["bugsnag.sampling.p"].nil?
13
+ span.set_attribute("bugsnag.sampling.p", @probability_manager.probability)
14
+ end
15
+
16
+ OpenTelemetry::SDK::Trace::Export::SUCCESS
17
+ end
18
+
19
+ def on_finish(span)
20
+ OpenTelemetry::SDK::Trace::Export::SUCCESS
21
+ end
22
+
23
+ def force_flush(timeout: nil)
24
+ OpenTelemetry::SDK::Trace::Export::SUCCESS
25
+ end
26
+
27
+ def shutdown(timeout: nil)
28
+ OpenTelemetry::SDK::Trace::Export::SUCCESS
29
+ end
30
+ end
31
+ end
32
+ end
@@ -7,36 +7,67 @@ module BugsnagPerformance
7
7
  logger,
8
8
  probability_manager,
9
9
  delivery,
10
+ sampler,
10
11
  payload_encoder,
11
12
  sampling_header_encoder
12
13
  )
13
14
  @logger = logger
14
15
  @probability_manager = probability_manager
15
16
  @delivery = delivery
17
+ @sampler = sampler
16
18
  @payload_encoder = payload_encoder
17
19
  @sampling_header_encoder = sampling_header_encoder
18
20
  @disabled = false
21
+ @unmanaged_mode = false
22
+ @logged_first_batch_destination = false
19
23
  end
20
24
 
21
25
  def disable!
22
26
  @disabled = true
23
27
  end
24
28
 
29
+ def unmanaged_mode!
30
+ @unmanaged_mode = true
31
+ end
32
+
33
+ def unmanaged_mode?
34
+ @unmanaged_mode
35
+ end
36
+
25
37
  def export(span_data, timeout: nil)
26
38
  return OpenTelemetry::SDK::Trace::Export::SUCCESS if @disabled
27
39
 
28
40
  with_timeout(timeout) do
41
+ # ensure we're in the correct managed or unmanaged mode
42
+ maybe_enter_unmanaged_mode
43
+ managed_status = unmanaged_mode? ? "unmanaged" : "managed"
44
+
29
45
  headers = {}
30
- sampling_header = @sampling_header_encoder.encode(span_data)
31
46
 
32
- if sampling_header.nil?
33
- @logger.warn("One or more spans are missing the 'bugsnag.sampling.p' attribute. This trace will be sent as 'unmanaged'.")
34
- else
35
- headers["Bugsnag-Span-Sampling"] = sampling_header
47
+ # resample the spans and attach the Bugsnag-Span-Sampling header only
48
+ # if we're in managed mode
49
+ unless unmanaged_mode?
50
+ span_data = span_data.filter { |span| @sampler.resample_span?(span) }
51
+
52
+ sampling_header = @sampling_header_encoder.encode(span_data)
53
+
54
+ if sampling_header.nil?
55
+ @logger.warn("One or more spans are missing the 'bugsnag.sampling.p' attribute. This trace will be sent as unmanaged")
56
+ managed_status = "unmanaged"
57
+ else
58
+ headers["Bugsnag-Span-Sampling"] = sampling_header
59
+ end
36
60
  end
37
61
 
38
62
  body = JSON.generate(@payload_encoder.encode(span_data))
39
63
 
64
+ # log whether we're sending managed or unmanaged spans on the first
65
+ # batch only
66
+ unless @logged_first_batch_destination
67
+ @logger.info("Sending #{managed_status} spans to #{@delivery.uri}")
68
+ @logged_first_batch_destination = true
69
+ end
70
+
40
71
  response = @delivery.deliver(headers, body)
41
72
 
42
73
  if response.sampling_probability
@@ -64,6 +95,18 @@ module BugsnagPerformance
64
95
 
65
96
  private
66
97
 
98
+ def maybe_enter_unmanaged_mode
99
+ # we're in unmanaged mode already so don't need to do anything
100
+ return if unmanaged_mode?
101
+
102
+ # our sampler is in use so we're in managed mode
103
+ return if OpenTelemetry.tracer_provider.sampler.is_a?(Sampler)
104
+
105
+ # the user has changed the sampler from ours to a custom one; enter
106
+ # unmanaged mode
107
+ unmanaged_mode!
108
+ end
109
+
67
110
  def with_timeout(timeout, &block)
68
111
  if timeout.nil?
69
112
  block.call
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BugsnagPerformance
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -23,6 +23,7 @@ require_relative "bugsnag_performance/internal/probability_manager"
23
23
  require_relative "bugsnag_performance/internal/configuration_validator"
24
24
  require_relative "bugsnag_performance/internal/sampling_header_encoder"
25
25
  require_relative "bugsnag_performance/internal/nil_errors_configuration"
26
+ require_relative "bugsnag_performance/internal/probability_attribute_span_processor"
26
27
 
27
28
  module BugsnagPerformance
28
29
  def self.configure(&block)
@@ -45,10 +46,17 @@ module BugsnagPerformance
45
46
  configuration.logger,
46
47
  probability_manager,
47
48
  delivery,
48
- Internal::PayloadEncoder.new(sampler),
49
+ sampler,
50
+ Internal::PayloadEncoder.new,
49
51
  Internal::SamplingHeaderEncoder.new,
50
52
  )
51
53
 
54
+ # enter unmanaged mode if the OTel sampler environment variable has been set
55
+ # note: we assume any value means a non-default sampler will be used because
56
+ # we don't control what the valid values are
57
+ user_has_custom_sampler = ENV.key?("OTEL_TRACES_SAMPLER")
58
+ exporter.unmanaged_mode! if user_has_custom_sampler
59
+
52
60
  if configuration.enabled_release_stages && !configuration.enabled_release_stages.include?(configuration.release_stage)
53
61
  configuration.logger.info("Not exporting spans as the current release stage is not in the enabled release stages.")
54
62
  exporter.disable!
@@ -76,10 +84,18 @@ module BugsnagPerformance
76
84
  otel_configurator.add_span_processor(
77
85
  OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(exporter)
78
86
  )
87
+
88
+ # ensure the "bugsnag.sampling.p" attribute is set on all spans even when
89
+ # our sampler is not in use
90
+ otel_configurator.add_span_processor(
91
+ Internal::ProbabilityAttributeSpanProcessor.new(probability_manager)
92
+ )
79
93
  end
80
94
 
81
- # use our sampler
82
- OpenTelemetry.tracer_provider.sampler = sampler
95
+ # don't use our sampler if the user has configured a sampler via the OTel
96
+ # environment variable
97
+ # note: the user can still replace our sampler with their own after this
98
+ OpenTelemetry.tracer_provider.sampler = sampler unless user_has_custom_sampler
83
99
 
84
100
  return_value
85
101
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugsnag_performance
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - BugSnag
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-08-21 00:00:00.000000000 Z
11
+ date: 2024-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -84,6 +84,7 @@ files:
84
84
  - lib/bugsnag_performance/internal/logger_wrapper.rb
85
85
  - lib/bugsnag_performance/internal/nil_errors_configuration.rb
86
86
  - lib/bugsnag_performance/internal/payload_encoder.rb
87
+ - lib/bugsnag_performance/internal/probability_attribute_span_processor.rb
87
88
  - lib/bugsnag_performance/internal/probability_fetcher.rb
88
89
  - lib/bugsnag_performance/internal/probability_manager.rb
89
90
  - lib/bugsnag_performance/internal/sampler.rb
@@ -99,7 +100,7 @@ metadata:
99
100
  homepage_uri: https://www.bugsnag.com
100
101
  source_code_uri: https://github.com/bugsnag/bugsnag-ruby-performance
101
102
  bug_tracker_uri: https://github.com/bugsnag/bugsnag-ruby-performance/issues
102
- changelog_uri: https://github.com/bugsnag/bugsnag-ruby-performance/blob/v0.1.0/CHANGELOG.md
103
+ changelog_uri: https://github.com/bugsnag/bugsnag-ruby-performance/blob/v0.2.0/CHANGELOG.md
103
104
  documentation_uri: https://docs.bugsnag.com/performance/integration-guides/ruby/
104
105
  rubygems_mfa_required: 'true'
105
106
  post_install_message: