provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev2__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.
- provide/foundation/__init__.py +41 -23
- provide/foundation/archive/__init__.py +23 -0
- provide/foundation/archive/base.py +70 -0
- provide/foundation/archive/bzip2.py +157 -0
- provide/foundation/archive/gzip.py +159 -0
- provide/foundation/archive/operations.py +334 -0
- provide/foundation/archive/tar.py +164 -0
- provide/foundation/archive/zip.py +203 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +13 -7
- provide/foundation/cli/commands/logs/__init__.py +1 -1
- provide/foundation/cli/commands/logs/query.py +1 -1
- provide/foundation/cli/commands/logs/send.py +1 -1
- provide/foundation/cli/commands/logs/tail.py +1 -1
- provide/foundation/cli/decorators.py +11 -10
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -35
- provide/foundation/cli/utils.py +21 -17
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +479 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +4 -19
- provide/foundation/config/loader.py +9 -3
- provide/foundation/config/sync.py +19 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +35 -13
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +85 -109
- provide/foundation/core.py +1 -2
- provide/foundation/crypto/__init__.py +2 -0
- provide/foundation/crypto/certificates/__init__.py +34 -0
- provide/foundation/crypto/certificates/base.py +173 -0
- provide/foundation/crypto/certificates/certificate.py +290 -0
- provide/foundation/crypto/certificates/factory.py +213 -0
- provide/foundation/crypto/certificates/generator.py +138 -0
- provide/foundation/crypto/certificates/loader.py +130 -0
- provide/foundation/crypto/certificates/operations.py +198 -0
- provide/foundation/crypto/certificates/trust.py +107 -0
- provide/foundation/errors/__init__.py +2 -3
- provide/foundation/errors/decorators.py +0 -231
- provide/foundation/errors/types.py +0 -97
- provide/foundation/eventsets/__init__.py +0 -0
- provide/foundation/eventsets/display.py +84 -0
- provide/foundation/eventsets/registry.py +160 -0
- provide/foundation/eventsets/resolver.py +192 -0
- provide/foundation/eventsets/sets/das.py +128 -0
- provide/foundation/eventsets/sets/database.py +125 -0
- provide/foundation/eventsets/sets/http.py +153 -0
- provide/foundation/eventsets/sets/llm.py +139 -0
- provide/foundation/eventsets/sets/task_queue.py +107 -0
- provide/foundation/eventsets/types.py +70 -0
- provide/foundation/file/directory.py +13 -22
- provide/foundation/file/lock.py +3 -1
- provide/foundation/hub/components.py +77 -515
- provide/foundation/hub/config.py +151 -0
- provide/foundation/hub/discovery.py +62 -0
- provide/foundation/hub/handlers.py +81 -0
- provide/foundation/hub/lifecycle.py +194 -0
- provide/foundation/hub/manager.py +4 -4
- provide/foundation/hub/processors.py +44 -0
- provide/foundation/integrations/__init__.py +11 -0
- provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
- provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/client.py +12 -12
- provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
- provide/foundation/logger/__init__.py +3 -10
- provide/foundation/logger/config/logging.py +68 -298
- provide/foundation/logger/config/telemetry.py +41 -121
- provide/foundation/logger/core.py +0 -2
- provide/foundation/logger/custom_processors.py +1 -0
- provide/foundation/logger/factories.py +11 -2
- provide/foundation/logger/processors/main.py +20 -84
- provide/foundation/logger/setup/__init__.py +5 -1
- provide/foundation/logger/setup/coordinator.py +76 -24
- provide/foundation/logger/setup/processors.py +2 -9
- provide/foundation/logger/trace.py +27 -0
- provide/foundation/metrics/otel.py +10 -10
- provide/foundation/observability/__init__.py +2 -2
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +47 -0
- provide/foundation/process/lifecycle.py +115 -59
- provide/foundation/resilience/__init__.py +35 -0
- provide/foundation/resilience/circuit.py +164 -0
- provide/foundation/resilience/decorators.py +220 -0
- provide/foundation/resilience/fallback.py +193 -0
- provide/foundation/resilience/retry.py +325 -0
- provide/foundation/streams/config.py +79 -0
- provide/foundation/streams/console.py +7 -8
- provide/foundation/streams/core.py +6 -3
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +84 -2
- provide/foundation/testing/archive/__init__.py +24 -0
- provide/foundation/testing/archive/fixtures.py +217 -0
- provide/foundation/testing/cli.py +30 -17
- provide/foundation/testing/common/__init__.py +32 -0
- provide/foundation/testing/common/fixtures.py +236 -0
- provide/foundation/testing/file/__init__.py +40 -0
- provide/foundation/testing/file/content_fixtures.py +316 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +52 -0
- provide/foundation/testing/file/special_fixtures.py +153 -0
- provide/foundation/testing/logger.py +117 -11
- provide/foundation/testing/mocking/__init__.py +46 -0
- provide/foundation/testing/mocking/fixtures.py +331 -0
- provide/foundation/testing/process/__init__.py +48 -0
- provide/foundation/testing/process/async_fixtures.py +405 -0
- provide/foundation/testing/process/fixtures.py +56 -0
- provide/foundation/testing/process/subprocess_fixtures.py +209 -0
- provide/foundation/testing/threading/__init__.py +38 -0
- provide/foundation/testing/threading/basic_fixtures.py +101 -0
- provide/foundation/testing/threading/data_fixtures.py +99 -0
- provide/foundation/testing/threading/execution_fixtures.py +263 -0
- provide/foundation/testing/threading/fixtures.py +54 -0
- provide/foundation/testing/threading/sync_fixtures.py +97 -0
- provide/foundation/testing/time/__init__.py +32 -0
- provide/foundation/testing/time/fixtures.py +409 -0
- provide/foundation/testing/transport/__init__.py +30 -0
- provide/foundation/testing/transport/fixtures.py +280 -0
- provide/foundation/tools/__init__.py +58 -0
- provide/foundation/tools/base.py +348 -0
- provide/foundation/tools/cache.py +268 -0
- provide/foundation/tools/downloader.py +224 -0
- provide/foundation/tools/installer.py +254 -0
- provide/foundation/tools/registry.py +223 -0
- provide/foundation/tools/resolver.py +321 -0
- provide/foundation/tools/verifier.py +186 -0
- provide/foundation/tracer/otel.py +7 -11
- provide/foundation/tracer/spans.py +2 -2
- provide/foundation/transport/__init__.py +155 -0
- provide/foundation/transport/base.py +171 -0
- provide/foundation/transport/client.py +266 -0
- provide/foundation/transport/config.py +140 -0
- provide/foundation/transport/errors.py +79 -0
- provide/foundation/transport/http.py +232 -0
- provide/foundation/transport/middleware.py +360 -0
- provide/foundation/transport/registry.py +167 -0
- provide/foundation/transport/types.py +45 -0
- provide/foundation/utils/deps.py +14 -12
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +5 -28
- provide_foundation-0.0.0.dev2.dist-info/RECORD +225 -0
- provide/foundation/cli/commands/logs/generate_old.py +0 -569
- provide/foundation/crypto/certificates.py +0 -896
- provide/foundation/logger/emoji/__init__.py +0 -44
- provide/foundation/logger/emoji/matrix.py +0 -209
- provide/foundation/logger/emoji/sets.py +0 -458
- provide/foundation/logger/emoji/types.py +0 -56
- provide/foundation/logger/setup/emoji_resolver.py +0 -64
- provide_foundation-0.0.0.dev0.dist-info/RECORD +0 -149
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/top_level.txt +0 -0
@@ -5,22 +5,39 @@
|
|
5
5
|
TelemetryConfig class for Foundation telemetry configuration.
|
6
6
|
"""
|
7
7
|
|
8
|
-
import os
|
9
|
-
|
10
8
|
from attrs import define
|
11
9
|
|
12
|
-
from provide.foundation.config import
|
10
|
+
from provide.foundation.config.env import RuntimeConfig
|
11
|
+
from provide.foundation.config.base import field
|
12
|
+
from provide.foundation.config.defaults import (
|
13
|
+
DEFAULT_TELEMETRY_GLOBALLY_DISABLED,
|
14
|
+
DEFAULT_TRACING_ENABLED,
|
15
|
+
DEFAULT_METRICS_ENABLED,
|
16
|
+
DEFAULT_OTLP_PROTOCOL,
|
17
|
+
DEFAULT_TRACE_SAMPLE_RATE,
|
18
|
+
)
|
19
|
+
import os
|
20
|
+
from provide.foundation.config.converters import (
|
21
|
+
parse_bool_extended,
|
22
|
+
parse_headers,
|
23
|
+
parse_sample_rate,
|
24
|
+
validate_sample_rate,
|
25
|
+
)
|
13
26
|
from provide.foundation.logger.config.logging import LoggingConfig
|
14
27
|
|
15
28
|
|
29
|
+
def _get_service_name() -> str | None:
|
30
|
+
"""Get service name from OTEL_SERVICE_NAME or PROVIDE_SERVICE_NAME (OTEL takes precedence)."""
|
31
|
+
return os.getenv("OTEL_SERVICE_NAME") or os.getenv("PROVIDE_SERVICE_NAME")
|
32
|
+
|
33
|
+
|
16
34
|
@define(slots=True, repr=False)
|
17
|
-
class TelemetryConfig(
|
35
|
+
class TelemetryConfig(RuntimeConfig):
|
18
36
|
"""Main configuration object for the Foundation Telemetry system."""
|
19
37
|
|
20
38
|
service_name: str | None = field(
|
21
|
-
|
22
|
-
|
23
|
-
description="Service name for telemetry",
|
39
|
+
factory=_get_service_name,
|
40
|
+
description="Service name for telemetry (from OTEL_SERVICE_NAME or PROVIDE_SERVICE_NAME)",
|
24
41
|
)
|
25
42
|
service_version: str | None = field(
|
26
43
|
default=None,
|
@@ -28,23 +45,27 @@ class TelemetryConfig(BaseConfig):
|
|
28
45
|
description="Service version for telemetry",
|
29
46
|
)
|
30
47
|
logging: LoggingConfig = field(
|
31
|
-
factory=LoggingConfig,
|
48
|
+
factory=lambda: LoggingConfig.from_env(),
|
49
|
+
description="Logging configuration"
|
32
50
|
)
|
33
51
|
globally_disabled: bool = field(
|
34
|
-
default=
|
52
|
+
default=DEFAULT_TELEMETRY_GLOBALLY_DISABLED,
|
35
53
|
env_var="PROVIDE_TELEMETRY_DISABLED",
|
54
|
+
converter=parse_bool_extended,
|
36
55
|
description="Globally disable telemetry",
|
37
56
|
)
|
38
57
|
|
39
58
|
# OpenTelemetry configuration
|
40
59
|
tracing_enabled: bool = field(
|
41
|
-
default=
|
60
|
+
default=DEFAULT_TRACING_ENABLED,
|
42
61
|
env_var="OTEL_TRACING_ENABLED",
|
62
|
+
converter=parse_bool_extended,
|
43
63
|
description="Enable OpenTelemetry tracing",
|
44
64
|
)
|
45
65
|
metrics_enabled: bool = field(
|
46
|
-
default=
|
66
|
+
default=DEFAULT_METRICS_ENABLED,
|
47
67
|
env_var="OTEL_METRICS_ENABLED",
|
68
|
+
converter=parse_bool_extended,
|
48
69
|
description="Enable OpenTelemetry metrics",
|
49
70
|
)
|
50
71
|
otlp_endpoint: str | None = field(
|
@@ -57,132 +78,31 @@ class TelemetryConfig(BaseConfig):
|
|
57
78
|
env_var="OTEL_EXPORTER_OTLP_TRACES_ENDPOINT",
|
58
79
|
description="OTLP endpoint specifically for traces",
|
59
80
|
)
|
60
|
-
otlp_headers: str
|
61
|
-
|
81
|
+
otlp_headers: dict[str, str] = field(
|
82
|
+
factory=lambda: {},
|
62
83
|
env_var="OTEL_EXPORTER_OTLP_HEADERS",
|
84
|
+
converter=parse_headers,
|
63
85
|
description="Headers to send with OTLP requests (key1=value1,key2=value2)",
|
64
86
|
)
|
65
87
|
otlp_protocol: str = field(
|
66
|
-
default=
|
88
|
+
default=DEFAULT_OTLP_PROTOCOL,
|
67
89
|
env_var="OTEL_EXPORTER_OTLP_PROTOCOL",
|
68
90
|
description="OTLP protocol (grpc, http/protobuf)",
|
69
91
|
)
|
70
92
|
trace_sample_rate: float = field(
|
71
|
-
default=
|
93
|
+
default=DEFAULT_TRACE_SAMPLE_RATE,
|
72
94
|
env_var="OTEL_TRACE_SAMPLE_RATE",
|
95
|
+
converter=parse_sample_rate,
|
96
|
+
validator=validate_sample_rate,
|
73
97
|
description="Sampling rate for traces (0.0 to 1.0)",
|
74
98
|
)
|
75
99
|
|
76
|
-
# OpenObserve configuration
|
77
|
-
openobserve_url: str | None = field(
|
78
|
-
default=None,
|
79
|
-
env_var="OPENOBSERVE_URL",
|
80
|
-
description="OpenObserve API endpoint URL",
|
81
|
-
)
|
82
|
-
openobserve_org: str = field(
|
83
|
-
default="default",
|
84
|
-
env_var="OPENOBSERVE_ORG",
|
85
|
-
description="OpenObserve organization name",
|
86
|
-
)
|
87
|
-
openobserve_user: str | None = field(
|
88
|
-
default=None,
|
89
|
-
env_var="OPENOBSERVE_USER",
|
90
|
-
description="OpenObserve username for authentication",
|
91
|
-
)
|
92
|
-
openobserve_password: str | None = field(
|
93
|
-
default=None,
|
94
|
-
env_var="OPENOBSERVE_PASSWORD",
|
95
|
-
description="OpenObserve password for authentication",
|
96
|
-
)
|
97
|
-
openobserve_stream: str = field(
|
98
|
-
default="default",
|
99
|
-
env_var="OPENOBSERVE_STREAM",
|
100
|
-
description="Default OpenObserve stream name",
|
101
|
-
)
|
102
|
-
|
103
|
-
@classmethod
|
104
|
-
def from_env(cls, strict: bool = True) -> "TelemetryConfig":
|
105
|
-
"""Creates a TelemetryConfig instance from environment variables."""
|
106
|
-
config_dict = {}
|
107
|
-
|
108
|
-
# Check OTEL_SERVICE_NAME first, then PROVIDE_SERVICE_NAME
|
109
|
-
service_name = os.getenv("OTEL_SERVICE_NAME") or os.getenv(
|
110
|
-
"PROVIDE_SERVICE_NAME"
|
111
|
-
)
|
112
|
-
if service_name:
|
113
|
-
config_dict["service_name"] = service_name
|
114
100
|
|
115
|
-
# Service version
|
116
|
-
service_version = os.getenv("OTEL_SERVICE_VERSION") or os.getenv(
|
117
|
-
"PROVIDE_SERVICE_VERSION"
|
118
|
-
)
|
119
|
-
if service_version:
|
120
|
-
config_dict["service_version"] = service_version
|
121
|
-
|
122
|
-
# Telemetry disable flag
|
123
|
-
if disabled := os.getenv("PROVIDE_TELEMETRY_DISABLED"):
|
124
|
-
config_dict["globally_disabled"] = disabled.lower() == "true"
|
125
|
-
|
126
|
-
# OpenTelemetry specific configuration
|
127
|
-
if tracing_enabled := os.getenv("OTEL_TRACING_ENABLED"):
|
128
|
-
config_dict["tracing_enabled"] = tracing_enabled.lower() == "true"
|
129
|
-
|
130
|
-
if metrics_enabled := os.getenv("OTEL_METRICS_ENABLED"):
|
131
|
-
config_dict["metrics_enabled"] = metrics_enabled.lower() == "true"
|
132
|
-
|
133
|
-
if otlp_endpoint := os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT"):
|
134
|
-
config_dict["otlp_endpoint"] = otlp_endpoint
|
135
|
-
|
136
|
-
if otlp_traces_endpoint := os.getenv("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"):
|
137
|
-
config_dict["otlp_traces_endpoint"] = otlp_traces_endpoint
|
138
|
-
|
139
|
-
if otlp_headers := os.getenv("OTEL_EXPORTER_OTLP_HEADERS"):
|
140
|
-
config_dict["otlp_headers"] = otlp_headers
|
141
|
-
|
142
|
-
if otlp_protocol := os.getenv("OTEL_EXPORTER_OTLP_PROTOCOL"):
|
143
|
-
config_dict["otlp_protocol"] = otlp_protocol
|
144
|
-
|
145
|
-
if trace_sample_rate := os.getenv("OTEL_TRACE_SAMPLE_RATE"):
|
146
|
-
try:
|
147
|
-
config_dict["trace_sample_rate"] = float(trace_sample_rate)
|
148
|
-
except ValueError:
|
149
|
-
if strict:
|
150
|
-
raise
|
151
|
-
# Use default value in non-strict mode
|
152
|
-
|
153
|
-
# OpenObserve configuration
|
154
|
-
if openobserve_url := os.getenv("OPENOBSERVE_URL"):
|
155
|
-
config_dict["openobserve_url"] = openobserve_url
|
156
|
-
|
157
|
-
if openobserve_org := os.getenv("OPENOBSERVE_ORG"):
|
158
|
-
config_dict["openobserve_org"] = openobserve_org
|
159
|
-
|
160
|
-
if openobserve_user := os.getenv("OPENOBSERVE_USER"):
|
161
|
-
config_dict["openobserve_user"] = openobserve_user
|
162
|
-
|
163
|
-
if openobserve_password := os.getenv("OPENOBSERVE_PASSWORD"):
|
164
|
-
config_dict["openobserve_password"] = openobserve_password
|
165
|
-
|
166
|
-
if openobserve_stream := os.getenv("OPENOBSERVE_STREAM"):
|
167
|
-
config_dict["openobserve_stream"] = openobserve_stream
|
168
|
-
|
169
|
-
# Load logging config from env
|
170
|
-
config_dict["logging"] = LoggingConfig.from_env(strict=strict)
|
171
|
-
|
172
|
-
return cls(**config_dict)
|
173
101
|
|
174
102
|
def get_otlp_headers_dict(self) -> dict[str, str]:
|
175
|
-
"""
|
103
|
+
"""Get OTLP headers dictionary.
|
176
104
|
|
177
105
|
Returns:
|
178
106
|
Dictionary of header key-value pairs
|
179
107
|
"""
|
180
|
-
|
181
|
-
return {}
|
182
|
-
|
183
|
-
headers = {}
|
184
|
-
for pair in self.otlp_headers.split(","):
|
185
|
-
if "=" in pair:
|
186
|
-
key, value = pair.split("=", 1)
|
187
|
-
headers[key.strip()] = value.strip()
|
188
|
-
return headers
|
108
|
+
return self.otlp_headers
|
@@ -16,7 +16,6 @@ from provide.foundation.types import TRACE_LEVEL_NAME
|
|
16
16
|
|
17
17
|
if TYPE_CHECKING:
|
18
18
|
from provide.foundation.logger.config import TelemetryConfig
|
19
|
-
from provide.foundation.logger.setup.emoji_resolver import ResolvedEmojiConfig
|
20
19
|
|
21
20
|
_LAZY_SETUP_LOCK = threading.Lock()
|
22
21
|
_LAZY_SETUP_STATE: dict[str, Any] = {"done": False, "error": None, "in_progress": False}
|
@@ -31,7 +30,6 @@ class FoundationLogger:
|
|
31
30
|
)
|
32
31
|
self._is_configured_by_setup: bool = False
|
33
32
|
self._active_config: TelemetryConfig | None = None
|
34
|
-
self._active_resolved_emoji_config: ResolvedEmojiConfig | None = None
|
35
33
|
|
36
34
|
def _check_structlog_already_disabled(self) -> bool:
|
37
35
|
try:
|
@@ -10,18 +10,27 @@ from typing import Any
|
|
10
10
|
from provide.foundation.logger.core import logger
|
11
11
|
|
12
12
|
|
13
|
-
def get_logger(
|
13
|
+
def get_logger(
|
14
|
+
name: str | None = None,
|
15
|
+
emoji: str | None = None,
|
16
|
+
emoji_hierarchy: dict[str, str] | None = None
|
17
|
+
) -> Any:
|
14
18
|
"""
|
15
|
-
Get a logger instance with the given name.
|
19
|
+
Get a logger instance with the given name and optional emoji customization.
|
16
20
|
|
17
21
|
This is a convenience function that uses the global FoundationLogger.
|
18
22
|
|
19
23
|
Args:
|
20
24
|
name: Logger name (e.g., __name__ from a module)
|
25
|
+
emoji: Override emoji for this specific logger instance
|
26
|
+
emoji_hierarchy: Define emoji mapping for module hierarchy patterns
|
21
27
|
|
22
28
|
Returns:
|
23
29
|
Configured structlog logger instance
|
24
30
|
"""
|
31
|
+
# Emoji hierarchy removed - using event sets now
|
32
|
+
# emoji and emoji_hierarchy parameters are deprecated
|
33
|
+
|
25
34
|
return logger.get_logger(name)
|
26
35
|
|
27
36
|
|
@@ -26,8 +26,6 @@ from provide.foundation.types import (
|
|
26
26
|
LogLevelStr,
|
27
27
|
)
|
28
28
|
|
29
|
-
if TYPE_CHECKING:
|
30
|
-
from provide.foundation.logger.setup.emoji_resolver import ResolvedEmojiConfig
|
31
29
|
|
32
30
|
_LEVEL_TO_NUMERIC: dict[LogLevelStr, int] = {
|
33
31
|
"CRITICAL": stdlib_logging.CRITICAL,
|
@@ -71,96 +69,34 @@ def _config_create_timestamp_processors(
|
|
71
69
|
return processors
|
72
70
|
|
73
71
|
|
74
|
-
def
|
75
|
-
logging_config: LoggingConfig
|
72
|
+
def _config_create_event_enrichment_processors(
|
73
|
+
logging_config: LoggingConfig
|
76
74
|
) -> list[StructlogProcessor]:
|
77
75
|
processors: list[StructlogProcessor] = []
|
78
76
|
if logging_config.logger_name_emoji_prefix_enabled:
|
79
77
|
processors.append(cast(StructlogProcessor, add_logger_name_emoji_prefix))
|
80
78
|
if logging_config.das_emoji_prefix_enabled:
|
81
|
-
|
82
|
-
resolved_field_definitions, resolved_emoji_sets_lookup = resolved_emoji_config
|
83
|
-
|
84
|
-
def add_das_emoji_prefix_closure(
|
79
|
+
def add_event_enrichment_processor(
|
85
80
|
_logger: Any, _method_name: str, event_dict: structlog.types.EventDict
|
86
81
|
) -> structlog.types.EventDict:
|
87
|
-
#
|
88
|
-
from provide.foundation.
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
)
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
event_dict.pop(field_def.log_key, None)
|
101
|
-
emoji_set = resolved_emoji_sets_lookup.get(
|
102
|
-
field_def.emoji_set_name
|
103
|
-
)
|
104
|
-
if emoji_set:
|
105
|
-
value_str_lower = str(value_from_event).lower()
|
106
|
-
specific_emoji = emoji_set.emojis.get(value_str_lower)
|
107
|
-
default_key = (
|
108
|
-
field_def.default_emoji_override_key
|
109
|
-
or emoji_set.default_emoji_key
|
110
|
-
)
|
111
|
-
default_emoji = emoji_set.emojis.get(default_key, "❓")
|
112
|
-
chosen_emoji = (
|
113
|
-
specific_emoji
|
114
|
-
if specific_emoji is not None
|
115
|
-
else default_emoji
|
116
|
-
)
|
117
|
-
final_das_prefix_parts.append(f"[{chosen_emoji}]")
|
118
|
-
else:
|
119
|
-
final_das_prefix_parts.append("[❓]")
|
120
|
-
else: # Fallback to Core DAS System
|
121
|
-
domain = event_dict.pop("domain", None)
|
122
|
-
action = event_dict.pop("action", None)
|
123
|
-
status = event_dict.pop("status", None)
|
124
|
-
if domain or action or status:
|
125
|
-
domain_emoji = (
|
126
|
-
PRIMARY_EMOJI.get(str(domain).lower(), PRIMARY_EMOJI["default"])
|
127
|
-
if domain
|
128
|
-
else PRIMARY_EMOJI["default"]
|
129
|
-
)
|
130
|
-
action_emoji = (
|
131
|
-
SECONDARY_EMOJI.get(
|
132
|
-
str(action).lower(), SECONDARY_EMOJI["default"]
|
133
|
-
)
|
134
|
-
if action
|
135
|
-
else SECONDARY_EMOJI["default"]
|
136
|
-
)
|
137
|
-
status_emoji = (
|
138
|
-
TERTIARY_EMOJI.get(
|
139
|
-
str(status).lower(), TERTIARY_EMOJI["default"]
|
140
|
-
)
|
141
|
-
if status
|
142
|
-
else TERTIARY_EMOJI["default"]
|
143
|
-
)
|
144
|
-
final_das_prefix_parts.extend(
|
145
|
-
[f"[{domain_emoji}]", f"[{action_emoji}]", f"[{status_emoji}]"]
|
146
|
-
)
|
147
|
-
|
148
|
-
if final_das_prefix_parts:
|
149
|
-
final_das_prefix_str = "".join(final_das_prefix_parts)
|
150
|
-
event_msg = event_dict.get("event")
|
151
|
-
event_dict["event"] = (
|
152
|
-
f"{final_das_prefix_str} {event_msg}"
|
153
|
-
if event_msg is not None
|
154
|
-
else final_das_prefix_str
|
155
|
-
)
|
156
|
-
return event_dict
|
157
|
-
|
158
|
-
processors.append(cast(StructlogProcessor, add_das_emoji_prefix_closure))
|
82
|
+
# Lazy import to avoid circular dependency
|
83
|
+
from provide.foundation.eventsets.resolver import get_resolver
|
84
|
+
from provide.foundation.eventsets.registry import discover_event_sets
|
85
|
+
|
86
|
+
# Initialize on first use
|
87
|
+
if not hasattr(add_event_enrichment_processor, '_initialized'):
|
88
|
+
discover_event_sets()
|
89
|
+
add_event_enrichment_processor._initialized = True
|
90
|
+
|
91
|
+
resolver = get_resolver()
|
92
|
+
return resolver.enrich_event(event_dict)
|
93
|
+
|
94
|
+
processors.append(cast(StructlogProcessor, add_event_enrichment_processor))
|
159
95
|
return processors
|
160
96
|
|
161
97
|
|
162
98
|
def _build_core_processors_list(
|
163
|
-
config: TelemetryConfig
|
99
|
+
config: TelemetryConfig
|
164
100
|
) -> list[StructlogProcessor]:
|
165
101
|
log_cfg = config.logging
|
166
102
|
processors: list[StructlogProcessor] = [
|
@@ -202,7 +138,7 @@ def _build_core_processors_list(
|
|
202
138
|
if config.tracing_enabled and not config.globally_disabled:
|
203
139
|
processors.append(cast(StructlogProcessor, inject_trace_context))
|
204
140
|
|
205
|
-
processors.extend(
|
141
|
+
processors.extend(_config_create_event_enrichment_processors(log_cfg))
|
206
142
|
return processors
|
207
143
|
|
208
144
|
|
@@ -243,10 +179,10 @@ def _build_formatter_processors_list(
|
|
243
179
|
# Unknown formatter, warn and default to key_value
|
244
180
|
# Use setup coordinator logger
|
245
181
|
from provide.foundation.logger.setup.coordinator import (
|
246
|
-
|
182
|
+
create_foundation_internal_logger,
|
247
183
|
)
|
248
184
|
|
249
|
-
setup_logger =
|
185
|
+
setup_logger = create_foundation_internal_logger()
|
250
186
|
setup_logger.warning(
|
251
187
|
f"Unknown formatter '{logging_config.console_formatter}', using default 'key_value'. "
|
252
188
|
f"Valid formatters: ['json', 'key_value']"
|
@@ -8,7 +8,10 @@ Handles structured logging configuration, processor setup, and emoji resolution.
|
|
8
8
|
Provides the core setup functionality for the Foundation logging system.
|
9
9
|
"""
|
10
10
|
|
11
|
-
from provide.foundation.logger.setup.coordinator import
|
11
|
+
from provide.foundation.logger.setup.coordinator import (
|
12
|
+
get_vanilla_logger,
|
13
|
+
internal_setup,
|
14
|
+
)
|
12
15
|
|
13
16
|
# Import testing utilities conditionally
|
14
17
|
try:
|
@@ -22,6 +25,7 @@ except ImportError:
|
|
22
25
|
reset_for_testing = None
|
23
26
|
|
24
27
|
__all__ = [
|
28
|
+
"get_vanilla_logger",
|
25
29
|
"internal_setup",
|
26
30
|
]
|
27
31
|
|
@@ -17,8 +17,6 @@ from provide.foundation.logger.core import (
|
|
17
17
|
_LAZY_SETUP_STATE,
|
18
18
|
logger as foundation_logger,
|
19
19
|
)
|
20
|
-
from provide.foundation.logger.emoji.sets import BUILTIN_EMOJI_SETS
|
21
|
-
from provide.foundation.logger.setup.emoji_resolver import resolve_active_emoji_config
|
22
20
|
from provide.foundation.logger.setup.processors import (
|
23
21
|
configure_structlog_output,
|
24
22
|
handle_globally_disabled_setup,
|
@@ -33,22 +31,35 @@ _FOUNDATION_LOG_LEVEL: int | None = None
|
|
33
31
|
|
34
32
|
|
35
33
|
def get_foundation_log_level() -> int:
|
36
|
-
"""Get
|
34
|
+
"""Get Foundation log level for setup phase, safely."""
|
37
35
|
global _FOUNDATION_LOG_LEVEL
|
38
36
|
if _FOUNDATION_LOG_LEVEL is None:
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
37
|
+
import os
|
38
|
+
|
39
|
+
# Direct env read - avoid config imports that cause circular deps
|
40
|
+
level_str = os.environ.get("FOUNDATION_LOG_LEVEL", "INFO").upper()
|
41
|
+
|
42
|
+
# Validate and map to numeric level
|
43
|
+
valid_levels = {
|
44
|
+
"CRITICAL": stdlib_logging.CRITICAL,
|
45
|
+
"ERROR": stdlib_logging.ERROR,
|
46
|
+
"WARNING": stdlib_logging.WARNING,
|
47
|
+
"INFO": stdlib_logging.INFO,
|
48
|
+
"DEBUG": stdlib_logging.DEBUG,
|
49
|
+
"NOTSET": stdlib_logging.NOTSET,
|
50
|
+
}
|
51
|
+
|
52
|
+
_FOUNDATION_LOG_LEVEL = valid_levels.get(level_str, stdlib_logging.INFO)
|
47
53
|
return _FOUNDATION_LOG_LEVEL
|
48
54
|
|
49
55
|
|
50
|
-
def
|
51
|
-
"""
|
56
|
+
def create_foundation_internal_logger(globally_disabled: bool = False) -> Any:
|
57
|
+
"""
|
58
|
+
Create Foundation's internal setup logger (structlog).
|
59
|
+
|
60
|
+
This is used internally by Foundation during its own initialization.
|
61
|
+
Components should use get_vanilla_logger() instead.
|
62
|
+
"""
|
52
63
|
if globally_disabled:
|
53
64
|
# Configure structlog to be a no-op for core setup logger
|
54
65
|
structlog.configure(
|
@@ -61,7 +72,7 @@ def create_core_setup_logger(globally_disabled: bool = False) -> Any:
|
|
61
72
|
else:
|
62
73
|
# Get the foundation log output stream
|
63
74
|
try:
|
64
|
-
logging_config = LoggingConfig.from_env(
|
75
|
+
logging_config = LoggingConfig.from_env()
|
65
76
|
foundation_stream = get_foundation_log_stream(
|
66
77
|
logging_config.foundation_log_output
|
67
78
|
)
|
@@ -84,6 +95,55 @@ def create_core_setup_logger(globally_disabled: bool = False) -> Any:
|
|
84
95
|
return structlog.get_logger(_CORE_SETUP_LOGGER_NAME)
|
85
96
|
|
86
97
|
|
98
|
+
def get_vanilla_logger(name: str):
|
99
|
+
"""
|
100
|
+
Get a vanilla Python logger without Foundation enhancements.
|
101
|
+
|
102
|
+
This provides a plain Python logger that respects FOUNDATION_LOG_LEVEL
|
103
|
+
but doesn't trigger Foundation's initialization. Use this for logging
|
104
|
+
during Foundation's setup phase or when you need to avoid circular
|
105
|
+
dependencies.
|
106
|
+
|
107
|
+
Args:
|
108
|
+
name: Logger name (e.g., "provide.foundation.otel.setup")
|
109
|
+
|
110
|
+
Returns:
|
111
|
+
A standard Python logging.Logger instance
|
112
|
+
|
113
|
+
Note:
|
114
|
+
"Vanilla" means plain/unmodified Python logging, without
|
115
|
+
Foundation's features like emoji prefixes or structured logging.
|
116
|
+
"""
|
117
|
+
import logging
|
118
|
+
import sys
|
119
|
+
import os
|
120
|
+
|
121
|
+
slog = logging.getLogger(name)
|
122
|
+
|
123
|
+
# Configure only once per logger
|
124
|
+
if not slog.handlers:
|
125
|
+
log_level = get_foundation_log_level()
|
126
|
+
slog.setLevel(log_level)
|
127
|
+
|
128
|
+
# Respect FOUNDATION_LOG_OUTPUT setting
|
129
|
+
output = os.environ.get("FOUNDATION_LOG_OUTPUT", "stderr").lower()
|
130
|
+
stream = sys.stderr if output != "stdout" else sys.stdout
|
131
|
+
|
132
|
+
handler = logging.StreamHandler(stream)
|
133
|
+
handler.setLevel(log_level)
|
134
|
+
formatter = logging.Formatter(
|
135
|
+
'%(asctime)s [%(levelname)-5s] %(message)s',
|
136
|
+
datefmt='%Y-%m-%dT%H:%M:%S'
|
137
|
+
)
|
138
|
+
handler.setFormatter(formatter)
|
139
|
+
slog.addHandler(handler)
|
140
|
+
|
141
|
+
# Don't propagate to avoid duplicate messages
|
142
|
+
slog.propagate = False
|
143
|
+
|
144
|
+
return slog
|
145
|
+
|
146
|
+
|
87
147
|
def internal_setup(
|
88
148
|
config: TelemetryConfig | None = None, is_explicit_call: bool = False
|
89
149
|
) -> None:
|
@@ -95,11 +155,10 @@ def internal_setup(
|
|
95
155
|
structlog.reset_defaults()
|
96
156
|
foundation_logger._is_configured_by_setup = False
|
97
157
|
foundation_logger._active_config = None
|
98
|
-
foundation_logger._active_resolved_emoji_config = None
|
99
158
|
_LAZY_SETUP_STATE.update({"done": False, "error": None, "in_progress": False})
|
100
159
|
|
101
160
|
current_config = config if config is not None else TelemetryConfig.from_env()
|
102
|
-
core_setup_logger =
|
161
|
+
core_setup_logger = create_foundation_internal_logger(
|
103
162
|
globally_disabled=current_config.globally_disabled
|
104
163
|
)
|
105
164
|
|
@@ -111,28 +170,21 @@ def internal_setup(
|
|
111
170
|
formatter=current_config.logging.console_formatter,
|
112
171
|
)
|
113
172
|
|
114
|
-
resolved_emoji_config = resolve_active_emoji_config(
|
115
|
-
current_config.logging, BUILTIN_EMOJI_SETS
|
116
|
-
)
|
117
173
|
|
118
174
|
if current_config.globally_disabled:
|
119
175
|
handle_globally_disabled_setup()
|
120
176
|
else:
|
121
177
|
configure_structlog_output(
|
122
|
-
current_config,
|
178
|
+
current_config, get_log_stream()
|
123
179
|
)
|
124
180
|
|
125
181
|
foundation_logger._is_configured_by_setup = is_explicit_call
|
126
182
|
foundation_logger._active_config = current_config
|
127
|
-
foundation_logger._active_resolved_emoji_config = resolved_emoji_config
|
128
183
|
_LAZY_SETUP_STATE["done"] = True
|
129
184
|
|
130
185
|
if not current_config.globally_disabled:
|
131
|
-
field_definitions, emoji_sets = resolved_emoji_config
|
132
186
|
core_setup_logger.debug(
|
133
187
|
"⚙️➡️✅ Foundation (structlog) setup completed",
|
134
|
-
emoji_sets_enabled=len(field_definitions) > 0,
|
135
|
-
emoji_sets_count=len(emoji_sets),
|
136
188
|
processors_configured=True,
|
137
189
|
log_file_enabled=current_config.logging.log_file is not None,
|
138
190
|
)
|
@@ -15,12 +15,8 @@ from provide.foundation.logger.processors import (
|
|
15
15
|
_build_core_processors_list,
|
16
16
|
_build_formatter_processors_list,
|
17
17
|
)
|
18
|
-
from provide.foundation.logger.setup.emoji_resolver import ResolvedEmojiConfig
|
19
|
-
|
20
|
-
|
21
18
|
def build_complete_processor_chain(
|
22
19
|
config: TelemetryConfig,
|
23
|
-
resolved_emoji_config: ResolvedEmojiConfig,
|
24
20
|
log_stream: TextIO,
|
25
21
|
) -> list[Any]:
|
26
22
|
"""
|
@@ -28,13 +24,12 @@ def build_complete_processor_chain(
|
|
28
24
|
|
29
25
|
Args:
|
30
26
|
config: Telemetry configuration
|
31
|
-
resolved_emoji_config: Resolved emoji configuration
|
32
27
|
log_stream: Output stream for logging
|
33
28
|
|
34
29
|
Returns:
|
35
30
|
List of processors for structlog
|
36
31
|
"""
|
37
|
-
core_processors = _build_core_processors_list(config
|
32
|
+
core_processors = _build_core_processors_list(config)
|
38
33
|
formatter_processors = _build_formatter_processors_list(config.logging, log_stream)
|
39
34
|
return cast(list[Any], core_processors + formatter_processors)
|
40
35
|
|
@@ -57,7 +52,6 @@ def apply_structlog_configuration(processors: list[Any], log_stream: TextIO) ->
|
|
57
52
|
|
58
53
|
def configure_structlog_output(
|
59
54
|
config: TelemetryConfig,
|
60
|
-
resolved_emoji_config: ResolvedEmojiConfig,
|
61
55
|
log_stream: TextIO,
|
62
56
|
) -> None:
|
63
57
|
"""
|
@@ -65,11 +59,10 @@ def configure_structlog_output(
|
|
65
59
|
|
66
60
|
Args:
|
67
61
|
config: Telemetry configuration
|
68
|
-
resolved_emoji_config: Resolved emoji configuration
|
69
62
|
log_stream: Output stream for logging
|
70
63
|
"""
|
71
64
|
processors = build_complete_processor_chain(
|
72
|
-
config,
|
65
|
+
config, log_stream
|
73
66
|
)
|
74
67
|
apply_structlog_configuration(processors, log_stream)
|
75
68
|
|
@@ -36,3 +36,30 @@ if not hasattr(stdlib_logging, TRACE_LEVEL_NAME): # pragma: no cover
|
|
36
36
|
(cast(Any, stdlib_logging.root)).trace = trace.__get__(
|
37
37
|
stdlib_logging.root, stdlib_logging.Logger
|
38
38
|
)
|
39
|
+
|
40
|
+
# Also patch PrintLogger from structlog to support trace method
|
41
|
+
try:
|
42
|
+
import structlog
|
43
|
+
from structlog import PrintLogger
|
44
|
+
|
45
|
+
if not hasattr(PrintLogger, "trace"): # pragma: no cover
|
46
|
+
def trace_for_print_logger(
|
47
|
+
self: PrintLogger, msg: object, *args: object, **kwargs: object
|
48
|
+
) -> None: # pragma: no cover
|
49
|
+
# PrintLogger doesn't have level checking, so just format and print like other methods
|
50
|
+
if args:
|
51
|
+
try:
|
52
|
+
formatted_msg = str(msg) % args
|
53
|
+
except (TypeError, ValueError):
|
54
|
+
formatted_msg = f"{msg} {args}"
|
55
|
+
else:
|
56
|
+
formatted_msg = str(msg)
|
57
|
+
|
58
|
+
# Use the same output mechanism as other PrintLogger methods
|
59
|
+
self._file.write(formatted_msg + "\n")
|
60
|
+
self._file.flush()
|
61
|
+
|
62
|
+
PrintLogger.trace = trace_for_print_logger # type: ignore[attr-defined]
|
63
|
+
|
64
|
+
except ImportError: # pragma: no cover
|
65
|
+
pass
|