Diagnostics components help preserve useful context when a program fails. They cover three related needs: raising option-aware exceptions, producing consistent object inspection strings, and retaining an application-level exception chain.
Use these components at boundaries where a generic Ruby exception would lose information that a caller needs to diagnose or report a failure.
- Loading
- Choosing a diagnostic tool
- ExceptionUtilities
- InspectBuilder
- Exceptions::WithCause
- Design considerations
Load all diagnostics components through the category entry point:
require 'xqsr3/diagnostics'Or load only the required component:
require 'xqsr3/diagnostics/exception_utilities'
require 'xqsr3/diagnostics/inspect_builder'
require 'xqsr3/diagnostics/exceptions/with_cause'The components are defined under Xqsr3::Diagnostics.
- Use
ExceptionUtilitieswhen an exception class accepts keyword options; - Use
InspectBuilderwhen diagnostic output should expose selected object state consistently; - Use
Exceptions::WithCausewhen an exception should retain a domain-level inner exception and report the chain; - Use Ruby's native exception cause when the interpreter's raise-chain semantics, rather than an application-owned chain, are wanted.
Xqsr3::Diagnostics::ExceptionUtilities.raise_with_options preserves the
usual Kernel#raise forms and adds keyword options when the first argument is
an exception class. The options are passed to that class's constructor.
require 'xqsr3/diagnostics/exception_utilities'
class ConfigurationError < ArgumentError
attr_reader :options
def initialize(message = nil, **options)
super(message)
@options = options
end
end
begin
Xqsr3::Diagnostics::ExceptionUtilities.raise_with_options(
ConfigurationError,
'invalid port',
section: :server,
value: 'abc',
)
rescue ConfigurationError => error
error.message # => 'invalid port'
error.options # => { section: :server, value: 'abc' }
endWhen no options are needed, the method behaves like raise:
Xqsr3::Diagnostics::ExceptionUtilities.raise_with_options 'failure'
# raises RuntimeError
Xqsr3::Diagnostics::ExceptionUtilities.raise_with_options ArgumentError
# raises ArgumentError
Xqsr3::Diagnostics::ExceptionUtilities.raise_with_options(
ArgumentError,
'failure',
)The class must be the first argument for options to be meaningful. Supplying
options with a message, exception instance, or other non-class first argument
causes a warning and falls back to ordinary Kernel#raise behaviour.
The method preserves an explicitly supplied backtrace and trims the helper's
own internal frames. This keeps reported failures focused on the caller's
operation rather than on the implementation of raise_with_options.
Xqsr3::Diagnostics::InspectBuilder is an includable module for classes that
need consistent inspect-style diagnostics. Its make_inspect method can
include class information, object identity, and instance fields:
require 'xqsr3/diagnostics/inspect_builder'
class Job
include Xqsr3::Diagnostics::InspectBuilder
def initialize(name, token)
@name = name
@token = token
end
end
job = Job.new('compile', 'secret')
job.make_inspect(show_fields: true, no_object_id: true)
# => "#<Job: @name(String)='compile'; @token(String)='secret'>"The options are:
no_classomits the class qualification;no_object_idomits the object identifier;show_fieldsincludes instance variables;shown_fieldsrestricts output to named fields;hidden_fieldsexcludes named fields;truncate_widthlimits field values using the String Utilities truncation rules;deep_inspectobtains field values through their owninspectmethods.
Field names may be supplied with or without the leading @. shown_fields
takes precedence over hidden_fields. A class can define
INSPECT_HIDDEN_FIELDS to establish default exclusions for its instances and
subclasses.
Use shown_fields or a class-level hidden-field list for secrets, credentials,
and other values that should not appear in logs. inspect output is a
diagnostic representation, not a serialization format.
Xqsr3::Diagnostics::Exceptions::WithCause is an inclusion module for custom
exception classes. It adds a cause attribute, preserves constructor options,
and exposes the chain through chainees, exceptions, chained_message, and
chained_backtrace.
require 'xqsr3/diagnostics/exceptions/with_cause'
class ImportError < StandardError
include Xqsr3::Diagnostics::Exceptions::WithCause
end
begin
begin
raise ArgumentError, 'port is not numeric'
rescue ArgumentError => cause
raise ImportError.new('configuration is invalid', cause: cause)
end
rescue ImportError => error
error.message # => 'configuration is invalid'
error.cause.message # => 'port is not numeric'
error.chained_message # => 'configuration is invalid: port is not numeric'
error.exceptions # => [error, error.cause]
endThe cause: keyword is the clearest way to specify the inner exception. The
module can also infer an exception supplied among the positional constructor
arguments. When an exception is supplied without a separate outer message,
its message can become the outer exception's message.
chained_message uses ': ' between levels by default. Pass separator to
choose another separator:
error.chained_message(separator: ' <- ')
# => 'configuration is invalid <- port is not numeric'chainees excludes the receiver; exceptions includes the receiver. Both
follow the cause chain in outer-to-inner order. chained_backtrace combines
the backtraces of the chain for diagnostic display.
WithCause#cause is an application-level attribute and intentionally shadows
Ruby's interpreter-managed Exception#cause for classes that include the
module. Do not mix the two models casually in a single exception hierarchy.
Avoid putting secrets into exception messages or inspectable instance
variables. Prefer InspectBuilder field selection and explicit exception
options when diagnostic context must be retained without exposing everything
in logs.
For executable behavioural examples, see the unit tests in
test/unit/diagnostics/ and test/unit/diagnostics/exceptions/.