Skip to content

Commit 193290d

Browse files
dchaplinskyclaude
andcommitted
Round out 2.0: HTML page handler, Jupyter support, numpy renderer, PyPI publishing
- New HTMLFileHandler + VisualFormatter: self-contained styled html pages with level-colored record cards, timestamps, logger names, escaped plain-text records and tracebacks; flushed per record so the page can be watched mid-run. Both are plain logging.FileHandler/Formatter subclasses and compose the stdlib way; 1.x usage with a bare FileHandler keeps working - VisualRecord displays inline in Jupyter via _repr_html_ - max_size now applies to matplotlib figures too, by lowering savefig dpi - New render_numpy fallback: numpy arrays render via PIL when OpenCV is not installed - Embedded images get loading="lazy" so large logs open fast - Type hints throughout and a py.typed marker - publish.yml: build + publish to PyPI via trusted publishing on v* tags - demo.py showcases the new handler; README rewritten accordingly Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ha16zNy2fDMdrZBMQGgpBt
1 parent 14d0744 commit 193290d

8 files changed

Lines changed: 390 additions & 32 deletions

File tree

.github/workflows/publish.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags: ["v*"]
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
13+
- uses: actions/setup-python@v5
14+
with:
15+
python-version: "3.13"
16+
17+
- name: Build sdist and wheel
18+
run: |
19+
pip install build
20+
python -m build
21+
22+
- uses: actions/upload-artifact@v4
23+
with:
24+
name: dist
25+
path: dist/
26+
27+
publish:
28+
needs: build
29+
runs-on: ubuntu-latest
30+
environment: pypi
31+
permissions:
32+
id-token: write
33+
34+
steps:
35+
- uses: actions/download-artifact@v4
36+
with:
37+
name: dist
38+
path: dist/
39+
40+
- uses: pypa/gh-action-pypi-publish@release/v1

README.md

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,19 @@ You can read about it in detail in a great blog post [visual-logging, my new fav
1616
pip install visual-logging
1717
```
1818

19-
No extra dependencies — whichever of OpenCV, PIL/Pillow and matplotlib you already have installed are picked up automatically. Requires Python 3.9+.
19+
No extra dependencies — whichever of OpenCV, PIL/Pillow and matplotlib you already have installed are picked up automatically (numpy arrays render through PIL when OpenCV isn't around). Requires Python 3.9+.
2020

2121
## Usage example (see demo.py)
2222

2323
```python
2424
import logging
25-
from logging import FileHandler
26-
from vlogging import VisualRecord
25+
from vlogging import HTMLFileHandler, VisualRecord
2726

2827
import cv2 # or PIL.Image, or matplotlib — whatever you use
2928

3029
logger = logging.getLogger("demo")
3130
logger.setLevel(logging.DEBUG)
32-
logger.addHandler(FileHandler("test.html", mode="w"))
31+
logger.addHandler(HTMLFileHandler("test.html", title="My debug log"))
3332

3433
cv_image = cv2.imread("lenna.jpg")
3534

@@ -42,10 +41,21 @@ logger.warning(VisualRecord(
4241
"Hello from all", [cv_image, pil_image, mpl_figure],
4342
fmt="png", max_size=(320, 240)))
4443

44+
# Ordinary log calls work too, and land in the same page:
45+
logger.info("Processed frame %d", 42)
46+
4547
logging.shutdown() # flushes and closes the html file
4648
```
4749

48-
Open `test.html` in a browser and enjoy. A sample of generated html is available [here](http://dchaplinsky.github.io/visual-logging/).
50+
Open `test.html` in a browser and enjoy: `HTMLFileHandler` writes a styled, self-contained page — records are color-coded by log level with timestamps and logger names, plain messages are escaped, and exceptions logged with `logger.exception(...)` include their traceback. Records are flushed as they happen, so you can watch the page mid-run.
51+
52+
Everything composes the stdlib way: `HTMLFileHandler` is a `logging.FileHandler` that installs a `VisualFormatter` (a `logging.Formatter`) by default — use either piece on its own if you prefer. Passing a `VisualRecord` to a plain `FileHandler` still produces bare html fragments, exactly as in 1.x.
53+
54+
In **Jupyter**, a `VisualRecord` displays itself inline — no logging setup needed:
55+
56+
```python
57+
VisualRecord("Detected edges", edges_img, "Canny output")
58+
```
4959

5060
### `VisualRecord` arguments
5161

@@ -55,12 +65,16 @@ Open `test.html` in a browser and enjoy. A sample of generated html is available
5565
| `imgs` | A single image or a list of images: OpenCV/numpy arrays, PIL images and matplotlib figures in any combination |
5666
| `footnotes` | Optional text rendered as `<pre>` under the images |
5767
| `fmt` | Image format to embed: `png` (default), `jpeg`, `webp` — anything your imaging library can encode |
58-
| `max_size` | Optional `(width, height)` tuple: OpenCV and PIL images bigger than that are downscaled proportionally before embedding, to keep log files readable and small (matplotlib figures are embedded as rendered) |
68+
| `max_size` | Optional `(width, height)` tuple: images bigger than that are downscaled proportionally before embedding (matplotlib figures by lowering the render dpi), to keep log files readable and small |
5969

6070
## Changelog
6171

6272
**2.0**
63-
- Modern packaging (`pyproject.toml`), Python 3.9+ only
64-
- New `max_size` option to downscale embedded images
73+
- Modern packaging (`pyproject.toml`), Python 3.9+ only, `py.typed` type hints
74+
- New `HTMLFileHandler` + `VisualFormatter`: styled, self-contained html pages with level colors, timestamps, escaped plain-text records and tracebacks
75+
- New `max_size` option to downscale embedded images (all renderers)
76+
- `VisualRecord` displays inline in Jupyter notebooks
77+
- numpy arrays render via PIL when OpenCV is not installed
78+
- Embedded images use `loading="lazy"`, so huge logs open fast
6579
- matplotlib support no longer relies on the deprecated `pylab` module
66-
- Tests run on GitHub Actions against Python 3.9–3.13
80+
- Tests run on GitHub Actions against Python 3.9–3.13; releases publish to PyPI from tags via trusted publishing

demo.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import logging
2-
from logging import FileHandler
32
from pathlib import Path
43

5-
from vlogging import VisualRecord
4+
from vlogging import HTMLFileHandler, VisualRecord
65

76
if __name__ == "__main__":
87
import cv2
@@ -22,10 +21,8 @@
2221
pil_image = Image.open(lenna)
2322

2423
logger = logging.getLogger("demo")
25-
fh = FileHandler("test.html", mode="w")
26-
2724
logger.setLevel(logging.DEBUG)
28-
logger.addHandler(fh)
25+
logger.addHandler(HTMLFileHandler("test.html", title="visual-logging demo"))
2926

3027
logger.debug(VisualRecord(
3128
"Hello from OpenCV", cv_image, "This is OpenCV image", fmt="png"))
@@ -45,4 +42,12 @@
4542
[cv_image, pil_image, fig1],
4643
fmt="png", max_size=(200, 200)))
4744

45+
logger.info("Plain text records work too, and are escaped: <html> & so on")
46+
47+
try:
48+
1 / 0
49+
except ZeroDivisionError:
50+
logger.exception(VisualRecord(
51+
"Exceptions come with tracebacks", cv_image))
52+
4853
logging.shutdown()

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,8 @@ test = [
4242
[tool.setuptools]
4343
packages = ["vlogging"]
4444

45+
[tool.setuptools.package-data]
46+
vlogging = ["py.typed"]
47+
4548
[tool.setuptools.dynamic]
4649
version = {attr = "vlogging.__version__"}

0 commit comments

Comments
 (0)