Skip to content

Commit

Permalink
Add Appsignal.set_session_data helper method
Browse files Browse the repository at this point in the history
I want to deprecate and remove the `Transaction#set_session_data`
method. To be able to remove it without breaking things for applications
calling it directly, add helpers for all types of sample data, starting
with session data. This is similar to our Node.js sample data helpers.
  • Loading branch information
tombruijn committed Jul 9, 2024
1 parent 41410a2 commit 48c7663
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 15 deletions.
10 changes: 10 additions & 0 deletions .changesets/add-appsignal-set_session_data-helper.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
bump: patch
type: add
---

Add `Appsignal.set_session_data` helper. Set custom session data on the current transaction with the `Appsignal.set_session_data` helper. Note that this will overwrite any request session data that would be set automatically on the transaction. When this method is called multiple times, it will overwrite the previously set value.

```ruby
Appsignal.set_session_data("data1" => "value1", "data2" => "value2")
```
62 changes: 60 additions & 2 deletions lib/appsignal/helpers/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,8 @@ def tag_request(tags = {})
# A block can be given to this method to defer the fetching and parsing
# of the parameters until and only when the transaction is sampled.
#
# When both the `params` and a block is given to this method, the
# `params` argument is leading and the block will _not_ be called.
# When both the `params` argument and a block is given to this method,
# the `params` argument is leading and the block will _not_ be called.
#
# @example Set parameters
# Appsignal.set_params("param1" => "value1")
Expand Down Expand Up @@ -608,6 +608,64 @@ def set_params(params = nil, &block)
transaction.set_params(params, &block)
end

# Set session data on the current transaction.
#
# Session data is automatically set by most of our integrations. It
# should not be necessary to call this method unless you want to report
# different session data.
#
# To filter session data, see our session data filtering guide.
#
# When this method is called multiple times, it will overwrite the
# previously set value.
#
# A block can be given to this method to defer the fetching and parsing
# of the session data until and only when the transaction is sampled.
#
# When both the `session_data` argument and a block is given to this
# method, the `session_data` argument is leading and the block will _not_
# be called.
#
# @example Set session data
# Appsignal.set_session_data("data" => "value")
#
# @example Calling `set_session_data` multiple times will only keep the last call
# Appsignal.set_session_data("data1" => "value1")
# Appsignal.set_session_data("data2" => "value2")
# # The session data is: { "data2" => "value2" }
#
# @example Calling `set_session_data` with a block
# Appsignal.set_session_data do
# # Some slow code to parse session data
# JSON.parse('{"data": "value"}')
# end
# # The session data is: { "data" => "value" }
#
# @example Calling `set_session_data` with a session_data argument and a block
# Appsignal.set_session_data("argument" => "argument value") do
# # Some slow code to parse session data
# JSON.parse('{"data": "value"}')
# end
# # The session data is: { "argument" => "argument value" }
#
# @since 3.11.0
# @param session_data [Hash] The session data to set on the transaction.
# @yield This block is called when the transaction is sampled. The block's
# return value will become the new session data.
# @see https://docs.appsignal.com/guides/custom-data/sample-data.html
# Sample data guide
# @see https://docs.appsignal.com/guides/filter-data/filter-session-data.html
# Session data filtering guide
# @see Transaction#set_session_data
# @return [void]
def set_session_data(session_data = nil, &block)
return unless active?
return unless Appsignal::Transaction.current?

transaction = Appsignal::Transaction.current
transaction.set_session_data(session_data, &block)
end

# Add breadcrumbs to the transaction.
#
# Breadcrumbs can be used to trace what path a user has taken
Expand Down
62 changes: 56 additions & 6 deletions lib/appsignal/transaction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def initialize(transaction_id, namespace, request, options = {})
@options = options
@options[:params_method] ||= :params
@params = nil
@session_data = nil

@ext = Appsignal::Extension.start_transaction(
@transaction_id,
Expand Down Expand Up @@ -207,6 +208,8 @@ def params=(given_params)
# @yield This block is called when the transaction is sampled. The block's
# return value will become the new parameters.
# @return [void]
#
# @see #set_params
# @see Helpers::Instrumentation#set_params_if_nil
def set_params_if_nil(given_params = nil, &block)
set_params(given_params, &block) unless @params
Expand All @@ -230,6 +233,45 @@ def set_tags(given_tags = {})
@tags.merge!(given_tags)
end

# Set session data on the transaction.
#
# When both the `given_session_data` and a block is given to this method,
# the `given_session_data` argument is leading and the block will _not_ be
# called.
#
# @param given_session_data [Hash] A hash containing session data.
# @yield This block is called when the transaction is sampled. The block's
# return value will become the new session data.
# @return [void]
#
# @since 3.10.1
# @see Helpers::Instrumentation#set_session_data
# @see https://docs.appsignal.com/guides/custom-data/sample-data.html
# Sample data guide
def set_session_data(given_session_data = nil, &block)
@session_data = block if block
@session_data = given_session_data if given_session_data
end

# Set session data on the transaction if not already set.
#
# When both the `given_session_data` and a block is given to this method,
# the `given_session_data` argument is leading and the block will _not_ be
# called.
#
# @param given_session_data [Hash] A hash containing session data.
# @yield This block is called when the transaction is sampled. The block's
# return value will become the new session data.
# @return [void]
#
# @since 3.10.1
# @see #set_session_data
# @see https://docs.appsignal.com/guides/custom-data/sample-data.html
# Sample data guide
def set_session_data_if_nil(given_session_data = nil, &block)
set_session_data(given_session_data, &block) unless @session_data
end

# Set custom data on the transaction.
#
# When this method is called multiple times, it will overwrite the
Expand Down Expand Up @@ -637,6 +679,18 @@ def sanitized_environment
end
end

def session_data
if @session_data
if @session_data.respond_to? :call
@session_data.call
else
@session_data
end
elsif request.respond_to?(:session)
request.session
end
end

# Returns sanitized session data.
#
# The session data is sanitized by the {Appsignal::Utils::HashSanitizer}.
Expand All @@ -646,14 +700,10 @@ def sanitized_environment
# @return [nil] if the {#request} session data is `nil`.
# @return [Hash<String, Object>]
def sanitized_session_data
return if !Appsignal.config[:send_session_data] ||
!request.respond_to?(:session)

session = request.session
return unless session
return unless Appsignal.config[:send_session_data]

Appsignal::Utils::HashSanitizer.sanitize(
session.to_hash, Appsignal.config[:filter_session_data]
session_data&.to_hash, Appsignal.config[:filter_session_data]
)
end

Expand Down
119 changes: 115 additions & 4 deletions spec/lib/appsignal/transaction_spec.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
describe Appsignal::Transaction do
before :context do
start_agent
end

let(:transaction_id) { "1" }
let(:time) { Time.at(fixed_time) }
let(:namespace) { Appsignal::Transaction::HTTP_REQUEST }
Expand All @@ -16,6 +12,7 @@
before { Timecop.freeze(time) }
after { Timecop.return }
around do |example|
start_agent
use_logger_with log do
example.run
end
Expand Down Expand Up @@ -488,6 +485,120 @@ def create_transaction(id = transaction_id)
end
end

describe "#set_session_data" do
around { |example| keep_transactions { example.run } }

context "when the session data is set" do
it "updates the session data on the transaction" do
data = { "key" => "value" }
transaction.set_session_data(data)

transaction._sample
expect(transaction).to include_session_data(data)
end

it "updates the session data on the transaction with a block" do
data = { "key" => "value" }
transaction.set_session_data { data }

transaction._sample
expect(transaction).to include_session_data(data)
end

it "updates with the session data argument when both an argument and block are given" do
arg_data = { "argument" => "value" }
block_data = { "block" => "value" }
transaction.set_session_data(arg_data) { block_data }

transaction._sample
expect(transaction).to include_session_data(arg_data)
end

it "does not include filtered out session data" do
Appsignal.config[:filter_session_data] = ["filtered_key"]
transaction.set_session_data("data" => "value1", "filtered_key" => "filtered_value")

transaction._sample
expect(transaction).to include_session_data("data" => "value1")
end
end

context "when the given session data is nil" do
it "does not update the session data on the transaction" do
data = { "key" => "value" }
transaction.set_session_data(data)
transaction.set_session_data(nil)

transaction._sample
expect(transaction).to include_session_data(data)
end
end
end

describe "#set_session_data_if_nil" do
around { |example| keep_transactions { example.run } }

context "when the params are not set" do
it "sets the params on the transaction" do
data = { "key" => "value" }
transaction.set_session_data_if_nil(data)

transaction._sample
expect(transaction).to include_session_data(data)
end

it "updates the params on the transaction with a block" do
data = { "key" => "value" }
transaction.set_session_data_if_nil { data }

transaction._sample
expect(transaction).to include_session_data(data)
end

it "updates with the params argument when both an argument and block are given" do
arg_data = { "argument" => "value" }
block_data = { "block" => "value" }
transaction.set_session_data_if_nil(arg_data) { block_data }

transaction._sample
expect(transaction).to include_session_data(arg_data)
end

context "when the given params is nil" do
it "does not update the params on the transaction" do
data = { "key" => "value" }
transaction.set_session_data(data)
transaction.set_session_data_if_nil(nil)

transaction._sample
expect(transaction).to include_session_data(data)
end
end
end

context "when the params are set" do
it "does not update the params on the transaction" do
preset_data = { "other" => "data" }
data = { "key" => "value" }
transaction.set_session_data(preset_data)
transaction.set_session_data_if_nil(data)

transaction._sample
expect(transaction).to include_session_data(preset_data)
end

it "does not update the params with a block on the transaction" do
preset_data = { "other" => "data" }
data = { "key" => "value" }
transaction.set_session_data(preset_data)
transaction.set_session_data_if_nil { data }

transaction._sample
expect(transaction).to include_session_data(preset_data)
end
end
end

describe "#set_tags" do
let(:long_string) { "a" * 10_001 }

Expand Down
43 changes: 40 additions & 3 deletions spec/lib/appsignal_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,7 @@
end

describe ".set_params" do
before do
start_agent
end
before { start_agent }

context "with transaction" do
let(:transaction) { http_request_transaction }
Expand Down Expand Up @@ -561,6 +559,45 @@
end
end

describe ".set_session_data" do
before { start_agent }

context "with transaction" do
let(:transaction) { http_request_transaction }
before { set_current_transaction(transaction) }

it "sets session data on the transaction" do
Appsignal.set_session_data("data" => "value1")

transaction._sample
expect(transaction).to include_session_data("data" => "value1")
end

it "overwrites the session data if called multiple times" do
Appsignal.set_session_data("data" => "value1")
Appsignal.set_session_data("data" => "value2")

transaction._sample
expect(transaction).to include_session_data("data" => "value2")
end

it "sets session data with a block on the transaction" do
Appsignal.set_session_data { { "data" => "value1" } }

transaction._sample
expect(transaction).to include_session_data("data" => "value1")
end
end

context "without transaction" do
it "does not set session data on the transaction" do
Appsignal.set_session_data("a" => "b")

expect_any_instance_of(Appsignal::Transaction).to_not receive(:set_session_data)
end
end
end

describe ".set_custom_data" do
before { start_agent }

Expand Down

0 comments on commit 48c7663

Please sign in to comment.