hexastack-logging 0.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.3
2
+ Name: hexastack-logging
3
+ Version: 0.0.0
4
+ Summary: Add your description here
5
+ Author: Richard West
6
+ Author-email: Richard West <dopplereffect.us@gmail.com>
7
+ Requires-Dist: hexastack-core
8
+ Requires-Dist: loguru>=0.7.0 ; extra == 'all'
9
+ Requires-Dist: rich>=13.0.0 ; extra == 'all'
10
+ Requires-Dist: structlog>=24.0.0 ; extra == 'all'
11
+ Requires-Dist: loguru>=0.7.0 ; extra == 'loguru'
12
+ Requires-Dist: rich>=13.0.0 ; extra == 'rich'
13
+ Requires-Dist: structlog>=24.0.0 ; extra == 'structlog'
14
+ Requires-Python: >=3.13
15
+ Provides-Extra: all
16
+ Provides-Extra: loguru
17
+ Provides-Extra: rich
18
+ Provides-Extra: structlog
19
+ Description-Content-Type: text/markdown
20
+
21
+ # hexastack-logging
22
+
23
+ > Structured logging, security sanitization, and adapter integrations (Loguru, Rich, Structlog) for Hexastack.
24
+
25
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
26
+
27
+ ---
28
+
29
+ ## 1. Overview & Capabilities
30
+
31
+ `hexastack-logging` provides high-performance, structured telemetry across the entire Hexastack lifecycle:
32
+
33
+ - **Multiple Backend Adapters**: Native implementations for `Loguru`, `Rich`, `Structlog`, and standard library structured logging.
34
+ - **Security & PII Sanitization**: Automatic masking of sensitive fields (passwords, tokens, authorization headers, credit cards).
35
+ - **Formatters**: JSON formatting for cloud aggregators (Datadog, CloudWatch) and colored console formatting for local development.
36
+ - **Context & Correlation Integration**: Injects active `correlation_id` from async context into all log outputs.
37
+ - **Log Filtering & Levels**: Fine-grained level filtering and module-based log routing.
38
+
39
+ ---
40
+
41
+ ## 2. Package Anatomy & Key Components
42
+
43
+ ```
44
+ hexastack_logging/
45
+ ├── domain/ # LogRecord, LogLevel, Logging exceptions
46
+ ├── ports/ # LoggerPort, FormatterPort, FilterPort, SanitizerPort
47
+ ├── adapters/ # LoguruAdapter, RichAdapter, StructlogAdapter, StructuredLogger
48
+ └── infra/ # LoggingBootstrapper (order=10), Formatters, Sanitizers, Config
49
+ ```
50
+
51
+ ### Key Exports
52
+
53
+ | Category | Exports |
54
+ |---|---|
55
+ | **Adapters** | `StructuredLogger`, `LoguruAdapter`, `RichAdapter`, `StructlogAdapter` |
56
+ | **Bootstrap** | `LoggingBootstrapper` (order=10), `HexastackLoggingConfig` |
57
+ | **Formatters** | `JsonFormatter`, `ConsoleFormatter` |
58
+ | **Sanitization** | `SanitizerFilter`, `mask_sensitive_data` |
59
+
60
+ ---
61
+
62
+ ## 3. Monorepo & Sibling Relationships
63
+
64
+ ```mermaid
65
+ graph TD
66
+ subgraph SiblingConsumers ["Consumers of LoggerPort"]
67
+ CQRS["hexastack-cqrs (LoggingMiddleware)"]
68
+ FASTAPI["hexastack-fastapi (HttpLoggingMiddleware)"]
69
+ GRPC["hexastack-grpc (LoggingServerInterceptor)"]
70
+ GRAPHQL["hexastack-graphql (CorrelationExtension)"]
71
+ CORE_CTX["hexastack-core (get_correlation_id)"]
72
+ end
73
+
74
+ subgraph LoggingSubsystem ["hexastack-logging"]
75
+ BOOT["LoggingBootstrapper (order=10)"]
76
+ ADAPTERS["Adapters (Loguru / Rich / Structlog / StdLib)"]
77
+ SAN["PII Sanitizer & JSON Formatter"]
78
+ end
79
+
80
+ BOOT --> ADAPTERS
81
+ ADAPTERS --> SAN
82
+ ADAPTERS -. reads context .-> CORE_CTX
83
+
84
+ CQRS -. resolves from DI .-> ADAPTERS
85
+ FASTAPI -. resolves from DI .-> ADAPTERS
86
+ GRPC -. resolves from DI .-> ADAPTERS
87
+ GRAPHQL -. resolves from DI .-> ADAPTERS
88
+ ```
89
+
90
+ ### Explicit Dependencies (Direct)
91
+ - `hexastack-core`: Abstract `LoggerPort`, DI container, and async context tracking.
92
+
93
+ ### Implied / Behavioral Relationships (DI-Mediated)
94
+ - **Dependency Provider**: Binds `LoggerPort` into the DI container at `order=10` (before CQRS `order=20`), ensuring telemetry is available to all subsequent bootstrappers and middlewares.
95
+ - **Correlation ID Synchronization**: Automatically reads and outputs `get_correlation_id()` stored in `hexastack_core.utils.context`.
96
+
97
+ ### Optional Integrations (Extras)
98
+ - `[loguru]`: Enables `loguru>=0.7.0`.
99
+ - `[rich]`: Enables `rich>=13.0.0` console formatting.
100
+ - `[structlog]`: Enables `structlog>=24.0.0`.
101
+ - `[all]`: Installs all optional logging backends.
102
+
103
+ ---
104
+
105
+ ## 4. Installation
106
+
107
+ ```bash
108
+ # Standalone standard library logging
109
+ pip install hexastack-logging
110
+
111
+ # With Loguru and Rich support
112
+ pip install "hexastack-logging[loguru,rich]"
113
+
114
+ # All logging backends
115
+ pip install "hexastack-logging[all]"
116
+
117
+ # Via umbrella package
118
+ pip install hexastack
119
+ ```
120
+
121
+ ---
122
+
123
+ ## 5. Configuration Reference
124
+
125
+ ```toml
126
+ [hexastack.logging]
127
+ level = "INFO" # "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
128
+ format = "json" # "json", "console"
129
+ backend = "structlog" # "standard", "loguru", "rich", "structlog"
130
+ sanitize_keys = ["password", "token", "secret", "authorization", "api_key"]
131
+ ```
132
+
133
+ ---
134
+
135
+ ## 6. Quickstart Example
136
+
137
+ ```python
138
+ from hexastack_core.infra.bootstrap import bootstrap
139
+ from hexastack_core.ports.logging import LoggerPort
140
+
141
+ runtime = bootstrap(
142
+ config_overrides={
143
+ "logging": {
144
+ "level": "DEBUG",
145
+ "format": "console",
146
+ }
147
+ }
148
+ )
149
+
150
+ logger = runtime.container.get(LoggerPort)
151
+ logger.info("Application initialized successfully", extra={"user_count": 42})
152
+ ```
@@ -0,0 +1,132 @@
1
+ # hexastack-logging
2
+
3
+ > Structured logging, security sanitization, and adapter integrations (Loguru, Rich, Structlog) for Hexastack.
4
+
5
+ [![Python 3.13+](https://img.shields.io/badge/python-3.13+-blue.svg)](https://www.python.org/downloads/)
6
+
7
+ ---
8
+
9
+ ## 1. Overview & Capabilities
10
+
11
+ `hexastack-logging` provides high-performance, structured telemetry across the entire Hexastack lifecycle:
12
+
13
+ - **Multiple Backend Adapters**: Native implementations for `Loguru`, `Rich`, `Structlog`, and standard library structured logging.
14
+ - **Security & PII Sanitization**: Automatic masking of sensitive fields (passwords, tokens, authorization headers, credit cards).
15
+ - **Formatters**: JSON formatting for cloud aggregators (Datadog, CloudWatch) and colored console formatting for local development.
16
+ - **Context & Correlation Integration**: Injects active `correlation_id` from async context into all log outputs.
17
+ - **Log Filtering & Levels**: Fine-grained level filtering and module-based log routing.
18
+
19
+ ---
20
+
21
+ ## 2. Package Anatomy & Key Components
22
+
23
+ ```
24
+ hexastack_logging/
25
+ ├── domain/ # LogRecord, LogLevel, Logging exceptions
26
+ ├── ports/ # LoggerPort, FormatterPort, FilterPort, SanitizerPort
27
+ ├── adapters/ # LoguruAdapter, RichAdapter, StructlogAdapter, StructuredLogger
28
+ └── infra/ # LoggingBootstrapper (order=10), Formatters, Sanitizers, Config
29
+ ```
30
+
31
+ ### Key Exports
32
+
33
+ | Category | Exports |
34
+ |---|---|
35
+ | **Adapters** | `StructuredLogger`, `LoguruAdapter`, `RichAdapter`, `StructlogAdapter` |
36
+ | **Bootstrap** | `LoggingBootstrapper` (order=10), `HexastackLoggingConfig` |
37
+ | **Formatters** | `JsonFormatter`, `ConsoleFormatter` |
38
+ | **Sanitization** | `SanitizerFilter`, `mask_sensitive_data` |
39
+
40
+ ---
41
+
42
+ ## 3. Monorepo & Sibling Relationships
43
+
44
+ ```mermaid
45
+ graph TD
46
+ subgraph SiblingConsumers ["Consumers of LoggerPort"]
47
+ CQRS["hexastack-cqrs (LoggingMiddleware)"]
48
+ FASTAPI["hexastack-fastapi (HttpLoggingMiddleware)"]
49
+ GRPC["hexastack-grpc (LoggingServerInterceptor)"]
50
+ GRAPHQL["hexastack-graphql (CorrelationExtension)"]
51
+ CORE_CTX["hexastack-core (get_correlation_id)"]
52
+ end
53
+
54
+ subgraph LoggingSubsystem ["hexastack-logging"]
55
+ BOOT["LoggingBootstrapper (order=10)"]
56
+ ADAPTERS["Adapters (Loguru / Rich / Structlog / StdLib)"]
57
+ SAN["PII Sanitizer & JSON Formatter"]
58
+ end
59
+
60
+ BOOT --> ADAPTERS
61
+ ADAPTERS --> SAN
62
+ ADAPTERS -. reads context .-> CORE_CTX
63
+
64
+ CQRS -. resolves from DI .-> ADAPTERS
65
+ FASTAPI -. resolves from DI .-> ADAPTERS
66
+ GRPC -. resolves from DI .-> ADAPTERS
67
+ GRAPHQL -. resolves from DI .-> ADAPTERS
68
+ ```
69
+
70
+ ### Explicit Dependencies (Direct)
71
+ - `hexastack-core`: Abstract `LoggerPort`, DI container, and async context tracking.
72
+
73
+ ### Implied / Behavioral Relationships (DI-Mediated)
74
+ - **Dependency Provider**: Binds `LoggerPort` into the DI container at `order=10` (before CQRS `order=20`), ensuring telemetry is available to all subsequent bootstrappers and middlewares.
75
+ - **Correlation ID Synchronization**: Automatically reads and outputs `get_correlation_id()` stored in `hexastack_core.utils.context`.
76
+
77
+ ### Optional Integrations (Extras)
78
+ - `[loguru]`: Enables `loguru>=0.7.0`.
79
+ - `[rich]`: Enables `rich>=13.0.0` console formatting.
80
+ - `[structlog]`: Enables `structlog>=24.0.0`.
81
+ - `[all]`: Installs all optional logging backends.
82
+
83
+ ---
84
+
85
+ ## 4. Installation
86
+
87
+ ```bash
88
+ # Standalone standard library logging
89
+ pip install hexastack-logging
90
+
91
+ # With Loguru and Rich support
92
+ pip install "hexastack-logging[loguru,rich]"
93
+
94
+ # All logging backends
95
+ pip install "hexastack-logging[all]"
96
+
97
+ # Via umbrella package
98
+ pip install hexastack
99
+ ```
100
+
101
+ ---
102
+
103
+ ## 5. Configuration Reference
104
+
105
+ ```toml
106
+ [hexastack.logging]
107
+ level = "INFO" # "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
108
+ format = "json" # "json", "console"
109
+ backend = "structlog" # "standard", "loguru", "rich", "structlog"
110
+ sanitize_keys = ["password", "token", "secret", "authorization", "api_key"]
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 6. Quickstart Example
116
+
117
+ ```python
118
+ from hexastack_core.infra.bootstrap import bootstrap
119
+ from hexastack_core.ports.logging import LoggerPort
120
+
121
+ runtime = bootstrap(
122
+ config_overrides={
123
+ "logging": {
124
+ "level": "DEBUG",
125
+ "format": "console",
126
+ }
127
+ }
128
+ )
129
+
130
+ logger = runtime.container.get(LoggerPort)
131
+ logger.info("Application initialized successfully", extra={"user_count": 42})
132
+ ```
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "hexastack-logging"
3
+ version = "0.0.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ dependencies = ["hexastack-core"]
8
+
9
+ [[project.authors]]
10
+ name = "Richard West"
11
+ email = "dopplereffect.us@gmail.com"
12
+
13
+ [project.optional-dependencies]
14
+ loguru = ["loguru>=0.7.0"]
15
+ rich = ["rich>=13.0.0"]
16
+ structlog = ["structlog>=24.0.0"]
17
+ all = [
18
+ "loguru>=0.7.0",
19
+ "rich>=13.0.0",
20
+ "structlog>=24.0.0",
21
+ ]
22
+
23
+ [project.entry-points."hexastack.bootstrappers"]
24
+ logging = "hexastack_logging.infra.bootstrap:LoggingBootstrapper"
25
+
26
+ [build-system]
27
+ requires = ["uv_build>=0.12.3,<0.13.0"]
28
+ build-backend = "uv_build"
29
+
30
+ [tool.uv.sources.hexastack-core]
31
+ workspace = true
32
+
33
+ [tool.importlinter]
34
+ root_packages = ["hexastack_logging"]
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "hexastack-logging"
3
+ version = "0.0.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Richard West", email = "dopplereffect.us@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.13"
10
+ dependencies = [
11
+ "hexastack-core",
12
+ ]
13
+
14
+ [project.optional-dependencies]
15
+ loguru = ["loguru>=0.7.0"]
16
+ rich = ["rich>=13.0.0"]
17
+ structlog = ["structlog>=24.0.0"]
18
+ all = [
19
+ "loguru>=0.7.0",
20
+ "rich>=13.0.0",
21
+ "structlog>=24.0.0",
22
+ ]
23
+
24
+ [project.entry-points."hexastack.bootstrappers"]
25
+ logging = "hexastack_logging.infra.bootstrap:LoggingBootstrapper"
26
+
27
+ [build-system]
28
+ requires = ["uv_build>=0.12.3,<0.13.0"]
29
+ build-backend = "uv_build"
30
+
31
+ [tool.uv.sources]
32
+ hexastack-core = { workspace = true }
33
+
34
+ [tool.importlinter]
35
+ root_packages = ["hexastack_logging"]
@@ -0,0 +1,6 @@
1
+ from hexastack_logging import adapters, infra
2
+
3
+ __all__ = [
4
+ "adapters",
5
+ "infra",
6
+ ]
@@ -0,0 +1,13 @@
1
+ from hexastack_logging.adapters.logger import (
2
+ LoguruAdapter,
3
+ RichLogger,
4
+ StructlogAdapter,
5
+ StructuredLogger,
6
+ )
7
+
8
+ __all__ = [
9
+ "LoguruAdapter",
10
+ "RichLogger",
11
+ "StructlogAdapter",
12
+ "StructuredLogger",
13
+ ]
@@ -0,0 +1,11 @@
1
+ from hexastack_logging.adapters.logger.loguru import LoguruAdapter
2
+ from hexastack_logging.adapters.logger.rich import RichLogger
3
+ from hexastack_logging.adapters.logger.structlog import StructlogAdapter
4
+ from hexastack_logging.adapters.logger.structured import StructuredLogger
5
+
6
+ __all__ = [
7
+ "LoguruAdapter",
8
+ "RichLogger",
9
+ "StructlogAdapter",
10
+ "StructuredLogger",
11
+ ]
@@ -0,0 +1,133 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ from hexastack_core.domain.exceptions import MissingDependencyError
5
+ from hexastack_core.ports.logging import Extras, LoggingPort
6
+ from hexastack_core.utils.context import get_correlation_id, get_user_context
7
+
8
+
9
+ class LoguruAdapter(LoggingPort):
10
+ """Optional Loguru adapter implementing LoggingPort.
11
+
12
+ Notes/Architectural Intent:
13
+ Delegates logging to loguru while binding Hexastack correlation
14
+ and user context.
15
+ """
16
+
17
+ def __init__(self, logger: Any = None) -> None:
18
+ """Initialize LoguruAdapter.
19
+
20
+ Args:
21
+ logger: Optional loguru logger instance.
22
+
23
+ Raises:
24
+ MissingDependencyError: If loguru package is not installed.
25
+ """
26
+ if logger is None:
27
+ try:
28
+ loguru_mod = importlib.import_module("loguru")
29
+ self._logger: Any = loguru_mod.logger
30
+ except ImportError as err:
31
+ raise MissingDependencyError(
32
+ "loguru is required for LoguruAdapter. Install via 'pip install hexastack-logging[loguru]'."
33
+ ) from err
34
+ else:
35
+ self._logger = logger
36
+
37
+ def _get_bound_logger(self, extra: Extras | None = None) -> Any:
38
+ bind_dict: dict[str, Any] = dict(extra) if extra else {}
39
+ cid = get_correlation_id()
40
+ if cid:
41
+ bind_dict["correlation_id"] = cid
42
+ user = get_user_context()
43
+ if user:
44
+ bind_dict["user_id"] = user.user_id
45
+ if user.tenant_id:
46
+ bind_dict["tenant_id"] = user.tenant_id
47
+ return self._logger.bind(**bind_dict)
48
+
49
+ def critical(
50
+ self, message: str, extra: Extras | None = None, exc: Exception | None = None
51
+ ) -> None:
52
+ """Log a critical message through loguru.
53
+
54
+ Args:
55
+ message: Text message to log.
56
+ extra: Optional key-value dictionary of contextual metadata.
57
+ exc: Optional exception instance to attach traceback.
58
+
59
+ Returns:
60
+ None.
61
+
62
+ Raises:
63
+ None.
64
+ """
65
+ log = self._get_bound_logger(extra)
66
+ log.opt(exception=exc).critical(message)
67
+
68
+ def debug(self, message: str, extra: Extras | None = None) -> None:
69
+ """Log a debug message through loguru.
70
+
71
+ Args:
72
+ message: Text message to log.
73
+ extra: Optional key-value dictionary of contextual metadata.
74
+
75
+ Returns:
76
+ None.
77
+
78
+ Raises:
79
+ None.
80
+ """
81
+ log = self._get_bound_logger(extra)
82
+ log.debug(message)
83
+
84
+ def error(
85
+ self, message: str, extra: Extras | None = None, exc: Exception | None = None
86
+ ) -> None:
87
+ """Log an error message through loguru.
88
+
89
+ Args:
90
+ message: Text message to log.
91
+ extra: Optional key-value dictionary of contextual metadata.
92
+ exc: Optional exception instance to attach traceback.
93
+
94
+ Returns:
95
+ None.
96
+
97
+ Raises:
98
+ None.
99
+ """
100
+ log = self._get_bound_logger(extra)
101
+ log.opt(exception=exc).error(message)
102
+
103
+ def info(self, message: str, extra: Extras | None = None) -> None:
104
+ """Log an informational message through loguru.
105
+
106
+ Args:
107
+ message: Text message to log.
108
+ extra: Optional key-value dictionary of contextual metadata.
109
+
110
+ Returns:
111
+ None.
112
+
113
+ Raises:
114
+ None.
115
+ """
116
+ log = self._get_bound_logger(extra)
117
+ log.info(message)
118
+
119
+ def warning(self, message: str, extra: Extras | None = None) -> None:
120
+ """Log a warning message through loguru.
121
+
122
+ Args:
123
+ message: Text message to log.
124
+ extra: Optional key-value dictionary of contextual metadata.
125
+
126
+ Returns:
127
+ None.
128
+
129
+ Raises:
130
+ None.
131
+ """
132
+ log = self._get_bound_logger(extra)
133
+ log.warning(message)
@@ -0,0 +1,131 @@
1
+ import importlib
2
+ from typing import Any
3
+
4
+ from hexastack_core.domain.exceptions import MissingDependencyError
5
+ from hexastack_core.ports.logging import Extras, LoggingPort
6
+ from hexastack_core.utils.context import get_correlation_id
7
+
8
+
9
+ class RichLogger(LoggingPort):
10
+ """Optional Rich terminal logger implementing LoggingPort.
11
+
12
+ Notes/Architectural Intent:
13
+ Renders rich stylized terminal logs with correlation ID tags when rich is installed.
14
+ """
15
+
16
+ def __init__(self, console: Any = None) -> None:
17
+ """Initialize RichLogger.
18
+
19
+ Args:
20
+ console: Optional rich.console.Console instance.
21
+
22
+ Raises:
23
+ MissingDependencyError: If rich package is not installed.
24
+ """
25
+ if console is None:
26
+ try:
27
+ rich_console_mod = importlib.import_module("rich.console")
28
+ self._console: Any = rich_console_mod.Console()
29
+ except ImportError as err:
30
+ raise MissingDependencyError(
31
+ "rich is required for RichLogger. Install via 'pip install hexastack-logging[rich]'."
32
+ ) from err
33
+ else:
34
+ self._console = console
35
+
36
+ def _render(
37
+ self,
38
+ level: str,
39
+ color: str,
40
+ message: str,
41
+ extra: Extras | None = None,
42
+ exc: Exception | None = None,
43
+ ) -> None:
44
+ cid = get_correlation_id()
45
+ cid_tag = f"[dim][corr:{cid[:8]}][/dim] " if cid else ""
46
+ extra_str = f" [dim]{extra}[/dim]" if extra else ""
47
+ exc_str = f"\n[red]{exc}[/red]" if exc else ""
48
+ self._console.print(
49
+ f"[{color}][{level:<8}][/{color}] {cid_tag}{message}{extra_str}{exc_str}"
50
+ )
51
+
52
+ def critical(
53
+ self, message: str, extra: Extras | None = None, exc: Exception | None = None
54
+ ) -> None:
55
+ """Log a critical message with Rich formatting.
56
+
57
+ Args:
58
+ message: Text message to log.
59
+ extra: Optional key-value dictionary of contextual metadata.
60
+ exc: Optional exception instance to attach traceback.
61
+
62
+ Returns:
63
+ None.
64
+
65
+ Raises:
66
+ None.
67
+ """
68
+ self._render("CRITICAL", "bold red", message, extra=extra, exc=exc)
69
+
70
+ def debug(self, message: str, extra: Extras | None = None) -> None:
71
+ """Log a debug message with Rich formatting.
72
+
73
+ Args:
74
+ message: Text message to log.
75
+ extra: Optional key-value dictionary of contextual metadata.
76
+
77
+ Returns:
78
+ None.
79
+
80
+ Raises:
81
+ None.
82
+ """
83
+ self._render("DEBUG", "cyan", message, extra=extra)
84
+
85
+ def error(
86
+ self, message: str, extra: Extras | None = None, exc: Exception | None = None
87
+ ) -> None:
88
+ """Log an error message with Rich formatting.
89
+
90
+ Args:
91
+ message: Text message to log.
92
+ extra: Optional key-value dictionary of contextual metadata.
93
+ exc: Optional exception instance to attach traceback.
94
+
95
+ Returns:
96
+ None.
97
+
98
+ Raises:
99
+ None.
100
+ """
101
+ self._render("ERROR", "red", message, extra=extra, exc=exc)
102
+
103
+ def info(self, message: str, extra: Extras | None = None) -> None:
104
+ """Log an informational message with Rich formatting.
105
+
106
+ Args:
107
+ message: Text message to log.
108
+ extra: Optional key-value dictionary of contextual metadata.
109
+
110
+ Returns:
111
+ None.
112
+
113
+ Raises:
114
+ None.
115
+ """
116
+ self._render("INFO", "green", message, extra=extra)
117
+
118
+ def warning(self, message: str, extra: Extras | None = None) -> None:
119
+ """Log a warning message with Rich formatting.
120
+
121
+ Args:
122
+ message: Text message to log.
123
+ extra: Optional key-value dictionary of contextual metadata.
124
+
125
+ Returns:
126
+ None.
127
+
128
+ Raises:
129
+ None.
130
+ """
131
+ self._render("WARNING", "yellow", message, extra=extra)