Automatic documentation from sources, for MkDocs.
—
Specialized logging system designed for template environments and debugging mkdocstrings processing. The logging system provides enhanced logger adapters, template-friendly logging interfaces, and utility functions for debugging documentation generation.
Enhanced logger adapter with message prefixes and once-only logging support for cleaner log output.
class LoggerAdapter(logging.LoggerAdapter):
"""Logger adapter with prefix and once-only logging support."""
def __init__(self, prefix: str, logger: logging.Logger) -> None:
"""
Initialize adapter with prefix.
Args:
prefix: Message prefix string
logger: Base logger instance
"""
def process(
self,
msg: str,
kwargs: MutableMapping[str, Any]
) -> tuple[str, Any]:
"""
Process log message and add prefix.
Args:
msg: Log message
kwargs: Logging keyword arguments
Returns:
Tuple of processed message and kwargs
"""
def log(
self,
level: int,
msg: object,
*args: object,
**kwargs: object
) -> None:
"""
Log message with enhanced functionality.
Args:
level: Logging level
msg: Message to log
*args: Message arguments
**kwargs: Logging options
"""
prefix: str
"""Message prefix string."""Usage Examples:
Create logger with prefix:
import logging
from mkdocstrings import LoggerAdapter
# Create base logger
base_logger = logging.getLogger("mkdocstrings.myhandler")
# Create adapter with prefix
logger = LoggerAdapter("MyHandler", base_logger)
# Log messages with automatic prefix
logger.info("Processing module") # Logs: "[MyHandler] Processing module"
logger.error("Failed to parse") # Logs: "[MyHandler] Failed to parse"
logger.debug("Debug information") # Logs: "[MyHandler] Debug information"Handler integration:
class MyHandler(BaseHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Create logger with handler-specific prefix
base_logger = logging.getLogger(f"mkdocstrings.{self.name}")
self.logger = LoggerAdapter(f"{self.name.title()}Handler", base_logger)
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
self.logger.info(f"Collecting documentation for {identifier}")
try:
data = self._do_collection(identifier)
self.logger.debug(f"Successfully collected {len(data.members)} members")
return data
except Exception as e:
self.logger.error(f"Collection failed for {identifier}: {e}")
raiseLogger wrapper designed for use within Jinja2 templates, providing template-friendly logging methods.
class TemplateLogger:
"""Logger wrapper for use in Jinja templates."""
def __init__(self, logger: LoggerAdapter) -> None:
"""
Initialize with logger adapter.
Args:
logger: LoggerAdapter instance
"""
debug: Callable
"""Log DEBUG level messages."""
info: Callable
"""Log INFO level messages."""
warning: Callable
"""Log WARNING level messages."""
error: Callable
"""Log ERROR level messages."""
critical: Callable
"""Log CRITICAL level messages."""Usage Examples:
Use in templates:
from mkdocstrings import TemplateLogger, LoggerAdapter
# Create template logger
base_logger = logging.getLogger("mkdocstrings.template")
adapter = LoggerAdapter("Template", base_logger)
template_logger = TemplateLogger(adapter)
# Make available in template context
template_globals = {
"logger": template_logger,
"data": data
}Template usage:
{# In Jinja2 template #}
{% if data.deprecated %}
{{ logger.warning("Rendering deprecated item: " + data.name) }}
{% endif %}
{% if not data.docstring %}
{{ logger.info("No docstring found for " + data.name) }}
{% endif %}
{# Log debug information #}
{{ logger.debug("Rendering " + data.type + " with " + data.members|length|string + " members") }}Handler template integration:
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
# Create template logger
template_logger = get_template_logger(self.name)
# Render template with logger
template = self.env.get_template("handler.html")
return template.render(
data=data,
options=options,
logger=template_logger
)Helper functions for creating and managing loggers in different contexts.
def get_logger(name: str) -> LoggerAdapter:
"""
Return a pre-configured logger for MkDocs.
Args:
name: Logger name
Returns:
LoggerAdapter instance
"""
def get_template_logger(handler_name: str | None = None) -> TemplateLogger:
"""
Return a logger for use in templates.
Args:
handler_name: Optional handler name for prefix
Returns:
TemplateLogger instance
"""
def get_template_logger_function(logger_func: Callable) -> Callable:
"""
Create wrapper function for template logging.
Args:
logger_func: Logger function to wrap
Returns:
Wrapped logger function
"""
def get_template_path(context: Context) -> str:
"""
Return path to template using given context.
Args:
context: Jinja2 template context
Returns:
Template path string
"""Usage Examples:
Get configured logger:
from mkdocstrings import get_logger
# Get logger for specific component
logger = get_logger("mkdocstrings.python")
logger.info("Python handler initialized")
logger.debug("Loading configuration")Get template logger:
from mkdocstrings import get_template_logger
# Get template logger for handler
template_logger = get_template_logger("python")
# Use in template rendering
template_context = {
"logger": template_logger,
"data": data,
"options": options
}Template path debugging:
from mkdocstrings import get_template_path
# In template rendering function
def render_template(self, template_name: str, **context):
template = self.env.get_template(template_name)
# Log template path for debugging
if template.environment.globals.get("logger"):
template_path = get_template_path(template.new_context())
template.environment.globals["logger"].debug(f"Rendering template: {template_path}")
return template.render(**context)Integrate logging throughout handler lifecycle:
class MyHandler(BaseHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set up handler logger
self.logger = get_logger(f"mkdocstrings.{self.name}")
self.logger.info(f"Initialized {self.name} handler")
def collect(self, identifier: str, options: HandlerOptions) -> CollectorItem:
self.logger.debug(f"Collecting {identifier}")
try:
# Collection logic
data = self._parse_identifier(identifier)
self.logger.info(f"Collected {data.type} {data.name} with {len(data.members)} members")
return data
except ImportError as e:
self.logger.error(f"Import failed for {identifier}: {e}")
raise CollectionError(f"Could not import {identifier}")
except Exception as e:
self.logger.exception(f"Unexpected error collecting {identifier}")
raise
def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
self.logger.debug(f"Rendering {data.name}")
# Create template logger
template_logger = get_template_logger(self.name)
try:
html = self.render_template("handler.html",
data=data,
options=options,
logger=template_logger
)
self.logger.debug(f"Rendered {len(html)} characters for {data.name}")
return html
except Exception as e:
self.logger.error(f"Rendering failed for {data.name}: {e}")
raisePlugin-level logging for configuration and lifecycle events:
class MkdocstringsPlugin(BasePlugin):
def __init__(self):
super().__init__()
self.logger = get_logger("mkdocstrings.plugin")
def on_config(self, config: MkDocsConfig) -> MkDocsConfig | None:
self.logger.info("Configuring mkdocstrings plugin")
# Log configuration details
if self.config.get("handlers"):
handler_names = list(self.config["handlers"].keys())
self.logger.info(f"Configured handlers: {', '.join(handler_names)}")
self.logger.debug(f"Default handler: {self.config['default_handler']}")
if self.config.get("custom_templates"):
self.logger.info(f"Using custom templates from: {self.config['custom_templates']}")
return config
def on_post_build(self, config: MkDocsConfig, **kwargs):
self.logger.info("mkdocstrings plugin build completed")
# Log inventory statistics
if hasattr(self, "handlers") and self.handlers.inventory:
item_count = len(self.handlers.inventory)
self.logger.info(f"Generated inventory with {item_count} items")Enhanced error reporting in templates:
{# Template: handler.html #}
{% set template_logger = get_template_logger("python") %}
{% if not data %}
{{ template_logger.error("No data provided to template") }}
<div class="error">No documentation data available</div>
{% else %}
{% if not data.name %}
{{ template_logger.warning("Data missing name attribute") }}
<div class="warning">Documentation object missing name</div>
{% endif %}
{% for member in data.members %}
{% if not member.signature %}
{{ template_logger.debug("Member " + member.name + " has no signature") }}
{% endif %}
{% endfor %}
{% endif %}Enhanced debugging with verbose logging:
def enable_debug_logging():
"""Enable verbose debug logging for troubleshooting."""
import logging
# Set debug level for all mkdocstrings loggers
logging.getLogger("mkdocstrings").setLevel(logging.DEBUG)
# Create debug file handler
debug_handler = logging.FileHandler("mkdocstrings_debug.log")
debug_handler.setLevel(logging.DEBUG)
# Format with detailed information
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
debug_handler.setFormatter(formatter)
# Add to root mkdocstrings logger
logging.getLogger("mkdocstrings").addHandler(debug_handler)
# Usage
if debug_mode:
enable_debug_logging()This logging system provides comprehensive debugging capabilities while maintaining clean, informative output for normal operation.
Install with Tessl CLI
npx tessl i tessl/pypi-mkdocstrings