Skip to content

Logger

provide.foundation.logger

TODO: Add module docstring.

Classes

FoundationLogger

FoundationLogger(hub: Any = None)

A structlog-based logger providing a standardized logging interface.

Source code in provide/foundation/logger/core.py
def __init__(self, hub: Any = None) -> None:
    self._internal_logger = structlog.get_logger().bind(
        logger_name=f"{self.__class__.__module__}.{self.__class__.__name__}",
    )
    self._is_configured_by_setup: bool = False
    self._active_config: TelemetryConfig | None = None
    self._hub = hub  # Hub dependency for DI pattern
Functions
__setattr__
__setattr__(name: str, value: Any) -> None

Override setattr to prevent accidental modification of logger state.

Source code in provide/foundation/logger/core.py
def __setattr__(self, name: str, value: Any) -> None:
    """Override setattr to prevent accidental modification of logger state."""
    if hasattr(self, name) and name.startswith("_"):
        super().__setattr__(name, value)
    else:
        super().__setattr__(name, value)
bind
bind(**kwargs: Any) -> Any

Create a new logger with additional context bound to it.

Parameters:

Name Type Description Default
**kwargs Any

Key-value pairs to bind to the logger

{}

Returns:

Type Description
Any

A new logger instance with the bound context

Source code in provide/foundation/logger/core.py
def bind(self, **kwargs: Any) -> Any:
    """Create a new logger with additional context bound to it.

    Args:
        **kwargs: Key-value pairs to bind to the logger

    Returns:
        A new logger instance with the bound context

    """
    self._ensure_configured()
    # Get the actual structlog logger and bind context to it
    if hasattr(self, "_logger") and self._logger:
        return self._logger.bind(**kwargs)
    # Fallback: get fresh logger and bind
    log = self.get_logger()
    return log.bind(**kwargs)
critical
critical(event: str, *args: Any, **kwargs: Any) -> None

Log critical-level event.

Source code in provide/foundation/logger/core.py
def critical(self, event: str, *args: Any, **kwargs: Any) -> None:
    """Log critical-level event."""
    formatted_event = self._format_message_with_args(event, args)
    self._log_with_level("critical", formatted_event, **kwargs)
debug
debug(event: str, *args: Any, **kwargs: Any) -> None

Log debug-level event.

Source code in provide/foundation/logger/core.py
def debug(self, event: str, *args: Any, **kwargs: Any) -> None:
    """Log debug-level event."""
    formatted_event = self._format_message_with_args(event, args)
    self._log_with_level("debug", formatted_event, **kwargs)
error
error(event: str, *args: Any, **kwargs: Any) -> None

Log error-level event.

Source code in provide/foundation/logger/core.py
def error(self, event: str, *args: Any, **kwargs: Any) -> None:
    """Log error-level event."""
    formatted_event = self._format_message_with_args(event, args)
    self._log_with_level("error", formatted_event, **kwargs)
exception
exception(event: str, *args: Any, **kwargs: Any) -> None

Log error-level event with exception traceback.

Source code in provide/foundation/logger/core.py
def exception(self, event: str, *args: Any, **kwargs: Any) -> None:
    """Log error-level event with exception traceback."""
    formatted_event = self._format_message_with_args(event, args)
    kwargs["exc_info"] = True
    self._log_with_level("error", formatted_event, **kwargs)
info
info(event: str, *args: Any, **kwargs: Any) -> None

Log info-level event.

Source code in provide/foundation/logger/core.py
def info(self, event: str, *args: Any, **kwargs: Any) -> None:
    """Log info-level event."""
    formatted_event = self._format_message_with_args(event, args)
    self._log_with_level("info", formatted_event, **kwargs)
setup
setup(config: TelemetryConfig) -> None

Setup the logger with configuration from Hub.

Parameters:

Name Type Description Default
config TelemetryConfig

TelemetryConfig to use for setup

required
Source code in provide/foundation/logger/core.py
def setup(self, config: TelemetryConfig) -> None:
    """Setup the logger with configuration from Hub.

    Args:
        config: TelemetryConfig to use for setup

    """
    self._active_config = config
    self._is_configured_by_setup = True

    # Run the internal setup process
    try:
        from provide.foundation.logger.setup.coordinator import internal_setup

        internal_setup(config, is_explicit_call=True)
    except Exception as e:
        # Fallback to emergency setup if regular setup fails
        self._setup_emergency_fallback()
        raise e
trace
trace(
    event: str,
    *args: Any,
    _foundation_logger_name: str | None = None,
    **kwargs: Any
) -> None

Log trace-level event for detailed debugging.

Source code in provide/foundation/logger/core.py
def trace(
    self,
    event: str,
    *args: Any,
    _foundation_logger_name: str | None = None,
    **kwargs: Any,
) -> None:
    """Log trace-level event for detailed debugging."""
    formatted_event = self._format_message_with_args(event, args)
    if _foundation_logger_name is not None:
        kwargs["_foundation_logger_name"] = _foundation_logger_name
    self._log_with_level(TRACE_LEVEL_NAME.lower(), formatted_event, **kwargs)
try_unbind
try_unbind(*keys: str) -> Any

Create a new logger with specified keys removed from context. Does not raise an error if keys don't exist.

Parameters:

Name Type Description Default
*keys str

Context keys to remove

()

Returns:

Type Description
Any

A new logger instance without the specified keys

Source code in provide/foundation/logger/core.py
def try_unbind(self, *keys: str) -> Any:
    """Create a new logger with specified keys removed from context.
    Does not raise an error if keys don't exist.

    Args:
        *keys: Context keys to remove

    Returns:
        A new logger instance without the specified keys

    """
    self._ensure_configured()
    # Get the actual structlog logger and try_unbind context from it
    if hasattr(self, "_logger") and self._logger:
        return self._logger.try_unbind(*keys)
    # Fallback: get fresh logger and try_unbind
    log = self.get_logger()
    return log.try_unbind(*keys)
unbind
unbind(*keys: str) -> Any

Create a new logger with specified keys removed from context.

Parameters:

Name Type Description Default
*keys str

Context keys to remove

()

Returns:

Type Description
Any

A new logger instance without the specified keys

Source code in provide/foundation/logger/core.py
def unbind(self, *keys: str) -> Any:
    """Create a new logger with specified keys removed from context.

    Args:
        *keys: Context keys to remove

    Returns:
        A new logger instance without the specified keys

    """
    self._ensure_configured()
    # Get the actual structlog logger and unbind context from it
    if hasattr(self, "_logger") and self._logger:
        return self._logger.unbind(*keys)
    # Fallback: get fresh logger and unbind
    log = self.get_logger()
    return log.unbind(*keys)
warning
warning(event: str, *args: Any, **kwargs: Any) -> None

Log warning-level event.

Source code in provide/foundation/logger/core.py
def warning(self, event: str, *args: Any, **kwargs: Any) -> None:
    """Log warning-level event."""
    formatted_event = self._format_message_with_args(event, args)
    self._log_with_level("warning", formatted_event, **kwargs)

LoggingConfig

Bases: RuntimeConfig

Configuration specific to logging behavior within Foundation Telemetry.

TelemetryConfig

Bases: RuntimeConfig

Main configuration object for the Foundation Telemetry system.

Functions
from_env classmethod
from_env(
    prefix: str = "",
    delimiter: str = "_",
    case_sensitive: bool = False,
) -> TelemetryConfig

Load configuration from environment variables.

This method explicitly provides the from_env() interface to ensure it's available on TelemetryConfig directly.

If OpenObserve is configured and reachable, OTLP settings are automatically configured if not already set.

Source code in provide/foundation/logger/config/telemetry.py
@classmethod
def from_env(
    cls,
    prefix: str = "",
    delimiter: str = "_",
    case_sensitive: bool = False,
) -> TelemetryConfig:
    """Load configuration from environment variables.

    This method explicitly provides the from_env() interface
    to ensure it's available on TelemetryConfig directly.

    If OpenObserve is configured and reachable, OTLP settings are
    automatically configured if not already set.
    """
    # Load base configuration
    config = super().from_env(prefix=prefix, delimiter=delimiter, case_sensitive=case_sensitive)

    # Auto-configure OTLP if OpenObserve is available and OTLP not already configured
    if not config.otlp_endpoint:
        config = cls._auto_configure_openobserve_otlp(config)

    return config
get_otlp_headers_dict
get_otlp_headers_dict() -> dict[str, str]

Get OTLP headers dictionary.

Returns:

Type Description
dict[str, str]

Dictionary of header key-value pairs

Source code in provide/foundation/logger/config/telemetry.py
def get_otlp_headers_dict(self) -> dict[str, str]:
    """Get OTLP headers dictionary.

    Returns:
        Dictionary of header key-value pairs

    """
    return self.otlp_headers

Functions

get_logger

get_logger(name: str | None = None) -> Any

Get a logger instance through Hub with circular import protection.

This function provides access to the global logger instance. It is preserved for backward compatibility but should be avoided in new application code in favor of explicit Dependency Injection.

Circular Import Protection

Uses thread-local state to detect recursive initialization and falls back to basic structlog when circular dependencies are detected.

Parameters:

Name Type Description Default
name str | None

Logger name (e.g., name from a module)

None

Returns:

Type Description
Any

Configured structlog logger instance

Note

For building testable and maintainable applications, the recommended approach is to inject a logger instance via a Container. See the Dependency Injection guide for more information.

Source code in provide/foundation/logger/factories.py
def get_logger(name: str | None = None) -> Any:
    """Get a logger instance through Hub with circular import protection.

    This function provides access to the global logger instance. It is preserved
    for backward compatibility but should be avoided in new application code in
    favor of explicit Dependency Injection.

    Circular Import Protection:
        Uses thread-local state to detect recursive initialization and falls
        back to basic structlog when circular dependencies are detected.

    Args:
        name: Logger name (e.g., __name__ from a module)

    Returns:
        Configured structlog logger instance

    Note:
        For building testable and maintainable applications, the recommended
        approach is to inject a logger instance via a `Container`. See the
        Dependency Injection guide for more information.
    """
    # Track recursion depth to prevent infinite loops
    depth = getattr(_is_initializing, "depth", 0)

    # Check if we're already in the middle of initialization to prevent circular import
    if depth > 0:
        # Already initializing - use fallback to break circular dependency
        import structlog

        return structlog.get_logger(name)

    # Safety check: enforce maximum recursion depth
    if depth >= _MAX_RECURSION_DEPTH:
        import structlog

        return structlog.get_logger(name)

    try:
        # Increment recursion depth
        _is_initializing.depth = depth + 1

        from provide.foundation.hub.manager import get_hub

        hub = get_hub()
        return hub.get_foundation_logger(name)
    except (ImportError, RecursionError):
        # Fallback to basic structlog if hub is not available or circular import detected
        import structlog

        return structlog.get_logger(name)
    finally:
        # Always decrement depth counter to allow future attempts
        _is_initializing.depth = max(0, depth)