/idbg

Ruby poormans debugger

Primary LanguageRuby

IDBG (I debug)

IDbg is a debugging toolkit for Ruby and Ruby on Rails projects. It aims to increase the velocity and accuracy of a mid-level debugging workflow:

  • isolated durable log stream
  • various break conditions
  • hit metrics and reactors
  • call inspectors
  • and more

It's a single file non intrusive (no need to commit) addition to any Ruby projects.

For docs/setup best to read the file:

idbg/i_dbg.rb

Lines 3 to 187 in f59b7ea

###############################################################################
# DOCUMENTATION
###############################################################################
#
# IDbg is an opinionated debug helper toolkit for the world where Ruby
# debugging is destroyed so much we need to back to `puts`.
#
# There are specific aspects this toolkit is trying to address:
# - debuggers are not reliable (JetBrain, debug-ide, pry, etc)
# - breaking is not always the best or desired way
# - updating code (even debugging code) can trigger a sluggish auto source-
# -reload mechanism
# - debugging sometimes requires tricks
###############################################################################
# How to install?
#
# IDbg was designed for a Ruby backend workspace where things should stay
# separate (out of git) and low footprint. Hence my workflow is the following:
# - copy `i_dbg.rb` into one of the project folder that is autoloading this
# file
# - add it to your global gitignore
# - edit the configurations (best is via environment variables, but if that
# does not work, just hardcode it in the section below)
# - make a folder for aid scripts and temp files and set it in the config:
# `IDBG_SCRIPTS_FOLDER`
###############################################################################
# How to use?
#
# Simplest example is to log messages uninterrupted. In a desired place insert
# a log:
#
# ```ruby
# # Some backend file you're debugging.
# ...
# class AppController < ApplicationController
# def update
# IDbg.log("Received params", params, @user, @ctx)
# end
# end
# ```
#
# Then open the log and watch:
#
# ```bash
# $> tail -F /tmp/idbg.log
# ```
###############################################################################
# Components
#
# Component: logger
#
# Logger is logging all input to a semi-structured file, so it's both isolated
# and convenient for a watcher, without stopping code execution.
#
# ```ruby
# IDbg.log(@user, "was logged in with", @user_access)
# IDbg << "Or simply this."
# ```
#
# On Apple OS-X only the system notification can be used too:
#
# ```ruby
# IDbg.flash("User is deleted", @user)
# ```
#
# ---
# Component: instance call tracker
#
# Say you're interested in knowing the order of execution and what functions
# were executed in a class during a flow. IDbg allows logging all calls and
# with or without arguments:
#
# ```ruby
# class SomeClass
# # Insert before closing `end`:
# include(IDbg.function_logger.with_args)
# end
# ```
#
# ---
# Component: complex debug script reactors
#
# Sometimes a debugging is just so convoluted or maybe it's even evolving into
# its own code that it's better to keep it somewhere else. As well - these
# scripts act as a signal.
# This has two flavors: execution of a custom script which breaks the flow
# with `pry` when it results truthy - and the other one that yields to a block
# when evals to truthy.
# Said scripts must be placed in `IDBG_SCRIPTS_FOLDER/break.rb` as functions.
#
# ```ruby
# # Inside IDBG_SCRIPTS_FOLDER/break.rb
# def my_script
# # do some things
# return true # in case we need a reaction
# end
#
# # Inside application code (will block with `binding.pry`, since its true):
# IDbg.break_if(:my_script)
#
# # Or a custom block version:
# IDbg.yield_if(:my_script) { Rails.cache.clear ; @user.reload }
# ```
# An expected side effect of these is that they do not trigger source-code
# reload when the script is updated.
#
# Call params can be passed/inspected too:
#
# ```ruby
# # Inside IDBG_SCRIPTS_FOLDER/break.rb
# def my_script
# args = IDbg::DataBank.data
# end
#
# # Inside application code (will block with `binding.pry`, since its true):
# IDbg.break_if(:my_script, "arg1", { arg2: "foo" })
# ```
#
# It's also possible to run whole script files without expected reaction when
# the logic deserves its own file. These files are expected to exist in
# `IDBG_SCRIPTS_FOLDER/<NAME>.rb`:
#
# ```ruby
# IDbg.run("user_registration_script")
# ```
#
# ---
# Component: backtrace
#
# Often you want to know where you are in the execution. IDbg's backtrace
# can be customized with length and levels.
#
# ```ruby
# # Log a backtrace to the log file.
# IDbg.backtrace(level: 2)
# # Dump it right on the current output:
# IDbg.dump_backtrace
# # Combine backtrace and logging
# IDbg.backtrace(@user, @ctx)
# ```
#
# Levels are generally used by gradually filtering out external components,
# such as: gems > external libs > internal libs > components > ... Level 0
# is always the full backtrace.
# For configuration see: `IDBG_BACKTRACE_LEVEL_FILTERS`
# Example of a level setting where level-1 is filtering to all-except-gems and
# level-2 is only-rails-app:
# `export IDBG_BACKTRACE_LEVEL_FILTERS="my_project,my_project/app"`
#
# ---
# Component: counter
#
# Counter is counting each call.
#
# ```ruby
# IDbg.count("user-reload")
# ```
#
# ---
# Component: once-calls
#
# Sometimes a debugging or testing code snippet only should be called once
# only.
#
# ```ruby
# # Hypothetical loop where we only care about the first iteration.
# IDbg.reset_once(:cache_check)
# loop do
# IDbg.once(:cache_check, @cache)
# end
# ```
#
# ---
# Component: measure
#
# If you need to measure the time spent in a block:
#
# ```ruby
# IDbg.measure(:my_critical_block) do
# # code ...
# end
# ```