sentry-ruby 5.10.0 → 5.26.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -1
  3. data/Gemfile +12 -13
  4. data/README.md +26 -11
  5. data/Rakefile +9 -11
  6. data/bin/console +2 -0
  7. data/lib/sentry/attachment.rb +40 -0
  8. data/lib/sentry/background_worker.rb +11 -5
  9. data/lib/sentry/backpressure_monitor.rb +45 -0
  10. data/lib/sentry/backtrace.rb +12 -9
  11. data/lib/sentry/baggage.rb +7 -7
  12. data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
  13. data/lib/sentry/breadcrumb.rb +13 -6
  14. data/lib/sentry/check_in_event.rb +61 -0
  15. data/lib/sentry/client.rb +214 -25
  16. data/lib/sentry/configuration.rb +221 -38
  17. data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
  18. data/lib/sentry/cron/configuration.rb +23 -0
  19. data/lib/sentry/cron/monitor_check_ins.rb +77 -0
  20. data/lib/sentry/cron/monitor_config.rb +53 -0
  21. data/lib/sentry/cron/monitor_schedule.rb +42 -0
  22. data/lib/sentry/dsn.rb +4 -4
  23. data/lib/sentry/envelope/item.rb +88 -0
  24. data/lib/sentry/envelope.rb +2 -68
  25. data/lib/sentry/error_event.rb +2 -2
  26. data/lib/sentry/event.rb +28 -47
  27. data/lib/sentry/excon/middleware.rb +77 -0
  28. data/lib/sentry/excon.rb +10 -0
  29. data/lib/sentry/faraday.rb +77 -0
  30. data/lib/sentry/graphql.rb +9 -0
  31. data/lib/sentry/hub.rb +138 -6
  32. data/lib/sentry/integrable.rb +10 -0
  33. data/lib/sentry/interface.rb +1 -0
  34. data/lib/sentry/interfaces/exception.rb +5 -3
  35. data/lib/sentry/interfaces/mechanism.rb +20 -0
  36. data/lib/sentry/interfaces/request.rb +8 -8
  37. data/lib/sentry/interfaces/single_exception.rb +13 -9
  38. data/lib/sentry/interfaces/stacktrace.rb +3 -1
  39. data/lib/sentry/interfaces/stacktrace_builder.rb +23 -2
  40. data/lib/sentry/linecache.rb +3 -3
  41. data/lib/sentry/log_event.rb +206 -0
  42. data/lib/sentry/log_event_buffer.rb +75 -0
  43. data/lib/sentry/logger.rb +1 -1
  44. data/lib/sentry/metrics/aggregator.rb +248 -0
  45. data/lib/sentry/metrics/configuration.rb +47 -0
  46. data/lib/sentry/metrics/counter_metric.rb +25 -0
  47. data/lib/sentry/metrics/distribution_metric.rb +25 -0
  48. data/lib/sentry/metrics/gauge_metric.rb +35 -0
  49. data/lib/sentry/metrics/local_aggregator.rb +53 -0
  50. data/lib/sentry/metrics/metric.rb +19 -0
  51. data/lib/sentry/metrics/set_metric.rb +28 -0
  52. data/lib/sentry/metrics/timing.rb +51 -0
  53. data/lib/sentry/metrics.rb +56 -0
  54. data/lib/sentry/net/http.rb +27 -44
  55. data/lib/sentry/profiler/helpers.rb +46 -0
  56. data/lib/sentry/profiler.rb +41 -60
  57. data/lib/sentry/propagation_context.rb +135 -0
  58. data/lib/sentry/puma.rb +12 -5
  59. data/lib/sentry/rack/capture_exceptions.rb +17 -8
  60. data/lib/sentry/rack.rb +2 -2
  61. data/lib/sentry/rake.rb +4 -15
  62. data/lib/sentry/redis.rb +10 -4
  63. data/lib/sentry/release_detector.rb +5 -5
  64. data/lib/sentry/rspec.rb +91 -0
  65. data/lib/sentry/scope.rb +75 -39
  66. data/lib/sentry/session.rb +2 -2
  67. data/lib/sentry/session_flusher.rb +15 -43
  68. data/lib/sentry/span.rb +92 -8
  69. data/lib/sentry/std_lib_logger.rb +50 -0
  70. data/lib/sentry/structured_logger.rb +138 -0
  71. data/lib/sentry/test_helper.rb +42 -13
  72. data/lib/sentry/threaded_periodic_worker.rb +39 -0
  73. data/lib/sentry/transaction.rb +44 -43
  74. data/lib/sentry/transaction_event.rb +10 -6
  75. data/lib/sentry/transport/configuration.rb +73 -1
  76. data/lib/sentry/transport/http_transport.rb +71 -41
  77. data/lib/sentry/transport/spotlight_transport.rb +50 -0
  78. data/lib/sentry/transport.rb +53 -49
  79. data/lib/sentry/utils/argument_checking_helper.rb +12 -0
  80. data/lib/sentry/utils/env_helper.rb +21 -0
  81. data/lib/sentry/utils/http_tracing.rb +74 -0
  82. data/lib/sentry/utils/logging_helper.rb +10 -7
  83. data/lib/sentry/utils/real_ip.rb +2 -2
  84. data/lib/sentry/utils/request_id.rb +1 -1
  85. data/lib/sentry/utils/uuid.rb +13 -0
  86. data/lib/sentry/vernier/output.rb +89 -0
  87. data/lib/sentry/vernier/profiler.rb +132 -0
  88. data/lib/sentry/version.rb +1 -1
  89. data/lib/sentry-ruby.rb +206 -35
  90. data/sentry-ruby-core.gemspec +3 -1
  91. data/sentry-ruby.gemspec +15 -6
  92. metadata +61 -11
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ # Event type that represents a log entry with its attributes
5
+ #
6
+ # @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
7
+ class LogEvent
8
+ TYPE = "log"
9
+
10
+ DEFAULT_PARAMETERS = [].freeze
11
+ DEFAULT_ATTRIBUTES = {}.freeze
12
+
13
+ SERIALIZEABLE_ATTRIBUTES = %i[
14
+ level
15
+ body
16
+ timestamp
17
+ environment
18
+ release
19
+ server_name
20
+ trace_id
21
+ attributes
22
+ contexts
23
+ ]
24
+
25
+ SENTRY_ATTRIBUTES = {
26
+ "sentry.trace.parent_span_id" => :parent_span_id,
27
+ "sentry.environment" => :environment,
28
+ "sentry.release" => :release,
29
+ "sentry.address" => :server_name,
30
+ "sentry.sdk.name" => :sdk_name,
31
+ "sentry.sdk.version" => :sdk_version,
32
+ "sentry.message.template" => :template
33
+ }
34
+
35
+ USER_ATTRIBUTES = {
36
+ "user.id" => :user_id,
37
+ "user.name" => :user_username,
38
+ "user.email" => :user_email
39
+ }
40
+
41
+ LEVELS = %i[trace debug info warn error fatal].freeze
42
+
43
+ attr_accessor :level, :body, :template, :attributes, :user
44
+
45
+ attr_reader :configuration, *SERIALIZEABLE_ATTRIBUTES
46
+
47
+ SERIALIZERS = %i[
48
+ attributes
49
+ body
50
+ level
51
+ parent_span_id
52
+ sdk_name
53
+ sdk_version
54
+ timestamp
55
+ trace_id
56
+ user_id
57
+ user_username
58
+ user_email
59
+ ].map { |name| [name, :"serialize_#{name}"] }.to_h
60
+
61
+ VALUE_TYPES = Hash.new("string").merge!({
62
+ TrueClass => "boolean",
63
+ FalseClass => "boolean",
64
+ Integer => "integer",
65
+ Float => "double"
66
+ }).freeze
67
+
68
+ TOKEN_REGEXP = /%\{(\w+)\}/
69
+
70
+ def initialize(configuration: Sentry.configuration, **options)
71
+ @configuration = configuration
72
+ @type = TYPE
73
+ @server_name = configuration.server_name
74
+ @environment = configuration.environment
75
+ @release = configuration.release
76
+ @timestamp = Sentry.utc_now
77
+ @level = options.fetch(:level)
78
+ @body = options[:body]
79
+ @template = @body if is_template?
80
+ @attributes = options[:attributes] || DEFAULT_ATTRIBUTES
81
+ @user = options[:user] || {}
82
+ @contexts = {}
83
+ end
84
+
85
+ def to_hash
86
+ SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |name, memo|
87
+ memo[name] = serialize(name)
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def serialize(name)
94
+ serializer = SERIALIZERS[name]
95
+
96
+ if serializer
97
+ __send__(serializer)
98
+ else
99
+ public_send(name)
100
+ end
101
+ end
102
+
103
+ def serialize_level
104
+ level.to_s
105
+ end
106
+
107
+ def serialize_sdk_name
108
+ Sentry.sdk_meta["name"]
109
+ end
110
+
111
+ def serialize_sdk_version
112
+ Sentry.sdk_meta["version"]
113
+ end
114
+
115
+ def serialize_timestamp
116
+ timestamp.to_f
117
+ end
118
+
119
+ def serialize_trace_id
120
+ contexts.dig(:trace, :trace_id)
121
+ end
122
+
123
+ def serialize_parent_span_id
124
+ contexts.dig(:trace, :parent_span_id)
125
+ end
126
+
127
+ def serialize_body
128
+ if parameters.empty?
129
+ body
130
+ elsif parameters.is_a?(Hash)
131
+ body % parameters
132
+ else
133
+ sprintf(body, *parameters)
134
+ end
135
+ end
136
+
137
+ def serialize_user_id
138
+ user[:id]
139
+ end
140
+
141
+ def serialize_user_username
142
+ user[:username]
143
+ end
144
+
145
+ def serialize_user_email
146
+ user[:email]
147
+ end
148
+
149
+ def serialize_attributes
150
+ hash = {}
151
+
152
+ attributes.each do |key, value|
153
+ hash[key] = attribute_hash(value)
154
+ end
155
+
156
+ SENTRY_ATTRIBUTES.each do |key, name|
157
+ if (value = serialize(name))
158
+ hash[key] = attribute_hash(value)
159
+ end
160
+ end
161
+
162
+ USER_ATTRIBUTES.each do |key, name|
163
+ if (value = serialize(name))
164
+ hash[key] = value
165
+ end
166
+ end
167
+
168
+ hash
169
+ end
170
+
171
+ def attribute_hash(value)
172
+ { value: value, type: value_type(value) }
173
+ end
174
+
175
+ def value_type(value)
176
+ VALUE_TYPES[value.class]
177
+ end
178
+
179
+ def parameters
180
+ @parameters ||= begin
181
+ return DEFAULT_PARAMETERS unless template
182
+
183
+ parameters = template_tokens.empty? ?
184
+ attributes.fetch(:parameters, DEFAULT_PARAMETERS) : attributes.slice(*template_tokens)
185
+
186
+ if parameters.is_a?(Hash)
187
+ parameters.each do |key, value|
188
+ attributes["sentry.message.parameter.#{key}"] = value
189
+ end
190
+ else
191
+ parameters.each_with_index do |param, index|
192
+ attributes["sentry.message.parameter.#{index}"] = param
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ def template_tokens
199
+ @template_tokens ||= body.scan(TOKEN_REGEXP).flatten.map(&:to_sym)
200
+ end
201
+
202
+ def is_template?
203
+ body.include?("%s") || TOKEN_REGEXP.match?(body)
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sentry/threaded_periodic_worker"
4
+
5
+ module Sentry
6
+ # LogEventBuffer buffers log events and sends them to Sentry in a single envelope.
7
+ #
8
+ # This is used internally by the `Sentry::Client`.
9
+ #
10
+ # @!visibility private
11
+ class LogEventBuffer < ThreadedPeriodicWorker
12
+ FLUSH_INTERVAL = 5 # seconds
13
+ DEFAULT_MAX_EVENTS = 100
14
+
15
+ # @!visibility private
16
+ attr_reader :pending_events
17
+
18
+ def initialize(configuration, client)
19
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
20
+
21
+ @client = client
22
+ @pending_events = []
23
+ @max_events = configuration.max_log_events || DEFAULT_MAX_EVENTS
24
+ @mutex = Mutex.new
25
+
26
+ log_debug("[Logging] Initialized buffer with max_events=#{@max_events}, flush_interval=#{FLUSH_INTERVAL}s")
27
+ end
28
+
29
+ def start
30
+ ensure_thread
31
+ self
32
+ end
33
+
34
+ def flush
35
+ @mutex.synchronize do
36
+ return if empty?
37
+
38
+ log_debug("[LogEventBuffer] flushing #{size} log events")
39
+
40
+ send_events
41
+ end
42
+
43
+ log_debug("[LogEventBuffer] flushed #{size} log events")
44
+
45
+ self
46
+ end
47
+ alias_method :run, :flush
48
+
49
+ def add_event(event)
50
+ raise ArgumentError, "expected a LogEvent, got #{event.class}" unless event.is_a?(LogEvent)
51
+
52
+ @mutex.synchronize do
53
+ @pending_events << event
54
+ send_events if size >= @max_events
55
+ end
56
+
57
+ self
58
+ end
59
+
60
+ def empty?
61
+ @pending_events.empty?
62
+ end
63
+
64
+ def size
65
+ @pending_events.size
66
+ end
67
+
68
+ private
69
+
70
+ def send_events
71
+ @client.send_logs(@pending_events)
72
+ @pending_events.clear
73
+ end
74
+ end
75
+ end
data/lib/sentry/logger.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'logger'
3
+ require "logger"
4
4
 
5
5
  module Sentry
6
6
  class Logger < ::Logger
@@ -0,0 +1,248 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class Aggregator < ThreadedPeriodicWorker
6
+ FLUSH_INTERVAL = 5
7
+ ROLLUP_IN_SECONDS = 10
8
+
9
+ # this is how far removed from user code in the backtrace we are
10
+ # when we record code locations
11
+ DEFAULT_STACKLEVEL = 4
12
+
13
+ KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.]+/
14
+ UNIT_SANITIZATION_REGEX = /[^a-zA-Z0-9_]+/
15
+ TAG_KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.\/]+/
16
+
17
+ TAG_VALUE_SANITIZATION_MAP = {
18
+ "\n" => "\\n",
19
+ "\r" => "\\r",
20
+ "\t" => "\\t",
21
+ "\\" => "\\\\",
22
+ "|" => "\\u{7c}",
23
+ "," => "\\u{2c}"
24
+ }
25
+
26
+ METRIC_TYPES = {
27
+ c: CounterMetric,
28
+ d: DistributionMetric,
29
+ g: GaugeMetric,
30
+ s: SetMetric
31
+ }
32
+
33
+ # exposed only for testing
34
+ attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
35
+
36
+ def initialize(configuration, client)
37
+ super(configuration.sdk_logger, FLUSH_INTERVAL)
38
+ @client = client
39
+ @before_emit = configuration.metrics.before_emit
40
+ @enable_code_locations = configuration.metrics.enable_code_locations
41
+ @stacktrace_builder = configuration.stacktrace_builder
42
+
43
+ @default_tags = {}
44
+ @default_tags["release"] = configuration.release if configuration.release
45
+ @default_tags["environment"] = configuration.environment if configuration.environment
46
+
47
+ @mutex = Mutex.new
48
+
49
+ # a nested hash of timestamp -> bucket keys -> Metric instance
50
+ @buckets = {}
51
+
52
+ # the flush interval needs to be shifted once per startup to create jittering
53
+ @flush_shift = Random.rand * ROLLUP_IN_SECONDS
54
+
55
+ # a nested hash of timestamp (start of day) -> meta keys -> frame
56
+ @code_locations = {}
57
+ end
58
+
59
+ def add(type,
60
+ key,
61
+ value,
62
+ unit: "none",
63
+ tags: {},
64
+ timestamp: nil,
65
+ stacklevel: nil)
66
+ return unless ensure_thread
67
+ return unless METRIC_TYPES.keys.include?(type)
68
+
69
+ updated_tags = get_updated_tags(tags)
70
+ return if @before_emit && !@before_emit.call(key, updated_tags)
71
+
72
+ timestamp ||= Sentry.utc_now
73
+
74
+ # this is integer division and thus takes the floor of the division
75
+ # and buckets into 10 second intervals
76
+ bucket_timestamp = (timestamp.to_i / ROLLUP_IN_SECONDS) * ROLLUP_IN_SECONDS
77
+
78
+ serialized_tags = serialize_tags(updated_tags)
79
+ bucket_key = [type, key, unit, serialized_tags]
80
+
81
+ added = @mutex.synchronize do
82
+ record_code_location(type, key, unit, timestamp, stacklevel: stacklevel) if @enable_code_locations
83
+ process_bucket(bucket_timestamp, bucket_key, type, value)
84
+ end
85
+
86
+ # for sets, we pass on if there was a new entry to the local gauge
87
+ local_value = type == :s ? added : value
88
+ process_span_aggregator(bucket_key, local_value)
89
+ end
90
+
91
+ def flush(force: false)
92
+ flushable_buckets = get_flushable_buckets!(force)
93
+ code_locations = get_code_locations!
94
+ return if flushable_buckets.empty? && code_locations.empty?
95
+
96
+ envelope = Envelope.new
97
+
98
+ unless flushable_buckets.empty?
99
+ payload = serialize_buckets(flushable_buckets)
100
+ envelope.add_item(
101
+ { type: "statsd", length: payload.bytesize },
102
+ payload
103
+ )
104
+ end
105
+
106
+ unless code_locations.empty?
107
+ code_locations.each do |timestamp, locations|
108
+ payload = serialize_locations(timestamp, locations)
109
+ envelope.add_item(
110
+ { type: "metric_meta", content_type: "application/json" },
111
+ payload
112
+ )
113
+ end
114
+ end
115
+
116
+ @client.capture_envelope(envelope)
117
+ end
118
+
119
+ alias_method :run, :flush
120
+
121
+ private
122
+
123
+ # important to sort for key consistency
124
+ def serialize_tags(tags)
125
+ tags.flat_map do |k, v|
126
+ if v.is_a?(Array)
127
+ v.map { |x| [k.to_s, x.to_s] }
128
+ else
129
+ [[k.to_s, v.to_s]]
130
+ end
131
+ end.sort
132
+ end
133
+
134
+ def get_flushable_buckets!(force)
135
+ @mutex.synchronize do
136
+ flushable_buckets = {}
137
+
138
+ if force
139
+ flushable_buckets = @buckets
140
+ @buckets = {}
141
+ else
142
+ cutoff = Sentry.utc_now.to_i - ROLLUP_IN_SECONDS - @flush_shift
143
+ flushable_buckets = @buckets.select { |k, _| k <= cutoff }
144
+ @buckets.reject! { |k, _| k <= cutoff }
145
+ end
146
+
147
+ flushable_buckets
148
+ end
149
+ end
150
+
151
+ def get_code_locations!
152
+ @mutex.synchronize do
153
+ code_locations = @code_locations
154
+ @code_locations = {}
155
+ code_locations
156
+ end
157
+ end
158
+
159
+ # serialize buckets to statsd format
160
+ def serialize_buckets(buckets)
161
+ buckets.map do |timestamp, timestamp_buckets|
162
+ timestamp_buckets.map do |metric_key, metric|
163
+ type, key, unit, tags = metric_key
164
+ values = metric.serialize.join(":")
165
+ sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(",")
166
+
167
+ "#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
168
+ end
169
+ end.flatten.join("\n")
170
+ end
171
+
172
+ def serialize_locations(timestamp, locations)
173
+ mapping = locations.map do |meta_key, location|
174
+ type, key, unit = meta_key
175
+ mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
176
+
177
+ # note this needs to be an array but it really doesn't serve a purpose right now
178
+ [mri, [location.merge(type: "location")]]
179
+ end.to_h
180
+
181
+ { timestamp: timestamp, mapping: mapping }
182
+ end
183
+
184
+ def sanitize_key(key)
185
+ key.gsub(KEY_SANITIZATION_REGEX, "_")
186
+ end
187
+
188
+ def sanitize_unit(unit)
189
+ unit.gsub(UNIT_SANITIZATION_REGEX, "")
190
+ end
191
+
192
+ def sanitize_tag_key(key)
193
+ key.gsub(TAG_KEY_SANITIZATION_REGEX, "")
194
+ end
195
+
196
+ def sanitize_tag_value(value)
197
+ value.chars.map { |c| TAG_VALUE_SANITIZATION_MAP[c] || c }.join
198
+ end
199
+
200
+ def get_transaction_name
201
+ scope = Sentry.get_current_scope
202
+ return nil unless scope && scope.transaction_name
203
+ return nil if scope.transaction_source_low_quality?
204
+
205
+ scope.transaction_name
206
+ end
207
+
208
+ def get_updated_tags(tags)
209
+ updated_tags = @default_tags.merge(tags)
210
+
211
+ transaction_name = get_transaction_name
212
+ updated_tags["transaction"] = transaction_name if transaction_name
213
+
214
+ updated_tags
215
+ end
216
+
217
+ def process_span_aggregator(key, value)
218
+ scope = Sentry.get_current_scope
219
+ return nil unless scope && scope.span
220
+ return nil if scope.transaction_source_low_quality?
221
+
222
+ scope.span.metrics_local_aggregator.add(key, value)
223
+ end
224
+
225
+ def process_bucket(timestamp, key, type, value)
226
+ @buckets[timestamp] ||= {}
227
+
228
+ if (metric = @buckets[timestamp][key])
229
+ old_weight = metric.weight
230
+ metric.add(value)
231
+ metric.weight - old_weight
232
+ else
233
+ metric = METRIC_TYPES[type].new(value)
234
+ @buckets[timestamp][key] = metric
235
+ metric.weight
236
+ end
237
+ end
238
+
239
+ def record_code_location(type, key, unit, timestamp, stacklevel: nil)
240
+ meta_key = [type, key, unit]
241
+ start_of_day = Time.utc(timestamp.year, timestamp.month, timestamp.day).to_i
242
+
243
+ @code_locations[start_of_day] ||= {}
244
+ @code_locations[start_of_day][meta_key] ||= @stacktrace_builder.metrics_code_location(caller[stacklevel || DEFAULT_STACKLEVEL])
245
+ end
246
+ end
247
+ end
248
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class Configuration
6
+ include ArgumentCheckingHelper
7
+
8
+ # Enable metrics usage.
9
+ # Starts a new {Sentry::Metrics::Aggregator} instance to aggregate metrics
10
+ # and a thread to aggregate flush every 5 seconds.
11
+ # @return [Boolean]
12
+ attr_accessor :enabled
13
+
14
+ # Enable code location reporting.
15
+ # Will be sent once per day.
16
+ # True by default.
17
+ # @return [Boolean]
18
+ attr_accessor :enable_code_locations
19
+
20
+ # Optional Proc, called before emitting a metric to the aggregator.
21
+ # Use it to filter keys (return false/nil) or update tags.
22
+ # Make sure to return true at the end.
23
+ #
24
+ # @example
25
+ # config.metrics.before_emit = lambda do |key, tags|
26
+ # return nil if key == 'foo'
27
+ # tags[:bar] = 42
28
+ # tags.delete(:baz)
29
+ # true
30
+ # end
31
+ #
32
+ # @return [Proc, nil]
33
+ attr_reader :before_emit
34
+
35
+ def initialize
36
+ @enabled = false
37
+ @enable_code_locations = true
38
+ end
39
+
40
+ def before_emit=(value)
41
+ check_callable!("metrics.before_emit", value)
42
+
43
+ @before_emit = value
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class CounterMetric < Metric
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = value.to_f
10
+ end
11
+
12
+ def add(value)
13
+ @value += value.to_f
14
+ end
15
+
16
+ def serialize
17
+ [value]
18
+ end
19
+
20
+ def weight
21
+ 1
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class DistributionMetric < Metric
6
+ attr_reader :value
7
+
8
+ def initialize(value)
9
+ @value = [value.to_f]
10
+ end
11
+
12
+ def add(value)
13
+ @value << value.to_f
14
+ end
15
+
16
+ def serialize
17
+ value
18
+ end
19
+
20
+ def weight
21
+ value.size
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Metrics
5
+ class GaugeMetric < Metric
6
+ attr_reader :last, :min, :max, :sum, :count
7
+
8
+ def initialize(value)
9
+ value = value.to_f
10
+ @last = value
11
+ @min = value
12
+ @max = value
13
+ @sum = value
14
+ @count = 1
15
+ end
16
+
17
+ def add(value)
18
+ value = value.to_f
19
+ @last = value
20
+ @min = [@min, value].min
21
+ @max = [@max, value].max
22
+ @sum += value
23
+ @count += 1
24
+ end
25
+
26
+ def serialize
27
+ [last, min, max, sum, count]
28
+ end
29
+
30
+ def weight
31
+ 5
32
+ end
33
+ end
34
+ end
35
+ end