0

I want to write to either STDOUT or STDERR a clean, simple error message for the user, without the (verbose) backtrace. I am currently using raise to write the error message and exit, as in this simplified example:

#!/usr/bin/env ruby

def bar
  raise "this needs to be clean, no backtrace"
end

bar

It writes this to STDERR:

/Users/foo/test/test1.rb:4:in `bar': this needs to be clean, no backtrace (RuntimeError)
        from /Users/foo/test/test1.rb:7:in `<main>'

I want to write just this part:

this needs to be clean, no backtrace 

The real-life example has a much more verbose backtrace, and multiple raise statements to handle different failure modes with customized messages.

I am aware that I can do something like this (for clean STDOUT), but I want to avoid repetitive code:

puts "this needs to be clean, no backtrace"
raise "this needs to be clean, no backtrace"

Related:

  • Unhandled Exceptions in Ruby - this answer suggests a more complex way of handling exceptions (namely, catching and printing the error message) than I need. All I need is: (a) raise - or something similar - to terminate the program immediately, and (b) print just the error message, not the full stack trace or backtrace, which is confusing for our users.
Timur Shtatland
  • 12,024
  • 2
  • 30
  • 47
  • You're raising an exception, that's only half the job. You should also be catching the exception and printing its message. Otherwise, don't use exceptions to terminate the program. Use `exit(-1)`. – user229044 May 15 '23 at 14:43
  • Well I was going to suggest that `$stdout` and `$stderr` are globals, so you can just write to them in anyway you see fit. – engineersmnky May 15 '23 at 15:02
  • You want certain exceptions to be logged with full backtrace but others to be logged without backtrace? – Arctodus May 15 '23 at 15:16
  • @Arctodus I prefer to log without any backtrace all the exceptions that I `raise` myself. They are all intended for the users that are not computer literate, so they need to be short and sweet. The full backtrace is confusing to many of them. I realize that the backtrace is useful for developers, but I am willing to sacrifice this feature in my case for this particular application. – Timur Shtatland May 15 '23 at 15:23
  • Also given this statement "The real-life example has a much more verbose backtrace, and multiple raise statements to handle different failure modes with customized messages." I would recommend a `catch` and `throw` block format. [Example](https://stackoverflow.com/questions/71084903/in-ruby-can-you-decide-from-a-main-method-to-return-or-continue-when-calling-a) – engineersmnky May 15 '23 at 16:25
  • 2
    The problem here is you're completely misusing `raise`. Exceptions are not for end-users to ever see, they are not for communicating with end users, and they do not "terminate the program immediately". If you want that behavior, write a function `die(message)` which simply does `puts message; exit`. – user229044 May 15 '23 at 17:12
  • 1
    The gist is that exceptions are a means of passing control from one part of your application to another, not for dumping an error message and terminating the program. You can read more about exceptions in any Ruby tutorial on the topic. The actual problem you have (print a message and exit) is well-covered by the linked duplciate. – user229044 May 15 '23 at 17:26
  • 1
    @user229044 Sorry overlooked that part. Added another post that provides additional options for (b). – engineersmnky May 15 '23 at 17:29
  • @engineersmnky Thank you for the additional link! Do you think that my question can be edited to be reopened (and if yes, how)? It seems that at least the existing 2 answers are making it **not** an exact dupe. TYIA! – Timur Shtatland May 15 '23 at 17:34
  • 2
    @TimurShtatland given that you have reduced your post to (a) and (b) above I am not certain there would be a value add in reopening the question. The answers posted below and/or linked provide a substantiative answer with numerous options on how to proceed. `abort` being the most straight forward and `at_exit` or finalizers providing full control and flexibility when combined with `Kernel#exit` – engineersmnky May 15 '23 at 17:44
  • 1
    I dont see any need to close this one (again) @user229044. The question is not a straight duplicate of the ones you are linking to. – Arctodus May 15 '23 at 19:08
  • A similar question was answered here, and this is what I ended up using: [Write a simple, clean error message without a backtrace and exit on failure - Code Review Stack Exchange](https://codereview.stackexchange.com/questions/285164/write-a-simple-clean-error-message-without-a-backtrace-and-exit-on-failure) – Timur Shtatland May 26 '23 at 17:22

2 Answers2

2

If you just want to output to stderr, you could use warn (or write to $stderr directly), maybe along with exit:

def bar
  warn "this needs to be clean, no backtrace"
  exit(false)
end

bar

To alter the way global exception handlers work, you could register an at_exit handler which checks for the exception class, prints its message and silences stdout in order to suppress the backtrace. Something like this:

class SimpleError < StandardError ; end

at_exit do
  if $!.is_a?(SimpleError)
    $stderr.puts($!.message)
    $stderr.reopen(IO::NULL)
  end
end

def bar
  raise SimpleError, "this needs to be clean, no backtrace"
end

bar

It would probably be a good idea to make that kind of error handling optional.

Stefan
  • 109,145
  • 14
  • 143
  • 218
1

Are you trying to rewrite the way Ruby writes uncaught exceptions to STDERR? I dont know if thats easily done (or advisable).

You can override the backtrace in a custom exception though, which wont affect anything else.

class ExceptionWithoutBacktrace < StandardError
  def backtrace
    []
  end
end

raise ExceptionWithoutBacktrace, "This should be printed clean"
Arctodus
  • 5,743
  • 3
  • 32
  • 44