FileLogObserver extends ShellObserver
📌 Overview
FileLogObserver is a concrete implementation of ShellObserver designed to record every aspect of a shell session lifecycle. It writes structured log entries to a file using Python's logging module with UTF-8 encoding, timestamps, severity levels, and detailed execution metrics. Ideal for audit trails, debugging automation scripts, or monitoring remote command execution.
- Automatic session start/end tracking with error differentiation
- Command logging (exact executable + arguments)
- Execution results: return code, duration, stdout snippet
- Exception capture and system error logging
- Configurable log file path (default:
shell_activity.log) - Isolated logger with no console propagation to avoid clutter
🏛️ Class Hierarchy & Design
Inheritance: FileLogObserver → ShellObserver (abstract/base observer). The observer pattern allows reacting to shell events without modifying core execution logic.
| Observer Method | Triggered when... |
|---|---|
on_session_start(shell_name) | A new shell session begins |
on_session_end(shell_name, error) | Session ends (normally or with exception) |
on_command_start(executable, final_args) | Just before a command is executed |
on_command_result(result: CommandResult) | After command finishes, provides result object |
on_error(message, error) | When an internal system-level error occurs |
Internal Logger: Uses Python's logging.getLogger("ShellFileLogger") with a dedicated FileHandler. Propagate is set to False to prevent duplicate logs to root console handlers.
⚙️ Constructor (__init__)
FileLogObserver(log_path: str = "shell_activity.log")
Initializes the file-based observer. Creates a log file at the given path, configures a UTF-8 file handler with formatter "%(asctime)s | %(levelname)s | %(message)s", and ensures no existing handlers remain to avoid duplicate logging.
Parameters:
log_path(str) – Path where the log file will be stored. Defaults to"shell_activity.log".
Behavior: Clears any previous handlers attached to the internal logger, then attaches a new FileHandler with UTF-8 encoding. The logger level is set to INFO (errors also captured).
📡 Event Handlers (Observer Methods)
on_session_start(shell_name: str)
Logs the beginning of a shell session. Entry format: === SESSION START: {shell_name} === at INFO level.
on_session_end(shell_name: str, error: Exception = None)
Records session termination. If error is provided, logs an ERROR line: === SESSION ENDED WITH ERROR: {error} ===. Otherwise logs a success message at INFO level.
on_command_start(executable: str, final_args: list[str])
Captures the exact command string built from the executable and argument list. Logs: EXECUTING: {cmd_str} (INFO). Useful to see raw command before execution.
on_command_result(result: CommandResult)
Processes command outcome. Extracts return code, execution time, and a truncated snippet (first 100 chars) of stdout. Status: SUCCESS if result.is_success() else FAILED(return_code). Example output: RESULT: SUCCESS | DURATION: 0.0234s | STDOUT: Hello world....
on_error(message: str, error: Exception = None)
Logs system-level failures using logger.error. Format: SYSTEM ERROR: {message} | Detail: {error}. Catches unexpected issues inside the shell controller.
💻 Usage Example (Python integration)
from shell_observer import ShellObserver, CommandResult # hypothetical base
from pathlib import Path
from your_module import FileLogObserver
# Create observer with custom log path
observer = FileLogObserver(log_path="./logs/audit.log")
# Attach observer to a Shell instance (pseudo code)
shell = YourShellImplementation()
shell.attach(observer)
# Start session
shell.start_session("remote_bash")
# Execute commands (triggers on_command_start + on_command_result)
shell.execute("ls -la /var/log")
shell.execute("python3 --version")
# End session normally
shell.end_session()
# If error occurs: observer.on_session_end(..., error=Exception("network down"))
print("Log written to:", observer.log_path)
📜 Log Output Format
All entries follow the pattern: TIMESTAMP | LEVEL | MESSAGE. Below is a sample log file generated by FileLogObserver:
2025-02-18 10:15:32,142 | INFO | === SESSION START: main_bash ===
2025-02-18 10:15:32,155 | INFO | EXECUTING: ls -la --color=auto
2025-02-18 10:15:32,287 | INFO | RESULT: SUCCESS | DURATION: 0.1320s | STDOUT: total 48 drwxr-xr-x 12 user staff 384 Feb 18 10:15 . drwxr-xr-x...
2025-02-18 10:15:34,002 | INFO | EXECUTING: git status --short
2025-02-18 10:15:34,211 | INFO | RESULT: SUCCESS | DURATION: 0.2090s | STDOUT: M README.md ?? new_script.py...
2025-02-18 10:15:36,100 | INFO | === SESSION ENDED SUCCESSFULLY ===
2025-02-18 10:17:01,421 | ERROR | SYSTEM ERROR: Failed to read environment | Detail: OSError(2, 'No such file')
Fields: asctime (YYYY-MM-DD HH:MM:SS,ms), levelname (INFO/ERROR), and the descriptive message. The stdout snippet is trimmed to 100 characters to keep logs manageable.
📎 Additional Implementation Notes
- UTF-8 encoding ensures full Unicode support for command outputs.
- No console propagation:
self.logger.propagate = Falseprevents logs from appearing in the terminal unless other handlers are added. - Duplicate handler prevention: The constructor explicitly clears any existing handlers from previous observer instances, avoiding double writes.
Pathobject frompathlibis used to storelog_path(flexible cross-platform path handling).CommandResultmust containreturn_code,execution_time,standard_output, and anis_success()method (convention).