ShellObserver Base Interface
class ShellObserver(ABC) ยท Designed as a toolbox of hooks with empty default implementations.
Implement only the events you need. Perfect for logging, telemetry, debugging or auditing shell interactions.
๐ Abstract Observer Contract
The ShellObserver acts as a bridge between the shell core and external monitoring tools.
It does not impose any logic; instead, it provides optional hooks that child classes can override
to capture granular lifecycle events. This pattern keeps business logic decoupled and promotes clean instrumentation.
โ Derived from
ABC (Abstract Base Class) โ explicit base for observers.โ All methods have
pass implementations โ no forced overrides.โ Each hook receives rich contextual information: shell name, exceptions, command args, result objects.
โ Designed to integrate with context managers, virtual builtins, and subprocess executors.
๐ Session Lifecycle Hooks
Monitor the lifespan of a shell context: from entering the with block until exit (even on exceptions).
๐ on_session_start(shell_name: str)
Triggered when entering the Shell context ('with' block). Use this to initialize logging resources, start telemetry timers, or allocate session-specific handles.
๐ on_session_end(shell_name: str, error: Exception = None)
Triggered when exiting the Shell context. Allows you to log whether the session ended cleanly or due to an unhandled exception. Perfect for sending session metrics or cleaning up resources.
None.โ๏ธ Context & State Mutation
React to changes in the shell's internal state, such as working directory (CWD), environment variables, or virtual built-in commands.
๐ on_context_change(key: str, value: Any)
Triggered whenever a state command (e.g., cd, export, or other virtual builtins) mutates the session context.
Ideal for tracking directory traversal, environment modifications, or custom state flags in real time.
value: new value assigned (any type).
โก Command Execution Hooks
Instrument every external (effect) command before execution and after the result is returned.
โถ๏ธ on_command_start(executable: str, final_args: list[str])
Invoked just before dispatching an effect command to the underlying operating system. Provides the fully resolved arguments as they will be executed by the adapter. Use this for command auditing, security logging, or custom pre-execution validation.
final_args: list of argument strings after shell expansion & parsing.
๐ on_command_result(result: "CommandResult")
Triggered after receiving the response from an effect command. The CommandResult object encapsulates return code, stdout, stderr, and execution metadata.
Ideal for metrics aggregation, failure alerts, or capturing output for logs.
.returncode, .stdout, .stderr, .execution_time (if measured).| Hook | Timing | Typical Use |
|---|---|---|
on_command_start | Before syscall | audit commands, measure latency, prevent dangerous invocations |
on_command_result | After command finishes | log output, track error rates, store structured telemetry |
โ ๏ธ Error Sink & Diagnostics
๐จ on_error(message: str, error: Exception = None)
Central error hook triggered when a controlled or unexpected error occurs within the Core. Acts as a global error sink, keeping business logic clean. Use it to forward errors to external monitoring (Sentry, DataDog) or to aggregate internal failures for debugging.
error: optional exception instance for stack traces and advanced context.
on_error calls to centralize fault reporting.
๐ก Practical Usage Example (Python)
Implement your own observer to track session duration, command history, and errors. The snippet below shows a concrete TelemetryObserver that overrides several hooks.
from typing import Any from shell_observer import ShellObserver, CommandResult class TelemetryObserver(ShellObserver): def on_session_start(self, shell_name: str): print(f"[๐ก] Session started: {shell_name}") self.command_count = 0 def on_session_end(self, shell_name: str, error: Exception = None): if error: print(f"[โ ๏ธ] Session ended with error: {error}") else: print(f"[โ ] Clean session end. Commands run: {self.command_count}") def on_context_change(self, key: str, value: Any): print(f"[๐ฟ] context mutated: {key} โ {value}") def on_command_start(self, executable: str, final_args: list[str]): self.command_count += 1 print(f"[โก] exec: {executable} {' '.join(final_args)}") def on_command_result(self, result: CommandResult): if result.returncode != 0: print(f"[โ] command failed (rc={result.returncode})") else: print(f"[โ๏ธ] success โ {len(result.stdout)} bytes out") def on_error(self, message: str, error: Exception = None): print(f"[๐ฅ] Core error: {message} | {error}") # Usage inside shell context: # with Shell(shell_name="mybash", observer=TelemetryObserver()) as sh: # sh.run("ls -la")
โ Extensibility: Because each method has a default empty implementation, your custom observer only needs to override the relevant hooks โ no boilerplate required.
๐ Complete Method Reference
| Method signature | Description | When called |
|---|---|---|
on_session_start(shell_name: str) | Initialize logging / telemetry resources | On entry to shell context |
on_session_end(shell_name, error) | Cleanup & session summary | Exit from with block |
on_context_change(key, value) | Track state mutations (CWD, env) | Virtual built-in command |
on_command_start(executable, final_args) | Pre-execution hook for external commands | Before OS subprocess call |
on_command_result(result: CommandResult) | Post-execution analysis | After command finishes |
on_error(message, error) | Central error handling / forwarding | On any core exception or controlled failure |
ShellObserver interface does not rely on inheritance constraints โ you can register multiple observers if needed, following the observer pattern.