DebugMe

DebugMe Logo ## A Classic Debugging Technique That Never Gets Old Printing labeled variable values to STDOUT is one of the oldest and most fundamental debugging techniques in computer science. From the earliest days of programming, developers have relied on simple output statements to understand what their code is doing. While modern debuggers offer sophisticated features like breakpoints and step-through execution, there's something elegantly simple and universally effective about printing variables to see their values in real-time. **DebugMe** embraces this time-tested approach, making it effortless to inspect local, instance, and class variables with clearly labeled output. Sometimes the old ways are the best ways.

Quick Start

gem install debug_me

require 'debug_me'
include DebugMe

my_variable = 42

debug_me { :my_variable } # Use a Symbol for the variable name

# or if you have several variables you want to see ...
debug_me { i[ var1 var2 var3 var4 ]}

DebugMe::debug_me(){} works with local, instance and class variables.

Installation

Add this line to your application's Gemfile:

bundle add 'debug_me'

Or install it yourself:

gem install debug_me

Examples Usage

require 'debug_me'
include DebugMe

debug_me # Prints only the header banner consisting of tag, method name, file name and line number

debug_me('INFO') # Also prints only the header but with a different tag

debug_me(levels: 5) # Along with the header, show the call stack back this many levels
debug_me{:backtrace}          # show the entire call stack
debug_me{[ :backtrace ]} will # show the entire call stack
debug_me(levels: 5){[ :backtrace ]} # will only show the first 5 levels of the backtrace - ie. the levels parameter supersedes :backtrace

debug_me {} # prints the default header and __ALL__ variables

debug_me {:just_this_variable} # prints the default header and the value of only one specific variable

debug_me { i[ this_one that_one that_other_one ]} # prints default header and three specific variables

# Use an array of symbols and strings to pass multiple variables for output
# Each element of the array is 'eval'ed with the context binding of the caller
debug_me(){[ :my_var, 'my_complex_var_or_method[my_var]' ]}

debug_me(header: false) {} # disables the printing of the header; prints all variables

debug_me(tag: 'MyTag', :header => false) {} # disables header, sets different tag, prints all variables

debug_me('=== LOOK ===') {} # changes the tag and prints all variables with a header line

debug_me('=== LOOK ===') {:@foo} # changes the tag, prints a header line and a specific instance variable

debug_me('=== LOOK ===') {:@@foo} # changes the tag, prints a header line and a specific class variable

debug_me(ivar: false, cvar: false) {} # print only the local variables with the default tag and a header line

Most of the examples above use symbols to designate the variables that you want to be shown with their name as a label. You can also use strings. With strings you are not limited to just variables. Consider these examples:

debug_me {[ 'some_array.size', 'SomeDatabaseModel.count' ]}

# What a backtrace with your variables?

debug_me {[
  :my_variable,
  'some_hash.keys.reject{|k| k.to_s.start_with?('A')}',
  'caller' ]}  # yes, caller is a kernel method that will give a backtrace

# You can also get into trouble so be careful.  The symbols and strings
# are evaluated in the context of the caller.  Within the string any
# command or line of code can be given.  SO DO NOT try to use
# something silly like debug_me{ 'system("rm -fr /")'}

Security Warning

IMPORTANT: debug_me uses eval() to evaluate the symbols and strings you pass to it in the context of the caller. This provides powerful flexibility but comes with significant security implications:

  • Never pass untrusted input to debug_me
  • Never use debug_me with user-supplied data or parameters
  • Only use debug_me during development and testing
  • Remove or disable debug_me calls in production code (use $DEBUG_ME = false)

The ability to execute arbitrary code is by design for debugging flexibility, but it means any string passed to debug_me will be evaluated as Ruby code. Treat this tool with the same caution you would any eval() statement.

For production environments, consider using a proper logging framework with structured logging instead of debug_me.

Disabling Debug Output with $DEBUG_ME

The $DEBUG_ME global variable provides a convenient way to enable or disable all debug_me output throughout your application without removing the code. This is particularly useful for QA environments.

The DEBUG_ME environment variable is automatically detected when the gem is loaded. This is the recommended approach for controlling debug output across environments without code changes.

# Disable debug output (QA)
export DEBUG_ME=false

# Enable debug output (development)
export DEBUG_ME=true

# Or set inline when running your app
DEBUG_ME=false bundle exec rails server

Supported values:

  • Truthy (enables output): true, yes, 1, on, or any other value
  • Falsy (disables output): false, no, 0, off, or empty string
  • Not set: Defaults to true (enabled)

Runtime Control

You can also control $DEBUG_ME programmatically at runtime:

# Enable debug output (default)
$DEBUG_ME = true

# Disable all debug_me output
$DEBUG_ME = false

When $DEBUG_ME is set to false:

  • All debug_me calls return nil immediately
  • No output is produced (even with custom file or logger options)
  • The block is not evaluated, so there's zero performance overhead

Production Setup Examples

Docker/Container deployment:

# Dockerfile
ENV DEBUG_ME=false

Systemd service:

[Service]
Environment="DEBUG_ME=false"

Heroku:

heroku config:set DEBUG_ME=false

For Rails with fallback (only if ENV not set):

# config/initializers/debug_me.rb
require 'debug_me'

# Override only if not already set by environment
$DEBUG_ME = Rails.env.development? || Rails.env.test? unless ENV.key?('DEBUG_ME')

Benefits

  • Leave debug code in place: No need to remove debug_me calls before deployment
  • Zero overhead: When disabled, blocks aren't evaluated, so no performance impact
  • Easy toggling: Can enable debugging in production temporarily if needed
  • Security: Prevents accidental debug output exposure in production

Example

# Your code with debug_me calls
def process_data(items)
  debug_me { :items }  # Only outputs in development/test

  result = items.map do |item|
    debug_me { :item }  # Only outputs in development/test
    transform(item)
  end

  debug_me { :result }  # Only outputs in development/test
  result
end

# In production with $DEBUG_ME = false:
# - No output produced
# - No performance overhead
# - Code works normally

Default Options

The default options is a global constant DebugMeDefaultOptions that is outside of the DebugMe name space. I did that so that if you do include DebugMe to make access to the method easier you could still have the constant with a function specific name that would be outside of anything that you may have already coded in you program.

Notice that this constant is outside of the DebugMe's module namespace.

DebugMeDefaultOptions = {
  tag:    'DEBUG',  # A tag to prepend to each output line
  time:   true,     # Include a time-stamp in front of the tag
  strftime:  '%Y-%m-%d %H:%M:%S.%6N', # timestamp format
  header: true,     # Print a header string before printing the variables
  levels: 0,        # Number of additional backtrack entries to display
  skip1:  false,    # skip 1 blank line in front of a new output block
  skip2:  false,    # skip 2 blank lines in front of a new output block
  lvar:   true,     # Include local variables
  ivar:   true,     # Include instance variables in the output
  cvar:   true,     # Include class variables in the output
  cconst: true,     # Include class constants
  logger: nil,      # Pass in a logger class instance like Rails.logger
  file:   $stdout   # The output file
}

If you want the output of the method to always go to STDERR then do this:

require 'debug_me'
DebugMeDefaultOptions[:file] = $stderr  # or STDERR

If you want the debug_me output to go to a real file:

DebugMeDefaultOptions[:file] = File.open('debug_me.log', 'w')

Using a Logger class instance

If you are working in Rails and want all the debug_me output to go to the Rails.logger its as easy as:

DebugMeDefaultOptions[:logger] = Rails.logger

Or while working in rails you only want to add a marker to the Rails.logger do this:

debug_me(logger: Rails.logger, tag: 'Hello World')

If you are working in Rails and want to use both the standard debug_me functions and put stuff into the Rails.logger but do not want to always remember the option settings then do something line this in a config/initializers/aaaaa_debug_me.rb file:

# config/initializers/aaaaa_debug_me.rb

require 'debug_me'

module DebugMe
  def log_me(message, options={}, &block)
    options = {logger: Rails.logger, time: false, header: false, tag: message}
    block_given? ? debug_me(options, block) : debug_me(options)
  end
end

include DebugMe

# Just setup the base name.  The parent path will be added below ...
debug_me_file_name  = 'debug_me'

# NOTE: you could add a timestamp to the filename
# debug_me_file_name += '_' + Time.now.strftime('%Y%m%d%H%M%S')

debug_me_file_name += '_' + Process.argv0.gsub('/',' ').split(' ').last
# NOTE: by using the Process.argv0 ... you get multiple log files for
#       rails, rake, sidekiq etc.

debug_me_file_name += '.log'

debug_me_filepath = Rails.root + 'log' + debug_me_file_name

debug_me_file = File.open(debug_me_filepath, 'w')

debug_me_file.puts "\n==================== Starting New Test Run --------->>>>>>\n\n"

# Set application wide options in the DebugMeDefaultOptions hash
DebugMeDefaultOptions[:file] = debug_me_file

debug_me{['ENV']}

What that does for your Rails application is dump all of your system environment variables and their values to a debug_me log file in the log directory of the application.

It also adds a new method log_me which you can use to send stuff to the Rails.logger instance. The method used by debug_me for the logger instance is always the debug method.

Conclusion

The rest of the default options are obvious.

You can always over-ride the default options on a case by case basis like this:

debug_me {...}
# ...
debug_me(header: false){...}

Contributing

  1. Fork it ( https://github.com/[my-github-username]/debug_me/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request