Wacktrace

Make your stacktraces wack! Insert arbitrary content into the call stack if that is, for some reason, something you want to do. Who am I to judge?

# in example.rb:
Wacktrace.add_to_stack([
  ['Fire in the disco!', 1, 'šŸ”„šŸ•ŗ '],
  ['Fire in the Taco Bell!', 2, 'šŸ”„šŸŒ® '],
  ['Fire in the disco!', 3, 'šŸ”„šŸ•ŗ '],
  ['Fire in the gates of hell!', 4, 'šŸ”„šŸ˜ˆ '],
]) do
  raise "High Voltage!"
end
$ ruby example.rb
example.rb:25:in `block in <main>': High Voltage! (RuntimeError)
    from /Users/kevin/.rvm/gems/ruby-3.0.0/gems/wacktrace-0.1.0/lib/wacktrace.rb:49:in `block in add_to_stack'
    from šŸ”„šŸ•ŗ :1:in `Ā FireĀ inĀ theĀ discoļø•Ā '
    from šŸ”„šŸŒ® :2:in `Ā FireĀ inĀ theĀ TacoĀ Bellļø•'
    from šŸ”„šŸ•ŗ :3:in `Ā FireĀ inĀ theĀ discoļø•'
    from šŸ”„šŸ˜ˆ :4:in `Ā FireĀ inĀ theĀ gatesĀ ofĀ hellļø•'
    from /Users/kevin/.rvm/gems/ruby-3.0.0/gems/wacktrace-0.1.0/lib/wacktrace.rb:50:in `add_to_stack'
    from example.rb:19:in `<main>'

Amaze your friends! Annoy your coworkers! Ok, it's mostly that second one! But maybe someone has a good use for this, like inserting warnings into stack traces:

Traceback (most recent call last):
    14: from example.rb:47:in `<main>'
    13: from example.rb:35:in `dangerous_method'
    12: from /Users/kevin/.rvm/gems/ruby-2.7.2/gems/wacktrace-0.1.0/lib/wacktrace.rb:60:in `add_to_stack_from_lyrics'
    11: from /Users/kevin/.rvm/gems/ruby-2.7.2/gems/wacktrace-0.1.0/lib/wacktrace.rb:50:in `add_to_stack'
    10: from !:in `Ā '
     9: from !:1:in `Ā Ā ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“Ā WARNINGļø•Ā ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“'
     8: from !:2:in `Ā ļ½œĀ IfĀ youĀ hitĀ anĀ errorĀ hereļ¼ŒĀ youĀ ļ¼Šreallyļ¼ŠĀ needĀ ļ½œ'
     7: from !:3:in `Ā ļ½œĀ toĀ goĀ overĀ toĀ theĀ FooBarBazĀ adminĀ panelĀ andĀ Ā Ā Ā ļ½œ'
     6: from !:4:in `Ā ļ½œĀ cleanĀ upĀ theĀ SpleemĀ thatĀ wereĀ erroniouslyĀ Ā Ā Ā Ā Ā ļ½œ'
     5: from !:5:in `Ā ļ½œĀ createdā€¤Ā Ā IfĀ youĀ donā€™tĀ doĀ thatļ¼ŒĀ badĀ thingsĀ Ā Ā Ā ļ½œ'
     4: from !:6:in `Ā ļ½œĀ willĀ happenļø•Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā Ā ļ½œ'
     3: from !:7:in `Ā Ā ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“ā€“'
     2: from /Users/kevin/.rvm/gems/ruby-2.7.2/gems/wacktrace-0.1.0/lib/wacktrace.rb:49:in `block in add_to_stack'
     1: from example.rb:44:in `block in dangerous_method'
example.rb:30:in `real_dangerous_method': terrible error (RuntimeError)

How's it work?

Sketchily! It's a set of wack stack hacks. The basic idea is that we just set up a series of methods like:

def a; b; end
def b; c; end
def c; actual_function; end

But to dynamically generate methods, you'd usually reach for define_method. Unfortunately, methods defined with define_method don't actually show up in call stacks because that'd be too easy. Instead we'll use eval("def #{method_name};#{next_method_name};end"). And, as a bonus, eval lets us pass in a filename and line number for stack trace purposes!

But to avoid having a hundred methods in the global namespace like baby_shark_do_do_do_do_do_do_do, we'll Class.new-up a temporary class and then instance_eval on that.

But now we're stuck with underscore-laden method names. We're not gonna stand for that kinda limitation. We're gonna reach for the sun and slap the face of god. We'll take arbitrary text and replace all the invalid characters like " and @ with unicode near-equivalents like ļ¼‚ and ļ¼ . Because def Cthulhu R'lyeh wgah'nagl fhtagn isn't a valid ruby method, but the visually-identical def Cthulhu Rā€™lyeh wgahā€™nagl fhtagn is!

Other fun tricks:

  • Detect which direction ruby is printing stack traces today (it's newest-first on a gibbous or waxing moon, and newest-last the rest of the month)
  • Deal with duplicate method names by padding them with non-breaking-spaces
  • Prepend all methods with non-breaking spaces so ruby doesn't get confused by methods that start with capital letters

Installation

Add this line to your application's Gemfile:

gem 'wacktrace'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install wacktrace

Usage

To have full(ish) control over the stack trace contents, call add_to_stack and give it a block. That block will be run with the lines you provide in its call stack:

Wacktrace.add_to_stack([
  ['method name 1', 123, 'file name 1'],
  ['method name 2', 123, 'file name 2'],
  ['method name 3', 123, 'file name 3'],
]) do
  puts caller_locations
  # Prints out:
  #  file name 1:123:in `Ā methodĀ nameĀ 1'
  #  file name 2:456:in `Ā methodĀ nameĀ 2'
  #  file name 3:789:in `Ā methodĀ nameĀ 3'
end

If you want a quicker way to shove a bunch of text (like a warning, comment, or classic poem on the fleeting nature of mankind's greatest achievements), you can use add_to_stack_from_lyrics with a single newline-delimited string:

lyrics = "I met a traveller from an antique land,
Who saidā€”ā€œTwo vast and trunkless legs of stone
Stand in the desert... Near them, on the sand,
Half sunk a shattered visage lies, whose frown,
And wrinkled lip, and sneer of cold command,
Tell that its sculptor well those passions read
Which yet survive, stamped on these lifeless things,
The hand that mocked them, and the heart that fed;
And on the pedestal!, these words appear:
My name is Ozymandias, King of Kings;
Look on my Works, ye Mighty, and despair!
Nothing beside remains. Round the decay
Of that colossal Wreck, boundless and bare
The lone and level sands stretch far away.ā€"
Wacktrace.add_to_stack_from_lyrics(lyrics, 'Percy Bysshe Shelley') { raise "Suck it, Horace Smith." }

...which will result in...

ruby example.rb
example.rb:17:in `block in <main>': Suck it, Horace Smith. (RuntimeError)
    from /Users/kevin/.rvm/gems/ruby-3.0.0/gems/wacktrace-0.1.0/lib/wacktrace.rb:56:in `block in add_to_stack'
    from Percy Bysshe Shelley:in `Ā IĀ metĀ aĀ travellerĀ fromĀ anĀ antiqueĀ landļ¼Œ'
    from Percy Bysshe Shelley:1:in `Ā WhoĀ saidā€”ā€œTwoĀ vastĀ andĀ trunklessĀ legsĀ ofĀ stone'
    from Percy Bysshe Shelley:2:in `Ā StandĀ inĀ theĀ desertā€¤ā€¤ā€¤Ā NearĀ themļ¼ŒĀ onĀ theĀ sandļ¼Œ'
    from Percy Bysshe Shelley:3:in `Ā HalfĀ sunkĀ aĀ shatteredĀ visageĀ liesļ¼ŒĀ whoseĀ frownļ¼Œ'
    from Percy Bysshe Shelley:4:in `Ā AndĀ wrinkledĀ lipļ¼ŒĀ andĀ sneerĀ ofĀ coldĀ commandļ¼Œ'
    from Percy Bysshe Shelley:5:in `Ā TellĀ thatĀ itsĀ sculptorĀ wellĀ thoseĀ passionsĀ read'
    from Percy Bysshe Shelley:6:in `Ā WhichĀ yetĀ surviveļ¼ŒĀ stampedĀ onĀ theseĀ lifelessĀ thingsļ¼Œ'
    from Percy Bysshe Shelley:7:in `Ā TheĀ handĀ thatĀ mockedĀ themļ¼ŒĀ andĀ theĀ heartĀ thatĀ fedĶ¾'
    from Percy Bysshe Shelley:8:in `Ā AndĀ onĀ theĀ pedestalļø•ļ¼ŒĀ theseĀ wordsĀ appearļ¼š'
    from Percy Bysshe Shelley:9:in `Ā MyĀ nameĀ isĀ Ozymandiasļ¼ŒĀ KingĀ ofĀ KingsĶ¾'
    from Percy Bysshe Shelley:10:in `Ā LookĀ onĀ myĀ Worksļ¼ŒĀ yeĀ Mightyļ¼ŒĀ andĀ despairļø•'
    from Percy Bysshe Shelley:11:in `Ā NothingĀ besideĀ remainsā€¤Ā RoundĀ theĀ decay'
    from Percy Bysshe Shelley:12:in `Ā OfĀ thatĀ colossalĀ Wreckļ¼ŒĀ boundlessĀ andĀ bare'
    from Percy Bysshe Shelley:13:in `Ā TheĀ loneĀ andĀ levelĀ sandsĀ stretchĀ farĀ awayā€¤ā€'
    from /Users/kevin/.rvm/gems/ruby-3.0.0/gems/wacktrace-0.1.0/lib/wacktrace.rb:57:in `add_to_stack'
    from /Users/kevin/.rvm/gems/ruby-3.0.0/gems/wacktrace-0.1.0/lib/wacktrace.rb:67:in `add_to_stack_from_lyrics'
    from example.rb:17:in `<main>'

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and the created tag, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/wacktrace. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the Wacktrace project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.