Module: ImLost

Defined in:
lib/im-lost.rb,
lib/im-lost/version.rb

Overview

If you have overlooked something again and don’t really understand what your code is doing. If you have to maintain this application but can’t really find your way around and certainly can’t track down that stupid error. If you feel lost in all that code, here’s the gem to help you out!

ImLost helps you by analyzing function calls of objects, informing you about exceptions and logging your way through your code. In short, ImLost is your debugging helper!

Defined Under Namespace

Classes: TimerStore

Constant Summary collapse

VERSION =

The version number of the gem.

'1.2.4'

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.caller_locationsBoolean

Enables/disables to include code location into traced call information. This is enabled by default.



21
22
23
# File 'lib/im-lost.rb', line 21

def caller_locations
  @caller_locations
end

.output#<<

The output device used to write information. This should be an ‘IO` device or any other object responding to `#<<` like a Logger.

‘STDERR` is configured by default.

Examples:

Write to a file

ImLost.output = File.new('./trace', 'w')

Write temporary into a memory stream

require 'stringio'

original = ImLost.output
begin
  ImLost.output = StringIO.new
  # ... collect trace information
  puts(ImLost.output.string) # or whatever
ensure
  ImLost.output = original
end


51
52
53
# File 'lib/im-lost.rb', line 51

def output
  @output
end

.timerTimerStore (readonly)



67
68
69
# File 'lib/im-lost.rb', line 67

def timer
  @timer
end

.trace_callsBoolean

Enables/disables tracing of method calls. This is enabled by default.



76
# File 'lib/im-lost.rb', line 76

def trace_calls = @trace_calls.enabled?

.trace_resultsBoolean

Enables/disables tracing of returned values of method calls. This is enabled by default.



130
# File 'lib/im-lost.rb', line 130

def trace_results = @trace_results.enabled?

Class Method Details

.heretrue .here(test) ⇒ Object .here { ... } ⇒ Object

Print the call location conditionally.

Examples:

Print current location

ImLost.here

Print current location when instance variable is empty

ImLost.here(@name.empty?)

Print current location when instance variable is nil or empty

ImLost.here { @name.nil? || @name.empty? }

Overloads:

  • .heretrue

    Prints the call location.

  • .here(test) ⇒ Object

    Prints the call location when given argument is truthy.

  • .here { ... } ⇒ Object

    Prints the call location when given block returns a truthy result.

    Yields:

    • When the block returns a truthy result the location will be print

    Yield Returns:

    • (Object)

      return result



166
167
168
169
170
171
# File 'lib/im-lost.rb', line 166

def here(test = true)
  return test if !test || (block_given? && !(test = yield))
  loc = Kernel.caller_locations(1, 1)[0]
  @output << "* #{loc.path}:#{loc.lineno}\n"
  test
end

.time(title = nil, &block) ⇒ Object

Measure runtime of given block.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/im-lost.rb', line 329

def time(title = nil, &block)
  title &&= "#{title} "
  if @caller_locations
    loc = Kernel.caller_locations(1, 1)[0]
    suffix = "  #{loc.path}:#{loc.lineno}\n"
  end
  unless block_given?
    @output << "t #{title}#{TimerStore.now}\n#{suffix}"
    return
  end
  t = TimerStore.now
  begin
    yield
  ensure
    t = (TimerStore.now - t).round(4)
    @output << "R #{title}#{t} sec.\n#{suffix}  #{block.inspect}\n"
  end
end

.trace(*args) ⇒ Array<Object> .trace(*args) {|args| ... } ⇒ Object

Trace objects.

The given arguments can be any object instance or module or class.

Examples:

Trace method calls of an instance variable for a while

ImLost.trace(@file)
# ...
ImLost.untrace(@file)

Temporary trace method calls

File.open('test.txt', 'w') do |file|
  ImLost.trace(file) do
    file << 'hello '
    file.puts(:world!)
  end
end

# output will look like
#   > IO#<<(?)
#     /examples/test.rb:1
#   > IO#write(*)
#     /examples/test.rb:1
#   > IO#puts(*)
#     /examples/test.rb:2
#   > IO#write(*)
#     /examples/test.rb:2

Overloads:

  • .trace(*args) ⇒ Array<Object>

    Start tracing the given objects.

    See Also:

  • .trace(*args) {|args| ... } ⇒ Object

    Traces the given object(s) inside the block only. The object(s) will not be traced any longer after the block call.

    Yield Parameters:

    • args (Object)

      the traced object(s)

    Yield Returns:

    • (Object)

      return result



215
216
217
218
219
# File 'lib/im-lost.rb', line 215

def trace(*args, &block)
  return block&.call if args.empty?
  return args.size == 1 ? _trace(args[0]) : _trace_all(args) unless block
  args.size == 1 ? _trace_b(args[0], &block) : _trace_all_b(args, &block)
end

.trace_exceptions(with_locations: true) ⇒ Object

Traces exceptions raised within a given block.

Examples:

Trace exception and rescue handling

ImLost.trace_exceptions do
  File.write('/', 'test')
rescue SystemCallError
  raise('something went wrong!')
end

# output will look like
#   x Errno::EEXIST: File exists @ rb_sysopen - /
#     /examples/test.rb:2
#   ! Errno::EEXIST: File exists @ rb_sysopen - /
#     /examples/test.rb:3
#   x RuntimeError: something went wrong!
#     /examples/test.rb:4

Yield Returns:

  • (Object)

    return result



109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/im-lost.rb', line 109

def trace_exceptions(with_locations: true)
  return unless block_given?
  begin
    we = @trace_exceptions.enabled?
    el = @exception_locations
    @exception_locations = with_locations
    @trace_exceptions.enable unless we
    yield
  ensure
    @trace_exceptions.disable unless we
    @exception_locations = el
  end
end

.traced?(obj) ⇒ Boolean

Test if a given object is currently traced.



227
# File 'lib/im-lost.rb', line 227

def traced?(obj) = @trace.key?(obj)

.untrace(*args) ⇒ Array<Object>?

Stop tracing objects.

Examples:

Trace some objects for some code lines

traced_obj = ImLost.trace(@file, @client)
# ...
ImLost.untrace(*traced_obj)

See Also:



244
245
246
247
# File 'lib/im-lost.rb', line 244

def untrace(*args)
  args = args.filter_map { @trace.delete(_1) }
  args.size < 2 ? args[0] : args
end

.untrace_all!self

Stop tracing any object. When you are really lost and just like to stop tracing of all your objects.

See Also:



257
258
259
260
# File 'lib/im-lost.rb', line 257

def untrace_all!
  @trace = {}.compare_by_identity
  self
end

.vars(object) ⇒ Object

Note:

The dedicated handling of ‘Fiber` is platform dependent!

Inspect internal variables of a given object.

When the given object is

  • a ‘Binding` it prints the local variables of the binding

  • a ‘Thread` it prints the fiber-local and thread variables

  • the current ‘Fiber` it prints the fibers’ storage

Be aware that only the current fiber can be inspected.

When the given object can not be inspected it prints an error message.

Examples:

Inspect current instance variables

@a = 22
b = 20
c = @a + b
ImLost.vars(self)
# => print value of `@a`

Inspect local variables

@a = 22
b = 20
c = @a + b
ImLost.vars(binding)
# => print values of `b` and 'c'

Inspect a thread’s variables

th = Thread.new { th[:var1] += 20 }
th[:var1] = 22
ImLost.vars(th)
# => print value of `var1`
th.join
ImLost.vars(th)

Inspect the current fiber’s storage

Fiber[:var1] = 22
Fiber[:var2] = 20
Fiber[:var3] = Fiber[:var1] + Fiber[:var2]
ImLost.vars(Fiber.current)


308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/im-lost.rb', line 308

def vars(object)
  out = LineStore.new
  traced = @trace.delete(object)
  return _local_vars(out, object) if Binding === object
  out.location(Kernel.caller_locations(1, 1)[0])
  return _thread_vars(out, object) if Thread === object
  return _fiber_vars(out, object) if @fiber_support && Fiber === object
  return _instance_vars(out, object) if defined?(object.instance_variables)
  out << '  !!! unable to retrieve vars'
  object
ensure
  @trace[traced] = traced if traced
  @output << out
end