Skip to main content

Flexible logging utilities with dependency injection support for use in GRIMOIRE

Project description

Grimoire Logging

Tests Python 3.8+ License: MIT Code style: Ruff

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 or None to 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) -> None
  • info(msg: str, *args, **kwargs) -> None
  • warning(msg: str, *args, **kwargs) -> None
  • error(msg: str, *args, **kwargs) -> None
  • critical(msg: str, *args, **kwargs) -> None

๐Ÿ“– Examples

See the examples/ directory for comprehensive examples:

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

grimoire_logging-0.1.0.tar.gz (19.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

grimoire_logging-0.1.0-py3-none-any.whl (8.4 kB view details)

Uploaded Python 3

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

Hashes for grimoire_logging-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ac74c48bdcebf7910729e910aec106ae2628e6ddec85f42cc53194475c90360d
MD5 5ed45c56553e132e93625c9e2677b6b8
BLAKE2b-256 290a09c6ec95b801f28b7f8eb735f8fa8076ebd5f8dbf9d6eca685cb45e6ea4f

See more details on using hashes here.

File details

Details for the file grimoire_logging-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for grimoire_logging-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 829a36b4efdd077dba3b2d2814f1e825f26c395ede9a97acf457df222db568bf
MD5 6cf4645716b6a2ba54d1a60826552e9d
BLAKE2b-256 e3cf7f0415e2b8379a51677a7e15cbfd06152ddf9742c14b0842c3a0767f1391

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page