hotlog
Generalized logging utility for Python projects
Overview
hotlog provides a simple, flexible, and structured logging interface for
Python applications and libraries. It wraps structlog and rich to offer:
- Three-level verbosity system for controlling output detail
- Rich colored output with YAML-formatted context
- Live logging support for dynamic status updates (at default level)
- Context-aware logging with prefix-based filtering (
_verbose_,_debug_) - Unified API:
from hotlog import get_logger, configure_logging - Works seamlessly in CLI tools, scripts, and libraries
Features
Three Verbosity Levels
- Level 0 (default): Essential info only, supports live updates that disappear
- Level 1 (-v): More context visible, includes
_verbose_prefixed keys - Level 2 (-vv): All debug info, includes
_debug_prefixed keys, no live updates
Why only three levels? Hotlog caps verbosity at level 2 because these three levels represent distinct user experience patterns that cover the vast majority of logging needs. Level 0 provides clean, interactive output with live updates. Level 1 adds useful context without overwhelming the user. Level 2 shows everything for debugging. Beyond level 2, you're typically showing internal implementation details that are better handled by traditional debug logging rather than a verbosity scale.
Prefix Filtering
Control which context appears at different verbosity levels by using key prefixes:
- Regular keys (no prefix): Always shown at all levels
_verbose_key: Only shown at level 1 (-v) or 2 (-vv)_debug_key: Only shown at level 2 (-vv)
The prefixes are automatically removed when displayed, so _verbose_source
becomes source.
logger.info(
"Processing dataset",
records=1000, # Always shown
_verbose_source="data.csv", # Only at -v or -vv
_debug_file_size="2.5MB" # Only at -vv
)
No special configuration needed - just prefix your keys and hotlog handles the rest!
Visibility Gating with _display_level
Sometimes you want to hide entire events unless the user explicitly opts into
higher verbosity. Add _display_level to the event context to specify the
minimum verbosity (0, 1, or 2) required to display it:
logger.info("download_entry_start", entry=item.name, _display_level=1)
- Messages log as normal when the active verbosity (set via
configure_loggingor environment detection) is at least_display_level. - When the required level is higher than the active verbosity, the event is skipped entirely. The key is removed before rendering, so it never appears in the formatted output.
_display_levelcomplements_verbose_*/_debug_*prefixes—use it to gate whole events, while prefixes still gate individual context values.
Custom Log Matchers
Configure custom formatting rules for specific log patterns using matchers. Matchers check if a log entry matches certain conditions and apply custom formatting.
Built-in Matchers
ToolMatch: Format tool execution logs (toolbelt style)
from hotlog import configure_logging, get_logger, ToolMatch
# Configure with ToolMatch
configure_logging(
verbosity=0,
matchers=[
ToolMatch(event="executing", prefix="tb")
]
)
logger = get_logger(__name__)
# Automatically formats as: tb[ruff-format] => ruff format .
logger.info(
"executing",
command="ruff format .",
tool="ruff-format",
_verbose_files_changed=14
)
The ToolMatch matcher checks for:
- Event name:
"executing"(configurable) - Log level:
"INFO"(configurable) - Required key:
command - Optional key:
tool(shows in brackets)
Customize the matcher:
ToolMatch(
event="executing", # Event name to match
prefix="pkg", # Prefix shown before tool name
level="INFO", # Log level to match
command_key="command", # Key containing command
tool_key="tool" # Key containing tool name
)
Creating Custom Matchers
Extend LogMatcher to create your own formatting rules:
from hotlog import LogMatcher, configure_logging, get_logger
from structlog.typing import EventDict
from typing import Optional
class InstallMatch(LogMatcher):
"""Custom matcher for package installation logs."""
def matches(self, level: str, event: str, event_dict: EventDict) -> bool:
"""Check if this log entry matches our pattern."""
return (
level == "INFO" and
event == "installed" and
"package" in event_dict
)
def format(self, level: str, event: str, event_dict: EventDict) -> Optional[str]:
"""Format matching log entries.
Note: Extract and remove keys you use from event_dict.
Return None to fall back to default formatting.
"""
package = event_dict.pop("package")
version = event_dict.pop("version", "unknown")
return f'[green]✓[/green] Installed [bold]{package}[/bold] [dim](version {version})[/dim]'
# Use your custom matcher
configure_logging(
verbosity=0,
matchers=[
InstallMatch(),
ToolMatch(event="executing", prefix="pkg"),
]
)
logger = get_logger(__name__)
# This will use InstallMatch formatting
logger.info("installed", package="requests", version="2.31.0")
# This will use ToolMatch formatting
logger.info("executing", command="uv pip install requests", tool="uv")
# This will use default formatting
logger.info("Build completed")
Key points about custom matchers:
- Matchers are checked in order - first match wins
- Use
event_dict.pop()to remove keys you've formatted - Return
Noneto fall back to default formatting - Remaining context keys are shown as YAML below the message
- Rich markup is supported in formatted strings
Rich Output
- Colored log levels (INFO=blue, WARNING=yellow, ERROR=red, DEBUG=magenta)
- YAML-formatted context for easy reading
- Syntax highlighting for structured data
- Clean output without log level prefixes (like
uv) - setshow_levels=Truefor traditional[INFO]style
Highlighting Important Information
Emphasize specific values in your messages using Rich markup or the
highlight() helper:
from hotlog import get_logger, highlight
logger = get_logger(__name__)
# Option 1: Direct Rich markup
logger.info("Installed [bold]5 packages[/bold] in [bold]3ms[/bold]")
# Option 2: Using highlight() helper
logger.info(highlight("Downloaded {} in {}", "14 files", "2.5s"))
# Renders as: "Downloaded [bold]14 files[/bold] in [bold]2.5s[/bold]"
This is perfect for level 0 summaries where you want to emphasize key metrics while keeping the output clean.
Live Logging
Use the live_logging context manager for operations with progress updates
(level 0 only):
with live_logging("Downloading..."):
# Your operation here
logger.info("Connected", host="example.com")
Conditional Live Logging Helper
When you only want live updates at verbosity 0, use
maybe_live_logging(message):
from hotlog import maybe_live_logging
with maybe_live_logging("Downloading repos...") as live:
if live:
live.info("Downloading", name=repo)
- At verbosity 0, it delegates to
live_logging()and yields aLiveLogger. - At verbosity 1 or 2, it yields
Nonewithout printing anything, so you can skip live output and avoid extra branching in your code.
Usage
Basic Example
from hotlog import configure_logging, get_logger
# Configure logging (verbosity: 0, 1, or 2)
configure_logging(verbosity=0)
logger = get_logger(__name__)
# Log messages
logger.info("Starting process", task_id=123)
logger.info("Processing data", records=100, _verbose_source="db.sqlite")
logger.warning("Rate limit approaching", current=95, limit=100)
With Different Verbosity Levels
# Level 0: Only essential info
configure_logging(verbosity=0)
logger.info("Processing", items=10, _verbose_detail="xyz", _debug_internals="abc")
# Output: items=10 only
# Level 1: Include verbose context
configure_logging(verbosity=1)
logger.info("Processing", items=10, _verbose_detail="xyz", _debug_internals="abc")
# Output: items=10, detail="xyz"
# Level 2: Include all debug context
configure_logging(verbosity=2)
logger.info("Processing", items=10, _verbose_detail="xyz", _debug_internals="abc")
# Output: items=10, detail="xyz", internals="abc"
Live Logging Example
from hotlog import configure_logging, get_logger, live_logging
import time
configure_logging(verbosity=0)
logger = get_logger(__name__)
with live_logging("Downloading repository..."):
time.sleep(1)
logger.info("Connected to server")
time.sleep(1)
logger.info("Fetching metadata")
logger.info("Download completed")
CLI Integration
import argparse
from hotlog import configure_logging, get_logger
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', action='count', default=0)
args = parser.parse_args()
# Map -v/-vv to verbosity levels
configure_logging(verbosity=min(args.verbose, 2))
logger = get_logger(__name__)
Typer Integration
Hotlog works seamlessly with Typer for modern CLI
apps. Note: Typer is an optional dependency - install with
pip install hotlog[typer].
import typer
from hotlog import verbosity_option, configure_logging, get_logger, resolve_verbosity
app = typer.Typer()
@app.command()
def my_command(verbose: int = verbosity_option):
# Resolve verbosity from CLI args and environment (CI detection)
verbosity = resolve_verbosity(verbose=verbose)
configure_logging(verbosity=verbosity)
logger = get_logger(__name__)
logger.info("Starting process", task_id=123)
logger.info("Processing data", records=100, _verbose_source="db.sqlite")
Key functions:
verbosity_option: Pre-configured Typer option for-v/--verboseflagsresolve_verbosity(verbose=count): Resolves final verbosity level from CLI and environment- All hotlog features work: live logging, prefixes, matchers, highlighting
Example with Typer:
# Default verbosity (level 0)
python my_app.py my-command
# Verbose (level 1)
python my_app.py my-command -v
# Debug (level 2)
python my_app.py my-command -vv
See examples_typer/ directory for complete Typer examples that mirror the
argparse versions.
Environment Variables
HOTLOG_FORCE_TERMINAL
Controls whether Rich's terminal formatting is forced, even in non-interactive environments.
Values:
1,true,yes,on,y,t(case-insensitive): Force terminal mode0,false,no(or any other value): Disable forced terminal mode- Not set: Auto-detect (enabled in CI/normal use, disabled during tests)
When to use:
- CI/CD pipelines: Some CI systems don't auto-detect as terminals. Set
HOTLOG_FORCE_TERMINAL=1to ensure colored output in CI logs. - Disable colors: Set
HOTLOG_FORCE_TERMINAL=0when redirecting to files or when colors interfere with log processing tools. - Debugging: Temporarily disable to see raw output without ANSI codes.
- Library integration: Downstream users can control formatting without code changes.
Example:
# Force colors in CI
HOTLOG_FORCE_TERMINAL=1 python my_script.py
# Disable colors for file output
HOTLOG_FORCE_TERMINAL=0 python my_script.py > output.log
Note: During test execution (pytest), force_terminal is automatically disabled to ensure live logging works correctly with captured output.
Installation
# Core dependencies (required)
pip install hotlog
# With Typer support (optional)
pip install hotlog[typer]
Note: Typer is optional and only needed if you want to use the
verbosity_option for Typer-based CLI apps. The core hotlog functionality works
without Typer.
Why hotlog?
Hotlog standardizes logging across multiple projects and CLIs, making it easy to:
- Provide consistent verbosity control
- Display beautiful, readable logs
- Handle live updates for better UX
- Filter context based on user's needs
- Customize log formatting with matchers
No more reinventing logging configuration for every project!
Examples
See the example_*.py files for more usage patterns:
example_quickstart.py- Comprehensive quick start guideexample_cli.py- Full CLI simulationexample_toolbelt.py- Tool execution logging with ToolMatchexample_custom_matcher.py- Creating custom matchersexample_highlight.py- Using highlight() and Rich markupexample_prefixes.py- Prefix filtering demonstration
Typer examples (equivalent functionality with Typer):
examples_typer/example_cli_typer.py- Full CLI simulation with Typerexamples_typer/example_prefixes_typer.py- Prefix filtering with Typer
Run examples with different verbosity levels:
python example_toolbelt.py # Level 0 (default)
python example_toolbelt.py -v # Level 1 (verbose)
python example_toolbelt.py -vv # Level 2 (debug)
Metadata
Release files for hotlog 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| hotlog-0.2.0.tar.gz | 15.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| hotlog-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.2 kB
Release files / hotlog-0.2.0.tar.gz
| Download URL | hotlog-0.2.0.tar.gz |
|---|---|
| Size | 15.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
231c66e58277cfae0fc70a4d06042cbf4e56abb59c468403144f26685a302a67
|
|
BLAKE2b-256 checksum How to use checksums |
e1e47993b2ee1833c89b37b4b20ff8284183e0210ed797cc6eb43fc73999e452
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / hotlog-0.2.0-py3-none-any.whl
| Download URL | hotlog-0.2.0-py3-none-any.whl |
|---|---|
| Size | 20.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ed11b2d25ca51430eaea3447ee389ed8ebe448b2007f587749e3b3521a161929
|
|
BLAKE2b-256 checksum How to use checksums |
e38aa02dc3d7f5c0fd988db3b3806f19fb3d4f5db1cdda22e507343fbdf08f96
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.27 {"installer":{"name":"uv","version":"0.9.27","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|