FileLogObserver extends ShellObserver

Persistent shell activity logger — captures commands, results, sessions & errors to a physical file.

📌 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.

🔍 Key features:
  • 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: FileLogObserverShellObserver (abstract/base observer). The observer pattern allows reacting to shell events without modifying core execution logic.

Observer MethodTriggered 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)
💡 Tip: The observer automatically flushes logs to disk after each event (default file handler behavior). Use absolute paths for reliability in production environments.

📜 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