This document provides an overview of the observability and debugging infrastructure in solid-process. It covers how process execution is instrumented, how events are logged with proper nesting for composed processes, and how exceptions are formatted with clean backtraces. For detailed information about specific subsystems, see Event Logging System, Backtrace Cleaning, and Pattern Matching and Result Inspection.
The observability system in solid-process enables detailed tracing and debugging of business logic without requiring explicit logging code in process implementations. All instrumentation happens automatically through the process execution lifecycle, capturing:
and_then chainThe system is designed to maintain code readability while providing production-grade observability.
Sources: README.md64-67 lib/solid/process/event_logs.rb1-8
The observability infrastructure consists of three main components that work together to provide comprehensive instrumentation:
Diagram: Observability Component Architecture
The event logging system is built on the solid-result gem's instrumentation infrastructure, which provides the base event capture mechanism. solid-process adds a specialized listener (BasicLoggerListener) that formats events for human-readable output and a backtrace cleaner to filter framework internals from exception traces.
Sources: lib/solid/process/event_logs.rb1-8 lib/solid/process/event_logs/basic_logger_listener.rb1-10 lib/solid/process/backtrace_cleaner.rb1-18
The observability system consists of three primary code entities:
This module serves as the namespace for event logging components and automatically loads the BasicLoggerListener.
Location: lib/solid/process/event_logs.rb3-6
This class implements the listener interface from solid-result and provides structured logging output. It maintains two class attributes:
| Attribute | Type | Default | Purpose |
|---|---|---|---|
logger | ActiveSupport::Logger | ActiveSupport::Logger.new($stdout) | Output destination for log messages |
backtrace_cleaner | Solid::Process::BacktraceCleaner | New instance | Exception backtrace filtering |
The listener responds to two lifecycle events:
on_finish(event_logs:) - Called when process execution completes successfullybefore_interruption(exception:, event_logs:) - Called when an exception interrupts executionLocation: lib/solid/process/event_logs/basic_logger_listener.rb3-77
This class extends ActiveSupport::BacktraceCleaner to filter out framework-internal stack frames from exception backtraces. It automatically adds a silencer that removes Ruby block frames using the pattern defined in BLOCKS_PATTERN.
The pattern matches:
in `block in - Ruby 2.x-3.x block notationin `Kernel#then' - Ruby 4.x block notationinternal:kernel - Ruby internal framesblock (N levels) in - Nested block framesLocation: lib/solid/process/backtrace_cleaner.rb3-17
Sources: lib/solid/process/event_logs/basic_logger_listener.rb1-77 lib/solid/process/backtrace_cleaner.rb1-18 CHANGELOG.md29-31
The following diagram illustrates how events flow from process execution through the logging system:
Diagram: Event Logging Execution Flow
Sources: lib/solid/process/event_logs/basic_logger_listener.rb62-76 test/solid/process/event_logs/basic_logger_listener_test.rb48-124
The BasicLoggerListener uses the MessagesNesting module to format hierarchical log output for composed processes. This module maps event records to human-readable messages with indentation indicating nesting depth.
The nested message system uses three lambda functions defined in lib/solid/process/event_logs/basic_logger_listener.rb11-59:
Diagram: Message Formatting Pipeline
| Lambda | Purpose | Output Format |
|---|---|---|
MAP_STEP_METHOD | Formats result into step description | "Given(attr:, ...)", "Success(:type, key:)" |
MAP_STEP_MESSAGE | Adds method context to step | " * Success(:type) from method: method_name" |
MAP_IDS_WITH_MESSAGES | Builds messages with process IDs | Array of [process_id, message] tuples |
The final indentation uses " " * ids_level_parent[id].first to create proper nesting, where ids_level_parent tracks the hierarchical depth of each process ID.
Sources: lib/solid/process/event_logs/basic_logger_listener.rb11-59
When a process executes, the logging system produces hierarchical output showing the execution trace. For a composed process like Account::OwnerCreation calling User::Creation which calls User::Token::Creation, the output looks like:
#0 Account::OwnerCreation
* Given(uuid:, owner:)
#1 User::Creation
* Given(uuid:, name:, email:, password:, password_confirmation:)
* Continue() from method: validate_email_uniqueness
* Continue(user:) from method: create_user
#2 User::Token::Creation
* Given(user:, executed_at:)
* Continue() from method: validate_token_existence
* Continue(token:) from method: create_token_if_not_exists
* Success(:token_created, token:)
* Continue(token:) from method: create_user_token
* Success(:user_created, user:, token:)
* Continue(user:, user_token:) from method: create_owner
* Continue(account:) from method: create_account
* Continue() from method: link_owner_to_account
* Success(:account_owner_created, user:, account:)
Each indentation level (3 spaces) represents one level of process nesting. The #N prefix indicates the unique process ID for that execution context.
Sources: test/solid/process/event_logs/basic_logger_listener_test.rb60-81
When an exception occurs during process execution, the system logs the event trace up to the failure point, followed by the exception message and a cleaned backtrace:
Diagram: Exception Backtrace Cleaning Flow
The BLOCKS_PATTERN constant at lib/solid/process/backtrace_cleaner.rb12 defines a regular expression that matches Ruby block frames, internal kernel frames, and Ruby 4.x-style Kernel#then frames. The backtrace cleaner removes these frames to show only application and process code, making it easier to identify the actual source of errors.
Example output when an exception occurs:
Exception:
Runtime breaker activated (USER_TOKEN_CREATION) (RuntimeBreaker::Interruption)
Backtrace:
runtime_breaker.rb:16:in `try_to_interrupt'
user_token_creation.rb:28:in `create_token_if_not_exists'
user_token_creation.rb:15:in `call'
user_creation.rb:61:in `create_user_token'
user_creation.rb:30:in `call'
account_owner_creation.rb:32:in `create_owner'
account_owner_creation.rb:21:in `call'
Sources: lib/solid/process/event_logs/basic_logger_listener.rb68-76 lib/solid/process/backtrace_cleaner.rb3-17 test/solid/process/event_logs/basic_logger_listener_test.rb83-124
The observability system is configured through the Solid::Result.config API, which is provided by the solid-result gem. The listener and logger can be customized:
The BasicLoggerListener uses class attributes for logger and backtrace_cleaner (defined at lib/solid/process/event_logs/basic_logger_listener.rb6-9), making them globally configurable for all process executions.
Sources: lib/solid/process/event_logs/basic_logger_listener.rb6-9 test/solid/process/event_logs/basic_logger_listener_test.rb12-24
The observability system includes test coverage for:
Tests use a RuntimeBreaker helper class to simulate failures at specific points in the execution chain, allowing verification of nested process logging behavior.
In test environments, the backtrace cleaner can be configured with additional silencers to filter test framework internals:
Sources: test/solid/process/backtrace_cleaner_test.rb1-59 test/solid/process/event_logs/basic_logger_listener_test.rb1-126
The observability system integrates with the process execution lifecycle through:
| Component | Integration Point | Purpose |
|---|---|---|
Solid::Process::Caller | Wraps process execution | Triggers event capture at process boundaries |
Solid::Result::EventLogs | Records execution steps | Captures each step in the and_then chain |
| Result outcomes | Success/Failure creation | Records result type and data keys |
| Exception handlers | rescue_from mechanism | Triggers before_interruption callback |
The system operates transparently - process implementations require no explicit logging code. All instrumentation is handled automatically by the framework.
Refresh this wiki