-
Notifications
You must be signed in to change notification settings - Fork 375
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
Instrument ActiveModelSerializers #340
Conversation
Hey @skisulli , thanks for the contribution! Looked through what you have so far; I think it's looks congruent with what we're doing. So far so good! Looking forward to giving it another look when you think it's ready! :) |
span.span_type = Datadog::Ext::HTTP::TEMPLATE | ||
|
||
# Will be nil in 0.9 | ||
span.set_tag('active_model_serializers.adapter', payload[:adapter]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we shouldn't set this tag if the value is nil
?
@delner I think this is ready now, I added tests, and refactored the implementation a bit. Let me know what you think. I also will try to pull this into one of my projects and make sure I get data flowing through. |
I got all errors with integration fixed, but when I use this branch on one of my apps, the project won't track. Is there anything that looks like it might cause tracking to not work? |
def call(_, started, finished, _, payload) | ||
ensure_clean_context! | ||
|
||
span = configuration[:tracer].trace(payload[:serializer]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think our API is a little misleading here, but the argument for trace
should be a name
, which is a generic name for a kind of span e.g. active_model.serializer.render
, not a resource
, which is a name that uniquely identifies what category of user data this belongs to e.g. FooSerializer
. (I think that's what this payload represents.)
I would change this argument to a fixed constant of a name. I think the reason you might not be seeing traces/spans show up might be because resource
is nil
, causing the spans to be filtered out. If you give resource
a value, that might fix it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@delner sorry for taking a bit to get back about this, I was out of town over the weekend.
I tried that, but the app is still not tracking in APM when I try to use this fork. It's the entire app that won't track, not just the active model serializers span. There are no errors in the logs, and the debug info looks like everything should be working.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@skisulli Sorry for the late reply! This sometimes happens when there are unfinished spans in the trace. If that happens, it will discard the whole trace.
You can enable debug tracing to see if there are any messages/warnings indicating this might be the case:
Datadog.configure do |c|
c.tracer debug: true
end
Want to give that a shot and see if it prints out any useful output?
I would also double check that all of your spans have assigned resource and service names.
@skisulli Okay, that could be some time still, but yeah, there's a lot in common between these two PRs. I can ping you when that gets merged. |
@skisulli we've merged #380 which introduces a module for tracing ActiveSupport::Notifications events. Want to check that out, see if that helps your implementation here? |
121ecd5
to
46c3939
Compare
3990ea1
to
b3ae141
Compare
@delner I updated the PR, but I'm not getting AMS traces in my project. It looks like the parent id isn't being set on the span.
|
@skisulli Can you reproduce the issue in a test? |
376db80
to
5aee96e
Compare
@delner I added a test, and after further digging in, it looks to be related to |
@skisulli Can you tell me a little more about why |
@delner I don't know why, specifically, but I'll dig in. I added a test for a parent span, that was failing. Removing call to |
@skisulli Good to hear that it's working for you. I think I want to pin why that |
@delner I dug into the code more, and now understand why the
|
@skisulli Ahhh okay. Here's what I suspect... I'm guessing I think this might apply when something like Racecar (from which integration this pattern was extracted) re-uses threads. Contexts are supposed thread bound, so if a thread gets re-used, then the old context object might leak into a new trace. This However, doing so could break Racecar in the process. I think that functionality might need to be fixed first, which would involve moving Such a change probably belongs in its own PR. I suppose I should probably make that, then you could rebase your PR on top of that once its merged. |
@skisulli Implemented what should be a fix in #390 (removes |
5aee96e
to
8349212
Compare
@skisulli Awesome. Plan here then is: 1) I'll do one more review/check to make sure nothing else needs to be addressed, 2) wait for #390 to be merged, then rebase this PR back on top, 3) then we'll merge this. |
@delner thanks! I appreciate you working with me on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some general feedback, and a possible bug to address.
end | ||
end | ||
|
||
def ams10? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Name of this function is a little misleading; thought it implied a specific major version number. Could we change this name to something more that reflects "AMS 0.10 or greater"?
Alternatively, maybe it'd be more clear to communicate intent with these function names e.g. use_render_event_name?
or use_serializer_name?
that are based on a common ams10_or_newer?
.
The latter suggestion is just food for thought; if you think the prior is better, that's probably fine too.
@@ -28,18 +28,17 @@ def after_trace(&block) | |||
@after_trace_callbacks << block if block_given? | |||
end | |||
|
|||
def call(name, start, _, id, payload) | |||
start_span(start) | |||
finish_span(name, id, payload) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a bug here: finish_span
would be applying the wrong finish time.
My suggestion here would be to:
def call(name, start, finish, id, payload)
start_span(name, id, payload, start)
finish_span(name, id, payload, finish)
end
def start(name, id, payload)
start_span(name, id, payload)
end
def finish(name, id, payload)
finish_span(name, id, payload)
end
protected
def start_span(name, id, payload, start = nil)
# Run callbacks
run_callbacks(@before_trace_callbacks)
# Start a trace
tracer.trace(@span_name, @options).tap do |span|
# Assign start time if provided
span.start_time = start unless start.nil?
end
end
def end_span(name, id, payload, finish = nil)
tracer.active_span.tap do |span|
# If no active span, return.
return nil if span.nil?
# Call block for event
block.call(span, name, id, payload)
# Assign finish time if provided. Else finish.
finish.nil? ? span.finish : span.finish(finish)
# Run callbacks
run_callbacks(@after_trace_callbacks)
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking on the example above, we might want to add some error handling around block.call
too, so an error in that block doesn't break the trace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@delner working on this now. For the error use case, should it still raise the error up? Thinking something like this, but not sure if I should sill raise the error up the chain.
def finish_span(name, id, payload, finish = nil)
tracer.active_span.tap do |span|
# If no span return
return nil if span.nil?
begin
# Call event block
block.call(span, name, id, payload)
rescue StandardError => error
span.set_error(error)
raise
ensure
# Finsih span `span.finish` will default to current time if finish.nil?
span.finish(finish)
# Run after callbacks
run_callbacks(@after_trace_callbacks)
end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@skisulli I don't think we should raise or set any kind of error, since generating trace data is meta, and shouldn't break legitimate user code if it fails. When tracing fails, we instead use the tracer logger to quietly log errors.
I'm going to open (another) PR to implement this change (with the call
) too, because I think this is important for ASN subscription, and I'd like to isolate it from ActiveModelSerializers. We'll rebase on top of that once its merged.
@@ -28,18 +28,17 @@ def after_trace(&block) | |||
@after_trace_callbacks << block if block_given? | |||
end | |||
|
|||
def call(name, start, _, id, payload) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If call
and start
/finish
both exist, which does ActiveSupport::Notifications use? Does it call both?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ActiveSupport 4 and 5 appear to just call start/finish
, and ActiveSupport 3 calls call
. I ran into this when testing the ActiveModelSerializers using contrib-old
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good to know.
@skisulli Merged #395. Go ahead on rebase on |
dd4d586
to
ad97fd0
Compare
c95b205
to
3bef40b
Compare
@delner rebased on |
Hey @skisulli, we added a new Think it could be a good fit here? Let me know what you think! |
@delner it looks like it would be a good fit. I'll take a look in more detail and update this PR. |
3bef40b
to
9db2276
Compare
module Serialize | ||
include ActiveModelSerializers::Event | ||
|
||
EVENT_NAME = '!serialize.active_model_serializers'.freeze |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the older < 0.10 event right? I would consider overriding the supported?
method here and checking the version number, so it doesn't subscribe in 0.10+. See the ActiveRecord for an example. Same likely applies to the Render
event.
93f1a13
to
859d6f7
Compare
@delner updated the tests and added the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing related to tests, otherwise I'm very happy with this.
@@ -0,0 +1,54 @@ | |||
require 'active_model_serializers' | |||
|
|||
module ActiveModelSerializersHelpers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this into the spec/ddtrace/contrib/active_model_serializers
folder, since these helpers are specific to the integration specs and not generally useful to other examples in the test suite?
Also, I'm generally not a fan of defining constants in test suites using class
or module
, because they tend to sit in the global namespace and potentially cause naming conflicts. You've mitigated this here by putting this in a module with a name that's less likely to conflict, so this point doesn't exactly constitute blocking feedback.
However, I would consider leveraging RSpec's stub_const
, Class.new
, or both to create test constants that only survive the scope of an example. This might look like in helpers.rb
:
module ActiveModelSerializersHelpers
def ams_0_10_or_newer?
Gem.loaded_specs['active_model_serializers'] \
&& Gem.loaded_specs['active_model_serializers'].version >= Gem::Version.new('0.10')
end
def disable_logging
if ams_0_10_or_newer?
ActiveModelSerializers.logger.level = Logger::Severity::UNKNOWN
end
end
end
RSpec.shared_context 'AMS serializer' do
extend ActiveModelSerializersHelpers
let(:serializer_class) do
stub_const('TestModelSerializer', Class.new(ActiveModel::Serializer) do
attributes :name
end)
end
if ams_0_10_or_newer?
let(:model_base_class) do
Class.new(ActiveModelSerializers::Model) do
attr_writer :id
end
end
let(:model_class) do
stub_const('TestModel', Class.new(model_base_class) do
attributes :name
end)
end
else
let(:model_base_class) do
Class.new(ActiveModelSerializers::Model) do
attr_writer :id
def initialize(hash = {})
@attributes = hash
end
def read_attribute_for_serialization(name)
if name == :id || name == 'id'
object_id
elsif respond_to?(name)
send name
else
@attributes[name]
end
end
end
end
let(:model_class) do
stub_const('TestModel', Class.new(model_base_class))
end
end
end
Then in your test file just add:
require_relative 'helpers'
RSpec.describe 'ActiveModelSerializers patcher' do
include_context 'AMS serializer'
# Perform test here...
end
The really cool benefit you get from doing this, other than not having conflicting constants, is you can also override behavior/definitions of these constants in your examples. This makes it easier to test different variations, implementations and edge cases without having to define constants for each.
* Subscribe to the appropriate event for AMS 0.10 and AMS 0.9
859d6f7
to
89ea490
Compare
@delner updated the tests per your feedback. I had tried that earlier and couldn't get it working right. Took some experimentation but got it working with a before block, for some reason using the lets didn't work right. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good to me! Let's merge it.
@skisulli This one has been a long time coming, but thanks so much for your patience and hard work on this one! 💯 It's looking great, and I'm looking forward to it being used! |
Instrument ActiveModelSerializers
I'm currently using ActiveModelSerializers on a few different Rails projects, and want to be able to trace the serialization like a rails view. This implementation borrows heavily from the racecar patcher.
I still need to write tests, but wanted to open the PR early to get feedback on the implementation. Thanks!