How do I log every method that's called in a Ruby program?
This is definitely possible -- in fact, there's even a method for it! Just add this somewhere in your code before the point that you want to start logging things:
set_trace_func proc { |event, file, line, id, binding, classname|
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
}
The secret sauce you want comes from Kernel#set_trace_func
, as noted above:
- set_trace_func(proc) => proc
- set_trace_func(nil) => nil
Establishes
proc
as the handler for tracing, or disables tracing if the parameter isnil
.proc
takes up to six parameters: an event name, a filename, a line number, an object id, a binding, and the name of a class.proc
is invoked whenever an event occurs. Events are:c-call
(call a C-language routine),c-return
(return from a C-language routine),call
(call a Ruby method),class
(start a class or module definition),end
(finish a class or module definition),line
(execute code on a new line),raise
(raise an exception), andreturn
(return from a Ruby method). Tracing is disabled within the context of proc.
Here's a handy example:
class Test
def test
a = 1
b = 2
end
end
set_trace_func proc { |event, file, line, id, binding, classname|
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
}
t = Test.new
t.test
(Note: don't try this in irb
unless you want a huge scrolling screen of text.) The resulting output is:
line test.rb:11 false
c-call test.rb:11 new Class
c-call test.rb:11 initialize Object
c-return test.rb:11 initialize Object
c-return test.rb:11 new Class
line test.rb:12 false
call test.rb:2 test Test
line test.rb:3 test Test
line test.rb:4 test Test
return test.rb:4 test Test
You can play around with the formatting string above to get just the results you want to log (for example, it sounds like you're only interested in call
events). Hope that helps, and good luck with sorting through all those unit tests!
Logging all method calls in a Rails app
It's easy to do. Just add 5 lines of code into your script/server:
#!/usr/bin/env ruby
set_trace_func proc {
|event, file, line, id, binding, classname|
if event == "call" or event == "return"
printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
end
}
require File.expand_path('../../config/boot', __FILE__)
require 'commands/server'
It's described at http://phrogz.net/ProgrammingRuby/ospace.html#tracingyourprogramsexecution
Your application will become quite slow and you might get more output than you want. You can easily add more conditions on file/class/function names to avoid printing unwanted stuff.
Ruby module to print all method calls and returns in class
You can achieve that by defining a new wrapper method for each public instance method of the class that performs the log logic that you need.
module Loginator
def logify_me
self.public_instance_methods.each do |method|
proxy = Module.new do
define_method(method) do |*args|
puts "Method #{method}(#{args.join(', ')}) called"
# added join to match the exact desired output
value = super *args
puts "returns #{value}"
value
end
end
self.prepend proxy
end
end
end
class A
extend Loginator
def add(a, b)
a + b
end
def sub(a, b)
a - b
end
logify_me
end
Which yields the following output:
>> A.new.sub(1,2)
Method 'sub' called with args '[1, 2]'
returns -1
=> -1
>> A.new.add(4,7)
Method 'add' called with args '[4, 7]'
returns 11
=> 11
Explanation for the future :)
define_method(method)
:Defines an instance method in the receiver. The method parameter can be a Proc, a Method, or an UnboundMethod object. If a block is specified, it is used as the method body. This block is evaluated using instance_eval ref. can be found here
prepend
prepend will insert the module at the bottom of the chain, even before the class itself.fourth ref on my post
Section to Edit by @pedrosfdcarneiro
please, add some brief explanation about the second inner module proxy = Module.new do end
and why it's important for the return value and the usage of the super.
plus why did you preferred to use the public_instance_methods
over instance_methods
.
please kindly add some explanation about it because first its a bit unclear to me and secondly for the other noobs out there.
thanks in advance :)
This answer was heavily based on this fantastic answer: https://stackoverflow.com/a/23898539/1781212.
Capturing logger output inside a method
I've done this in several different ways, and the most convenient I've found is to build a delegation object that routes messages to two or more loggers:
require 'stringio'
require 'logger'
class LoggerTee
def initialize *loggers
@loggers = loggers
end
def method_missing meth, *args, &block
@loggers.each { |logger| logger.send(meth, *args, &block) }
end
end
capture_stringio = StringIO.new
console_log = Logger.new(STDOUT)
string_log = Logger.new(capture_stringio)
log = LoggerTee.new(console_log, string_log)
log.debug 'Hello world'
puts capture_stringio.string
Output:
D, [2013-04-30T18:59:18.026285 #14533] DEBUG -- : Hello world
D, [2013-04-30T18:59:18.026344 #14533] DEBUG -- : Hello world
In this example, the LoggerTee class is instantiated with two separate loggers, one that goes to the console, the other to a StringIO instance. The resulting LoggerTee instance is a drop-in replacement for any standard logger object.
Executing code for every method call in a Ruby module
Like this:
module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end
module M
def hello
puts "yo"
end
def bye
puts "bum"
end
before(*instance_methods) { puts "start" }
end
class C
include M
end
C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"
Ruby - share logger instance among module/classes
With the design you've laid out, it looks like the easiest solution is to give Crawler a module method that returns a module ivar.
module Crawler
def self.logger
@logger
end
def self.logger=(logger)
@logger = logger
end
end
Or you could use "class <<self
magic" if you wanted:
module Crawler
class <<self
attr_accessor :logger
end
end
It does the exact same thing.
How should I log in ruby with minimum assumptions about the calling code
There is a pretty common pattern in various ruby logger implementations to mimic the method interface of the standards library Logger, namely
- the logger object responds to the methods
debug
,info
,warn
,error
,fatal
where you can pass a string containing the log message. Many logger implementations also allow to pass a block (which returns the log message) rather than an actual string - You also generally have an
add
method where you can pass a numeric log severity and the log message.
In your code, you can generally expect that all logger implementations implement at least this interface. You can then allow users of your code to provide their own logger
object (which should implement those methods) which you can then use. This pattern is called dependency injection
Generally, in Ruby, you don't care about actual types of a given object. You just assume that is responds to messages send to it ("methods called"). As such, you don't need to care about a specific logger implementation, but just that it implements the expected interface. In Ruby, this is often called duck typing.
Related Topics
Advantage of Tap Method in Ruby
How to Define a 'Before_Save' Callback in a Module
Finding What Is Common to Two Arrays
Return Two and More Values from a Method
How to Create a Delete Link for a Related Object in Ruby on Rails
Rspec 3 How to Test Flash Messages
Deploying Sinatra App (With Config.Ru) on Heroku Cedar Stack
How Many Rails Apps on 1 Heroku Dyno
Ruby: How to Load a File into Interactive Ruby Console (Irb)
How to Get Activerecord Associations via Reflection
Find or Create Record Through Factory_Girl Association
Rubocop: Line Is Too Long ← How to Ignore
How to Select Option in Drop Down Using Capybara
Ruby/Rails CSV Parsing, Invalid Byte Sequence in Utf-8