provide-foundation 0.0.0.dev1__py3-none-any.whl → 0.0.0.dev3__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 +36 -10
- provide/foundation/archive/__init__.py +1 -1
- provide/foundation/archive/base.py +15 -14
- provide/foundation/archive/bzip2.py +40 -40
- provide/foundation/archive/gzip.py +42 -42
- provide/foundation/archive/operations.py +93 -96
- provide/foundation/archive/tar.py +33 -31
- provide/foundation/archive/zip.py +52 -50
- provide/foundation/asynctools/__init__.py +20 -0
- provide/foundation/asynctools/core.py +126 -0
- provide/foundation/cli/__init__.py +2 -2
- provide/foundation/cli/commands/deps.py +15 -9
- provide/foundation/cli/commands/logs/__init__.py +3 -3
- provide/foundation/cli/commands/logs/generate.py +2 -2
- provide/foundation/cli/commands/logs/query.py +4 -4
- provide/foundation/cli/commands/logs/send.py +3 -3
- provide/foundation/cli/commands/logs/tail.py +3 -3
- provide/foundation/cli/decorators.py +11 -11
- provide/foundation/cli/main.py +1 -1
- provide/foundation/cli/testing.py +2 -40
- provide/foundation/cli/utils.py +21 -18
- provide/foundation/config/__init__.py +35 -2
- provide/foundation/config/base.py +2 -2
- provide/foundation/config/converters.py +477 -0
- provide/foundation/config/defaults.py +67 -0
- provide/foundation/config/env.py +6 -20
- provide/foundation/config/loader.py +10 -4
- provide/foundation/config/sync.py +8 -6
- provide/foundation/config/types.py +5 -5
- provide/foundation/config/validators.py +4 -4
- provide/foundation/console/input.py +5 -5
- provide/foundation/console/output.py +36 -14
- provide/foundation/context/__init__.py +8 -4
- provide/foundation/context/core.py +88 -110
- provide/foundation/crypto/certificates/__init__.py +9 -5
- provide/foundation/crypto/certificates/base.py +2 -2
- provide/foundation/crypto/certificates/certificate.py +48 -19
- provide/foundation/crypto/certificates/factory.py +26 -18
- provide/foundation/crypto/certificates/generator.py +24 -23
- provide/foundation/crypto/certificates/loader.py +24 -16
- provide/foundation/crypto/certificates/operations.py +17 -10
- provide/foundation/crypto/certificates/trust.py +21 -21
- provide/foundation/env/__init__.py +28 -0
- provide/foundation/env/core.py +218 -0
- provide/foundation/errors/__init__.py +3 -3
- provide/foundation/errors/decorators.py +0 -234
- provide/foundation/errors/types.py +0 -98
- provide/foundation/eventsets/display.py +13 -14
- provide/foundation/eventsets/registry.py +61 -31
- provide/foundation/eventsets/resolver.py +50 -46
- provide/foundation/eventsets/sets/das.py +8 -8
- provide/foundation/eventsets/sets/database.py +14 -14
- provide/foundation/eventsets/sets/http.py +21 -21
- provide/foundation/eventsets/sets/llm.py +16 -16
- provide/foundation/eventsets/sets/task_queue.py +13 -13
- provide/foundation/eventsets/types.py +7 -7
- provide/foundation/file/directory.py +14 -23
- provide/foundation/file/lock.py +4 -3
- provide/foundation/hub/components.py +75 -389
- provide/foundation/hub/config.py +157 -0
- provide/foundation/hub/discovery.py +63 -0
- provide/foundation/hub/handlers.py +89 -0
- provide/foundation/hub/lifecycle.py +195 -0
- provide/foundation/hub/manager.py +7 -4
- provide/foundation/hub/processors.py +49 -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 +14 -14
- provide/foundation/{observability → integrations}/openobserve/commands.py +12 -12
- provide/foundation/integrations/openobserve/config.py +37 -0
- provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
- provide/foundation/{observability → integrations}/openobserve/otlp.py +2 -2
- provide/foundation/{observability → integrations}/openobserve/search.py +2 -3
- provide/foundation/{observability → integrations}/openobserve/streaming.py +5 -5
- provide/foundation/logger/__init__.py +0 -1
- provide/foundation/logger/config/base.py +1 -1
- provide/foundation/logger/config/logging.py +69 -299
- provide/foundation/logger/config/telemetry.py +39 -121
- provide/foundation/logger/factories.py +2 -2
- provide/foundation/logger/processors/main.py +12 -10
- provide/foundation/logger/ratelimit/limiters.py +4 -4
- provide/foundation/logger/ratelimit/processor.py +1 -1
- provide/foundation/logger/setup/coordinator.py +39 -25
- provide/foundation/logger/setup/processors.py +3 -3
- provide/foundation/logger/setup/testing.py +14 -0
- provide/foundation/logger/trace.py +5 -5
- provide/foundation/metrics/__init__.py +1 -1
- provide/foundation/metrics/otel.py +3 -1
- provide/foundation/observability/__init__.py +3 -3
- provide/foundation/process/__init__.py +9 -0
- provide/foundation/process/exit.py +48 -0
- provide/foundation/process/lifecycle.py +69 -46
- provide/foundation/resilience/__init__.py +36 -0
- provide/foundation/resilience/circuit.py +166 -0
- provide/foundation/resilience/decorators.py +236 -0
- provide/foundation/resilience/fallback.py +208 -0
- provide/foundation/resilience/retry.py +327 -0
- provide/foundation/serialization/__init__.py +16 -0
- provide/foundation/serialization/core.py +70 -0
- provide/foundation/streams/config.py +78 -0
- provide/foundation/streams/console.py +4 -5
- provide/foundation/streams/core.py +5 -2
- provide/foundation/streams/file.py +12 -2
- provide/foundation/testing/__init__.py +29 -9
- provide/foundation/testing/archive/__init__.py +7 -7
- provide/foundation/testing/archive/fixtures.py +58 -54
- provide/foundation/testing/cli.py +30 -20
- provide/foundation/testing/common/__init__.py +13 -15
- provide/foundation/testing/common/fixtures.py +27 -57
- provide/foundation/testing/file/__init__.py +15 -15
- provide/foundation/testing/file/content_fixtures.py +289 -0
- provide/foundation/testing/file/directory_fixtures.py +107 -0
- provide/foundation/testing/file/fixtures.py +42 -516
- provide/foundation/testing/file/special_fixtures.py +145 -0
- provide/foundation/testing/logger.py +89 -8
- provide/foundation/testing/mocking/__init__.py +21 -21
- provide/foundation/testing/mocking/fixtures.py +80 -67
- provide/foundation/testing/process/__init__.py +23 -23
- provide/foundation/testing/process/async_fixtures.py +414 -0
- provide/foundation/testing/process/fixtures.py +48 -571
- provide/foundation/testing/process/subprocess_fixtures.py +210 -0
- provide/foundation/testing/threading/__init__.py +17 -17
- provide/foundation/testing/threading/basic_fixtures.py +105 -0
- provide/foundation/testing/threading/data_fixtures.py +101 -0
- provide/foundation/testing/threading/execution_fixtures.py +278 -0
- provide/foundation/testing/threading/fixtures.py +32 -502
- provide/foundation/testing/threading/sync_fixtures.py +100 -0
- provide/foundation/testing/time/__init__.py +11 -11
- provide/foundation/testing/time/fixtures.py +95 -83
- provide/foundation/testing/transport/__init__.py +9 -9
- provide/foundation/testing/transport/fixtures.py +54 -54
- provide/foundation/time/__init__.py +18 -0
- provide/foundation/time/core.py +63 -0
- provide/foundation/tools/__init__.py +2 -2
- provide/foundation/tools/base.py +68 -67
- provide/foundation/tools/cache.py +69 -74
- provide/foundation/tools/downloader.py +68 -62
- provide/foundation/tools/installer.py +51 -57
- provide/foundation/tools/registry.py +38 -45
- provide/foundation/tools/resolver.py +70 -68
- provide/foundation/tools/verifier.py +39 -50
- provide/foundation/tracer/spans.py +2 -14
- provide/foundation/transport/__init__.py +26 -33
- provide/foundation/transport/base.py +32 -30
- provide/foundation/transport/client.py +44 -49
- provide/foundation/transport/config.py +36 -107
- provide/foundation/transport/errors.py +13 -27
- provide/foundation/transport/http.py +69 -55
- provide/foundation/transport/middleware.py +113 -114
- provide/foundation/transport/registry.py +29 -27
- provide/foundation/transport/types.py +6 -6
- provide/foundation/utils/deps.py +17 -14
- provide/foundation/utils/parsing.py +49 -4
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/METADATA +2 -2
- provide_foundation-0.0.0.dev3.dist-info/RECORD +233 -0
- provide_foundation-0.0.0.dev1.dist-info/RECORD +0 -200
- /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
- /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/WHEEL +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/entry_points.txt +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/licenses/LICENSE +0 -0
- {provide_foundation-0.0.0.dev1.dist-info → provide_foundation-0.0.0.dev3.dist-info}/top_level.txt +0 -0
@@ -5,390 +5,160 @@
|
|
5
5
|
LoggingConfig class for Foundation logger configuration.
|
6
6
|
"""
|
7
7
|
|
8
|
-
import json
|
9
|
-
import os
|
10
8
|
from pathlib import Path
|
11
9
|
|
12
10
|
from attrs import define
|
13
11
|
|
14
|
-
from provide.foundation.config import
|
15
|
-
from provide.foundation.config.
|
16
|
-
|
17
|
-
|
12
|
+
from provide.foundation.config.base import field
|
13
|
+
from provide.foundation.config.converters import (
|
14
|
+
parse_bool_extended,
|
15
|
+
parse_console_formatter,
|
16
|
+
parse_float_with_validation,
|
17
|
+
parse_foundation_log_output,
|
18
|
+
parse_log_level,
|
19
|
+
parse_module_levels,
|
20
|
+
parse_rate_limits,
|
21
|
+
validate_log_level,
|
22
|
+
validate_overflow_policy,
|
23
|
+
validate_positive,
|
24
|
+
)
|
25
|
+
from provide.foundation.config.defaults import (
|
26
|
+
DEFAULT_CONSOLE_FORMATTER,
|
27
|
+
DEFAULT_DAS_EMOJI_ENABLED,
|
28
|
+
DEFAULT_FOUNDATION_LOG_OUTPUT,
|
29
|
+
DEFAULT_FOUNDATION_SETUP_LOG_LEVEL,
|
30
|
+
DEFAULT_LOG_LEVEL,
|
31
|
+
DEFAULT_LOGGER_NAME_EMOJI_ENABLED,
|
32
|
+
DEFAULT_OMIT_TIMESTAMP,
|
33
|
+
DEFAULT_RATE_LIMIT_EMIT_WARNINGS,
|
34
|
+
DEFAULT_RATE_LIMIT_ENABLED,
|
35
|
+
DEFAULT_RATE_LIMIT_GLOBAL,
|
36
|
+
DEFAULT_RATE_LIMIT_GLOBAL_CAPACITY,
|
37
|
+
DEFAULT_RATE_LIMIT_OVERFLOW_POLICY,
|
38
|
+
)
|
39
|
+
from provide.foundation.config.env import RuntimeConfig
|
18
40
|
from provide.foundation.types import (
|
19
|
-
_VALID_FORMATTER_TUPLE,
|
20
|
-
_VALID_LOG_LEVEL_TUPLE,
|
21
41
|
ConsoleFormatterStr,
|
22
42
|
LogLevelStr,
|
23
43
|
)
|
24
44
|
|
25
45
|
|
26
46
|
@define(slots=True, repr=False)
|
27
|
-
class LoggingConfig(
|
47
|
+
class LoggingConfig(RuntimeConfig):
|
28
48
|
"""Configuration specific to logging behavior within Foundation Telemetry."""
|
29
49
|
|
30
50
|
default_level: LogLevelStr = field(
|
31
|
-
default=
|
51
|
+
default=DEFAULT_LOG_LEVEL,
|
32
52
|
env_var="PROVIDE_LOG_LEVEL",
|
53
|
+
converter=parse_log_level,
|
54
|
+
validator=validate_log_level,
|
33
55
|
description="Default logging level",
|
34
56
|
)
|
35
57
|
module_levels: dict[str, LogLevelStr] = field(
|
36
58
|
factory=lambda: {},
|
37
59
|
env_var="PROVIDE_LOG_MODULE_LEVELS",
|
60
|
+
converter=parse_module_levels,
|
38
61
|
description="Per-module log levels (format: module1:LEVEL,module2:LEVEL)",
|
39
62
|
)
|
40
63
|
console_formatter: ConsoleFormatterStr = field(
|
41
|
-
default=
|
64
|
+
default=DEFAULT_CONSOLE_FORMATTER,
|
42
65
|
env_var="PROVIDE_LOG_CONSOLE_FORMATTER",
|
66
|
+
converter=parse_console_formatter,
|
43
67
|
description="Console output formatter (key_value or json)",
|
44
68
|
)
|
45
69
|
logger_name_emoji_prefix_enabled: bool = field(
|
46
|
-
default=
|
70
|
+
default=DEFAULT_LOGGER_NAME_EMOJI_ENABLED,
|
47
71
|
env_var="PROVIDE_LOG_LOGGER_NAME_EMOJI_ENABLED",
|
72
|
+
converter=parse_bool_extended,
|
48
73
|
description="Enable emoji prefixes based on logger names",
|
49
74
|
)
|
50
75
|
das_emoji_prefix_enabled: bool = field(
|
51
|
-
default=
|
76
|
+
default=DEFAULT_DAS_EMOJI_ENABLED,
|
52
77
|
env_var="PROVIDE_LOG_DAS_EMOJI_ENABLED",
|
78
|
+
converter=parse_bool_extended,
|
53
79
|
description="Enable Domain-Action-Status emoji prefixes",
|
54
80
|
)
|
55
81
|
omit_timestamp: bool = field(
|
56
|
-
default=
|
82
|
+
default=DEFAULT_OMIT_TIMESTAMP,
|
57
83
|
env_var="PROVIDE_LOG_OMIT_TIMESTAMP",
|
84
|
+
converter=parse_bool_extended,
|
58
85
|
description="Omit timestamps from console output",
|
59
86
|
)
|
60
|
-
|
61
|
-
factory=lambda: [],
|
62
|
-
env_var="PROVIDE_LOG_ENABLED_EMOJI_SETS",
|
63
|
-
description="Comma-separated list of emoji sets to enable",
|
64
|
-
)
|
65
|
-
custom_emoji_sets: list[EventSet] = field(
|
66
|
-
factory=lambda: [],
|
67
|
-
env_var="PROVIDE_LOG_CUSTOM_EMOJI_SETS",
|
68
|
-
description="JSON array of custom emoji set configurations",
|
69
|
-
)
|
70
|
-
user_defined_emoji_sets: list[EventMapping] = field(
|
71
|
-
factory=lambda: [],
|
72
|
-
env_var="PROVIDE_LOG_USER_DEFINED_EMOJI_SETS",
|
73
|
-
description="JSON array of user-defined emoji sets",
|
74
|
-
)
|
87
|
+
# Event sets have replaced emoji sets - these fields are deprecated
|
75
88
|
log_file: Path | None = field(
|
76
|
-
default=None,
|
89
|
+
default=None,
|
90
|
+
env_var="PROVIDE_LOG_FILE",
|
91
|
+
converter=lambda x: Path(x) if x else None,
|
92
|
+
description="Path to log file",
|
77
93
|
)
|
78
94
|
foundation_setup_log_level: LogLevelStr = field(
|
79
|
-
default=
|
95
|
+
default=DEFAULT_FOUNDATION_SETUP_LOG_LEVEL,
|
80
96
|
env_var="FOUNDATION_LOG_LEVEL",
|
97
|
+
converter=parse_log_level,
|
98
|
+
validator=validate_log_level,
|
81
99
|
description="Log level for Foundation internal setup messages",
|
82
100
|
)
|
83
101
|
foundation_log_output: str = field(
|
84
|
-
default=
|
102
|
+
default=DEFAULT_FOUNDATION_LOG_OUTPUT,
|
85
103
|
env_var="FOUNDATION_LOG_OUTPUT",
|
104
|
+
converter=parse_foundation_log_output,
|
86
105
|
description="Output destination for Foundation internal messages (stderr, stdout, main)",
|
87
106
|
)
|
88
|
-
show_emoji_matrix: bool = field(
|
89
|
-
default=False,
|
90
|
-
env_var="PROVIDE_SHOW_EMOJI_MATRIX",
|
91
|
-
description="Whether to display emoji matrix on startup",
|
92
|
-
)
|
93
107
|
rate_limit_enabled: bool = field(
|
94
|
-
default=
|
108
|
+
default=DEFAULT_RATE_LIMIT_ENABLED,
|
95
109
|
env_var="PROVIDE_LOG_RATE_LIMIT_ENABLED",
|
110
|
+
converter=parse_bool_extended,
|
96
111
|
description="Enable rate limiting for log output",
|
97
112
|
)
|
98
113
|
rate_limit_global: float | None = field(
|
99
114
|
default=None,
|
100
115
|
env_var="PROVIDE_LOG_RATE_LIMIT_GLOBAL",
|
116
|
+
converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else None,
|
101
117
|
description="Global rate limit (logs per second)",
|
102
118
|
)
|
103
119
|
rate_limit_global_capacity: float | None = field(
|
104
120
|
default=None,
|
105
121
|
env_var="PROVIDE_LOG_RATE_LIMIT_GLOBAL_CAPACITY",
|
122
|
+
converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else None,
|
106
123
|
description="Global rate limit burst capacity",
|
107
124
|
)
|
108
125
|
rate_limit_per_logger: dict[str, tuple[float, float]] = field(
|
109
126
|
factory=lambda: {},
|
110
127
|
env_var="PROVIDE_LOG_RATE_LIMIT_PER_LOGGER",
|
128
|
+
converter=parse_rate_limits,
|
111
129
|
description="Per-logger rate limits (format: logger1:rate:capacity,logger2:rate:capacity)",
|
112
130
|
)
|
113
131
|
rate_limit_emit_warnings: bool = field(
|
114
|
-
default=
|
132
|
+
default=DEFAULT_RATE_LIMIT_EMIT_WARNINGS,
|
115
133
|
env_var="PROVIDE_LOG_RATE_LIMIT_EMIT_WARNINGS",
|
134
|
+
converter=parse_bool_extended,
|
116
135
|
description="Emit warnings when logs are rate limited",
|
117
136
|
)
|
118
137
|
rate_limit_summary_interval: float = field(
|
119
|
-
default=
|
138
|
+
default=DEFAULT_RATE_LIMIT_GLOBAL,
|
120
139
|
env_var="PROVIDE_LOG_RATE_LIMIT_SUMMARY_INTERVAL",
|
140
|
+
converter=lambda x: parse_float_with_validation(x, min_val=0.0)
|
141
|
+
if x
|
142
|
+
else DEFAULT_RATE_LIMIT_GLOBAL,
|
143
|
+
validator=validate_positive,
|
121
144
|
description="Seconds between rate limit summary reports",
|
122
145
|
)
|
123
146
|
rate_limit_max_queue_size: int = field(
|
124
|
-
default=
|
147
|
+
default=DEFAULT_RATE_LIMIT_GLOBAL_CAPACITY,
|
125
148
|
env_var="PROVIDE_LOG_RATE_LIMIT_MAX_QUEUE_SIZE",
|
149
|
+
converter=int,
|
150
|
+
validator=validate_positive,
|
126
151
|
description="Maximum number of logs to queue when rate limited",
|
127
152
|
)
|
128
153
|
rate_limit_max_memory_mb: float | None = field(
|
129
154
|
default=None,
|
130
155
|
env_var="PROVIDE_LOG_RATE_LIMIT_MAX_MEMORY_MB",
|
156
|
+
converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else None,
|
131
157
|
description="Maximum memory (MB) for queued logs",
|
132
158
|
)
|
133
159
|
rate_limit_overflow_policy: str = field(
|
134
|
-
default=
|
160
|
+
default=DEFAULT_RATE_LIMIT_OVERFLOW_POLICY,
|
135
161
|
env_var="PROVIDE_LOG_RATE_LIMIT_OVERFLOW_POLICY",
|
162
|
+
validator=validate_overflow_policy,
|
136
163
|
description="Policy when queue is full: drop_oldest, drop_newest, or block",
|
137
164
|
)
|
138
|
-
|
139
|
-
@classmethod
|
140
|
-
def from_env(cls, strict: bool = True) -> "LoggingConfig":
|
141
|
-
"""Load LoggingConfig from environment variables."""
|
142
|
-
config_dict = {}
|
143
|
-
|
144
|
-
# Parse standard fields
|
145
|
-
if level := os.getenv("PROVIDE_LOG_LEVEL"):
|
146
|
-
level = level.upper()
|
147
|
-
if level in _VALID_LOG_LEVEL_TUPLE:
|
148
|
-
config_dict["default_level"] = level
|
149
|
-
elif strict:
|
150
|
-
get_config_logger().warning(
|
151
|
-
"[Foundation Config Warning] Invalid configuration value, using default",
|
152
|
-
config_key="PROVIDE_LOG_LEVEL",
|
153
|
-
invalid_value=level,
|
154
|
-
valid_options=list(_VALID_LOG_LEVEL_TUPLE),
|
155
|
-
default_value="DEBUG",
|
156
|
-
)
|
157
|
-
|
158
|
-
if formatter := os.getenv("PROVIDE_LOG_CONSOLE_FORMATTER"):
|
159
|
-
formatter = formatter.lower()
|
160
|
-
if formatter in _VALID_FORMATTER_TUPLE:
|
161
|
-
config_dict["console_formatter"] = formatter
|
162
|
-
elif strict:
|
163
|
-
get_config_logger().warning(
|
164
|
-
"[Foundation Config Warning] Invalid configuration value, using default",
|
165
|
-
config_key="PROVIDE_LOG_CONSOLE_FORMATTER",
|
166
|
-
invalid_value=formatter,
|
167
|
-
valid_options=list(_VALID_FORMATTER_TUPLE),
|
168
|
-
default_value="key_value",
|
169
|
-
)
|
170
|
-
|
171
|
-
if omit_ts := os.getenv("PROVIDE_LOG_OMIT_TIMESTAMP"):
|
172
|
-
config_dict["omit_timestamp"] = omit_ts.lower() == "true"
|
173
|
-
|
174
|
-
if logger_emoji := os.getenv("PROVIDE_LOG_LOGGER_NAME_EMOJI_ENABLED"):
|
175
|
-
config_dict["logger_name_emoji_prefix_enabled"] = (
|
176
|
-
logger_emoji.lower() == "true"
|
177
|
-
)
|
178
|
-
|
179
|
-
if das_emoji := os.getenv("PROVIDE_LOG_DAS_EMOJI_ENABLED"):
|
180
|
-
config_dict["das_emoji_prefix_enabled"] = das_emoji.lower() == "true"
|
181
|
-
|
182
|
-
if log_file := os.getenv("PROVIDE_LOG_FILE"):
|
183
|
-
config_dict["log_file"] = Path(log_file)
|
184
|
-
|
185
|
-
if foundation_level := os.getenv("FOUNDATION_LOG_LEVEL"):
|
186
|
-
foundation_level = foundation_level.upper()
|
187
|
-
if foundation_level in _VALID_LOG_LEVEL_TUPLE:
|
188
|
-
config_dict["foundation_setup_log_level"] = foundation_level
|
189
|
-
elif strict:
|
190
|
-
get_config_logger().warning(
|
191
|
-
"[Foundation Config Warning] Invalid configuration value, using default",
|
192
|
-
config_key="FOUNDATION_LOG_LEVEL",
|
193
|
-
invalid_value=foundation_level,
|
194
|
-
valid_options=list(_VALID_LOG_LEVEL_TUPLE),
|
195
|
-
default_value="INFO",
|
196
|
-
)
|
197
|
-
|
198
|
-
if foundation_output := os.getenv("FOUNDATION_LOG_OUTPUT"):
|
199
|
-
foundation_output = foundation_output.lower()
|
200
|
-
valid_outputs = ("stderr", "stdout", "main")
|
201
|
-
if foundation_output in valid_outputs:
|
202
|
-
config_dict["foundation_log_output"] = foundation_output
|
203
|
-
elif strict:
|
204
|
-
get_config_logger().warning(
|
205
|
-
"[Foundation Config Warning] Invalid configuration value, using default",
|
206
|
-
config_key="FOUNDATION_LOG_OUTPUT",
|
207
|
-
invalid_value=foundation_output,
|
208
|
-
valid_options=list(valid_outputs),
|
209
|
-
default_value="stderr",
|
210
|
-
)
|
211
|
-
|
212
|
-
if show_matrix := os.getenv("PROVIDE_SHOW_EMOJI_MATRIX"):
|
213
|
-
config_dict["show_emoji_matrix"] = show_matrix.strip().lower() in (
|
214
|
-
"true",
|
215
|
-
"1",
|
216
|
-
"yes",
|
217
|
-
)
|
218
|
-
|
219
|
-
# Parse rate limiting configuration
|
220
|
-
if rate_limit_enabled := os.getenv("PROVIDE_LOG_RATE_LIMIT_ENABLED"):
|
221
|
-
config_dict["rate_limit_enabled"] = rate_limit_enabled.lower() == "true"
|
222
|
-
|
223
|
-
if rate_limit_global := os.getenv("PROVIDE_LOG_RATE_LIMIT_GLOBAL"):
|
224
|
-
try:
|
225
|
-
config_dict["rate_limit_global"] = float(rate_limit_global)
|
226
|
-
except ValueError:
|
227
|
-
if strict:
|
228
|
-
get_config_logger().warning(
|
229
|
-
"[Foundation Config Warning] Invalid rate limit value",
|
230
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_GLOBAL",
|
231
|
-
invalid_value=rate_limit_global,
|
232
|
-
)
|
233
|
-
|
234
|
-
if rate_limit_capacity := os.getenv("PROVIDE_LOG_RATE_LIMIT_GLOBAL_CAPACITY"):
|
235
|
-
try:
|
236
|
-
config_dict["rate_limit_global_capacity"] = float(rate_limit_capacity)
|
237
|
-
except ValueError:
|
238
|
-
if strict:
|
239
|
-
get_config_logger().warning(
|
240
|
-
"[Foundation Config Warning] Invalid rate limit capacity",
|
241
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_GLOBAL_CAPACITY",
|
242
|
-
invalid_value=rate_limit_capacity,
|
243
|
-
)
|
244
|
-
|
245
|
-
if per_logger_limits := os.getenv("PROVIDE_LOG_RATE_LIMIT_PER_LOGGER"):
|
246
|
-
limits_dict = {}
|
247
|
-
for item in per_logger_limits.split(","):
|
248
|
-
parts = item.split(":")
|
249
|
-
if len(parts) == 3:
|
250
|
-
logger_name, rate, capacity = parts
|
251
|
-
try:
|
252
|
-
limits_dict[logger_name.strip()] = (
|
253
|
-
float(rate.strip()),
|
254
|
-
float(capacity.strip()),
|
255
|
-
)
|
256
|
-
except ValueError:
|
257
|
-
if strict:
|
258
|
-
get_config_logger().warning(
|
259
|
-
"[Foundation Config Warning] Invalid per-logger rate limit",
|
260
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_PER_LOGGER",
|
261
|
-
invalid_item=item,
|
262
|
-
)
|
263
|
-
if limits_dict:
|
264
|
-
config_dict["rate_limit_per_logger"] = limits_dict
|
265
|
-
|
266
|
-
if emit_warnings := os.getenv("PROVIDE_LOG_RATE_LIMIT_EMIT_WARNINGS"):
|
267
|
-
config_dict["rate_limit_emit_warnings"] = emit_warnings.lower() == "true"
|
268
|
-
|
269
|
-
if summary_interval := os.getenv("PROVIDE_LOG_RATE_LIMIT_SUMMARY_INTERVAL"):
|
270
|
-
try:
|
271
|
-
config_dict["rate_limit_summary_interval"] = float(summary_interval)
|
272
|
-
except ValueError:
|
273
|
-
if strict:
|
274
|
-
get_config_logger().warning(
|
275
|
-
"[Foundation Config Warning] Invalid summary interval",
|
276
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_SUMMARY_INTERVAL",
|
277
|
-
invalid_value=summary_interval,
|
278
|
-
)
|
279
|
-
|
280
|
-
if max_queue := os.getenv("PROVIDE_LOG_RATE_LIMIT_MAX_QUEUE_SIZE"):
|
281
|
-
try:
|
282
|
-
config_dict["rate_limit_max_queue_size"] = int(max_queue)
|
283
|
-
except ValueError:
|
284
|
-
if strict:
|
285
|
-
get_config_logger().warning(
|
286
|
-
"[Foundation Config Warning] Invalid max queue size",
|
287
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_MAX_QUEUE_SIZE",
|
288
|
-
invalid_value=max_queue,
|
289
|
-
)
|
290
|
-
|
291
|
-
if max_memory := os.getenv("PROVIDE_LOG_RATE_LIMIT_MAX_MEMORY_MB"):
|
292
|
-
try:
|
293
|
-
config_dict["rate_limit_max_memory_mb"] = float(max_memory)
|
294
|
-
except ValueError:
|
295
|
-
if strict:
|
296
|
-
get_config_logger().warning(
|
297
|
-
"[Foundation Config Warning] Invalid max memory",
|
298
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_MAX_MEMORY_MB",
|
299
|
-
invalid_value=max_memory,
|
300
|
-
)
|
301
|
-
|
302
|
-
if overflow_policy := os.getenv("PROVIDE_LOG_RATE_LIMIT_OVERFLOW_POLICY"):
|
303
|
-
valid_policies = ("drop_oldest", "drop_newest", "block")
|
304
|
-
if overflow_policy in valid_policies:
|
305
|
-
config_dict["rate_limit_overflow_policy"] = overflow_policy
|
306
|
-
elif strict:
|
307
|
-
get_config_logger().warning(
|
308
|
-
"[Foundation Config Warning] Invalid overflow policy",
|
309
|
-
config_key="PROVIDE_LOG_RATE_LIMIT_OVERFLOW_POLICY",
|
310
|
-
invalid_value=overflow_policy,
|
311
|
-
valid_options=list(valid_policies),
|
312
|
-
)
|
313
|
-
|
314
|
-
# Parse complex fields
|
315
|
-
if module_levels := os.getenv("PROVIDE_LOG_MODULE_LEVELS"):
|
316
|
-
levels_dict = {}
|
317
|
-
for item in module_levels.split(","):
|
318
|
-
if ":" in item:
|
319
|
-
module, level = item.split(":", 1)
|
320
|
-
module = module.strip()
|
321
|
-
level = level.strip().upper()
|
322
|
-
if module and level in _VALID_LOG_LEVEL_TUPLE:
|
323
|
-
levels_dict[module] = level
|
324
|
-
elif strict and module and level not in _VALID_LOG_LEVEL_TUPLE:
|
325
|
-
get_config_logger().warning(
|
326
|
-
"[Foundation Config Warning] Invalid module log level, skipping",
|
327
|
-
config_key="PROVIDE_LOG_MODULE_LEVELS",
|
328
|
-
module_name=module,
|
329
|
-
invalid_level=level,
|
330
|
-
valid_options=list(_VALID_LOG_LEVEL_TUPLE),
|
331
|
-
)
|
332
|
-
if levels_dict:
|
333
|
-
config_dict["module_levels"] = levels_dict
|
334
|
-
|
335
|
-
if emoji_sets := os.getenv("PROVIDE_LOG_ENABLED_EMOJI_SETS"):
|
336
|
-
config_dict["enabled_emoji_sets"] = [
|
337
|
-
s.strip() for s in emoji_sets.split(",") if s.strip()
|
338
|
-
]
|
339
|
-
|
340
|
-
if custom_sets := os.getenv("PROVIDE_LOG_CUSTOM_EMOJI_SETS"):
|
341
|
-
try:
|
342
|
-
parsed = json.loads(custom_sets)
|
343
|
-
if isinstance(parsed, list):
|
344
|
-
config_dict["custom_emoji_sets"] = [
|
345
|
-
EventSet(**item) if isinstance(item, dict) else item
|
346
|
-
for item in parsed
|
347
|
-
]
|
348
|
-
except json.JSONDecodeError as e:
|
349
|
-
if strict:
|
350
|
-
get_config_logger().warning(
|
351
|
-
"[Foundation Config Warning] Invalid JSON in configuration",
|
352
|
-
config_key="PROVIDE_LOG_CUSTOM_EMOJI_SETS",
|
353
|
-
error=str(e),
|
354
|
-
config_value=custom_sets[:100] + "..."
|
355
|
-
if len(custom_sets) > 100
|
356
|
-
else custom_sets,
|
357
|
-
)
|
358
|
-
except (TypeError, ValueError) as e:
|
359
|
-
if strict:
|
360
|
-
get_config_logger().warning(
|
361
|
-
"[Foundation Config Warning] Error parsing custom emoji set configuration",
|
362
|
-
config_key="PROVIDE_LOG_CUSTOM_EMOJI_SETS",
|
363
|
-
error=str(e),
|
364
|
-
error_type=type(e).__name__,
|
365
|
-
)
|
366
|
-
|
367
|
-
if user_sets := os.getenv("PROVIDE_LOG_USER_DEFINED_EMOJI_SETS"):
|
368
|
-
try:
|
369
|
-
parsed = json.loads(user_sets)
|
370
|
-
if isinstance(parsed, list):
|
371
|
-
config_dict["user_defined_emoji_sets"] = [
|
372
|
-
EventMapping(**item) if isinstance(item, dict) else item
|
373
|
-
for item in parsed
|
374
|
-
]
|
375
|
-
except json.JSONDecodeError as e:
|
376
|
-
if strict:
|
377
|
-
get_config_logger().warning(
|
378
|
-
"[Foundation Config Warning] Invalid JSON in configuration",
|
379
|
-
config_key="PROVIDE_LOG_USER_DEFINED_EMOJI_SETS",
|
380
|
-
error=str(e),
|
381
|
-
config_value=user_sets[:100] + "..."
|
382
|
-
if len(user_sets) > 100
|
383
|
-
else user_sets,
|
384
|
-
)
|
385
|
-
except (TypeError, ValueError) as e:
|
386
|
-
if strict:
|
387
|
-
get_config_logger().warning(
|
388
|
-
"[Foundation Config Warning] Error parsing user emoji set configuration",
|
389
|
-
config_key="PROVIDE_LOG_USER_DEFINED_EMOJI_SETS",
|
390
|
-
error=str(e),
|
391
|
-
error_type=type(e).__name__,
|
392
|
-
)
|
393
|
-
|
394
|
-
return cls.from_dict(config_dict, source=ConfigSource.ENV)
|
@@ -9,18 +9,36 @@ import os
|
|
9
9
|
|
10
10
|
from attrs import define
|
11
11
|
|
12
|
-
from provide.foundation.config import
|
12
|
+
from provide.foundation.config.base import field
|
13
|
+
from provide.foundation.config.converters import (
|
14
|
+
parse_bool_extended,
|
15
|
+
parse_headers,
|
16
|
+
parse_sample_rate,
|
17
|
+
validate_sample_rate,
|
18
|
+
)
|
19
|
+
from provide.foundation.config.defaults import (
|
20
|
+
DEFAULT_METRICS_ENABLED,
|
21
|
+
DEFAULT_OTLP_PROTOCOL,
|
22
|
+
DEFAULT_TELEMETRY_GLOBALLY_DISABLED,
|
23
|
+
DEFAULT_TRACE_SAMPLE_RATE,
|
24
|
+
DEFAULT_TRACING_ENABLED,
|
25
|
+
)
|
26
|
+
from provide.foundation.config.env import RuntimeConfig
|
13
27
|
from provide.foundation.logger.config.logging import LoggingConfig
|
14
28
|
|
15
29
|
|
30
|
+
def _get_service_name() -> str | None:
|
31
|
+
"""Get service name from OTEL_SERVICE_NAME or PROVIDE_SERVICE_NAME (OTEL takes precedence)."""
|
32
|
+
return os.getenv("OTEL_SERVICE_NAME") or os.getenv("PROVIDE_SERVICE_NAME")
|
33
|
+
|
34
|
+
|
16
35
|
@define(slots=True, repr=False)
|
17
|
-
class TelemetryConfig(
|
36
|
+
class TelemetryConfig(RuntimeConfig):
|
18
37
|
"""Main configuration object for the Foundation Telemetry system."""
|
19
38
|
|
20
39
|
service_name: str | None = field(
|
21
|
-
|
22
|
-
|
23
|
-
description="Service name for telemetry",
|
40
|
+
factory=_get_service_name,
|
41
|
+
description="Service name for telemetry (from OTEL_SERVICE_NAME or PROVIDE_SERVICE_NAME)",
|
24
42
|
)
|
25
43
|
service_version: str | None = field(
|
26
44
|
default=None,
|
@@ -28,23 +46,26 @@ class TelemetryConfig(BaseConfig):
|
|
28
46
|
description="Service version for telemetry",
|
29
47
|
)
|
30
48
|
logging: LoggingConfig = field(
|
31
|
-
factory=LoggingConfig, description="Logging configuration"
|
49
|
+
factory=lambda: LoggingConfig.from_env(), 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,29 @@ 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
|
-
|
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
|
-
|
174
100
|
def get_otlp_headers_dict(self) -> dict[str, str]:
|
175
|
-
"""
|
101
|
+
"""Get OTLP headers dictionary.
|
176
102
|
|
177
103
|
Returns:
|
178
104
|
Dictionary of header key-value pairs
|
179
105
|
"""
|
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
|
106
|
+
return self.otlp_headers
|