{"id":13416338,"url":"https://github.com/Shopify/rotoscope","last_synced_at":"2025-03-14T23:31:36.280Z","repository":{"id":45795777,"uuid":"76675732","full_name":"Shopify/rotoscope","owner":"Shopify","description":"High-performance logger of Ruby method invocations","archived":false,"fork":false,"pushed_at":"2024-07-31T19:01:52.000Z","size":446,"stargazers_count":201,"open_issues_count":4,"forks_count":11,"subscribers_count":421,"default_branch":"main","last_synced_at":"2025-03-07T02:46:08.775Z","etag":null,"topics":["callgraph","high-performance","introspection","invocation","ruby"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Shopify.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-12-16T18:20:19.000Z","updated_at":"2025-01-31T13:40:28.000Z","dependencies_parsed_at":"2024-01-15T23:27:04.389Z","dependency_job_id":"148e74f2-8bb9-414d-b9b4-8d320a131854","html_url":"https://github.com/Shopify/rotoscope","commit_stats":{"total_commits":190,"total_committers":10,"mean_commits":19.0,"dds":0.5842105263157895,"last_synced_commit":"0df1312c19af811d29098dbeb15013e9d2277224"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Frotoscope","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Frotoscope/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Frotoscope/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Shopify%2Frotoscope/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Shopify","download_url":"https://codeload.github.com/Shopify/rotoscope/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243165538,"owners_count":20246725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["callgraph","high-performance","introspection","invocation","ruby"],"created_at":"2024-07-30T21:00:57.318Z","updated_at":"2025-03-14T23:31:36.249Z","avatar_url":"https://github.com/Shopify.png","language":"Ruby","readme":"# Rotoscope\n\nRotoscope is a high-performance logger of Ruby method invocations.\n\n## Status\n\n[![Build Status](https://github.com/Shopify/rotoscope/actions/workflows/ci.yml/badge.svg)](https://github.com/Shopify/rotoscope/actions?query=branch%3Amain)\n[![Gem Version](https://badge.fury.io/rb/rotoscope.svg)](https://badge.fury.io/rb/rotoscope)\n\nRotoscope is subject to breaking changes in minor versions until `1.0` is available.\n\n## Table of Contents\n\n- [Example](https://github.com/Shopify/rotoscope?tab=readme-ov-file#example)\n- [API](https://github.com/Shopify/rotoscope?tab=readme-ov-file#api)\n  - [Default Logging Interface](https://github.com/Shopify/rotoscope?tab=readme-ov-file#default-logging-interface)\n  - [Low-level API](https://github.com/Shopify/rotoscope?tab=readme-ov-file#low-level-api)\n\n## Example\n\n```ruby\nrequire 'rotoscope'\n\nclass Dog\n  def bark\n    Noisemaker.speak('woof!')\n  end\nend\n\nclass Noisemaker\n  def self.speak(str)\n    puts(str)\n  end\nend\n\nlog_file = File.expand_path('dog_trace.log')\nputs \"Writing to #{log_file}...\"\n\nRotoscope::CallLogger.trace(log_file) do\n  dog1 = Dog.new\n  dog1.bark\nend\n```\n\nThe resulting method calls are saved in the specified `dest` in the order they were received.\n\nSample output:\n\n```\nentity,method_name,method_level,filepath,lineno,caller_entity,caller_method_name,caller_method_level\nDog,new,class,example/dog.rb,19,\u003cROOT\u003e,\u003cUNKNOWN\u003e,\u003cUNKNOWN\u003e\nDog,initialize,instance,example/dog.rb,19,Dog,new,class\nDog,bark,instance,example/dog.rb,20,\u003cROOT\u003e,\u003cUNKNOWN\u003e,\u003cUNKNOWN\u003e\nNoisemaker,speak,class,example/dog.rb,5,Dog,bark,instance\nNoisemaker,puts,class,example/dog.rb,11,Noisemaker,speak,class\nIO,puts,instance,example/dog.rb,11,Noisemaker,puts,class\nIO,write,instance,example/dog.rb,11,IO,puts,instance\nIO,write,instance,example/dog.rb,11,IO,puts,instance\n```\n\n## API\n\n### Default Logging Interface\n\nRotoscope ships with a default logger, `Rotoscope::CallLogger`. This provides a simple-to-use interface to the tracing engine that maintains performance as much as possible.\n\n- [`.trace`](#rotoscopecallloggertracedest-excludelist-)\n- [`.new`](#rotoscopecallloggernewdest-excludelist-)\n- [`#trace`](#rotoscopecallloggertraceblock)\n- [`#start_trace`](#rotoscopecallloggerstart_trace)\n- [`#stop_trace`](#rotoscopecallloggerstop_trace)\n- [`#mark`](#rotoscopecallloggermarkstr--)\n- [`#close`](#rotoscopecallloggerclose)\n- [`#state`](#rotoscopecallloggerstate)\n- [`#closed?`](#rotoscopecallloggerclosed)\n\n#### `Rotoscope::CallLogger.trace(dest, excludelist: [])`\n\nWrites all calls of methods to `dest`, except for those whose filepath contains any entry in `excludelist`. `dest` is either a filename or an `IO`. Methods invoked at the top of the trace will have a caller entity of `\u003cROOT\u003e` and a caller method name of `\u003cUNKNOWN\u003e`.\n\n```ruby\nRotoscope::CallLogger.trace(dest) { |call| ... }\n# or...\nRotoscope::CallLogger.trace(dest, excludelist: [\"/.gem/\"]) { |call| ... }\n```\n\n#### `Rotoscope::CallLogger.new(dest, excludelist: [])`\n\nSame interface as `Rotoscope::CallLogger::trace`, but returns a `Rotoscope::CallLogger` instance, allowing fine-grain control via `Rotoscope::CallLogger#start_trace` and `Rotoscope::CallLogger#stop_trace`.\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\n# or...\nrs = Rotoscope::CallLogger.new(dest, excludelist: [\"/.gem/\"])\n```\n\n#### `Rotoscope::CallLogger#trace(\u0026block)`\n\nSimilar to `Rotoscope::CallLogger::trace`, but does not need to create a file handle on invocation.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.trace do |rotoscope|\n  # code to trace...\nend\n```\n\n#### `Rotoscope::CallLogger#start_trace`\n\nBegins writing method calls to the `dest` specified in the initializer.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.start_trace\n# code to trace...\nrs.stop_trace\n```\n\n#### `Rotoscope::CallLogger#stop_trace`\n\nStops writing method invocations to the `dest`. Subsequent calls to `Rotoscope::CallLogger#start_trace` may be invoked to resume tracing.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.start_trace\n# code to trace...\nrs.stop_trace\n```\n\n#### `Rotoscope::CallLogger#mark(str = \"\")`\n\n Inserts a marker '--- ' to divide output. Useful for segmenting multiple blocks of code that are being profiled. If `str` is provided, the line will be prefixed by '--- ', followed by the string passed.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.start_trace\n# code to trace...\nrs.mark('Something goes wrong here') # produces `--- Something goes wrong here` in the output\n# more code ...\nrs.stop_trace\n```\n\n#### `Rotoscope::CallLogger#close`\n\nFlushes the buffer and closes the file handle. Once this is invoked, no more writes can be performed on the `Rotoscope::CallLogger` object. Sets `state` to `:closed`.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.trace { |rotoscope| ... }\nrs.close\n```\n\n#### `Rotoscope::CallLogger#state`\n\nReturns the current state of the Rotoscope::CallLogger object. Valid values are `:open`, `:tracing` and `:closed`.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.state # :open\nrs.trace do\n  rs.state # :tracing\nend\nrs.close\nrs.state # :closed\n```\n\n#### `Rotoscope::CallLogger#closed?`\n\nShorthand to check if the `state` is set to `:closed`.\n\n```ruby\nrs = Rotoscope::CallLogger.new(dest)\nrs.closed? # false\nrs.close\nrs.closed? # true\n```\n\n### Low-level API\n\nFor those who prefer to define their own logging logic, Rotoscope also provides a low-level API. This is the same one used by `Rotoscope::CallLogger` internally. Users may specify a block that is invoked on each detected method call.\n\n- [`.new`](#rotoscopenewblk)\n- [`#trace`](#rotoscopetraceblk)\n- [`#start_trace`](#rotoscopestart_trace)\n- [`#stop_trace`](#rotoscopestop_trace)\n- [`#tracing?`](#rotoscopetracing)\n- [`#receiver`](#rotoscopereceiver)\n- [`#receiver_class`](#rotoscopereceiver_class)\n- [`#receiver_class_name`](#rotoscopereceiver_class_name)\n- [`#method_name`](#rotoscopemethod_name)\n- [`#singleton_method?`](#rotoscopesingleton_method)\n- [`#caller_object`](#rotoscopecaller_object)\n- [`#caller_class`](#rotoscopecaller_class)\n- [`#caller_class_name`](#rotoscopecaller_class_name)\n- [`#caller_method_name`](#rotoscopecaller_method_name)\n- [`#caller_singleton_method?`](#rotoscopecaller_singleton_method)\n- [`#caller_path`](#rotoscopecaller_path)\n- [`#caller_lineno`](#rotoscopecaller_lineno)\n\n#### `Rotoscope.new(\u0026blk)`\n\nCreates a new instance of the `Rotoscope` class. The block argument is invoked on every call detected by Rotoscope. The block is passed the same instance returned by `Rotoscope#new` allowing the low-level methods to be called.\n\n```ruby\nrs = Rotoscope.new do |call|\n  # We likely don't want to record calls to Rotoscope\n  return if self == call.receiver\n  ...\nend\n```\n\n\n#### `Rotoscope#trace(\u0026blk)`\n\nThe equivalent of calling [`Rotoscope#start_trace`](#rotoscopestart_trace) and then [`Rotoscope#stop_trace`](#rotoscopestop_trace). The call to `#stop_trace` is within an `ensure` block so it is always called when the block terminates.\n\n```ruby\nrs = Rotoscope.new do |call|\n  ...\nend\n\nrs.trace do\n  # call some code\nend\n```\n\n#### `Rotoscope#start_trace`\n\nBegins detecting method calls invoked after this point.\n\n```ruby\nrs = Rotoscope.new do |call|\n  ...\nend\n\nrs.start_trace\n# Calls after this points invoke the\n# block passed to `Rotoscope.new`\n```\n\n#### `Rotoscope#stop_trace`\n\nDisables method call detection invoked after this point.\n\n```ruby\nrs = Rotoscope.new do |call|\n  ...\nend\n\nrs.start_trace\n...\nrs.stop_trace\n# Calls after this points will no longer\n# invoke the block passed to `Rotoscope.new`\n```\n\n#### `Rotoscope#tracing?`\n\nIdentifies whether the Rotoscope object is actively tracing method calls.\n\n```ruby\nrs = Rotoscope.new do |call|\n  ...\nend\n\nrs.tracing? # =\u003e false\nrs.start_trace\nrs.tracing? # =\u003e true\n```\n\n#### `Rotoscope#receiver`\n\nReturns the object that the method is being called against.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.receiver # =\u003e #\u003cFoo:0x00007fa3d2197c10\u003e\nend\n```\n\n#### `Rotoscope#receiver_class`\n\nReturns the class of the object that the method is being called against.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.receiver_class # =\u003e Foo\nend\n```\n\n#### `Rotoscope#receiver_class_name`\n\nReturns the stringified class of the object that the method is being called against.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.receiver_class_name # =\u003e \"Foo\"\nend\n```\n\n#### `Rotoscope#method_name`\n\nReturns the name of the method being invoked.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.method_name # =\u003e \"bar\"\nend\n```\n\n#### `Rotoscope#singleton_method?`\n\nReturns `true` if the method called is defined at the class level. If the call is to an instance method, this returns `false`.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.singleton_method? # =\u003e false\nend\n```\n\n#### `Rotoscope#caller_object`\n\nReturns the object whose context we invoked the call from.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_object # =\u003e #\u003cSomeClass:0x00008aa6d2cd91b61\u003e\nend\n```\n\n#### `Rotoscope#caller_class`\n\nReturns the class of the object whose context we invoked the call from.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_class # =\u003e SomeClass\nend\n```\n\n#### `Rotoscope#caller_class_name`\n\nReturns the tringified class of the object whose context we invoked the call from.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_class_name # =\u003e \"SomeClass\"\nend\n```\n\n#### `Rotoscope#caller_method_name`\n\nReturns the stringified class of the object whose context we invoked the call from.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_method_name # =\u003e \"call_foobar\"\nend\n```\n\n#### `Rotoscope#caller_singleton_method?`\n\nReturns `true` if the method invoking the call is defined at the class level. If the call is to an instance method, this returns `false`.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_singleton_method? # =\u003e true\nend\n```\n\n#### `Rotoscope#caller_path`\n\nReturns the path to the file where the call was invoked.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_path # =\u003e \"/rotoscope_test.rb\"\nend\n```\n\n#### `Rotoscope#caller_lineno`\n\nReturns the line number corresponding to the `#caller_path` where the call was invoked. If unknown, returns `-1`.\n\n```ruby\nrs = Rotoscope.new do |call|\n  call.caller_lineno # =\u003e 113\nend\n```\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FShopify%2Frotoscope","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FShopify%2Frotoscope","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FShopify%2Frotoscope/lists"}