hexastack-logging 0.0.0__py3-none-any.whl
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.
- hexastack_logging/__init__.py +6 -0
- hexastack_logging/adapters/__init__.py +13 -0
- hexastack_logging/adapters/logger/__init__.py +11 -0
- hexastack_logging/adapters/logger/loguru.py +133 -0
- hexastack_logging/adapters/logger/rich.py +131 -0
- hexastack_logging/adapters/logger/structlog.py +128 -0
- hexastack_logging/adapters/logger/structured.py +139 -0
- hexastack_logging/infra/__init__.py +31 -0
- hexastack_logging/infra/bootstrap.py +55 -0
- hexastack_logging/infra/config.py +254 -0
- hexastack_logging/infra/filters.py +131 -0
- hexastack_logging/infra/formatters/__init__.py +9 -0
- hexastack_logging/infra/formatters/console.py +82 -0
- hexastack_logging/infra/formatters/json.py +51 -0
- hexastack_logging/infra/sanitizer.py +154 -0
- hexastack_logging/py.typed +0 -0
- hexastack_logging-0.0.0.dist-info/METADATA +152 -0
- hexastack_logging-0.0.0.dist-info/RECORD +20 -0
- hexastack_logging-0.0.0.dist-info/WHEEL +4 -0
- hexastack_logging-0.0.0.dist-info/entry_points.txt +3 -0
|
@@ -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)
|
|
@@ -0,0 +1,128 @@
|
|
|
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 StructlogAdapter(LoggingPort):
|
|
10
|
+
"""Optional Structlog adapter implementing LoggingPort.
|
|
11
|
+
|
|
12
|
+
Notes/Architectural Intent:
|
|
13
|
+
Delegates logging to structlog BoundLogger while binding Hexastack correlation
|
|
14
|
+
and user context.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, logger: Any = None) -> None:
|
|
18
|
+
"""Initialize StructlogAdapter.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
logger: Optional structlog logger instance.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
MissingDependencyError: If structlog package is not installed.
|
|
25
|
+
"""
|
|
26
|
+
if logger is None:
|
|
27
|
+
try:
|
|
28
|
+
structlog_mod = importlib.import_module("structlog")
|
|
29
|
+
self._logger: Any = structlog_mod.get_logger()
|
|
30
|
+
except ImportError as err:
|
|
31
|
+
raise MissingDependencyError(
|
|
32
|
+
"structlog is required for StructlogAdapter. Install via 'pip install hexastack-logging[structlog]'."
|
|
33
|
+
) from err
|
|
34
|
+
else:
|
|
35
|
+
self._logger = logger
|
|
36
|
+
|
|
37
|
+
def _bind_context(self, extra: Extras | None = None) -> dict[str, Any]:
|
|
38
|
+
context: dict[str, Any] = dict(extra) if extra else {}
|
|
39
|
+
cid = get_correlation_id()
|
|
40
|
+
if cid:
|
|
41
|
+
context["correlation_id"] = cid
|
|
42
|
+
user = get_user_context()
|
|
43
|
+
if user:
|
|
44
|
+
context["user_id"] = user.user_id
|
|
45
|
+
if user.tenant_id:
|
|
46
|
+
context["tenant_id"] = user.tenant_id
|
|
47
|
+
return context
|
|
48
|
+
|
|
49
|
+
def critical(
|
|
50
|
+
self, message: str, extra: Extras | None = None, exc: Exception | None = None
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Log a critical message through structlog.
|
|
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
|
+
self._logger.critical(message, exc_info=exc, **self._bind_context(extra))
|
|
66
|
+
|
|
67
|
+
def debug(self, message: str, extra: Extras | None = None) -> None:
|
|
68
|
+
"""Log a debug message through structlog.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
message: Text message to log.
|
|
72
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
None.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
None.
|
|
79
|
+
"""
|
|
80
|
+
self._logger.debug(message, **self._bind_context(extra))
|
|
81
|
+
|
|
82
|
+
def error(
|
|
83
|
+
self, message: str, extra: Extras | None = None, exc: Exception | None = None
|
|
84
|
+
) -> None:
|
|
85
|
+
"""Log an error message through structlog.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
message: Text message to log.
|
|
89
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
90
|
+
exc: Optional exception instance to attach traceback.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
None.
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
None.
|
|
97
|
+
"""
|
|
98
|
+
self._logger.error(message, exc_info=exc, **self._bind_context(extra))
|
|
99
|
+
|
|
100
|
+
def info(self, message: str, extra: Extras | None = None) -> None:
|
|
101
|
+
"""Log an informational message through structlog.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
message: Text message to log.
|
|
105
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
None.
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
None.
|
|
112
|
+
"""
|
|
113
|
+
self._logger.info(message, **self._bind_context(extra))
|
|
114
|
+
|
|
115
|
+
def warning(self, message: str, extra: Extras | None = None) -> None:
|
|
116
|
+
"""Log a warning message through structlog.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
message: Text message to log.
|
|
120
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
None.
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
None.
|
|
127
|
+
"""
|
|
128
|
+
self._logger.warning(message, **self._bind_context(extra))
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from logging.handlers import QueueListener
|
|
3
|
+
|
|
4
|
+
from hexastack_core.ports.logging import Extras, LoggingPort
|
|
5
|
+
from hexastack_logging.infra.config import (
|
|
6
|
+
HexastackLoggingConfig,
|
|
7
|
+
configure_logging,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class StructuredLogger(LoggingPort):
|
|
12
|
+
"""Production-grade structured logger implementing LoggingPort.
|
|
13
|
+
|
|
14
|
+
Notes/Architectural Intent:
|
|
15
|
+
Delegates to standard library logging.Logger configured with CorrelationIdFilter,
|
|
16
|
+
SanitizerFilter, rotating file handlers, and optional background QueueListener.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
name: str = "hexastack",
|
|
22
|
+
config: HexastackLoggingConfig | None = None,
|
|
23
|
+
logger: logging.Logger | None = None,
|
|
24
|
+
listener: QueueListener | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Initialize StructuredLogger with logger name, configuration, and optional listener.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
name: Logger hierarchy name identifier.
|
|
30
|
+
config: Optional configuration model.
|
|
31
|
+
logger: Optional pre-configured Logger instance.
|
|
32
|
+
listener: Optional active QueueListener instance.
|
|
33
|
+
"""
|
|
34
|
+
self._logger = logger or logging.getLogger(name)
|
|
35
|
+
if logger is None:
|
|
36
|
+
self._listener = configure_logging(
|
|
37
|
+
config=config, target_logger=self._logger
|
|
38
|
+
)
|
|
39
|
+
else:
|
|
40
|
+
self._listener = listener
|
|
41
|
+
|
|
42
|
+
def close(self) -> None:
|
|
43
|
+
"""Stop and flush the background QueueListener if active.
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
None.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
None.
|
|
50
|
+
"""
|
|
51
|
+
if self._listener is not None:
|
|
52
|
+
self._listener.stop()
|
|
53
|
+
self._listener = None
|
|
54
|
+
|
|
55
|
+
def critical(
|
|
56
|
+
self, message: str, extra: Extras | None = None, exc: Exception | None = None
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Log a critical message with extra context.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
message: The message string to log.
|
|
62
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
63
|
+
exc: Optional exception instance to attach traceback.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
None.
|
|
67
|
+
|
|
68
|
+
Raises:
|
|
69
|
+
None.
|
|
70
|
+
"""
|
|
71
|
+
self._logger.critical(message, exc_info=exc, extra=extra)
|
|
72
|
+
|
|
73
|
+
def debug(self, message: str, extra: Extras | None = None) -> None:
|
|
74
|
+
"""Log a debug message with extra context.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
message: The message string to log.
|
|
78
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
None.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
None.
|
|
85
|
+
"""
|
|
86
|
+
self._logger.debug(message, extra=extra)
|
|
87
|
+
|
|
88
|
+
def error(
|
|
89
|
+
self, message: str, extra: Extras | None = None, exc: Exception | None = None
|
|
90
|
+
) -> None:
|
|
91
|
+
"""Log an error message with extra context.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
message: The message string to log.
|
|
95
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
96
|
+
exc: Optional exception instance to attach traceback.
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
None.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
None.
|
|
103
|
+
"""
|
|
104
|
+
self._logger.error(message, exc_info=exc, extra=extra)
|
|
105
|
+
|
|
106
|
+
def info(self, message: str, extra: Extras | None = None) -> None:
|
|
107
|
+
"""Log an informational message with extra context.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
message: The message string to log.
|
|
111
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
None.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
None.
|
|
118
|
+
"""
|
|
119
|
+
self._logger.info(message, extra=extra)
|
|
120
|
+
|
|
121
|
+
@property
|
|
122
|
+
def listener(self) -> QueueListener | None:
|
|
123
|
+
"""The active QueueListener instance if async queueing is enabled."""
|
|
124
|
+
return self._listener
|
|
125
|
+
|
|
126
|
+
def warning(self, message: str, extra: Extras | None = None) -> None:
|
|
127
|
+
"""Log a warning message with extra context.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
message: The message string to log.
|
|
131
|
+
extra: Optional key-value dictionary of contextual metadata.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
None.
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
None.
|
|
138
|
+
"""
|
|
139
|
+
self._logger.warning(message, extra=extra)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from hexastack_logging.infra.config import (
|
|
2
|
+
AsyncQueueConfig,
|
|
3
|
+
FileLoggingConfig,
|
|
4
|
+
HexastackLoggingConfig,
|
|
5
|
+
SanitizerConfig,
|
|
6
|
+
configure_logging,
|
|
7
|
+
register_logging_config,
|
|
8
|
+
)
|
|
9
|
+
from hexastack_logging.infra.filters import (
|
|
10
|
+
CorrelationIdFilter,
|
|
11
|
+
SanitizerFilter,
|
|
12
|
+
)
|
|
13
|
+
from hexastack_logging.infra.formatters import (
|
|
14
|
+
ConsoleFormatter,
|
|
15
|
+
JsonFormatter,
|
|
16
|
+
)
|
|
17
|
+
from hexastack_logging.infra.sanitizer import Sanitizer
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"AsyncQueueConfig",
|
|
21
|
+
"configure_logging",
|
|
22
|
+
"ConsoleFormatter",
|
|
23
|
+
"CorrelationIdFilter",
|
|
24
|
+
"FileLoggingConfig",
|
|
25
|
+
"HexastackLoggingConfig",
|
|
26
|
+
"JsonFormatter",
|
|
27
|
+
"register_logging_config",
|
|
28
|
+
"Sanitizer",
|
|
29
|
+
"SanitizerConfig",
|
|
30
|
+
"SanitizerFilter",
|
|
31
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from hexastack_core.infra.bootstrap import BootstrapContext
|
|
2
|
+
from hexastack_core.infra.registries.config import ConfigRegistry
|
|
3
|
+
from hexastack_core.ports.bootstrap import BootstrapperPort
|
|
4
|
+
from hexastack_core.ports.logging import LoggingPort
|
|
5
|
+
from hexastack_logging.adapters.logger.structured import StructuredLogger
|
|
6
|
+
from hexastack_logging.infra.config import (
|
|
7
|
+
HexastackLoggingConfig,
|
|
8
|
+
register_logging_config,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LoggingBootstrapper(BootstrapperPort):
|
|
13
|
+
"""Bootstrap extension initializing logging configuration and LoggingPort adapter.
|
|
14
|
+
|
|
15
|
+
Notes/Architectural Intent:
|
|
16
|
+
Implements BootstrapperPort for hexastack-logging, registering 'logging'
|
|
17
|
+
config section in Phase 1 and binding StructuredLogger into rodi DI in Phase 2.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
name: str = "logging"
|
|
21
|
+
order: int = 10
|
|
22
|
+
|
|
23
|
+
def configure(self, context: BootstrapContext) -> None:
|
|
24
|
+
"""Phase 2: Configure root logging and register StructuredLogger in container.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
context: BootstrapContext containing DI container and loaded config.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
None.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
None.
|
|
34
|
+
"""
|
|
35
|
+
if LoggingPort not in context.container:
|
|
36
|
+
cfg = context.get_config("logging", HexastackLoggingConfig)
|
|
37
|
+
logger = StructuredLogger(config=cfg)
|
|
38
|
+
context.container.add_instance(logger, declared_class=LoggingPort)
|
|
39
|
+
context.properties["logger"] = logger
|
|
40
|
+
else:
|
|
41
|
+
context.properties["logger"] = context.container.resolve(LoggingPort)
|
|
42
|
+
|
|
43
|
+
def register_config(self, registry: ConfigRegistry) -> None:
|
|
44
|
+
"""Phase 1: Register logging configuration schema under 'logging'.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
registry: Target ConfigRegistry instance.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
None.
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
None.
|
|
54
|
+
"""
|
|
55
|
+
register_logging_config(registry)
|