Skip to content

Commit

Permalink
Add ActiveSupport for cache module
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikspang committed Aug 27, 2024
1 parent 6181b7b commit f29fb78
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 2 deletions.
2 changes: 2 additions & 0 deletions sentry-rails/lib/sentry/rails/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "sentry/rails/tracing/action_view_subscriber"
require "sentry/rails/tracing/active_record_subscriber"
require "sentry/rails/tracing/active_storage_subscriber"
require "sentry/rails/tracing/active_support_subscriber"

module Sentry
class Configuration
Expand Down Expand Up @@ -162,6 +163,7 @@ def initialize
end
@tracing_subscribers = Set.new([
Sentry::Rails::Tracing::ActionViewSubscriber,
Sentry::Rails::Tracing::ActiveSupportSubscriber,
Sentry::Rails::Tracing::ActiveRecordSubscriber,
Sentry::Rails::Tracing::ActiveStorageSubscriber
])
Expand Down
63 changes: 63 additions & 0 deletions sentry-rails/lib/sentry/rails/tracing/active_support_subscriber.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require "sentry/rails/tracing/abstract_subscriber"

module Sentry
module Rails
module Tracing
class ActiveSupportSubscriber < AbstractSubscriber
READ_EVENT_NAMES = %w[
cache_read.active_support
].freeze

WRITE_EVENT_NAMES = %w[
cache_write.active_support
cache_increment.active_support
cache_decrement.active_support
].freeze

REMOVE_EVENT_NAMES = %w[
cache_delete.active_support
].freeze

FLUSH_EVENT_NAMES = %w[
cache_prune.active_support
].freeze

EVENT_NAMES = READ_EVENT_NAMES + WRITE_EVENT_NAMES + REMOVE_EVENT_NAMES + FLUSH_EVENT_NAMES

SPAN_ORIGIN = "auto.cache.rails".freeze

def self.subscribe!
subscribe_to_event(EVENT_NAMES) do |event_name, duration, payload|
record_on_current_span(
op: operation_name(event_name),
origin: SPAN_ORIGIN,
start_timestamp: payload[START_TIMESTAMP_NAME],
description: payload[:store],
duration: duration
) do |span|
span.set_data("cache.key", [*payload[:key]].select { |key| Utils::EncodingHelper.valid_utf_8?(key) })
span.set_data("cache.hit", payload[:hit] == true) # Handle nil case
end
end
end

private

def self.operation_name(event_name)
case
when READ_EVENT_NAMES.include?(event_name)
"cache.get"
when WRITE_EVENT_NAMES.include?(event_name)
"cache.put"
when REMOVE_EVENT_NAMES.include?(event_name)
"cache.remove"
when FLUSH_EVENT_NAMES.include?(event_name)
"cache.flush"
else
"other"
end
end
end
end
end
end
4 changes: 2 additions & 2 deletions sentry-rails/spec/sentry/rails/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
class MySubscriber; end

it "returns the default subscribers" do
expect(subject.tracing_subscribers.size).to eq(3)
expect(subject.tracing_subscribers.size).to eq(4)
end

it "is customizable" do
subject.tracing_subscribers << MySubscriber
expect(subject.tracing_subscribers.size).to eq(4)
expect(subject.tracing_subscribers.size).to eq(5)
end

it "is replaceable" do
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
require "spec_helper"

RSpec.describe Sentry::Rails::Tracing::ActiveSupportSubscriber, :subscriber, type: :request do
let(:transport) do
Sentry.get_current_client.transport
end

context "when transaction is sampled" do
before do
make_basic_app do |config, app|
config.traces_sample_rate = 1.0
config.rails.tracing_subscribers = [described_class]

app.config.action_controller.perform_caching = true
app.config.cache_store = :memory_store
end
end

it "tracks cache write" do
transaction = Sentry::Transaction.new(sampled: true, hub: Sentry.get_current_hub)
Sentry.get_current_scope.set_span(transaction)

Rails.cache.write("my_cache_key", "my_cache_value")
transaction.finish

expect(transport.events.count).to eq(1)
cache_transaction = transport.events.first.to_hash
expect(cache_transaction[:type]).to eq("transaction")

expect(cache_transaction[:spans].count).to eq(1)
expect(cache_transaction[:spans][0][:op]).to eq("cache.put")
expect(cache_transaction[:spans][0][:origin]).to eq("auto.cache.rails")
end

it "tracks cache increment" do
Rails.cache.write("my_cache_key", 0)

transaction = Sentry::Transaction.new(sampled: true, hub: Sentry.get_current_hub)
Sentry.get_current_scope.set_span(transaction)
Rails.cache.increment("my_cache_key")

transaction.finish

expect(transport.events.count).to eq(1)
cache_transaction = transport.events.first.to_hash
expect(cache_transaction[:type]).to eq("transaction")
expect(cache_transaction[:spans].count).to eq(2)
expect(cache_transaction[:spans][1][:op]).to eq("cache.put")
expect(cache_transaction[:spans][1][:origin]).to eq("auto.cache.rails")
end

it "tracks cache decrement" do
Rails.cache.write("my_cache_key", 0)

transaction = Sentry::Transaction.new(sampled: true, hub: Sentry.get_current_hub)
Sentry.get_current_scope.set_span(transaction)
Rails.cache.decrement("my_cache_key")

transaction.finish

expect(transport.events.count).to eq(1)
cache_transaction = transport.events.first.to_hash
expect(cache_transaction[:type]).to eq("transaction")
expect(cache_transaction[:spans].count).to eq(2)
expect(cache_transaction[:spans][1][:op]).to eq("cache.put")
expect(cache_transaction[:spans][1][:origin]).to eq("auto.cache.rails")
end

it "tracks cache read" do
transaction = Sentry::Transaction.new(sampled: true, hub: Sentry.get_current_hub)
Sentry.get_current_scope.set_span(transaction)
Rails.cache.read("my_cache_key")

transaction.finish

expect(transport.events.count).to eq(1)
cache_transaction = transport.events.first.to_hash
expect(cache_transaction[:type]).to eq("transaction")
expect(cache_transaction[:spans].count).to eq(1)
expect(cache_transaction[:spans][0][:op]).to eq("cache.get")
expect(cache_transaction[:spans][0][:origin]).to eq("auto.cache.rails")
end

it "tracks sets cache hit" do
Rails.cache.write("my_cache_key", "my_cache_value")
transaction = Sentry::Transaction.new(sampled: true, hub: Sentry.get_current_hub)
Sentry.get_current_scope.set_span(transaction)
Rails.cache.read("my_cache_key")
Rails.cache.read("my_cache_key_non_existing")

transaction.finish

expect(transport.events.count).to eq(1)
cache_transaction = transport.events.first.to_hash
expect(cache_transaction[:type]).to eq("transaction")
expect(cache_transaction[:spans].count).to eq(2)
expect(cache_transaction[:spans][0][:op]).to eq("cache.get")
expect(cache_transaction[:spans][0][:origin]).to eq("auto.cache.rails")
expect(cache_transaction[:spans][0][:data]['cache.hit']).to eq(true)

expect(cache_transaction[:spans][1][:op]).to eq("cache.get")
expect(cache_transaction[:spans][1][:origin]).to eq("auto.cache.rails")
expect(cache_transaction[:spans][1][:data]['cache.hit']).to eq(false)
end
end

context "when transaction is not sampled" do
before do
make_basic_app
end

it "doesn't record spans" do
Rails.cache.write("my_cache_key", "my_cache_value")

expect(transport.events.count).to eq(0)
end
end
end

0 comments on commit f29fb78

Please sign in to comment.