Production-grade structured logging with optional real-time web viewer.
Simple. One function to set up structured logging. Powerful. File, JSON, HTTP, console—all at once. Developer-Friendly. Type-safe, zero-config defaults.
✨ Structured Logging Context-aware, nested logging with built-in support for sensitive data redaction.
📊 Multiple Outputs Simultaneously log to console (colored), text files, JSON files, and HTTP endpoints.
🌐 Web Viewer (Optional) Real-time log streaming dashboard. Perfect for development and debugging.
🛠 Production-Ready Automatic log rotation, third-party library suppression, and thread-safe operations.
🔍 Full Type Safety Fully typed with Python 3.13+ support. Works great with type checkers.
⚙️ Smart Defaults Intelligent log file management, automatic cleanup, and sensible configuration presets.
# Core library
pip install bozo
# With web viewer support
pip install bozo[viewer]import bozo
# Initialize once at startup
bozo.setup("myapp")
log = bozo.get(__name__)
# Log with structured data
log.info("user_login", user_id=42, email="user@example.com")
log.warning("slow_query", query_time=1.5, threshold=1.0)
log.error("payment_failed", amount=99.99, reason="insufficient_funds")Output (console, file, JSON—simultaneously):
2026-02-04 10:23:45 | INFO | myapp.main | user_login
2026-02-04 10:23:46 | WARNING | myapp.main | slow_query
2026-02-04 10:23:47 | ERROR | myapp.main | payment_failed
Automatically redact passwords, tokens, and secrets:
log.info(
"auth_attempt",
username="alice",
password=bozo.redact("secret123") # Will log as [REDACTED]
)Attach request context that persists across async/threaded operations:
with bozo.log_context(request_id="abc-123", user_id=42):
log.info("processing") # request_id and user_id added automatically
await some_async_operation()
log.info("completed") # context still presentView logs in real-time from a beautiful dashboard:
# Enable during setup
bozo.setup(
"myapp",
enable_viewer=True,
viewer_port=8080
)
# Or start it manually
bozo.start_viewer(open_browser=True) # Opens http://127.0.0.1:8080# Development: Pretty-printed, colored console output
bozo.setup("myapp", environment=bozo.Environment.DEVELOPMENT)
# Production: Plain text, structured JSON, minimal console
bozo.setup("myapp", environment=bozo.Environment.PRODUCTION)Control what gets logged and where:
bozo.setup(
"myapp",
# Console
enable_console=True,
console_level=bozo.LogLevel.INFO,
# Text files
enable_file=True,
file_level=bozo.LogLevel.DEBUG,
enable_error_file=True, # Separate ERROR+ logs
# JSON files (for log aggregation)
enable_json=True,
json_level=bozo.LogLevel.DEBUG,
# HTTP shipping (for external platforms)
enable_http=True,
)Logs are organized by run:
~/.local/share/bozo/myapp/ # macOS/Linux (or platform-specific)
├── current/ # Latest run
│ ├── 2026-02-04_10-23-45.log
│ ├── 2026-02-04_10-23-45.error
│ └── 2026-02-04_10-23-45.json
├── 2026-02-03_18-15-22/ # Previous runs
│ ├── 2026-02-03_18-15-22.log
│ ├── 2026-02-03_18-15-22.error
│ └── 2026-02-03_18-15-22.json
└── ...Automatically cleaned up (configurable):
bozo.setup("myapp", max_runs_to_keep=10) # Keep only 10 most recent runsRoute Python warnings through bozo:
import warnings
bozo.setup("myapp", capture_warnings=True)
warnings.warn("deprecated feature") # Will be loggedReduce noise from verbose libraries:
bozo.setup("myapp", suppress_third_party=True)
# Automatically reduces verbosity for: urllib3, httpx, httpcore, etc.# Set context that persists
bozo.set_context(request_id="req-123")
log.info("operation_started") # Has request_id
# Clear specific context
bozo.clear_context()All standard levels are supported:
log.debug("verbose_info")
log.info("important_event")
log.warning("potential_issue")
log.error("something_failed", error=str(exception))Full IDE autocomplete and type checking:
import bozo
from bozo import BoundLogger, LogLevel, Environment
log: BoundLogger = bozo.get(__name__)
env: Environment = bozo.Environment.PRODUCTION
level: LogLevel = bozo.LogLevel.DEBUGFastAPI:
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
import bozo
bozo.setup("api", environment=bozo.Environment.PRODUCTION)
log = bozo.get(__name__)
@app.middleware("http")
async def log_requests(request: Request, call_next):
with bozo.log_context(
method=request.method,
path=request.url.path,
client=request.client.host
):
response = await call_next(request)
log.info("request", status_code=response.status_code)
return responseDjango:
# settings.py
import bozo
bozo.setup(
"myproject",
environment=bozo.Environment.PRODUCTION,
enable_viewer=False,
)
LOGGING = {"version": 1, "disable_existing_loggers": False}with bozo.log_context(operation="user_insert"):
try:
db.insert_user(user_data)
log.info("user_created", user_id=user_data["id"])
except Exception as e:
log.error("insert_failed", error=str(e), data=user_data)
raiseimport asyncio
async def process_queue():
while True:
item = await queue.get()
with bozo.log_context(job_id=item["id"]):
try:
await process_item(item)
log.info("job_completed")
except Exception as e:
log.error("job_failed", error=str(e))
await queue.put(item) # Retry
await asyncio.sleep(1)- Minimal overhead: Structured logging is negligible vs. application logic
- Thread-safe: All operations are safe for concurrent use
- Bounded memory: Web viewer queue has configurable limits
- Efficient rotation: Automatic file rotation prevents disk bloat
Q: Where are my log files?
A: Check ~/.local/share/bozo/yourapp/current/ (or the path printed at startup)
Q: Web viewer not working?
A: Install viewer dependencies: pip install bozo[viewer]
Q: How do I disable file logging?
A: bozo.setup("app", enable_file=False, enable_json=False)
Q: Can I use bozo with existing logging setup?
A: bozo replaces your logging setup. Call bozo.setup() once at startup.
Contributions welcome! See CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
Made with ❤️ by Tejus Gupta