From 7c22df6b00d31107d68bd6de9efff998fece2406 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Tue, 28 Oct 2025 13:06:02 +0000 Subject: [PATCH 1/5] feat(rails): add binds to ActiveRecord logs --- .../active_record_subscriber.rb | 9 +++ .../spec/dummy/test_rails_app/apps/5-2.rb | 2 + .../spec/dummy/test_rails_app/apps/6-0.rb | 2 + .../spec/dummy/test_rails_app/apps/6-1.rb | 2 + .../spec/dummy/test_rails_app/apps/7-0.rb | 2 + .../spec/dummy/test_rails_app/apps/7-1.rb | 2 + .../active_record_subscriber_spec.rb | 56 +++++++++++++++++++ 7 files changed, 75 insertions(+) diff --git a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb index c159e2423..f8bf5ad9f 100644 --- a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb +++ b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb @@ -46,6 +46,15 @@ def sql(event) cached: cached } + if Sentry.configuration.send_default_pii && !(binds = event.payload[:binds]).empty? + type_casted_binds = event.payload[:type_casted_binds] + + binds.each_with_index do |bind, index| + name = bind.is_a?(Symbol) ? bind : bind.name.to_sym + attributes[name] = type_casted_binds[index] + end + end + attributes[:statement_name] = statement_name if statement_name && statement_name != "SQL" attributes[:connection_id] = connection_id if connection_id diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb b/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb index 9dfcf0d62..273698df3 100644 --- a/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb +++ b/sentry-rails/spec/dummy/test_rails_app/apps/5-2.rb @@ -30,6 +30,8 @@ end create_table :posts, force: true do |t| + t.string :title + t.timestamps end create_table :comments, force: true do |t| diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb b/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb index 9dfcf0d62..273698df3 100644 --- a/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb +++ b/sentry-rails/spec/dummy/test_rails_app/apps/6-0.rb @@ -30,6 +30,8 @@ end create_table :posts, force: true do |t| + t.string :title + t.timestamps end create_table :comments, force: true do |t| diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb b/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb index a9148a3ee..494fe0eeb 100644 --- a/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb +++ b/sentry-rails/spec/dummy/test_rails_app/apps/6-1.rb @@ -30,6 +30,8 @@ end create_table :posts, force: true do |t| + t.string :title + t.timestamps end create_table :comments, force: true do |t| diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb b/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb index a9148a3ee..494fe0eeb 100644 --- a/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb +++ b/sentry-rails/spec/dummy/test_rails_app/apps/7-0.rb @@ -30,6 +30,8 @@ end create_table :posts, force: true do |t| + t.string :title + t.timestamps end create_table :comments, force: true do |t| diff --git a/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb b/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb index a9148a3ee..494fe0eeb 100644 --- a/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb +++ b/sentry-rails/spec/dummy/test_rails_app/apps/7-1.rb @@ -30,6 +30,8 @@ end create_table :posts, force: true do |t| + t.string :title + t.timestamps end create_table :comments, force: true do |t| diff --git a/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb b/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb index 3f4c578a0..f6c69a07b 100644 --- a/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb +++ b/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb @@ -12,6 +12,7 @@ config.rails.structured_logging.subscribers = { active_record: Sentry::Rails::LogSubscribers::ActiveRecordSubscriber } end end + describe "integration with ActiveSupport::Notifications" do it "logs SQL events when database queries are executed" do Post.create! @@ -45,6 +46,61 @@ expect(log_event[:attributes][:sql][:value]).to include("posts") end + context "when send_default_pii is enabled" do + before do + Sentry.configuration.send_default_pii = true + end + + after do + Sentry.configuration.send_default_pii = false + end + + it "logs SELECT queries with binds in attributes" do + post = Post.create!(title: "test") + + Sentry.get_current_client.flush + sentry_transport.events.clear + sentry_transport.envelopes.clear + + created_at = Time.new(2025, 10, 28, 13, 11, 44) + Post.where(id: post.id, title: post.title, created_at: created_at).to_a + + Sentry.get_current_client.flush + + log_event = sentry_logs.find { |log| log[:body]&.include?("Database query") } + expect(log_event).not_to be_nil + + expect(log_event[:attributes][:id][:value]).to be(post.id) + expect(log_event[:attributes][:id][:type]).to eql("integer") + + expect(log_event[:attributes][:title][:value]).to eql(post.title) + expect(log_event[:attributes][:title][:type]).to eql("string") + + expect(log_event[:attributes][:created_at][:value]).to include("2025-10-28 13:11:44") + expect(log_event[:attributes][:created_at][:type]).to eql("string") + end + end + + context "when send_default_pii is disabled" do + it "logs SELECT queries without binds in attributes" do + post = Post.create!(title: "test") + + Sentry.get_current_client.flush + sentry_transport.events.clear + sentry_transport.envelopes.clear + + Post.where(id: post.id, title: post.title).to_a + + Sentry.get_current_client.flush + + log_event = sentry_logs.find { |log| log[:body]&.include?("Database query") } + expect(log_event).not_to be_nil + + expect(log_event[:attributes][:id]).to be_nil + expect(log_event[:attributes][:title]).to be_nil + end + end + if Rails.version.to_f > 5.1 it "excludes SCHEMA events" do ActiveSupport::Notifications.instrument("sql.active_record", From cdd9895feddd4e06094f760bd52dc258bf427bb1 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Tue, 28 Oct 2025 13:31:50 +0000 Subject: [PATCH 2/5] Update CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f73be0415..083f2c569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## Unreleased + +### Features + +- Add support for ActiveRecord binds in the log events ([#2761](https://github.com/getsentry/sentry-ruby/pull/2761)) + ## 6.0.0 ### Breaking Changes From d04339372b1e9e05bdd2c2f8e9b6efa62752fd15 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Tue, 28 Oct 2025 15:03:44 +0000 Subject: [PATCH 3/5] Binds could be `nil` so don't crash if they are --- .../sentry/rails/log_subscribers/active_record_subscriber.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb index f8bf5ad9f..733cd2977 100644 --- a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb +++ b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb @@ -46,7 +46,9 @@ def sql(event) cached: cached } - if Sentry.configuration.send_default_pii && !(binds = event.payload[:binds]).empty? + binds = event.payload[:binds] + + if Sentry.configuration.send_default_pii && !binds&.empty? type_casted_binds = event.payload[:type_casted_binds] binds.each_with_index do |bind, index| From 28d141ebf480c0cb0cb7ec91d445a66fe356cfbc Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Tue, 28 Oct 2025 15:07:49 +0000 Subject: [PATCH 4/5] Follow conventions for db.query.params attributes --- .../log_subscribers/active_record_subscriber.rb | 4 ++-- .../active_record_subscriber_spec.rb | 17 +++++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb index 733cd2977..ae64b4886 100644 --- a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb +++ b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb @@ -52,8 +52,8 @@ def sql(event) type_casted_binds = event.payload[:type_casted_binds] binds.each_with_index do |bind, index| - name = bind.is_a?(Symbol) ? bind : bind.name.to_sym - attributes[name] = type_casted_binds[index] + name = bind.is_a?(Symbol) ? bind : bind.name + attributes["db.query.parameter.#{name}"] = type_casted_binds[index].to_s end end diff --git a/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb b/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb index f6c69a07b..c97b44eec 100644 --- a/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb +++ b/sentry-rails/spec/sentry/rails/log_subscribers/active_record_subscriber_spec.rb @@ -70,14 +70,15 @@ log_event = sentry_logs.find { |log| log[:body]&.include?("Database query") } expect(log_event).not_to be_nil - expect(log_event[:attributes][:id][:value]).to be(post.id) - expect(log_event[:attributes][:id][:type]).to eql("integer") + # Follow Sentry convention: db.query.parameter. with string values + expect(log_event[:attributes]["db.query.parameter.id"][:value]).to eq(post.id.to_s) + expect(log_event[:attributes]["db.query.parameter.id"][:type]).to eql("string") - expect(log_event[:attributes][:title][:value]).to eql(post.title) - expect(log_event[:attributes][:title][:type]).to eql("string") + expect(log_event[:attributes]["db.query.parameter.title"][:value]).to eql(post.title) + expect(log_event[:attributes]["db.query.parameter.title"][:type]).to eql("string") - expect(log_event[:attributes][:created_at][:value]).to include("2025-10-28 13:11:44") - expect(log_event[:attributes][:created_at][:type]).to eql("string") + expect(log_event[:attributes]["db.query.parameter.created_at"][:value]).to include("2025-10-28 13:11:44") + expect(log_event[:attributes]["db.query.parameter.created_at"][:type]).to eql("string") end end @@ -96,8 +97,8 @@ log_event = sentry_logs.find { |log| log[:body]&.include?("Database query") } expect(log_event).not_to be_nil - expect(log_event[:attributes][:id]).to be_nil - expect(log_event[:attributes][:title]).to be_nil + expect(log_event[:attributes]["db.query.parameter.id"]).to be_nil + expect(log_event[:attributes]["db.query.parameter.title"]).to be_nil end end From 20e70903db6f3c162aacb6fdd7a25addb1d9b535 Mon Sep 17 00:00:00 2001 From: Peter Solnica Date: Wed, 29 Oct 2025 09:57:10 +0000 Subject: [PATCH 5/5] Make it work under jruby too --- .../log_subscribers/active_record_subscriber.rb | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb index ae64b4886..72f563356 100644 --- a/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb +++ b/sentry-rails/lib/sentry/rails/log_subscribers/active_record_subscriber.rb @@ -49,7 +49,7 @@ def sql(event) binds = event.payload[:binds] if Sentry.configuration.send_default_pii && !binds&.empty? - type_casted_binds = event.payload[:type_casted_binds] + type_casted_binds = type_casted_binds(event) binds.each_with_index do |bind, index| name = bind.is_a?(Symbol) ? bind : bind.name @@ -71,6 +71,16 @@ def sql(event) ) end + if RUBY_ENGINE == "jruby" + def type_casted_binds(event) + event.payload[:type_casted_binds].call + end + else + def type_casted_binds(event) + event.payload[:type_casted_binds] + end + end + private def build_log_message(statement_name)