TeeStream — module documentation
Purpose
TeeStream mirrors a TextIO stream (sys.stdout, sys.stderr, or sys.stdin) to disk via a FileInstance while preserving normal terminal output. It is a drop-in replacement for the standard streams: the rest of application code simply calls print(), sys.stdout.write(), etc. without knowing the stream was replaced.
Constructor
TeeStream(
root,
original_stream,
*,
max_size_mb=None,
flush_size=None,
mode=CONST.StdMode.STDUNKNOWN,
error_mode=CONST.ErrorMode.WARN_NO_PIPE,
encoding=None,
log_to_file=True,
log_function_calls=False,
)
| Parameter | Type | Description | Default |
root | Union[str, Path, FileInstance] | Log destination: path string, Path, or a pre-built FileInstance | — |
original_stream | TextIO | The stream to mirror (e.g. sys.stdout) | — |
max_size_mb | Optional[int] | Max log-file size in MB; forwarded to FileInstance | None |
flush_size | Optional[int] | Buffer-flush threshold in bytes; forwarded to FileInstance | None |
mode | CONST.StdMode | Which standard stream this instance wraps | STDUNKNOWN |
error_mode | CONST.ErrorMode | Broken-pipe handling policy | WARN_NO_PIPE |
encoding | Optional[str] | Encoding override; forwarded to FileInstance | None |
log_to_file | bool | Whether disk logging is enabled on construction | True |
log_function_calls | bool | Prefix each log entry with the method name ([WRITE], [READLINE], …) | False |
Raises ValueError if root is not a str, Path, or FileInstance.
Public methods
Write path
| Method | Signature | Description |
write | write(message: str) | Write message to the terminal and buffer it for disk |
writelines | writelines(lines: List[str]) | Call write() for each line |
flush | flush() | Flush the terminal stream and trigger a FileInstance flush |
Read path (stdin wrapping)
| Method | Signature | Description |
read | read(size=-1) → str | Read from the original stream; optionally log the result |
readline | readline(size=-1) → str | Read one line from the original stream |
readlines | readlines(hint=-1) → list[str] | Read all lines from the original stream |
Capability probes (TextIO compatibility)
| Method | Returns | Description |
readable() | bool | True when mode is STDIN |
writable() | bool | True when mode is not STDIN |
seekable() | bool | Always False |
Behavior and guarantees
- No background threads: all I/O runs on the caller's thread. Terminal writes are wrapped in specific exception handlers (
BrokenPipeError, OSError) and will never raise unexpected exceptions back into the application.
- Disk writes are cheap: buffering is delegated to
FileInstance. Each write() call appends to an in-memory list; actual disk I/O and rotation happen in FileInstance._flush_buffer().
- Broken-pipe semantics are controlled by
error_mode:
WARN / WARN_NO_PIPE: print a warning to the real sys.stderr.
EXIT / EXIT_NO_PIPE: call sys.exit() on the caller thread.
Threading and resource notes
- A tiny
RLock guards reference reads; disk I/O is performed outside the lock.
- Because terminal writes block the caller, a slow or blocked
original_stream can delay the calling thread — an unavoidable trade-off without a dedicated I/O worker.
- For high-throughput services, consider tuning
flush_size in FileInstance to amortise syscall overhead.
Usage example
import sys
import pathlib
from rotary_logger import constants as CONST
fi = FileInstance(pathlib.Path("/var/log/myapp"))
tee = TeeStream(
fi,
sys.stdout,
mode=CONST.StdMode.STDOUT,
log_function_calls=True,
)
sys.stdout = tee
print("hello")
Operator notes
flush() is best-effort; for deterministic shutdown call FileInstance.flush() directly.
- The
log_function_calls flag adds a PrefixFunctionCall tag ([WRITE], [READLINE], …) to each log entry, making it easy to distinguish write origins in mixed-stream log files.
TeeStream does not close or restore the original_stream; that is the responsibility of RotaryLogger.stop_logging().