Skip to content

Commit

Permalink
Add config.metrics.before_emit callback
Browse files Browse the repository at this point in the history
  • Loading branch information
sl0thentr0py committed Mar 8, 2024
1 parent bf28b5c commit 39b339a
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 7 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Add main APIs and `Aggregator` thread [#2247](https://github.com/getsentry/sentry-ruby/pull/2247)
- Add `Sentry::Metrics.timing` API for measuring block duration [#2254](https://github.com/getsentry/sentry-ruby/pull/2254)
- Add metric summaries on spans [#2255](https://github.com/getsentry/sentry-ruby/pull/2255)
- Add `config.metrics.before_emit` callback [#2258](https://github.com/getsentry/sentry-ruby/pull/2258)

The SDK now supports recording and aggregating metrics. A new thread will be started
for aggregation and will flush the pending data to Sentry every 5 seconds.
Expand Down Expand Up @@ -44,6 +45,18 @@
Sentry::Metrics.timing('how_long') { sleep(1) }
# timing - measure duration of code block in other duraton units
Sentry::Metrics.timing('how_long_ms', unit: 'millisecond') { sleep(0.5) }
# add a before_emit callback to filter keys or update tags
Sentry.init do |config|
# ...
config.metrics.enabled = true
config.metrics.before_emit = lambda do |key, tags|
return nil if key == 'foo'
tags[:bar] = 42
tags.delete(:baz)
true
end
end
```

### Bug Fixes
Expand Down
6 changes: 0 additions & 6 deletions sentry-ruby/lib/sentry/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,6 @@ def error_messages

private

def check_callable!(name, value)
unless value == nil || value.respond_to?(:call)
raise ArgumentError, "#{name} must be callable (or nil to disable)"
end
end

def init_dsn(dsn_string)
return if dsn_string.nil? || dsn_string.empty?

Expand Down
6 changes: 5 additions & 1 deletion sentry-ruby/lib/sentry/metrics/aggregator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class Aggregator
def initialize(configuration, client)
@client = client
@logger = configuration.logger
@before_emit = configuration.metrics.before_emit

Check warning on line 27 in sentry-ruby/lib/sentry/metrics/aggregator.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/metrics/aggregator.rb#L27

Added line #L27 was not covered by tests

@default_tags = {}
@default_tags['release'] = configuration.release if configuration.release
Expand Down Expand Up @@ -55,8 +56,11 @@ def add(type,
# this is integer division and thus takes the floor of the division
# and buckets into 10 second intervals
bucket_timestamp = (timestamp / ROLLUP_IN_SECONDS) * ROLLUP_IN_SECONDS
updated_tags = get_updated_tags(tags)

Check warning on line 59 in sentry-ruby/lib/sentry/metrics/aggregator.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/metrics/aggregator.rb#L59

Added line #L59 was not covered by tests

serialized_tags = serialize_tags(get_updated_tags(tags))
return if @before_emit && !@before_emit.call(key, updated_tags)

Check warning on line 61 in sentry-ruby/lib/sentry/metrics/aggregator.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/metrics/aggregator.rb#L61

Added line #L61 was not covered by tests

serialized_tags = serialize_tags(updated_tags)

Check warning on line 63 in sentry-ruby/lib/sentry/metrics/aggregator.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/metrics/aggregator.rb#L63

Added line #L63 was not covered by tests
bucket_key = [type, key, unit, serialized_tags]

added = @mutex.synchronize do
Expand Down
23 changes: 23 additions & 0 deletions sentry-ruby/lib/sentry/metrics/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,38 @@
module Sentry
module Metrics
class Configuration
include ArgumentCheckingHelper

# Enable metrics usage
# Starts a new {Sentry::Metrics::Aggregator} instance to aggregate metrics
# and a thread to aggregate flush every 5 seconds.
# @return [Boolean]
attr_accessor :enabled

# Optional Proc, called before emitting a metric to the aggregator.
# Use it to filter keys (return false/nil) or update tags.
# Make sure to return true at the end.
#
# @example
# config.metrics.before_emit = lambda do |key, tags|
# return nil if key == 'foo'
# tags[:bar] = 42
# tags.delete(:baz)
# true
# end
#
# @return [Proc, nil]
attr_reader :before_emit

def initialize
@enabled = false
end

def before_emit=(value)
check_callable!("metrics.before_emit", value)

Check warning on line 34 in sentry-ruby/lib/sentry/metrics/configuration.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/metrics/configuration.rb#L34

Added line #L34 was not covered by tests

@before_emit = value

Check warning on line 36 in sentry-ruby/lib/sentry/metrics/configuration.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/metrics/configuration.rb#L36

Added line #L36 was not covered by tests
end
end
end
end
6 changes: 6 additions & 0 deletions sentry-ruby/lib/sentry/utils/argument_checking_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,11 @@ def check_argument_includes!(argument, values)
raise ArgumentError, "expect the argument to be one of #{values.map(&:inspect).join(' or ')}, got #{argument.inspect}"
end
end

def check_callable!(name, value)
unless value == nil || value.respond_to?(:call)
raise ArgumentError, "#{name} must be callable (or nil to disable)"

Check warning on line 21 in sentry-ruby/lib/sentry/utils/argument_checking_helper.rb

View check run for this annotation

Codecov / codecov/patch

sentry-ruby/lib/sentry/utils/argument_checking_helper.rb#L21

Added line #L21 was not covered by tests
end
end
end
end
32 changes: 32 additions & 0 deletions sentry-ruby/spec/sentry/metrics/aggregator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,38 @@
expect(metric.value).to eq(Set[1])
end

describe 'with before_emit callback' do
before do
perform_basic_setup do |config|
config.metrics.enabled = true
config.enable_tracing = true
config.release = 'test-release'
config.environment = 'test'
config.logger = Logger.new(string_io)

config.metrics.before_emit = lambda do |key, tags|
return nil if key == 'foo'
tags[:add_tag] = 42
tags.delete(:remove_tag)
true
end
end
end

it 'does not emit metric with filtered key' do
expect(Sentry::Metrics::CounterMetric).not_to receive(:new)
subject.add(:c, 'foo', 1)
expect(subject.buckets).to eq({})
end

it 'updates the tags according to the callback' do
subject.add(:c, 'bar', 1, tags: { remove_tag: 99 })
_, _, _, tags = subject.buckets.values.first.keys.first
expect(tags).not_to include(['remove_tag', '99'])
expect(tags).to include(['add_tag', '42'])
end
end

describe 'local aggregation for span metric summaries' do
it 'does nothing without an active scope span' do
expect_any_instance_of(Sentry::Metrics::LocalAggregator).not_to receive(:add)
Expand Down
11 changes: 11 additions & 0 deletions sentry-ruby/spec/sentry/metrics/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
require 'spec_helper'

RSpec.describe Sentry::Metrics::Configuration do
describe '#before_emit=' do
it 'raises error when setting before_emit to anything other than callable or nil' do
subject.before_emit = -> { }
subject.before_emit = nil
expect { subject.before_emit = true }.to raise_error(ArgumentError, 'metrics.before_emit must be callable (or nil to disable)')
end
end
end

0 comments on commit 39b339a

Please sign in to comment.