log4lab 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
log4lab/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
log4lab/cli.py ADDED
@@ -0,0 +1,123 @@
1
+ import typer
2
+ import uvicorn
3
+ from pathlib import Path
4
+ from typing import Optional
5
+ from . import server
6
+ from .tail import LogTailer
7
+ from .export import export_logs_to_html
8
+
9
+ app = typer.Typer(help="Log4Lab — a lightweight structured log dashboard")
10
+
11
+
12
+ @app.command()
13
+ def serve(
14
+ logfile: Path = typer.Argument(
15
+ "logs/app.log",
16
+ exists=False,
17
+ help="Path to the JSONL log file to stream (default: logs/app.log)"
18
+ ),
19
+ host: str = typer.Option("127.0.0.1", help="Host to bind to"),
20
+ port: int = typer.Option(8000, help="Port to listen on"),
21
+ reload: bool = typer.Option(False, help="Enable auto-reload"),
22
+ ):
23
+ """Start the Log4Lab web server."""
24
+ server.set_log_path(logfile)
25
+ try:
26
+ uvicorn.run(
27
+ "log4lab.server:app",
28
+ host=host,
29
+ port=port,
30
+ reload=reload,
31
+ timeout_graceful_shutdown=0 # Exit immediately on CTRL-C
32
+ )
33
+ except KeyboardInterrupt:
34
+ pass # Exit immediately without waiting
35
+
36
+
37
+ @app.command()
38
+ def tail(
39
+ logfile: Path = typer.Argument(
40
+ "logs/app.log",
41
+ help="Path to the JSONL log file to tail"
42
+ ),
43
+ level: Optional[str] = typer.Option(None, "--level", "-l", help="Filter by log level (e.g., INFO, ERROR)"),
44
+ section: Optional[str] = typer.Option(None, "--section", "-s", help="Filter by section name"),
45
+ run_name: Optional[str] = typer.Option(None, "--run-name", "-r", help="Filter by run name"),
46
+ run_id: Optional[str] = typer.Option(None, "--run-id", help="Filter by run ID"),
47
+ group: Optional[str] = typer.Option(None, "--group", "-g", help="Filter by group name"),
48
+ time_range: Optional[int] = typer.Option(None, "--time-range", "-t", help="Only show logs from last N seconds"),
49
+ follow: bool = typer.Option(True, "--follow/--no-follow", "-f", help="Follow log file for new entries"),
50
+ show_images: bool = typer.Option(True, "--images/--no-images", help="Try to show images inline in terminal"),
51
+ open_images: bool = typer.Option(False, "--open-images", help="Open images in system default viewer (Preview, etc.)"),
52
+ ):
53
+ """Tail logs to the terminal with rich formatting and filters."""
54
+ tailer = LogTailer(
55
+ log_path=logfile,
56
+ level=level,
57
+ section=section,
58
+ run_name=run_name,
59
+ run_id=run_id,
60
+ group=group,
61
+ time_range=time_range,
62
+ follow=follow,
63
+ show_images=show_images,
64
+ open_images=open_images,
65
+ )
66
+ try:
67
+ tailer.tail()
68
+ except KeyboardInterrupt:
69
+ pass # Clean exit on Ctrl+C
70
+
71
+
72
+ @app.command()
73
+ def export(
74
+ logfile: Path = typer.Argument(
75
+ "logs/app.log",
76
+ help="Path to the JSONL log file to export"
77
+ ),
78
+ output: Path = typer.Option(
79
+ "logs-export.html",
80
+ "--output", "-o",
81
+ help="Output HTML file path"
82
+ ),
83
+ title: str = typer.Option(
84
+ "Log4Lab Export",
85
+ "--title", "-t",
86
+ help="Title for the HTML page"
87
+ ),
88
+ no_embed_images: bool = typer.Option(
89
+ False,
90
+ "--no-embed-images",
91
+ help="Don't embed images as base64 (reduces file size but images won't be included)"
92
+ ),
93
+ ):
94
+ """Export logs to a self-contained HTML file with embedded images and working filters."""
95
+ if not logfile.exists():
96
+ typer.echo(f"Error: Log file '{logfile}' does not exist.", err=True)
97
+ raise typer.Exit(1)
98
+
99
+ typer.echo(f"Exporting logs from '{logfile}' to '{output}'...")
100
+
101
+ try:
102
+ export_logs_to_html(
103
+ log_path=logfile,
104
+ output_path=output,
105
+ title=title,
106
+ embed_images=not no_embed_images
107
+ )
108
+
109
+ file_size = output.stat().st_size
110
+ size_mb = file_size / (1024 * 1024)
111
+
112
+ typer.echo(f"✓ Export complete! File size: {size_mb:.2f} MB")
113
+ typer.echo(f"✓ Saved to: {output.absolute()}")
114
+ typer.echo(f"\nOpen the file in your browser to view the logs with working filters.")
115
+
116
+ except Exception as e:
117
+ typer.echo(f"Error during export: {e}", err=True)
118
+ raise typer.Exit(1)
119
+
120
+
121
+ if __name__ == "__main__":
122
+ app()
123
+