Skip to content

Log Viewer

The LogViewer class provides interactive log viewing capabilities with support for both JSON and plaintext log files.

Quick Reference

Python
from pylogshield import LogViewer
from pathlib import Path

# Create a viewer for a log file
viewer = LogViewer(Path("~/.logs/my_app.log").expanduser())

# Display last 100 logs
viewer.display_logs(limit=100)

# Filter by level and keyword
viewer.display_logs(limit=50, level="ERROR", keyword="database")

# Live follow (like tail -f)
viewer.follow_logs(level="INFO", interval=0.5)

Features

Feature Description
Static viewing Display last N lines with filtering
Live following Real-time updates as logs are written
Level filtering Show only logs at or above a level
Keyword search Filter logs containing specific text
JSON support Auto-detects and parses JSON logs
Rich output Colorized table output with Rich

Examples

Basic Log Viewing

Python
from pylogshield import LogViewer
from pathlib import Path

viewer = LogViewer(Path("/var/log/app.log"))

# Display last 200 lines (default)
viewer.display_logs()

# Display last 50 lines
viewer.display_logs(limit=50)

Filtering by Log Level

Python
viewer = LogViewer(Path("app.log"))

# Show only ERROR and above
viewer.display_logs(level="ERROR")

# Show only WARNING and above
viewer.display_logs(level="WARNING", limit=100)

# Using numeric level
viewer.display_logs(level=30)  # WARNING = 30

Keyword Filtering

Python
viewer = LogViewer(Path("app.log"))

# Search for logs containing "user_id"
viewer.display_logs(keyword="user_id")

# Combine level and keyword filters
viewer.display_logs(level="ERROR", keyword="timeout", limit=50)

Live Following

Python
viewer = LogViewer(Path("app.log"))

# Follow logs in real-time (press Ctrl+C to stop)
viewer.follow_logs()

# With filtering
viewer.follow_logs(level="ERROR", keyword="critical")

# Custom refresh interval (default: 0.5 seconds)
viewer.follow_logs(interval=1.0)

# Limit buffer size (default: 500 lines)
viewer.follow_logs(max_lines=200)

JSON Log Parsing

The viewer automatically detects and parses JSON-formatted logs:

Python
# If your log file contains lines like:
# {"timestamp": "2024-01-15T10:30:00", "level": "INFO", "message": "User logged in"}

viewer = LogViewer(Path("json_app.log"))
viewer.display_logs(limit=50)

# The viewer extracts timestamp, level, and message fields

API Reference

LogViewer(log_file: Path)

Interactive log viewer with support for JSON and plaintext log files.

Provides both static viewing (tail) and live following (tail -f) capabilities with filtering by log level and keyword search.

PARAMETER DESCRIPTION

log_file

Path to the log file to view.

TYPE: Path

ATTRIBUTE DESCRIPTION
log_file

Resolved path to the log file.

TYPE: Path

console

Rich console instance for output.

TYPE: Console

Examples:

Python Console Session
>>> viewer = LogViewer(Path("/var/log/app.log"))
>>> viewer.display_logs(limit=100, level="ERROR")
>>> viewer.follow_logs(level="INFO", keyword="user")
METHOD DESCRIPTION
display_logs

Display log entries in a formatted Rich table.

follow_logs

Live-follow the log file, displaying new lines as they are written.

Source code in src/pylogshield/viewer.py
Python
def __init__(self, log_file: Path) -> None:
    if not _HAS_RICH:
        raise ImportError(
            "LogViewer requires the 'rich' library. "
            "Install it with: pip install rich"
        )
    self.log_file = Path(log_file).expanduser().resolve()
    self.console = Console()
    self._reader = _LogReader(self.log_file) if _HAS_READER else None

display_logs(*, limit: int = 200, level: Optional[Union[int, str]] = None, keyword: Optional[str] = None) -> bool

Display log entries in a formatted Rich table.

PARAMETER DESCRIPTION

limit

Maximum number of lines to display from the end. Default is 200.

TYPE: int DEFAULT: 200

level

Minimum log level to display (e.g., "INFO" or 20). Default is None (show all levels).

TYPE: int or str or None DEFAULT: None

keyword

Only show lines containing this keyword (case-insensitive). Default is None (no filtering).

TYPE: str or None DEFAULT: None

RETURNS DESCRIPTION
bool

True if logs were rendered successfully, False if file not found.

Source code in src/pylogshield/viewer.py
Python
def display_logs(
    self,
    *,
    limit: int = 200,
    level: Optional[Union[int, str]] = None,
    keyword: Optional[str] = None,
) -> bool:
    """Display log entries in a formatted Rich table.

    Parameters
    ----------
    limit : int, optional
        Maximum number of lines to display from the end. Default is 200.
    level : int or str or None, optional
        Minimum log level to display (e.g., "INFO" or 20). Default is None
        (show all levels).
    keyword : str or None, optional
        Only show lines containing this keyword (case-insensitive).
        Default is None (no filtering).

    Returns
    -------
    bool
        True if logs were rendered successfully, False if file not found.
    """
    if not self.log_file.exists():
        self.console.print(
            Panel(
                f"[bold]{self.log_file}[/bold]",
                title="[bold red] File Not Found [/bold red]",
                border_style="red",
                box=box.ROUNDED,
                expand=False,
            )
        )
        return False
    table = self._build_table(limit, level, keyword)
    self.console.print(table)
    if table.row_count == 0:
        self.console.print(
            "[yellow]No log entries matched the current filters.[/yellow]"
        )
    return True

follow_logs(*, level: Optional[Union[int, str]] = None, keyword: Optional[str] = None, interval: float = 0.5, max_lines: int = 500) -> bool

Live-follow the log file, displaying new lines as they are written.

Similar to tail -f, this method continuously monitors the log file and displays new entries in a Rich live-updating table.

PARAMETER DESCRIPTION

level

Minimum log level to display. Default is None (show all levels).

TYPE: int or str or None DEFAULT: None

keyword

Only show lines containing this keyword (case-insensitive). Default is None (no filtering).

TYPE: str or None DEFAULT: None

interval

Refresh interval in seconds. Default is 0.5.

TYPE: float DEFAULT: 0.5

max_lines

Maximum lines to keep in the rolling display window. Default is 500.

TYPE: int DEFAULT: 500

RETURNS DESCRIPTION
bool

True if following completed (via Ctrl+C), False if file not found.

Notes
  • Starts at end of file (only shows new lines).
  • Automatically handles log rotation (file truncation).
  • Press Ctrl+C to stop following.
Source code in src/pylogshield/viewer.py
Python
def follow_logs(
    self,
    *,
    level: Optional[Union[int, str]] = None,
    keyword: Optional[str] = None,
    interval: float = 0.5,
    max_lines: int = 500,
) -> bool:
    """Live-follow the log file, displaying new lines as they are written.

    Similar to ``tail -f``, this method continuously monitors the log file
    and displays new entries in a Rich live-updating table.

    Parameters
    ----------
    level : int or str or None, optional
        Minimum log level to display. Default is None (show all levels).
    keyword : str or None, optional
        Only show lines containing this keyword (case-insensitive).
        Default is None (no filtering).
    interval : float, optional
        Refresh interval in seconds. Default is 0.5.
    max_lines : int, optional
        Maximum lines to keep in the rolling display window. Default is 500.

    Returns
    -------
    bool
        True if following completed (via Ctrl+C), False if file not found.

    Notes
    -----
    - Starts at end of file (only shows new lines).
    - Automatically handles log rotation (file truncation).
    - Press Ctrl+C to stop following.
    """
    if not self.log_file.exists():
        self.console.print(
            Panel(
                f"[bold]{self.log_file}[/bold]",
                title="[bold red] File Not Found [/bold red]",
                border_style="red",
                box=box.ROUNDED,
                expand=False,
            )
        )
        return False

    # Rolling buffer of *raw* lines to feed batch renderer
    window: deque[str] = deque(maxlen=max_lines)

    # Start with a headers-only table; show a "waiting" placeholder caption
    table = self._build_table_from_lines([], level, keyword)
    table.caption = "[dim]Waiting for new log entries…[/dim]"

    # Configure Live (only override refresh rate if interval > 0)
    live_kwargs = {"console": self.console, "transient": True}
    if interval > 0:
        live_kwargs["refresh_per_second"] = max(1, int(1.0 / interval))

    try:
        with self.log_file.open("r", encoding="utf-8", errors="replace") as f:
            # Start at EOF — only new lines will be shown
            f.seek(0, os.SEEK_END)
            try:
                last_size = os.fstat(f.fileno()).st_size
            except OSError:
                last_size = 0  # type: ignore[arg-type]

            with Live(table, **live_kwargs) as live:  # type: ignore[arg-type]
                while True:
                    try:
                        cur_size = os.fstat(f.fileno()).st_size
                    except OSError:
                        cur_size = 0
                    if cur_size < last_size:
                        f.seek(0, os.SEEK_SET)
                        last_size = cur_size

                    pos = f.tell()
                    line = f.readline()
                    if not line:
                        f.seek(pos)
                        last_size = cur_size
                        if interval > 0:
                            time.sleep(interval)
                        continue

                    window.append(line)
                    new_table = self._build_table_from_lines(
                        list(window), level, keyword
                    )
                    live.update(new_table)
                    table = new_table
                    last_size = cur_size
    except KeyboardInterrupt:
        return True

    return True