Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stripe instrumentation #2180

Merged
merged 23 commits into from
Sep 8, 2023
Merged

Stripe instrumentation #2180

merged 23 commits into from
Sep 8, 2023

Conversation

hannahramadan
Copy link
Contributor

@hannahramadan hannahramadan commented Aug 30, 2023

Adds instrumentation for Stripe 5.38.0+. Makes use of Stripe's pub/sub instrumentation pattern.

Stripe users can attach custom attributes to Stripe events via the user_data hash created on a Stripe request event initialization. No attributes are collected by default. Two new config options allow customers to fine-tune reported attributes: stripe.user_data.include and stripe.user_data.exclude

closes #2057

Dependency detection, subscriber, config
@hannahramadan hannahramadan marked this pull request as ready for review September 7, 2023 20:57
def add_custom_attributes(segment, event)
return if NewRelic::Agent.config[:'stripe.user_data.include'].empty?

event.user_data.delete(:newrelic_segment)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to move these 2 lines from 2 separate methods that need not know about each other out into their own method that deletes and returns the segment:

event.user_data.delete(:newrelic_segment)

segment = event.user_data[:newrelic_segment]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made some changes. Let me know if this works c59f04c

end

def metric_name(event)
"Stripe#{event.path} #{event.method}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the metric name elements be slash delimited?

Suggested change
"Stripe#{event.path} #{event.method}"
"Stripe#{event.path}/#{event.method}"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this. c59f04c

end

def finish_segment(event)
begin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing as the begin wraps the entire method body, there's no need for it, and you can simply remove the begin and end and outdent the ensure one level (to be on a peer level with def and rescue).

add_stripe_attributes(segment, event)
add_custom_attributes(segment, event)
ensure
segment.finish
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to reach the ensure block with a nil segment, and calling #finish on nil will raise another error which won't be handled. We can use the safe navigation operator to proactively anticipate and defend against this possibility.

Suggested change
segment.finish
segment&.finish

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call c59f04c

test/multiverse/suites/stripe/config/newrelic.yml Outdated Show resolved Hide resolved
object: 'list',
data: [{'id': '12134'}],
has_more: false,
url: '/v1/charges'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't really matter given that the tests don't operate on the url contained in the response, but from a "the specs should tell the story" perspective, perhaps you should store 'v1/customers' as a constant and use it both here and in the #detect calls that the tests perform.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated 63014dd

end

def test_newrelic_segment
Stripe::StripeClient.stub(:default_connection_manager, @connection) do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing as you use this wrapper multiple times, it'd be a good candidate for a test helper.

def with_stubbed_connection_manager(&block)
  Stripe::StripeClient.stub(:default_connection_manager, @connection) do
    @connection.stub(:execute_request, @response) do
      block.call
    end
  end
end

then for each test you could use it:

with_stubbed_connection_manager do
  in_transaction do |txn|
    Stripe::Customer.list({limit: 3})
    stripe_segment = txn.segments.detect { |s| s.name == 'Stripe/v1/customers get' }

    assert(stripe_segment)
  end
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pattern into a method.. love and done! 63014dd

@connection.stub(:execute_request, @response) do
in_transaction do |txn|
Stripe::Customer.list({limit: 3})
stripe_segment = txn.segments.detect { |s| s.name == 'Stripe/v1/customers get' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could see you doing something like:

stripe_segment = stripe_segment_from_txn(txn)

for this repeated pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this. Kayla had a similar suggestion to put repeated patterns into methods. I've updated 63014dd

Stripe::Customer.list({limit: 3})
stripe_segment = txn.segments.detect { |s| s.name == 'Stripe/v1/customers get' }

assert_empty txn.segments
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's always a single "dummy" segment when you use in_transaction. You should be able to do:

refute stripe_segment

but you don't want the transaction's segment array itself to be empty, as it'll always have at least 1 entry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I've updated what we're looking for 63014dd

Copy link
Contributor

@kaylareopelle kaylareopelle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so cool, Hannah! Awesome work. I found a few small things for you to consider before approval.

end

def metric_name(event)
"Stripe#{event.path} #{event.method}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can metric names have spaces in them? (I have no idea, but it struck me as odd)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both you and James called this out. I've removed the space c59f04c

license_key: bootstrap_newrelic_admin_license_key_000
ca_bundle_path: ../../../config/test.cert.crt
instrumentation:
sinatra: <%= $instrumentation_method %>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you want this to be stripe?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because pub/sub, I've removed this!

end

def test_version_supported
assert(Stripe::VERSION >= '5.38.0')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need Gem::Version.new to compare the values?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were running fine without it, but I've definitely run into issues with this in the past. To be safe and match instrumentation, I've updated c997c51

Stripe::StripeClient.stub(:default_connection_manager, @connection) do
@connection.stub(:execute_request, @response) do
in_transaction do |txn|
Stripe::Customer.list({limit: 3})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add the start a Stripe event comment here too? I loved seeing it on the later tests.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, you could name a method start_stripe_event and have it call Stripe::Customer.list({limit: 3}), if the event is started the same way in all the tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion! 63014dd

hannahramadan and others added 4 commits September 8, 2023 09:47
While the Stripe include list is only concerned with user data hash
keys, the exclude list looks at both the keys and their corresponding
values in case there's anything sensitive or otherwise unwanted in the
value.

- Added a new test to demonstrate hash value filtration
- Updated existing exclusion test which was yielding a false positive by
  not setting an include list (excludes won't work without an include
  list)
- Updated the exclude list config option description
Stripe: exclude list works on user data values
fallwith
fallwith previously approved these changes Sep 8, 2023
kaylareopelle
kaylareopelle previously approved these changes Sep 8, 2023
CHANGELOG.md Outdated Show resolved Hide resolved
Co-authored-by: Kayla Reopelle (she/her) <87386821+kaylareopelle@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Sep 8, 2023

SimpleCov Report

Coverage Threshold
Line 94.29% 94%
Branch 85.83% 85%

@hannahramadan hannahramadan merged commit 942607b into dev Sep 8, 2023
25 checks passed
@hannahramadan hannahramadan deleted the stripe_instrumentation branch September 8, 2023 19:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Ruby - Add Instrumentation Support for Stripe
3 participants