lightlogger 0.1.0__tar.gz
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.
- lightlogger-0.1.0/.github/workflows/ci.yml +36 -0
- lightlogger-0.1.0/.github/workflows/publish.yml +44 -0
- lightlogger-0.1.0/.gitignore +27 -0
- lightlogger-0.1.0/.pre-commit-config.yaml +7 -0
- lightlogger-0.1.0/CHANGELOG.md +32 -0
- lightlogger-0.1.0/CLAUDE.md +219 -0
- lightlogger-0.1.0/CONTRIBUTING.md +34 -0
- lightlogger-0.1.0/LICENSE +21 -0
- lightlogger-0.1.0/PKG-INFO +122 -0
- lightlogger-0.1.0/README.md +90 -0
- lightlogger-0.1.0/STATUS.md +61 -0
- lightlogger-0.1.0/assets/.gitkeep +0 -0
- lightlogger-0.1.0/assets/demo.gif +0 -0
- lightlogger-0.1.0/pyproject.toml +61 -0
- lightlogger-0.1.0/src/lightlogger/__init__.py +249 -0
- lightlogger-0.1.0/src/lightlogger/buffer.py +89 -0
- lightlogger-0.1.0/src/lightlogger/handler.py +40 -0
- lightlogger-0.1.0/src/lightlogger/py.typed +0 -0
- lightlogger-0.1.0/src/lightlogger/server.py +147 -0
- lightlogger-0.1.0/src/lightlogger/sse.py +30 -0
- lightlogger-0.1.0/src/lightlogger/static/help.html +247 -0
- lightlogger-0.1.0/src/lightlogger/static/index.html +947 -0
- lightlogger-0.1.0/tests/test_buffer.py +270 -0
- lightlogger-0.1.0/tests/test_handler.py +115 -0
- lightlogger-0.1.0/tests/test_help.py +49 -0
- lightlogger-0.1.0/tests/test_server.py +391 -0
- lightlogger-0.1.0/tests/test_sse.py +63 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
19
|
+
uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install package with dev extras
|
|
24
|
+
run: pip install -e ".[dev]"
|
|
25
|
+
|
|
26
|
+
- name: Lint with ruff
|
|
27
|
+
run: ruff check src tests
|
|
28
|
+
|
|
29
|
+
- name: Check formatting with ruff
|
|
30
|
+
run: ruff format --check src tests
|
|
31
|
+
|
|
32
|
+
- name: Type check with mypy
|
|
33
|
+
run: mypy src/lightlogger
|
|
34
|
+
|
|
35
|
+
- name: Run tests with coverage
|
|
36
|
+
run: pytest -v --cov=lightlogger --cov-report=term-missing
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.12"
|
|
17
|
+
|
|
18
|
+
- name: Install build tool
|
|
19
|
+
run: pip install build
|
|
20
|
+
|
|
21
|
+
- name: Build sdist and wheel
|
|
22
|
+
run: python -m build
|
|
23
|
+
|
|
24
|
+
- name: Upload build artifacts
|
|
25
|
+
uses: actions/upload-artifact@v4
|
|
26
|
+
with:
|
|
27
|
+
name: dist
|
|
28
|
+
path: dist/
|
|
29
|
+
|
|
30
|
+
publish:
|
|
31
|
+
needs: build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment: pypi
|
|
34
|
+
permissions:
|
|
35
|
+
id-token: write
|
|
36
|
+
steps:
|
|
37
|
+
- name: Download build artifacts
|
|
38
|
+
uses: actions/download-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
|
|
43
|
+
- name: Publish to PyPI
|
|
44
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.eggs/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# Testing / type-checking / linting caches
|
|
18
|
+
.pytest_cache/
|
|
19
|
+
.mypy_cache/
|
|
20
|
+
.ruff_cache/
|
|
21
|
+
.coverage
|
|
22
|
+
htmlcov/
|
|
23
|
+
|
|
24
|
+
# Editors / OS
|
|
25
|
+
.vscode/
|
|
26
|
+
.idea/
|
|
27
|
+
.DS_Store
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Ideas (post-v1, not in scope for now)
|
|
11
|
+
|
|
12
|
+
- Multiple UI layouts / themes beyond dark mode
|
|
13
|
+
- AI-assisted log summarization
|
|
14
|
+
- Auth/login for the dashboard
|
|
15
|
+
- Flask/Django/FastAPI middleware integrations
|
|
16
|
+
- Node.js port
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2026-09-09
|
|
19
|
+
|
|
20
|
+
Initial release.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `lightlogger.start()` / `stop()` — a `ThreadingHTTPServer` on a daemon thread, binding `127.0.0.1:4356` by default with automatic port fallback on conflict; `host="0.0.0.0"` is an explicit, loudly-warned opt-in.
|
|
25
|
+
- `debug()` / `info()` / `warn()` / `error()` / `var()` / `request()` — write into a bounded in-memory ring buffer (`max_logs`, default 5000), with caller file/line captured via `sys._getframe` (never `inspect.stack()`).
|
|
26
|
+
- `lightlogger.group(name)` — a context manager for nested log grouping, backed by `contextvars` for thread/async safety.
|
|
27
|
+
- Zero-code `logging` capture: `start(capture_logging=True)` (the default) attaches a handler to the root logger, so existing and third-party `logging` calls appear automatically.
|
|
28
|
+
- Live dashboard UI (`GET /`, single self-contained HTML file, no CDN): dark theme, level-colored rows, search box, level filter, pause/resume with no dropped records, click-to-expand detail panels, download-as-JSON, a collapsible group tree with per-group counts and a worst-level badge.
|
|
29
|
+
- `GET /api/logs` (JSON backlog), `GET /api/stream` (Server-Sent Events, replacing polling), `POST /api/clear`.
|
|
30
|
+
- `open_browser=True` opens the dashboard automatically on `start()`.
|
|
31
|
+
- `lightlogger.help()` prints a full API cheatsheet to the terminal; `GET /help` serves the same reference as an offline HTML page, linked from the dashboard header.
|
|
32
|
+
- 100% test coverage, `mypy --strict` clean, CI across Python 3.9–3.13, PyPI publishing via Trusted Publishing (OIDC) — no long-lived API token ever stored.
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# CLAUDE.md — Developer Brief for "lightlogger"
|
|
2
|
+
|
|
3
|
+
You (Claude Code) are the developer of this project. The owner is a solo student developer building this part-time. Follow this brief exactly. When in doubt, choose the simpler option.
|
|
4
|
+
|
|
5
|
+
> **Naming note:** the product was originally conceived as "lightlog" but that name is taken on PyPI. The distribution/import name is **`lightlogger`** everywhere below (PyPI verified free on 2026-09-09).
|
|
6
|
+
|
|
7
|
+
> **Scope amendment (post-Phase 6, 2026-09-09):** log grouping (`lightlogger.group(name)`) was added to the frozen v1 API as **Phase 6.5**, before Phase 7. This is a deliberate exception to golden rule "exactly this, nothing more" — the owner evaluated it as a real differentiator (no competing zero-dep local dashboard has nested log grouping) rather than scope creep, and chose to formalize it here rather than build it as an undocumented add-on. See the new API entry in section 5, the amended record structure in section 3, section 6.5, and Phase 6.5 in section 7.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. WHAT WE ARE BUILDING
|
|
12
|
+
|
|
13
|
+
**lightlogger** — a zero-dependency Python library that gives developers a live web dashboard for their application logs.
|
|
14
|
+
|
|
15
|
+
Developer experience (this is sacred, never break it):
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
pip install lightlogger
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import lightlogger
|
|
23
|
+
lightlogger.start() # UI now live at http://127.0.0.1:4356
|
|
24
|
+
lightlogger.info("user logged in")
|
|
25
|
+
lightlogger.error("payment failed", data={"order_id": 123})
|
|
26
|
+
lightlogger.var("cart", cart_dict) # expandable JSON in UI
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Two lines to a live, beautiful, dark-theme log dashboard in the browser. Zero config. Zero third-party dependencies. That is the entire product.
|
|
30
|
+
|
|
31
|
+
**Why this wins (from competitive research):** No existing package does "import → background thread → live localhost web UI, zero config, zero deps." Logdy is a Go binary, Chronologer needs a separate server, cutelog needs PyQt, Logfire is closed-source cloud, lnav/klp are terminal-only, Django/Flask debug toolbars are framework-locked. Our moat = developer experience + zero dependencies + in-process capture.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. GOLDEN RULES (NEVER VIOLATE)
|
|
36
|
+
|
|
37
|
+
1. **ZERO third-party dependencies.** Python standard library ONLY. `dependencies = []` in pyproject.toml stays empty forever. If you ever feel you need a package — you don't; find the stdlib way.
|
|
38
|
+
2. **Bind to `127.0.0.1` ONLY.** Never `0.0.0.0`. LAN exposure must be an explicit opt-in parameter (`host="0.0.0.0"`) that prints a loud warning when used. Logs are sensitive data.
|
|
39
|
+
3. **Bounded memory.** All logs live in `collections.deque(maxlen=5000)` (configurable). The tool must NEVER be the reason a user's app runs out of RAM.
|
|
40
|
+
4. **Never block the user's app.** Server runs in a daemon thread. Logging calls must return in microseconds. No disk I/O in the logging hot path.
|
|
41
|
+
5. **Off unless started.** Nothing runs until the user calls `lightlogger.start()`. Document clearly: do not run in production.
|
|
42
|
+
6. **Simple > clever.** Vanilla HTML/JS/CSS in ONE file for the UI. No React, no build step, no npm anywhere in v1.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. HARD TECHNICAL REQUIREMENTS (from research)
|
|
47
|
+
|
|
48
|
+
### Server
|
|
49
|
+
- Use `http.server.ThreadingHTTPServer` (NOT plain `HTTPServer` — SSE holds connections open and would block a single-threaded server).
|
|
50
|
+
- Subclass with `daemon_threads = True` and `allow_reuse_address = True`.
|
|
51
|
+
- Run `serve_forever()` inside `threading.Thread(daemon=True)`.
|
|
52
|
+
- Provide `lightlogger.stop()` → calls `httpd.shutdown()` + `server_close()` (needed for tests/notebooks).
|
|
53
|
+
- Port: default 4356. On `OSError` (port busy), auto-increment to next free port. Always print the final URL to stdout: `lightlogger UI → http://127.0.0.1:4356`.
|
|
54
|
+
|
|
55
|
+
### Live streaming (SSE, not websockets)
|
|
56
|
+
- Endpoint `/api/stream` responds with headers: `Content-Type: text/event-stream`, `Cache-Control: no-cache`, `Connection: keep-alive`.
|
|
57
|
+
- Each event: `data: <json>\n\n` (double newline is mandatory).
|
|
58
|
+
- Send `retry: 3000` at connection start and a heartbeat comment line (`: ping\n\n`) every ~15s.
|
|
59
|
+
- Fan-out pattern: each connected client gets its own `queue.Queue`. New log records get pushed to every client queue. Guard the client-queue set with `threading.Lock`.
|
|
60
|
+
- Browser side is simply `new EventSource('/api/stream')`.
|
|
61
|
+
|
|
62
|
+
### Ring buffer
|
|
63
|
+
- `collections.deque(maxlen=N)`. `append()` is atomic in CPython, but take a lock when ITERATING (serving backlog to a new client).
|
|
64
|
+
|
|
65
|
+
### Caller file/line capture — PERFORMANCE CRITICAL
|
|
66
|
+
- **NEVER use `inspect.stack()`** — it reads source files from disk on every call. Forbidden in the hot path.
|
|
67
|
+
- Use the stdlib logging approach: `sys._getframe()` / walk `frame.f_back`, read `frame.f_code.co_filename` and `frame.f_lineno`. Cheap attribute reads, no I/O.
|
|
68
|
+
|
|
69
|
+
### stdlib logging integration (killer feature — must have in v1)
|
|
70
|
+
- Ship `LightloggerHandler(logging.Handler)` whose `emit(record)` pushes into the buffer + SSE queues.
|
|
71
|
+
- `lightlogger.start(capture_logging=True)` attaches it to the root logger → user's EXISTING `logging` calls and even third-party library logs appear in the UI with zero code changes.
|
|
72
|
+
- Record structure (amended in Phase 6.5): `{time, level, message, data, file, line, logger_name, group_id, parent_group_id}`. `group_id`/`parent_group_id` are always-present keys (never omitted), typed `str | None` — every field defaults to `None` for code untouched by `group()`, so this is a value-level amendment, not a structural one. No `typing.NotRequired`/`typing_extensions` — that would violate zero-dependency on Python 3.9/3.10, where `NotRequired` doesn't exist in stdlib `typing`.
|
|
73
|
+
|
|
74
|
+
### Log grouping (`lightlogger.group(name)`) — Phase 6.5
|
|
75
|
+
- A context manager: `with lightlogger.group("process_order #123"): ...`. On enter, emits one group-marker record immediately (so even an empty group is visible); on exit, restores the previous group context. Nesting supported (a `group()` inside a `group()`).
|
|
76
|
+
- A group-marker record is just a normal `LogRecord` with `group_id` set to a fresh id (e.g. `uuid.uuid4().hex`) and `message` set to the group's display name. Every OTHER record (marker or plain log) gets `parent_group_id` set to whatever group directly contains it (`None` if top-level) — this is how nesting and containment are reconstructed client-side, by walking `parent_group_id` chains.
|
|
77
|
+
- Thread/async safety via `contextvars.ContextVar` (stdlib) holding "the current group id" — NOT a plain module global, which would leak across threads. Each thread that doesn't explicitly share context gets its own independent value; each asyncio `Task` gets its own copy at creation. This is what makes concurrent, non-interleaving grouping possible without extra locking.
|
|
78
|
+
- Group-marker records stream over `/api/stream` and appear in `/api/logs` exactly like any other record — no special-casing needed in `buffer.py`/`server.py`, since `LogBuffer.add()` already treats every record uniformly.
|
|
79
|
+
|
|
80
|
+
### Packaging the UI
|
|
81
|
+
- One file: `src/lightlogger/static/index.html` (HTML + CSS + JS inline).
|
|
82
|
+
- Read it at runtime with `importlib.resources.files("lightlogger") / "static" / "index.html"` — NEVER build paths from `__file__`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 4. FOLDER STRUCTURE (create exactly this — src layout)
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
lightlog/ # workspace/repo root (kept as-is on disk)
|
|
90
|
+
├── src/
|
|
91
|
+
│ └── lightlogger/ # actual PyPI distribution + import name
|
|
92
|
+
│ ├── __init__.py # public API: start, stop, info, warn, error, debug, var, request
|
|
93
|
+
│ ├── server.py # ThreadingHTTPServer + daemon thread + routes
|
|
94
|
+
│ ├── handler.py # LightloggerHandler (logging.Handler subclass)
|
|
95
|
+
│ ├── buffer.py # ring buffer + SSE client fan-out
|
|
96
|
+
│ ├── sse.py # SSE framing helpers
|
|
97
|
+
│ ├── py.typed # empty PEP 561 marker
|
|
98
|
+
│ └── static/
|
|
99
|
+
│ └── index.html # entire UI, single file
|
|
100
|
+
├── tests/
|
|
101
|
+
│ ├── test_buffer.py
|
|
102
|
+
│ ├── test_handler.py
|
|
103
|
+
│ └── test_server.py
|
|
104
|
+
├── .github/workflows/
|
|
105
|
+
│ ├── ci.yml # ruff + pytest, matrix Python 3.9–3.13
|
|
106
|
+
│ └── publish.yml # PyPI Trusted Publishing on GitHub Release
|
|
107
|
+
├── assets/
|
|
108
|
+
│ └── demo.gif # README demo (added later)
|
|
109
|
+
├── pyproject.toml # PEP 621
|
|
110
|
+
├── README.md
|
|
111
|
+
├── CHANGELOG.md # Keep a Changelog format
|
|
112
|
+
├── CONTRIBUTING.md
|
|
113
|
+
├── LICENSE # MIT
|
|
114
|
+
├── .pre-commit-config.yaml # ruff lint + format
|
|
115
|
+
└── .gitignore
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### pyproject.toml requirements
|
|
119
|
+
- Build backend: **Hatchling** (auto-detects src layout, includes package data).
|
|
120
|
+
- `[project]`: name `lightlogger`, version `0.1.0`, `requires-python = ">=3.9"`, description, readme, MIT license, classifiers, urls.
|
|
121
|
+
- `dependencies = []` — EMPTY, forever.
|
|
122
|
+
- `[project.optional-dependencies] dev = ["pytest", "ruff", "mypy"]`.
|
|
123
|
+
- Tool configs in same file: `[tool.pytest.ini_options]`, `[tool.ruff]`, `[tool.mypy]` (strict).
|
|
124
|
+
- Semantic Versioning. Full type hints everywhere + `py.typed`.
|
|
125
|
+
|
|
126
|
+
### Publishing
|
|
127
|
+
- PyPI **Trusted Publishing (OIDC)** via `pypa/gh-action-pypi-publish` with `permissions: id-token: write`. **NEVER store a long-lived PyPI API token as a GitHub secret** (this exact mistake caused the LiteLLM supply-chain attack, March 2026).
|
|
128
|
+
- Name "lightlogger" confirmed free on PyPI (checked 2026-09-09).
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 5. PUBLIC API (v1 — exactly this, nothing more)
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
lightlogger.start(port=4356, host="127.0.0.1", max_logs=5000, capture_logging=True, open_browser=False)
|
|
136
|
+
lightlogger.stop()
|
|
137
|
+
lightlogger.debug(msg, data=None)
|
|
138
|
+
lightlogger.info(msg, data=None)
|
|
139
|
+
lightlogger.warn(msg, data=None)
|
|
140
|
+
lightlogger.error(msg, data=None)
|
|
141
|
+
lightlogger.var(name, value) # logs any variable as expandable JSON
|
|
142
|
+
lightlogger.request(method, url, status, duration_ms) # API/request logging
|
|
143
|
+
lightlogger.group(name) # context manager; nested groups supported (Phase 6.5)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
HTTP routes: `/` (UI), `/api/logs` (JSON backlog), `/api/stream` (SSE), `/api/clear` (POST, clears buffer).
|
|
147
|
+
|
|
148
|
+
`data`/`value` serialization: `json.dumps(..., default=str)` so any object works without crashing.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 6. UI REQUIREMENTS (single index.html)
|
|
153
|
+
|
|
154
|
+
Must have in v1: dark theme (default), color-coded levels (debug grey, info blue, warn yellow, error red), live auto-scroll with **pause** button, **clear logs** button (calls /api/clear), search box (client-side filter), level filter dropdown, click row → expand full details (file:line, logger, pretty JSON data), download logs as .json button, connection status dot (connected/reconnecting), log counter.
|
|
155
|
+
|
|
156
|
+
Style: clean, modern, monospace font for messages, subtle borders, feels like a premium dev tool. No external fonts/CDNs (zero network deps — must work offline).
|
|
157
|
+
|
|
158
|
+
## 6.5. UI REQUIREMENTS — log grouping (Phase 6.5)
|
|
159
|
+
|
|
160
|
+
- A group-marker record renders as a collapsible header row, not a normal log line: chevron/dropdown icon (inline SVG, no icon fonts, no CDN — same offline constraint as everything else), group name, child count, and a "worst-level" hint (e.g. a red tint/badge if any descendant is `error`-level) — computed client-side by walking descendants, no new server logic needed.
|
|
161
|
+
- Nested groups indent their children (mirrors the browser DevTools `console.group`/`console.groupEnd` UX — a familiar reference point, not an arbitrary accordion pattern). Chevron rotates on toggle — this is user-triggered motion answering a click, not ambient decoration, so it's fine alongside the existing single ambient motion moment (the reconnecting-dot pulse from Phase 6).
|
|
162
|
+
- Collapsed by default. Toolbar gains expand-all / collapse-all buttons.
|
|
163
|
+
- Search/filter must work across grouped records: a match on a nested record auto-expands its ancestor chain so the match is visible; a group the user manually expanded/collapsed keeps that state once the search is cleared (search-driven expansion doesn't overwrite a person's own manual choice).
|
|
164
|
+
- Visual theme gets richer/more colorful for this phase specifically — level colors (debug/info/warn/error) stay meaningful and unchanged, but group-related chrome (headers, chevrons, badges) can use more vivid accents than Phase 6's restrained palette, as a deliberate one-time exception to "keep it restrained."
|
|
165
|
+
- Still one file, no CDN, fully offline, zero third-party dependencies.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 7. BUILD PHASES (do IN ORDER, one phase per session, test before moving on)
|
|
170
|
+
|
|
171
|
+
- **Phase 0:** Scaffold repo (structure above), pyproject.toml, LICENSE, ruff, pre-commit, empty CI. Verify `pip install -e .` works.
|
|
172
|
+
- **Phase 1:** buffer.py + logger functions writing to deque. Unit tests. No server yet.
|
|
173
|
+
- **Phase 2:** server.py — daemon thread, `/api/logs` returns JSON. Milestone: see JSON in browser.
|
|
174
|
+
- **Phase 3:** Basic index.html — polls `/api/logs` every 2s, renders list. Ugly is fine.
|
|
175
|
+
- **Phase 4:** SSE — `/api/stream`, EventSource, live updates, heartbeat, reconnect. Remove polling.
|
|
176
|
+
- **Phase 5:** LightloggerHandler + `capture_logging` + file/line capture via `sys._getframe`.
|
|
177
|
+
- **Phase 6:** Full UI polish — search, filters, pause, clear, expand, download, status dot.
|
|
178
|
+
- **Phase 6.5:** Log grouping — `lightlogger.group(name)` context manager (contextvars-based, thread/async-safe, nested), amended record structure, UI collapsible group rows with expand/collapse-all and search-aware auto-expand. Scope amendment, see note at top of this file.
|
|
179
|
+
- **Phase 7:** Tests to ~80% coverage, CI green on 3.9–3.13, mypy strict passes, README + demo GIF, publish 0.1.0 via Trusted Publishing.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 8. DO NOT (explicit bans for v1)
|
|
184
|
+
|
|
185
|
+
- ❌ NO third-party packages (not even in tests beyond pytest/ruff/mypy as dev deps)
|
|
186
|
+
- ❌ NO websockets (SSE only), NO React/Vue/build tools/npm, NO CDN links in the UI
|
|
187
|
+
- ❌ NO database, NO writing logs to disk
|
|
188
|
+
- ❌ NO `inspect.stack()` in the logging path
|
|
189
|
+
- ❌ NO `0.0.0.0` default binding
|
|
190
|
+
- ❌ NO multiple layouts, NO AI features, NO auth/login, NO Flask/Django/FastAPI middleware, NO Node.js port — these are ALL post-v1. If tempted, add a note to CHANGELOG "Unreleased/Ideas" instead of building.
|
|
191
|
+
- ❌ NO flat layout — src layout only
|
|
192
|
+
- ❌ NO committing secrets/tokens ever
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 9. README (write at Phase 7 — it's our landing page)
|
|
197
|
+
|
|
198
|
+
Order: name + one-liner → badges (PyPI, CI, license, Python versions) → **demo GIF** (before any scrolling — the single biggest factor for stars) → install + 2-line quickstart within first 200 words → features table → "Why lightlogger" (problem story) → contributing invite → MIT.
|
|
199
|
+
|
|
200
|
+
One-liner: "Live web dashboard for your Python logs — pip install, add one line, open localhost:4356. Zero dependencies."
|
|
201
|
+
|
|
202
|
+
Length 500–1500 words. GIF: <15s, ~640px, <8MB, stored in /assets. Add a security note: localhost-only by default, not for production.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## 10. DEFINITION OF DONE (v1)
|
|
207
|
+
|
|
208
|
+
- [ ] `pip install lightlogger` + 2 lines → working live UI
|
|
209
|
+
- [ ] Zero runtime dependencies (verify: fresh venv, install, run)
|
|
210
|
+
- [ ] Existing `logging` calls appear in UI automatically
|
|
211
|
+
- [ ] RAM bounded (deque maxlen honored under log flood)
|
|
212
|
+
- [ ] Binds 127.0.0.1 only by default; warning on override
|
|
213
|
+
- [ ] Search/filter/pause/clear/expand/download all work
|
|
214
|
+
- [ ] `lightlogger.group()` nests correctly and stays isolated across concurrent threads
|
|
215
|
+
- [ ] Survives: port conflict, browser refresh, SSE reconnect, `stop()` + `start()` again
|
|
216
|
+
- [ ] Tests pass 3.9–3.13, mypy strict clean, ruff clean
|
|
217
|
+
- [ ] README with GIF, CHANGELOG, published on PyPI via Trusted Publishing
|
|
218
|
+
|
|
219
|
+
Owner's context: this project is also his portfolio piece for LinkedIn/recruiters — so code quality, comments, commit messages, and docs must look professional throughout. Write commit messages in conventional style (`feat:`, `fix:`, `docs:`).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Contributing to lightlogger
|
|
2
|
+
|
|
3
|
+
Thanks for considering a contribution! This project intentionally stays small and dependency-free — please read the ground rules before opening a PR.
|
|
4
|
+
|
|
5
|
+
## Ground rules
|
|
6
|
+
|
|
7
|
+
- **Zero third-party runtime dependencies.** Standard library only. Dev-only tools (`pytest`, `ruff`, `mypy`) are the sole exception.
|
|
8
|
+
- **Binds to `127.0.0.1` by default.** Any change touching networking must preserve this.
|
|
9
|
+
- **No `inspect.stack()`** in the logging hot path — use `sys._getframe()`.
|
|
10
|
+
- **Vanilla HTML/CSS/JS** for the dashboard UI — no frameworks, no build step, no CDN links.
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git clone https://github.com/Rahuwale123/lightlogger.git
|
|
16
|
+
cd lightlogger
|
|
17
|
+
python -m venv .venv
|
|
18
|
+
source .venv/bin/activate
|
|
19
|
+
pip install -e ".[dev]"
|
|
20
|
+
pre-commit install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Before opening a PR
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ruff check .
|
|
27
|
+
ruff format --check .
|
|
28
|
+
mypy src/lightlogger
|
|
29
|
+
pytest
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Commit style
|
|
33
|
+
|
|
34
|
+
This repo uses [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, ...).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rahul wale
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lightlogger
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Live web dashboard for your Python logs — one line of code, zero dependencies
|
|
5
|
+
Project-URL: Homepage, https://github.com/Rahuwale123/lightlogger
|
|
6
|
+
Project-URL: Repository, https://github.com/Rahuwale123/lightlogger
|
|
7
|
+
Project-URL: Changelog, https://github.com/Rahuwale123/lightlogger/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: rahul wale <fthedrive@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: dashboard,debugging,developer-tools,logging,logs,sse
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
23
|
+
Classifier: Topic :: System :: Logging
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# lightlogger
|
|
34
|
+
|
|
35
|
+
Live web dashboard for your Python logs — pip install, add one line, open localhost:4356. Zero dependencies.
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/lightlogger/)
|
|
38
|
+
[](https://github.com/Rahuwale123/lightlogger/actions/workflows/ci.yml)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[](pyproject.toml)
|
|
41
|
+
|
|
42
|
+

|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install lightlogger
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import lightlogger
|
|
52
|
+
lightlogger.start()
|
|
53
|
+
lightlogger.info("user logged in")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
That's it — open `http://127.0.0.1:4356` and watch it stream in live, in a dark, searchable dashboard. No config file, no separate server process to run, no npm install.
|
|
57
|
+
|
|
58
|
+
Your existing `logging` calls show up too, with zero code changes:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
lightlogger.start(capture_logging=True) # the default
|
|
62
|
+
import logging
|
|
63
|
+
logging.getLogger("some.third.party.lib").warning("disk almost full") # appears in the UI automatically
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
And when one log message is really four related operations, group them:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
with lightlogger.group("process_order #4821"):
|
|
70
|
+
lightlogger.info("validating cart")
|
|
71
|
+
lightlogger.info("charging payment", data={"amount": 49.99})
|
|
72
|
+
with lightlogger.group("send_notifications"):
|
|
73
|
+
lightlogger.info("email sent")
|
|
74
|
+
lightlogger.info("sms sent")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
They render as a collapsible tree — click to expand, nested groups indent, a red badge appears if anything inside failed.
|
|
78
|
+
|
|
79
|
+
## Features
|
|
80
|
+
|
|
81
|
+
| | |
|
|
82
|
+
|---|---|
|
|
83
|
+
| **Live streaming** | Server-Sent Events, not polling — logs appear the instant they're written |
|
|
84
|
+
| **Zero-code `logging` capture** | Attaches to the root logger; your own and third-party libraries' logs show up automatically |
|
|
85
|
+
| **Nested log grouping** | `with lightlogger.group(name):` — collapsible, nested, thread- and async-safe |
|
|
86
|
+
| **Search & level filter** | Client-side, instant, works across grouped and ungrouped logs alike |
|
|
87
|
+
| **Pause / Resume** | Freeze the view to read something — nothing is dropped, a counter shows what's waiting |
|
|
88
|
+
| **Click to expand** | File, line, logger name, and pretty-printed JSON data for any entry |
|
|
89
|
+
| **Download as JSON** | One click, the full current backlog |
|
|
90
|
+
| **Bounded memory** | A fixed-size ring buffer — lightlogger can never be the reason your app runs out of RAM |
|
|
91
|
+
| **Zero dependencies** | Python standard library only, from the HTTP server to the JSON encoding |
|
|
92
|
+
|
|
93
|
+
## Built-in help
|
|
94
|
+
|
|
95
|
+
Forgot the API? It's in the package, not just this README:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
>>> import lightlogger
|
|
99
|
+
>>> lightlogger.help()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Prints a full cheatsheet to your terminal — every function, one-line descriptions, tiny examples, the `capture_logging` gotcha, no need to leave your shell. And once the dashboard is running, open `http://127.0.0.1:4356/help` for the same reference as a page, with copyable code blocks.
|
|
103
|
+
|
|
104
|
+
## Why lightlogger
|
|
105
|
+
|
|
106
|
+
Debugging a running Python process usually means one of three things: `print()` statements you'll forget to remove, a terminal window full of scrolling text you can't search, or reaching for a heavyweight observability platform to answer a question that takes ten seconds to answer once you can actually *see* your logs.
|
|
107
|
+
|
|
108
|
+
We looked at what else exists. [Logdy](https://logdy.dev/) is a Go binary you install separately from your app. [Chronologer](https://github.com/nkconnor/chronologer) needs its own server process. [cutelog](https://github.com/busimus/cutelog) needs PyQt. Logfire is closed-source and cloud-hosted. `lnav` and `klp` are terminal-only. Django and Flask debug toolbars only work inside those specific frameworks, in that specific request/response cycle.
|
|
109
|
+
|
|
110
|
+
None of them do the one thing that actually matches how a Python developer debugs: `import`, call one function, and get a live web page — from *inside* the process you're already running, with no extra install, no extra service, no dependencies to audit. That's the gap lightlogger fills. And once your logs have a real UI instead of a scrolling terminal, grouping related operations into a collapsible tree stops being a nice-to-have — it's the difference between reading a wall of text and reading a story.
|
|
111
|
+
|
|
112
|
+
## Security
|
|
113
|
+
|
|
114
|
+
lightlogger binds to `127.0.0.1` only, by default — nothing is reachable outside your machine unless you explicitly pass `host="0.0.0.0"` (which prints a loud warning when you do). It's a local development tool, not a production observability system: don't run it against a public-facing process, and don't leave it running longer than your debugging session needs.
|
|
115
|
+
|
|
116
|
+
## Contributing
|
|
117
|
+
|
|
118
|
+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the ground rules (short version: zero dependencies, stay on stdlib, keep it simple). Bug reports and PRs both go through [GitHub Issues](https://github.com/Rahuwale123/lightlogger/issues).
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# lightlogger
|
|
2
|
+
|
|
3
|
+
Live web dashboard for your Python logs — pip install, add one line, open localhost:4356. Zero dependencies.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/lightlogger/)
|
|
6
|
+
[](https://github.com/Rahuwale123/lightlogger/actions/workflows/ci.yml)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](pyproject.toml)
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install lightlogger
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import lightlogger
|
|
20
|
+
lightlogger.start()
|
|
21
|
+
lightlogger.info("user logged in")
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That's it — open `http://127.0.0.1:4356` and watch it stream in live, in a dark, searchable dashboard. No config file, no separate server process to run, no npm install.
|
|
25
|
+
|
|
26
|
+
Your existing `logging` calls show up too, with zero code changes:
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
lightlogger.start(capture_logging=True) # the default
|
|
30
|
+
import logging
|
|
31
|
+
logging.getLogger("some.third.party.lib").warning("disk almost full") # appears in the UI automatically
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
And when one log message is really four related operations, group them:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
with lightlogger.group("process_order #4821"):
|
|
38
|
+
lightlogger.info("validating cart")
|
|
39
|
+
lightlogger.info("charging payment", data={"amount": 49.99})
|
|
40
|
+
with lightlogger.group("send_notifications"):
|
|
41
|
+
lightlogger.info("email sent")
|
|
42
|
+
lightlogger.info("sms sent")
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
They render as a collapsible tree — click to expand, nested groups indent, a red badge appears if anything inside failed.
|
|
46
|
+
|
|
47
|
+
## Features
|
|
48
|
+
|
|
49
|
+
| | |
|
|
50
|
+
|---|---|
|
|
51
|
+
| **Live streaming** | Server-Sent Events, not polling — logs appear the instant they're written |
|
|
52
|
+
| **Zero-code `logging` capture** | Attaches to the root logger; your own and third-party libraries' logs show up automatically |
|
|
53
|
+
| **Nested log grouping** | `with lightlogger.group(name):` — collapsible, nested, thread- and async-safe |
|
|
54
|
+
| **Search & level filter** | Client-side, instant, works across grouped and ungrouped logs alike |
|
|
55
|
+
| **Pause / Resume** | Freeze the view to read something — nothing is dropped, a counter shows what's waiting |
|
|
56
|
+
| **Click to expand** | File, line, logger name, and pretty-printed JSON data for any entry |
|
|
57
|
+
| **Download as JSON** | One click, the full current backlog |
|
|
58
|
+
| **Bounded memory** | A fixed-size ring buffer — lightlogger can never be the reason your app runs out of RAM |
|
|
59
|
+
| **Zero dependencies** | Python standard library only, from the HTTP server to the JSON encoding |
|
|
60
|
+
|
|
61
|
+
## Built-in help
|
|
62
|
+
|
|
63
|
+
Forgot the API? It's in the package, not just this README:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
>>> import lightlogger
|
|
67
|
+
>>> lightlogger.help()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Prints a full cheatsheet to your terminal — every function, one-line descriptions, tiny examples, the `capture_logging` gotcha, no need to leave your shell. And once the dashboard is running, open `http://127.0.0.1:4356/help` for the same reference as a page, with copyable code blocks.
|
|
71
|
+
|
|
72
|
+
## Why lightlogger
|
|
73
|
+
|
|
74
|
+
Debugging a running Python process usually means one of three things: `print()` statements you'll forget to remove, a terminal window full of scrolling text you can't search, or reaching for a heavyweight observability platform to answer a question that takes ten seconds to answer once you can actually *see* your logs.
|
|
75
|
+
|
|
76
|
+
We looked at what else exists. [Logdy](https://logdy.dev/) is a Go binary you install separately from your app. [Chronologer](https://github.com/nkconnor/chronologer) needs its own server process. [cutelog](https://github.com/busimus/cutelog) needs PyQt. Logfire is closed-source and cloud-hosted. `lnav` and `klp` are terminal-only. Django and Flask debug toolbars only work inside those specific frameworks, in that specific request/response cycle.
|
|
77
|
+
|
|
78
|
+
None of them do the one thing that actually matches how a Python developer debugs: `import`, call one function, and get a live web page — from *inside* the process you're already running, with no extra install, no extra service, no dependencies to audit. That's the gap lightlogger fills. And once your logs have a real UI instead of a scrolling terminal, grouping related operations into a collapsible tree stops being a nice-to-have — it's the difference between reading a wall of text and reading a story.
|
|
79
|
+
|
|
80
|
+
## Security
|
|
81
|
+
|
|
82
|
+
lightlogger binds to `127.0.0.1` only, by default — nothing is reachable outside your machine unless you explicitly pass `host="0.0.0.0"` (which prints a loud warning when you do). It's a local development tool, not a production observability system: don't run it against a public-facing process, and don't leave it running longer than your debugging session needs.
|
|
83
|
+
|
|
84
|
+
## Contributing
|
|
85
|
+
|
|
86
|
+
Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the ground rules (short version: zero dependencies, stay on stdlib, keep it simple). Bug reports and PRs both go through [GitHub Issues](https://github.com/Rahuwale123/lightlogger/issues).
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT — see [LICENSE](LICENSE).
|