In Ruby, what do Exception#detailed_message and full_message add over message, and which should a custom error override for extra context?
answer
- message: plain text only
- detailed_message adds the class name
- the error printer calls it since 3.2
- full_message adds backtrace and causes
- override detailed_message, accept **
basics
~20 smessage 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 linesclass 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
Know that message is the plain text and full_message adds the backtrace.
Explain detailed_message: the class name, highlighting, and that the default error printer has used it since Ruby 3.2.
Override detailed_message with super and keyword tolerance to enrich crash output, and log full_message with highlight: false.
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