skip to content

In Ruby, what do Exception#detailed_message and full_message add over message, and which should a custom error override for extra context?

level: seniorimportance: nice to knowfreq 20%

answer

  1. message: plain text only
  2. detailed_message adds the class name
  3. the error printer calls it since 3.2
  4. full_message adds backtrace and causes
  5. override detailed_message, accept **

basics

~20 s

message is the plain text; detailed_message adds the class name and optional highlighting, and Ruby's error printer uses it since 3.2; full_message adds the backtrace and cause chain. Override detailed_message for extra context, keeping message machine-readable.

solid answer

~40 s

`Exception#message` returns `to_s`, the plain text you raised with. `detailed_message(highlight: false, **)`, added in Ruby 3.2, returns that text decorated with the class name, `"divided by 0 (ZeroDivisionError)"`, and ANSI bold/underline when `highlight: true`; the default printer for uncaught exceptions calls it instead of `message`. `full_message(highlight:, order:)` produces the whole report: detailed message, backtrace and each cause, highlighting by default only when `$stderr` is a terminal. To add context such as a carrier name or request id to printed errors, override `detailed_message`, call `super` and append, and accept arbitrary keywords as the documentation requires, because did_you_mean and error_highlight pass their own. Leave `message` alone so code that matches or stores it still sees the plain text.

code

ruby · 17 lines
ruby
class CarrierError < StandardError
  attr_reader :carrier, :status

  def initialize(msg = "carrier request failed", carrier: nil, status: nil)
    super(msg)
    @carrier = carrier
    @status = status
  end

  def detailed_message(highlight: false, **)
    "#{super}\n  carrier: #{carrier}, upstream status: #{status}"
  end
end

error = CarrierError.new(carrier: "UPS", status: 503)
error.message                # => "carrier request failed"
error.detailed_message       # => "carrier request failed (CarrierError)\n  carrier: UPS, upstream status: 503"

go deeper

for a junior

Know that message is the plain text and full_message adds the backtrace.

for a middle

Explain detailed_message: the class name, highlighting, and that the default error printer has used it since Ruby 3.2.

for a senior

Override detailed_message with super and keyword tolerance to enrich crash output, and log full_message with highlight: false.

for a principal

Separate machine-readable messages from human diagnostics across a codebase so logs, translations and reporters each get what they need.

## Three layers of text A Ruby exception can describe itself at three levels of detail: | Method | Returns | Used by | |---|---|---| | `message` | `to_s`: the stored message, or the class name if none | your code, logs, assertions | | `detailed_message(highlight: false, **)` | message plus the class name, optionally with ANSI codes | the default error printer, `full_message` | | `full_message(highlight:, order:)` | detailed message, backtrace, then each cause | crash output, custom reporters | For `1 / 0`, `message` is `"divided by 0"` and `detailed_message` is `"divided by 0 (ZeroDivisionError)"`. ## detailed_message: the decoration hook `Exception#detailed_message` was added in **Ruby 3.2**, and at the same time the default printer for uncaught exceptions started calling it instead of `message`. The point was to separate two jobs: - **`message`** stays the plain, stable text that programs compare, store or translate. - **`detailed_message`** is where extra, human-oriented output goes. Ruby's own tooling uses this hook. The core documentation lists `DidYouMean::Correctable#detailed_message`, `ErrorHighlight::CoreExt#detailed_message` and `SyntaxSuggest#detailed_message` as overrides that append suggestions and code snippets. Ruby 4.0's error_highlight, for instance, shows snippets for both the caller and the method definition when an `ArgumentError` is raised, all through `detailed_message`, while `message` is unchanged. ## Overriding it correctly The documentation sets two rules for an override: 1. **Be tolerant of keyword arguments.** Callers may pass `highlight:`, `did_you_mean:`, `error_highlight:`, `syntax_suggest:` and possibly others, so declare `highlight: false, **` and forward to `super`. 2. **Be careful with ANSI codes.** Output may end up in HTML or a log file. Stick to the widely supported codes the documentation lists, and add them only when `highlight` is true. The usual pattern is `super` plus appended lines, so the class name and other gems' additions are kept. ## full_message: the whole report `full_message` builds the same text Ruby prints for a crash: - the detailed message of the exception, with the innermost backtrace entry first when `order: :top` (the default), or last with `order: :bottom`; - the backtrace lines; - each exception in the **cause chain**, with its own detailed message and backtrace. `highlight:` defaults to whether `$stderr` is a terminal, which is why the same call produces colour in a console and plain text under a process manager. Pass `highlight: false` explicitly when writing to a log file. ## Choosing where to put context - **Data the rescuer acts on**: fields with readers, such as `retry_after`. - **Short human text**: the message, set through `super(msg)`. - **Extra diagnostic context for printed output**: a `detailed_message` override. - **Everything for a log line**: `full_message(highlight: false)`. In the shipping-rates gem, `CarrierError#detailed_message` might append the carrier and upstream status, so a crash in a background job shows them without every caller having to log them, while `message` remains `"carrier request failed"` for code that matches on it. ## Pitfalls - **Declaring only `highlight:`** in an override. A keyword such as `did_you_mean:` passed by the printer then raises `ArgumentError` from inside the printer, and the crash output itself fails. - **Heavy work in `detailed_message`.** It runs while printing a crash, possibly under memory pressure; keep it to string building from fields already stored. - **Assuming `full_message` is plain text.** Without `highlight: false`, output to a terminal contains ANSI escape codes, which look like noise when copied into a ticket. - **Putting secrets in fields that `detailed_message` prints.** Everything appended there reaches logs and error trackers. For a senior answer, the key sentence is that `message` is data and `detailed_message` is presentation, and Ruby 3.2 split them so each can change without breaking the other.

  • Why not override message to include the carrier and status?
    Because `message` is what programs compare, store and translate, and it is what `raise Klass, "text"` sets. Overriding it changes behaviour for every caller and can drop the custom text. `detailed_message` exists since Ruby 3.2 precisely so printed output can be enriched while `message` stays plain.
  • Why must a detailed_message override accept arbitrary keywords?
    Ruby and default gems pass their own keywords, such as `highlight:`, `did_you_mean:`, `error_highlight:` and `syntax_suggest:`. An override that declares only `highlight:` raises `ArgumentError` for an unknown keyword when one of them is passed, breaking crash output. Declare `**` and forward to `super`.

saying these in an interview costs you the question

  • message already includes the exception class name
  • Ruby's crash printer still calls message on Ruby 4.0
  • full_message returns only the message and the class name
  • An override may declare only highlight: and ignore other keywords
  • Overriding message is the recommended way to add context