Flexible logging utilities with dependency injection support for use in GRIMOIRE
Project description
Grimoire Logging
Flexible logging utilities with dependency injection support for the Grimoire engine.
Grimoire Logging provides a clean, thread-safe logging system with dependency injection capabilities. It allows applications to easily switch between different logging implementations without changing their logging code, making it ideal for libraries, applications, and testing scenarios.
โจ Features
- ๐ Dependency Injection: Inject custom logger implementations at runtime
- ๐งต Thread-Safe: All operations are thread-safe and can be used in concurrent environments
- ๐ Fallback Support: Automatically falls back to Python's standard logging when no custom logger is injected
- ๐ฆ Zero Dependencies: Core functionality requires no external dependencies
- ๐ฏ Protocol-Based: Clean interface definition using Python protocols
- ๐ง Easy Integration: Simple API that works with existing codebases
- ๐งช Test-Friendly: Easy to inject mock loggers for testing
๐ Quick Start
Installation
pip install grimoire-logging
Basic Usage
from grimoire_logging import get_logger, inject_logger
# Get a logger - falls back to standard Python logging by default
logger = get_logger(__name__)
logger.info("Hello, world!")
# Create a custom logger implementation
class CustomLogger:
def info(self, msg: str, *args, **kwargs) -> None:
print(f"๐ {msg}")
def debug(self, msg: str, *args, **kwargs) -> None:
print(f"๐ {msg}")
def warning(self, msg: str, *args, **kwargs) -> None:
print(f"โ ๏ธ {msg}")
def error(self, msg: str, *args, **kwargs) -> None:
print(f"โ {msg}")
def critical(self, msg: str, *args, **kwargs) -> None:
print(f"๐ {msg}")
# Inject your custom logger - all existing loggers will now use it
inject_logger(CustomLogger())
logger.info("Now using custom logger!") # Output: ๐ Now using custom logger!
Thread-Safe Logger Management
import threading
from grimoire_logging import get_logger, inject_logger
def worker_function(worker_id):
logger = get_logger(f"worker_{worker_id}")
logger.info(f"Worker {worker_id} starting")
# Do work...
logger.info(f"Worker {worker_id} finished")
# Inject a logger that will be used by all threads
inject_logger(CustomLogger())
# Start multiple threads - all will use the same injected logger
threads = []
for i in range(5):
thread = threading.Thread(target=worker_function, args=(i,))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
Integration with Standard Logging
import logging
from grimoire_logging import get_logger, inject_logger
# Set up standard logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# Create an adapter to integrate with standard logging
class StandardLoggingAdapter:
def __init__(self, logger_name: str = "grimoire"):
self.logger = logging.getLogger(logger_name)
def debug(self, msg: str, *args, **kwargs) -> None:
self.logger.debug(msg, *args, **kwargs)
def info(self, msg: str, *args, **kwargs) -> None:
self.logger.info(msg, *args, **kwargs)
def warning(self, msg: str, *args, **kwargs) -> None:
self.logger.warning(msg, *args, **kwargs)
def error(self, msg: str, *args, **kwargs) -> None:
self.logger.error(msg, *args, **kwargs)
def critical(self, msg: str, *args, **kwargs) -> None:
self.logger.critical(msg, *args, **kwargs)
# Use standard logging as the backend
inject_logger(StandardLoggingAdapter())
logger = get_logger("myapp")
logger.info("This will use standard Python logging with proper formatting")
Structured Logging
import json
from datetime import datetime
from grimoire_logging import get_logger, inject_logger
class JSONLogger:
def _log(self, level: str, message: str):
log_entry = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": level,
"message": message
}
print(json.dumps(log_entry))
def debug(self, msg: str, *args, **kwargs) -> None:
self._log("DEBUG", msg)
def info(self, msg: str, *args, **kwargs) -> None:
self._log("INFO", msg)
def warning(self, msg: str, *args, **kwargs) -> None:
self._log("WARNING", msg)
def error(self, msg: str, *args, **kwargs) -> None:
self._log("ERROR", msg)
def critical(self, msg: str, *args, **kwargs) -> None:
self._log("CRITICAL", msg)
inject_logger(JSONLogger())
logger = get_logger("app")
logger.info("Application started")
# Output: {"timestamp": "2025-01-01T12:00:00Z", "level": "INFO", "message": "Application started"}
๐ Core Concepts
Logger Protocol
Custom loggers must implement the LoggerProtocol interface:
from typing import Protocol
class LoggerProtocol(Protocol):
def debug(self, msg: str, *args, **kwargs) -> None: ...
def info(self, msg: str, *args, **kwargs) -> None: ...
def warning(self, msg: str, *args, **kwargs) -> None: ...
def error(self, msg: str, *args, **kwargs) -> None: ...
def critical(self, msg: str, *args, **kwargs) -> None: ...
Dependency Injection
The library uses a global injection mechanism that affects all logger instances:
from grimoire_logging import inject_logger, clear_logger_injection
# Inject a custom logger
inject_logger(my_custom_logger)
# All loggers now use the custom implementation
logger1 = get_logger("module1")
logger2 = get_logger("module2")
# Both use my_custom_logger
# Clear injection to return to standard logging
clear_logger_injection()
Thread Safety
All operations are thread-safe and can be called from multiple threads:
import threading
from grimoire_logging import get_logger, inject_logger
# Safe to call from any thread
inject_logger(custom_logger)
logger = get_logger(__name__)
logger.info("Thread-safe logging")
๐ง API Reference
Main Functions
get_logger(name: str) -> LoggerProtocol
Get a logger instance for the given name.
Parameters:
name: Logger name (typically__name__)
Returns:
- Logger proxy that conforms to
LoggerProtocol
Example:
logger = get_logger(__name__)
logger.info("Hello, world!")
inject_logger(logger: Optional[LoggerProtocol]) -> None
Inject a custom logger implementation.
Parameters:
logger: Logger implementation orNoneto revert to standard logging
Example:
inject_logger(CustomLogger())
# or
inject_logger(None) # Clear injection
clear_logger_injection() -> None
Clear any injected logger and revert to default.
Equivalent to inject_logger(None).
Example:
clear_logger_injection()
Protocol
LoggerProtocol
Protocol defining the interface for custom loggers.
Methods:
debug(msg: str, *args, **kwargs) -> Noneinfo(msg: str, *args, **kwargs) -> Nonewarning(msg: str, *args, **kwargs) -> Noneerror(msg: str, *args, **kwargs) -> Nonecritical(msg: str, *args, **kwargs) -> None
๐ Examples
See the examples/ directory for comprehensive examples:
basic_usage.py- Core functionality and basic patternsadvanced_usage.py- Structured logging, filtering, and concurrent usageintegration.py- Integration with standard logging and frameworks
Run examples:
python examples/basic_usage.py
python examples/advanced_usage.py
python examples/integration.py
๐งช Development
Setup
git clone https://github.com/wyrdbound/grimoire-logging.git
cd grimoire-logging
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -e ".[dev]"
Running Tests
# Run all tests
pytest
# Run with coverage
pytest --cov=grimoire_logging
# Run specific test file
pytest tests/test_logging.py
Code Quality
# Linting and formatting
ruff check .
ruff format .
# Type checking
mypy src/
๐ Requirements
- Python 3.8+
- No runtime dependencies
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2025 The Wyrd One
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate and follow the existing code style.
If you have questions about the project, please contact: wyrdbound@proton.me
๐ฏ Use Cases
Grimoire Logging is particularly well-suited for:
- ๐ฒ Game Development: Flexible logging for game engines and RPG systems
- ๐ Library Development: Allow users to control logging behavior
- ๐งช Testing: Easy injection of mock loggers for testing
- ๐ Plugin Systems: Different logging implementations for different environments
- ๐ Web Applications: Request-scoped logging and structured output
- ๐ง Configuration Management: Runtime logging configuration changes
๐๏ธ Architecture
The library is built around several key components:
- LoggerProxy: Thread-safe proxy that delegates to injected or fallback loggers
- Protocol Design: Clean interface definition using Python protocols
- Thread Safety: Re-entrant locks ensure safe concurrent access
- Minimal Dependencies: Zero runtime dependencies for maximum compatibility
๐ Performance
- Memory Efficient: Logger instances are cached and reused
- Thread Safe: Designed for high-concurrency environments
- Low Overhead: Minimal impact when using standard logging fallback
- Scalable: Efficient delegation pattern supports many logger instances
๐ Comparison with Other Solutions
| Feature | Grimoire Logging | Python logging | loguru | structlog |
|---|---|---|---|---|
| Dependency Injection | โ | โ | โ | Partial |
| Zero Dependencies | โ | โ | โ | โ |
| Thread Safety | โ | โ | โ | โ |
| Protocol Based | โ | โ | โ | Partial |
| Easy Testing | โ | Partial | โ | โ |
| Fallback Support | โ | N/A | โ | โ |
Grimoire Logging is designed specifically for scenarios where you need clean dependency injection and the ability to completely swap out logging implementations at runtime, making it ideal for libraries and applications that need maximum flexibility.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file grimoire_logging-0.1.0.tar.gz.
File metadata
- Download URL: grimoire_logging-0.1.0.tar.gz
- Upload date:
- Size: 19.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac74c48bdcebf7910729e910aec106ae2628e6ddec85f42cc53194475c90360d
|
|
| MD5 |
5ed45c56553e132e93625c9e2677b6b8
|
|
| BLAKE2b-256 |
290a09c6ec95b801f28b7f8eb735f8fa8076ebd5f8dbf9d6eca685cb45e6ea4f
|
File details
Details for the file grimoire_logging-0.1.0-py3-none-any.whl.
File metadata
- Download URL: grimoire_logging-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
829a36b4efdd077dba3b2d2814f1e825f26c395ede9a97acf457df222db568bf
|
|
| MD5 |
6cf4645716b6a2ba54d1a60826552e9d
|
|
| BLAKE2b-256 |
e3cf7f0415e2b8379a51677a7e15cbfd06152ddf9742c14b0842c3a0767f1391
|