bugsnag_performance 0.1.0 → 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: 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: