2

What is the proper way to prevent Sinatra from displaying the full backtrace, when it fails to properly run the server (for example, due to the port being already in use)?

This is a sample sinatra app:

# test.rb
require 'sinatra'
require 'bundler/inline'

gemfile do
  gem 'sinatra'
  gem 'puma'
end

set :bind, "0.0.0.0"
set :port, 3000

get '/' do
  "hello"
end

Then, running it with ruby test.rb once, to occupy the port.

Then, running it again in another terminal window, and this full error backtrace is shown:

$ ruby test.rb
== Sinatra (v2.0.4) has taken the stage on 3000 for development with backup from Puma
Puma starting in single mode...
* Version 3.12.0 (ruby 2.5.0-p0), codename: Llamas in Pajamas
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000
== Someone is already performing on port 3000!
Traceback (most recent call last):
        5: from /store/gems/ruby-2.5.0/gems/sinatra-2.0.4/lib/sinatra/main.rb:26:in `block in <module:Sinatra>'
        4: from /store/gems/ruby-2.5.0/gems/sinatra-2.0.4/lib/sinatra/base.rb:1464:in `run!'
        3: from /store/gems/ruby-2.5.0/gems/sinatra-2.0.4/lib/sinatra/base.rb:1464:in `ensure in run!'
        2: from /store/gems/ruby-2.5.0/gems/sinatra-2.0.4/lib/sinatra/base.rb:1439:in `quit!'
        1: from /store/gems/ruby-2.5.0/gems/puma-3.12.0/lib/puma/launcher.rb:147:in `stop'
/store/gems/ruby-2.5.0/gems/puma-3.12.0/lib/puma/single.rb:27:in `stop': undefined method `stop' for nil:NilClass (NoMethodError)
Traceback (most recent call last):
        3: from /store/gems/ruby-2.5.0/gems/sinatra-2.0.4/lib/sinatra/base.rb:1545:in `block in setup_traps'
        2: from /store/gems/ruby-2.5.0/gems/sinatra-2.0.4/lib/sinatra/base.rb:1439:in `quit!'
        1: from /store/gems/ruby-2.5.0/gems/puma-3.12.0/lib/puma/launcher.rb:147:in `stop'
/store/gems/ruby-2.5.0/gems/puma-3.12.0/lib/puma/single.rb:27:in `stop': undefined method `stop' for nil:NilClass (NoMethodError)

Since I am using it as an embedded server, I would like the output to simply and with the friendly error that Sinatra is already showing:

== Someone is already performing on port 3000!

and avoid showing the backtrace.

DannyB
  • 12,810
  • 5
  • 55
  • 65

3 Answers3

1

Ruby by default outputs error messages to the STDOUT. But if you're on *nix system you can do this:

ruby test.rb > /dev/null 2>&1

For windows you can probably do

ruby test.rb > NULL

windows powershell

ruby test.rb > $null

but for windows also see Is there a /dev/null on Windows?

But if you want programmatically suppress output when server is running this should work on *nix but not sure on windows

# test.rb
require 'sinatra'
require 'bundler/inline'

gemfile do
  gem 'sinatra'
  gem 'puma'
end

set :bind, "0.0.0.0"
set :port, 3000

get '/' do
  "hello"
end

unless `ps aux | grep sinatra`.match('tcp://0.0.0.0:3000')
  STDOUT.reopen('/dev/null', 'w')
  STDERR.reopen('/dev/null', 'w')
end

See suppresing output to console with ruby

lacostenycoder
  • 10,623
  • 4
  • 31
  • 48
  • Thanks, but none of these solutions seem to be viable in my case. I am providing a gem that runs an embedded SInatra with it, so it is executed by the user (so they are not doing output redirects). SInatra already rescues the error and understands the port is open, and I expected to find a way to tell it to just exit without printing the backtrace. Suppressing output with `STDERR.reopen` also hides the Sinatra message that the port is open... – DannyB Oct 20 '18 at 11:27
  • Thanks, but no it does not. First, I dont want to completely hide STDOUT - so only STDERR. Then, the second run just exits without showing why. Even if it did, it is very hackish. If there is no built in sinatra option to do this, I think there should be - at least they should raise an error I can rescue. RIght now, it is Puma that raises NoMethodError... I will probably open a sinatra issue, thanks. – DannyB Oct 20 '18 at 11:32
  • ok but your original question did not have all of these conditions, and it's still unclear ast WHY you need this. – lacostenycoder Oct 20 '18 at 15:30
1

You can test to see if the port is in use by attempting to listen on the port before allowing Sinatra and Puma to take over. This isn't 100% effective because there's a race condition where you may open and close the port, but before Sinatra/Puma finish initializing some other process comes along and listens on the same port, but it should work for your use-case (which appears to be a cosmetic hack only).

Insert this code anywhere in test.rb:

require 'socket'
include Socket::Constants

begin
  # Open and close the port
  socket = Socket.new(AF_INET, SOCK_STREAM, 0)
  sockaddr = Socket.pack_sockaddr_in(3000, '0.0.0.0')
  socket.bind(sockaddr)
  socket.listen(1)
  socket.close
rescue Errno::EADDRINUSE => error
  # Traps the same error that is trapped by Sinatra and exits if raised
  puts error.message
  exit
end

Start the first one with ruby test.rb:

== Sinatra (v2.0.4) has taken the stage on 3000 for development with backup from Puma
Puma starting in single mode...
* Version 3.12.0 (ruby 2.6.0-p-1), codename: Llamas in Pajamas
* Min threads: 0, max threads: 16
* Environment: development
* Listening on tcp://0.0.0.0:3000
Use Ctrl-C to stop

Start the second one with ruby test.rb:

Address already in use - bind(2) for 0.0.0.0:3000

You can flesh out what you want printed to the console inside the rescue block.

anothermh
  • 9,815
  • 3
  • 33
  • 52
  • Thanks, but I was looking more for a sinatra configuration that I might have missed. Sinatra already shows a friendly error "== Someone is already performing on port 3000!" - but then it continues to show backtrace. I was hoping I missed some environment variable or a `set :something` command that rescues these traces completely. – DannyB Oct 21 '18 at 19:11
  • You didn't ask for a built-in Sinatra way of suppressing the stack trace, you asked for the **proper** way for the given example. Did you bother to look at [the Sinatra source code](https://github.com/sinatra/sinatra/blob/9590706ec6691520970c67b929776fd97d3c9ddd/lib/sinatra/base.rb#L1458-L1465)? I did, which is how I know there's no built-in way of doing what you ask for the example given. – anothermh Oct 21 '18 at 19:52
  • Even if you were using `config.ru` and starting Sinatra with `rackup -p 3000`, you would _still_ need to have a `begin/rescue/end` block for your call to `run Sinatra::Application` and you would _still_ have to print something manually to the console indicating the error. Both answers provided by myself and @lacostenycoder are correct and complete; you keep moving the goalposts for unnecessary non-technical reasons despite the fact that both answers successfully prevent the stack trace from appearing when the port is already in use. – anothermh Oct 21 '18 at 19:57
1

This appears to be caused by an issue with Puma, that is fixed by this PR.

DannyB
  • 12,810
  • 5
  • 55
  • 65