st0012 / object_tracer Goto Github PK
View Code? Open in Web Editor NEWObjectTracer tracks objects and records their activities
License: MIT License
ObjectTracer tracks objects and records their activities
License: MIT License
Not every class respond to ancestors
method.
# InitializationTracker
def filter_condition_satisfied?(tp)
receiver = tp.self
method_name = tp.callee_id
if target.ancestors.include?(ActiveRecord::Base)
method_name == :new && receiver.ancestors.include?(target)
else
method_name == :initialize && receiver.is_a?(target)
end
end
Currently, we track objects with their object_id. But for ActiveRecord::Base
's instances, we can track them with record's id. This will allow us to track records from different places and don't need to trace where the object's initialized.
tap_on_mutation!
can track method calls that change certain object's state, e.g. updating instance variables. This can be very useful for certain cases.
However, for objects like AR records, we need to provide a way to specify what internal states to track (maybe just attributes).
:mutations_from_database (private) # ActiveModel::Dirty
from: /var/lib/gems/2.7.0/gems/activerecord-6.0.3.4/lib/active_record/attribute_methods/dirty.rb:161
changes:
@mutations_from_database: [undefined] =>
#ActiveModel::AttributeMutationTracker:0x00007f9ee9963040
my questions:
It should be smart enough to tap on an unique instance just once, especially in a Rails application where files will be auto-reloaded.
class PostsController
print_instance_traces(self)
end
print_*
helpers prints the output to stdout directly, which can be hard to read when mixed with other log outputs. So we should also support write_*
helpers that write all output into a designated file (default should be something like /tmp/tapping_device.log
). And then users can do tail -f /tmp/tapping_device.log
or cat /tmp/tapping_device.log
with cleaner information.
write_calls(object)
write_traces(object)
write_mutations(object)
like print_instance_mutations(Spree::Order, watch: :price) then all price changes will print
When using something like
print_calls(self)
It prints the print_calls
call. Which should be filtered out
:print_calls # TappingDevice::Trackable
from: /Users/st0012/projects/ticketsolve/app/controllers/api/ticketoffice/v1/view_download_options_controller.rb:6
<= {target: #<Api::Ticketoffice::V1::ViewDownloadOptionsController:0x00007fc667e63f90>, options: {filter_by_paths: [], exclude_by_paths: [], with_trace_to: 50, root_device: #<TappingDevice:0x00007fc667f0b0d8>, event_type: return, descendants: [], track_as_records: false}}
=> #<TappingDevice:0x00007fc667f0b0d8>
Sometimes we'll tap multiple objects at the same time and it'll be very useful to add tag to the payload/output messages to help determine the target. for example:
print_calls(seat_assignment_1, tag: "left assignment")
print_calls(seat_assignment_2, tag: "right assignment")
:x [left assignment] # SeatAssignment::GeneratedAttributeMethods
from: /app/models/seat_picker.rb:228
<= {}
=> 0
:x [right assignment] # SeatAssignment::GeneratedAttributeMethods
from: /app/models/seat_picker.rb:230
<= {}
=> 10
Sometimes it's annoying to break a series of methods chain into several lines, just to call print_calls
on an object. For example:
# we need to change this
SomeService.new(params).do_something
# into
s = SomeService.new(params)
print_calls(s)
s.do_something
For such cases, I'd like to add .with_print_calls
method to the Object
class. Then we can do
SomeService.new(params).with_print_calls.do_something
TappingDevice
doesn't convey the idea of the gem very well. But ObjectTracer
seems to be a very good name for that purpose.
Besides arguments, sometimes internal states will affect a method's result too. But currently print_calls
doesn't contain such information. So in such cases, users still need to dig into the method to check internal states.
Sample output:
:update? # CartOperationsService::OperationSet
from: /Users/st0012/projects/ticketsolve/app/services/cart_operations_service.rb:102
states:
@price: 10.0
<= {args: [], block: nil}
=> true
Assume we have a class like:
class Operation
def extras
dig_attribute("extras")
end
private
def data
@data
end
def dig_attribute(attr)
data.dig("attributes", attr)
end
end
Every time we call extras
, it'll call dig_attribute
and data
as well. So the output of print_calls(operation)
would look like this
As you can see, the entries of private helpers like data
and dig_attribute
provide duplicated or redundant information.
So we should have an option to filter out calls of private helpers.
print_calls(operation, ignore_private: true)
print_calls_in_detail(ActionDispatch::Http::URL) do
ActionDispatch::Http::URL.url_for(host: "www.ror.co.uk", subdomain: "api", tld_length: 2)
end
# ignore what happens outside the block
stop_tracing
print_calls_in_detail(ActionDispatch::Http::URL)
result = ActionDispatch::Http::URL.url_for(host: "www.ror.co.uk", subdomain: "api", tld_length: 2)
stop_tracing # ignore what happens after this line
Adding options in every helper call can by annoying, we should have something like
TappingDevice.config[:exclude_by_paths] = [/gems/]
TappingDevice.config[:with_traces_to] = 10
Right now it only listens to return
events, which makes it possible to get a method call's return value but method calls' order may look confusing.
It should allow users to choose if they want to listen to call
events instead. If the user doesn't need return_value
and cares about calls' order more, he/she should be able to do it.
Hello.
Some additional methods (or options) would be helpful for tapping any instances of specific class.
Example: print_calls_of_any_instance(MyClass)
.
Handful for cases when you can't access all objects, like internal work of ORM, or it'd be ugly.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.