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.
Files changed (161) hide show
  1. provide/foundation/__init__.py +41 -23
  2. provide/foundation/archive/__init__.py +23 -0
  3. provide/foundation/archive/base.py +70 -0
  4. provide/foundation/archive/bzip2.py +157 -0
  5. provide/foundation/archive/gzip.py +159 -0
  6. provide/foundation/archive/operations.py +334 -0
  7. provide/foundation/archive/tar.py +164 -0
  8. provide/foundation/archive/zip.py +203 -0
  9. provide/foundation/cli/__init__.py +2 -2
  10. provide/foundation/cli/commands/deps.py +13 -7
  11. provide/foundation/cli/commands/logs/__init__.py +1 -1
  12. provide/foundation/cli/commands/logs/query.py +1 -1
  13. provide/foundation/cli/commands/logs/send.py +1 -1
  14. provide/foundation/cli/commands/logs/tail.py +1 -1
  15. provide/foundation/cli/decorators.py +11 -10
  16. provide/foundation/cli/main.py +1 -1
  17. provide/foundation/cli/testing.py +2 -35
  18. provide/foundation/cli/utils.py +21 -17
  19. provide/foundation/config/__init__.py +35 -2
  20. provide/foundation/config/base.py +2 -2
  21. provide/foundation/config/converters.py +479 -0
  22. provide/foundation/config/defaults.py +67 -0
  23. provide/foundation/config/env.py +4 -19
  24. provide/foundation/config/loader.py +9 -3
  25. provide/foundation/config/sync.py +19 -4
  26. provide/foundation/console/input.py +5 -5
  27. provide/foundation/console/output.py +35 -13
  28. provide/foundation/context/__init__.py +8 -4
  29. provide/foundation/context/core.py +85 -109
  30. provide/foundation/core.py +1 -2
  31. provide/foundation/crypto/__init__.py +2 -0
  32. provide/foundation/crypto/certificates/__init__.py +34 -0
  33. provide/foundation/crypto/certificates/base.py +173 -0
  34. provide/foundation/crypto/certificates/certificate.py +290 -0
  35. provide/foundation/crypto/certificates/factory.py +213 -0
  36. provide/foundation/crypto/certificates/generator.py +138 -0
  37. provide/foundation/crypto/certificates/loader.py +130 -0
  38. provide/foundation/crypto/certificates/operations.py +198 -0
  39. provide/foundation/crypto/certificates/trust.py +107 -0
  40. provide/foundation/errors/__init__.py +2 -3
  41. provide/foundation/errors/decorators.py +0 -231
  42. provide/foundation/errors/types.py +0 -97
  43. provide/foundation/eventsets/__init__.py +0 -0
  44. provide/foundation/eventsets/display.py +84 -0
  45. provide/foundation/eventsets/registry.py +160 -0
  46. provide/foundation/eventsets/resolver.py +192 -0
  47. provide/foundation/eventsets/sets/das.py +128 -0
  48. provide/foundation/eventsets/sets/database.py +125 -0
  49. provide/foundation/eventsets/sets/http.py +153 -0
  50. provide/foundation/eventsets/sets/llm.py +139 -0
  51. provide/foundation/eventsets/sets/task_queue.py +107 -0
  52. provide/foundation/eventsets/types.py +70 -0
  53. provide/foundation/file/directory.py +13 -22
  54. provide/foundation/file/lock.py +3 -1
  55. provide/foundation/hub/components.py +77 -515
  56. provide/foundation/hub/config.py +151 -0
  57. provide/foundation/hub/discovery.py +62 -0
  58. provide/foundation/hub/handlers.py +81 -0
  59. provide/foundation/hub/lifecycle.py +194 -0
  60. provide/foundation/hub/manager.py +4 -4
  61. provide/foundation/hub/processors.py +44 -0
  62. provide/foundation/integrations/__init__.py +11 -0
  63. provide/foundation/{observability → integrations}/openobserve/__init__.py +10 -7
  64. provide/foundation/{observability → integrations}/openobserve/auth.py +1 -1
  65. provide/foundation/{observability → integrations}/openobserve/client.py +12 -12
  66. provide/foundation/{observability → integrations}/openobserve/commands.py +3 -3
  67. provide/foundation/integrations/openobserve/config.py +37 -0
  68. provide/foundation/{observability → integrations}/openobserve/formatters.py +1 -1
  69. provide/foundation/{observability → integrations}/openobserve/otlp.py +1 -1
  70. provide/foundation/{observability → integrations}/openobserve/search.py +2 -2
  71. provide/foundation/{observability → integrations}/openobserve/streaming.py +4 -4
  72. provide/foundation/logger/__init__.py +3 -10
  73. provide/foundation/logger/config/logging.py +68 -298
  74. provide/foundation/logger/config/telemetry.py +41 -121
  75. provide/foundation/logger/core.py +0 -2
  76. provide/foundation/logger/custom_processors.py +1 -0
  77. provide/foundation/logger/factories.py +11 -2
  78. provide/foundation/logger/processors/main.py +20 -84
  79. provide/foundation/logger/setup/__init__.py +5 -1
  80. provide/foundation/logger/setup/coordinator.py +76 -24
  81. provide/foundation/logger/setup/processors.py +2 -9
  82. provide/foundation/logger/trace.py +27 -0
  83. provide/foundation/metrics/otel.py +10 -10
  84. provide/foundation/observability/__init__.py +2 -2
  85. provide/foundation/process/__init__.py +9 -0
  86. provide/foundation/process/exit.py +47 -0
  87. provide/foundation/process/lifecycle.py +115 -59
  88. provide/foundation/resilience/__init__.py +35 -0
  89. provide/foundation/resilience/circuit.py +164 -0
  90. provide/foundation/resilience/decorators.py +220 -0
  91. provide/foundation/resilience/fallback.py +193 -0
  92. provide/foundation/resilience/retry.py +325 -0
  93. provide/foundation/streams/config.py +79 -0
  94. provide/foundation/streams/console.py +7 -8
  95. provide/foundation/streams/core.py +6 -3
  96. provide/foundation/streams/file.py +12 -2
  97. provide/foundation/testing/__init__.py +84 -2
  98. provide/foundation/testing/archive/__init__.py +24 -0
  99. provide/foundation/testing/archive/fixtures.py +217 -0
  100. provide/foundation/testing/cli.py +30 -17
  101. provide/foundation/testing/common/__init__.py +32 -0
  102. provide/foundation/testing/common/fixtures.py +236 -0
  103. provide/foundation/testing/file/__init__.py +40 -0
  104. provide/foundation/testing/file/content_fixtures.py +316 -0
  105. provide/foundation/testing/file/directory_fixtures.py +107 -0
  106. provide/foundation/testing/file/fixtures.py +52 -0
  107. provide/foundation/testing/file/special_fixtures.py +153 -0
  108. provide/foundation/testing/logger.py +117 -11
  109. provide/foundation/testing/mocking/__init__.py +46 -0
  110. provide/foundation/testing/mocking/fixtures.py +331 -0
  111. provide/foundation/testing/process/__init__.py +48 -0
  112. provide/foundation/testing/process/async_fixtures.py +405 -0
  113. provide/foundation/testing/process/fixtures.py +56 -0
  114. provide/foundation/testing/process/subprocess_fixtures.py +209 -0
  115. provide/foundation/testing/threading/__init__.py +38 -0
  116. provide/foundation/testing/threading/basic_fixtures.py +101 -0
  117. provide/foundation/testing/threading/data_fixtures.py +99 -0
  118. provide/foundation/testing/threading/execution_fixtures.py +263 -0
  119. provide/foundation/testing/threading/fixtures.py +54 -0
  120. provide/foundation/testing/threading/sync_fixtures.py +97 -0
  121. provide/foundation/testing/time/__init__.py +32 -0
  122. provide/foundation/testing/time/fixtures.py +409 -0
  123. provide/foundation/testing/transport/__init__.py +30 -0
  124. provide/foundation/testing/transport/fixtures.py +280 -0
  125. provide/foundation/tools/__init__.py +58 -0
  126. provide/foundation/tools/base.py +348 -0
  127. provide/foundation/tools/cache.py +268 -0
  128. provide/foundation/tools/downloader.py +224 -0
  129. provide/foundation/tools/installer.py +254 -0
  130. provide/foundation/tools/registry.py +223 -0
  131. provide/foundation/tools/resolver.py +321 -0
  132. provide/foundation/tools/verifier.py +186 -0
  133. provide/foundation/tracer/otel.py +7 -11
  134. provide/foundation/tracer/spans.py +2 -2
  135. provide/foundation/transport/__init__.py +155 -0
  136. provide/foundation/transport/base.py +171 -0
  137. provide/foundation/transport/client.py +266 -0
  138. provide/foundation/transport/config.py +140 -0
  139. provide/foundation/transport/errors.py +79 -0
  140. provide/foundation/transport/http.py +232 -0
  141. provide/foundation/transport/middleware.py +360 -0
  142. provide/foundation/transport/registry.py +167 -0
  143. provide/foundation/transport/types.py +45 -0
  144. provide/foundation/utils/deps.py +14 -12
  145. provide/foundation/utils/parsing.py +49 -4
  146. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/METADATA +5 -28
  147. provide_foundation-0.0.0.dev2.dist-info/RECORD +225 -0
  148. provide/foundation/cli/commands/logs/generate_old.py +0 -569
  149. provide/foundation/crypto/certificates.py +0 -896
  150. provide/foundation/logger/emoji/__init__.py +0 -44
  151. provide/foundation/logger/emoji/matrix.py +0 -209
  152. provide/foundation/logger/emoji/sets.py +0 -458
  153. provide/foundation/logger/emoji/types.py +0 -56
  154. provide/foundation/logger/setup/emoji_resolver.py +0 -64
  155. provide_foundation-0.0.0.dev0.dist-info/RECORD +0 -149
  156. /provide/foundation/{observability → integrations}/openobserve/exceptions.py +0 -0
  157. /provide/foundation/{observability → integrations}/openobserve/models.py +0 -0
  158. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/WHEEL +0 -0
  159. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/entry_points.txt +0 -0
  160. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/licenses/LICENSE +0 -0
  161. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev2.dist-info}/top_level.txt +0 -0
@@ -12,16 +12,16 @@ from requests.adapters import HTTPAdapter
12
12
  from urllib3.util.retry import Retry
13
13
 
14
14
  from provide.foundation.logger import get_logger
15
- from provide.foundation.observability.openobserve.auth import (
15
+ from provide.foundation.integrations.openobserve.auth import (
16
16
  get_auth_headers,
17
17
  validate_credentials,
18
18
  )
19
- from provide.foundation.observability.openobserve.exceptions import (
19
+ from provide.foundation.integrations.openobserve.exceptions import (
20
20
  OpenObserveConfigError,
21
21
  OpenObserveConnectionError,
22
22
  OpenObserveQueryError,
23
23
  )
24
- from provide.foundation.observability.openobserve.models import (
24
+ from provide.foundation.integrations.openobserve.models import (
25
25
  SearchQuery,
26
26
  SearchResponse,
27
27
  StreamInfo,
@@ -74,7 +74,7 @@ class OpenObserveClient:
74
74
 
75
75
  @classmethod
76
76
  def from_config(cls) -> "OpenObserveClient":
77
- """Create client from TelemetryConfig.
77
+ """Create client from OpenObserveConfig.
78
78
 
79
79
  Returns:
80
80
  Configured OpenObserveClient instance
@@ -82,26 +82,26 @@ class OpenObserveClient:
82
82
  Raises:
83
83
  OpenObserveConfigError: If configuration is missing
84
84
  """
85
- from provide.foundation.logger.config import TelemetryConfig
85
+ from provide.foundation.integrations.openobserve.config import OpenObserveConfig
86
86
 
87
- config = TelemetryConfig.from_env()
87
+ config = OpenObserveConfig.from_env()
88
88
 
89
- if not config.openobserve_url:
89
+ if not config.url:
90
90
  raise OpenObserveConfigError(
91
91
  "OpenObserve URL not configured. Set OPENOBSERVE_URL environment variable."
92
92
  )
93
93
 
94
- if not config.openobserve_user or not config.openobserve_password:
94
+ if not config.user or not config.password:
95
95
  raise OpenObserveConfigError(
96
96
  "OpenObserve credentials not configured. "
97
97
  "Set OPENOBSERVE_USER and OPENOBSERVE_PASSWORD environment variables."
98
98
  )
99
99
 
100
100
  return cls(
101
- url=config.openobserve_url,
102
- username=config.openobserve_user,
103
- password=config.openobserve_password,
104
- organization=config.openobserve_org,
101
+ url=config.url,
102
+ username=config.user,
103
+ password=config.password,
104
+ organization=config.org or "default",
105
105
  )
106
106
 
107
107
  def _make_request(
@@ -18,7 +18,7 @@ log = get_logger(__name__)
18
18
 
19
19
 
20
20
  if _HAS_CLICK:
21
- from provide.foundation.observability.openobserve import (
21
+ from provide.foundation.integrations.openobserve import (
22
22
  OpenObserveClient,
23
23
  format_output,
24
24
  search_logs,
@@ -198,7 +198,7 @@ if _HAS_CLICK:
198
198
  return 1
199
199
 
200
200
  try:
201
- from provide.foundation.observability.openobserve import search_errors
201
+ from provide.foundation.integrations.openobserve import search_errors
202
202
 
203
203
  response = search_errors(
204
204
  stream=stream,
@@ -240,7 +240,7 @@ if _HAS_CLICK:
240
240
  return 1
241
241
 
242
242
  try:
243
- from provide.foundation.observability.openobserve import search_by_trace_id
243
+ from provide.foundation.integrations.openobserve import search_by_trace_id
244
244
 
245
245
  response = search_by_trace_id(
246
246
  trace_id=trace_id,
@@ -0,0 +1,37 @@
1
+ """OpenObserve integration configuration."""
2
+
3
+ from attrs import define
4
+
5
+ from provide.foundation.config.env import RuntimeConfig
6
+ from provide.foundation.config.base import field
7
+
8
+
9
+ @define(slots=True, repr=False)
10
+ class OpenObserveConfig(RuntimeConfig):
11
+ """Configuration for OpenObserve integration."""
12
+
13
+ url: str | None = field(
14
+ default=None,
15
+ env_var="OPENOBSERVE_URL",
16
+ description="OpenObserve URL endpoint",
17
+ )
18
+ org: str | None = field(
19
+ default=None,
20
+ env_var="OPENOBSERVE_ORG",
21
+ description="OpenObserve organization",
22
+ )
23
+ user: str | None = field(
24
+ default=None,
25
+ env_var="OPENOBSERVE_USER",
26
+ description="OpenObserve username",
27
+ )
28
+ password: str | None = field(
29
+ default=None,
30
+ env_var="OPENOBSERVE_PASSWORD",
31
+ description="OpenObserve password",
32
+ )
33
+ stream: str | None = field(
34
+ default=None,
35
+ env_var="OPENOBSERVE_STREAM",
36
+ description="OpenObserve stream name",
37
+ )
@@ -8,7 +8,7 @@ import io
8
8
  import json
9
9
  from typing import Any
10
10
 
11
- from provide.foundation.observability.openobserve.models import SearchResponse
11
+ from provide.foundation.integrations.openobserve.models import SearchResponse
12
12
 
13
13
 
14
14
  def format_json(response: SearchResponse | dict[str, Any], pretty: bool = True) -> str:
@@ -7,7 +7,7 @@ import json
7
7
  from typing import Any
8
8
 
9
9
  from provide.foundation.logger import get_logger
10
- from provide.foundation.observability.openobserve.client import OpenObserveClient
10
+ from provide.foundation.integrations.openobserve.client import OpenObserveClient
11
11
 
12
12
  log = get_logger(__name__)
13
13
 
@@ -4,8 +4,8 @@ Search operations for OpenObserve.
4
4
 
5
5
 
6
6
  from provide.foundation.logger import get_logger
7
- from provide.foundation.observability.openobserve.client import OpenObserveClient
8
- from provide.foundation.observability.openobserve.models import SearchResponse
7
+ from provide.foundation.integrations.openobserve.client import OpenObserveClient
8
+ from provide.foundation.integrations.openobserve.models import SearchResponse
9
9
 
10
10
  log = get_logger(__name__)
11
11
 
@@ -10,12 +10,12 @@ from typing import Any
10
10
  import requests
11
11
 
12
12
  from provide.foundation.logger import get_logger
13
- from provide.foundation.observability.openobserve.auth import get_auth_headers
14
- from provide.foundation.observability.openobserve.client import OpenObserveClient
15
- from provide.foundation.observability.openobserve.exceptions import (
13
+ from provide.foundation.integrations.openobserve.auth import get_auth_headers
14
+ from provide.foundation.integrations.openobserve.client import OpenObserveClient
15
+ from provide.foundation.integrations.openobserve.exceptions import (
16
16
  OpenObserveStreamingError,
17
17
  )
18
- from provide.foundation.observability.openobserve.models import parse_relative_time
18
+ from provide.foundation.integrations.openobserve.models import parse_relative_time
19
19
 
20
20
  log = get_logger(__name__)
21
21
 
@@ -6,6 +6,9 @@ Foundation Telemetry Logger Sub-package.
6
6
  Re-exports key components related to logging functionality.
7
7
  """
8
8
 
9
+ # Import trace module early to ensure PrintLogger gets patched
10
+ from provide.foundation.logger import trace # noqa: F401
11
+
9
12
  from provide.foundation.logger.base import (
10
13
  FoundationLogger, # Class definition
11
14
  get_logger, # Factory function
@@ -17,17 +20,8 @@ from provide.foundation.logger.config import (
17
20
  LoggingConfig,
18
21
  TelemetryConfig,
19
22
  )
20
- from provide.foundation.logger.emoji.matrix import (
21
- PRIMARY_EMOJI, # Core domain emojis
22
- SECONDARY_EMOJI, # Core action emojis
23
- TERTIARY_EMOJI, # Core status emojis
24
- show_emoji_matrix, # Utility to display emoji configurations
25
- )
26
23
 
27
24
  __all__ = [
28
- "PRIMARY_EMOJI",
29
- "SECONDARY_EMOJI",
30
- "TERTIARY_EMOJI",
31
25
  "FoundationLogger",
32
26
  "LoggingConfig",
33
27
  "TelemetryConfig",
@@ -35,7 +29,6 @@ __all__ = [
35
29
  "logger",
36
30
  "setup_logger", # Consistent naming
37
31
  "setup_logging", # Backward compatibility
38
- "show_emoji_matrix",
39
32
  ]
40
33
 
41
34
  # 🐍📝
@@ -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 BaseConfig, field
15
- from provide.foundation.config.types import ConfigSource
16
- from provide.foundation.logger.config.base import get_config_logger
17
- from provide.foundation.logger.emoji.types import EmojiSet, EmojiSetConfig
12
+ from provide.foundation.config.env import RuntimeConfig
13
+ from provide.foundation.config.base import field
14
+ from provide.foundation.config.defaults import (
15
+ DEFAULT_LOG_LEVEL,
16
+ DEFAULT_CONSOLE_FORMATTER,
17
+ DEFAULT_LOGGER_NAME_EMOJI_ENABLED,
18
+ DEFAULT_DAS_EMOJI_ENABLED,
19
+ DEFAULT_OMIT_TIMESTAMP,
20
+ DEFAULT_FOUNDATION_SETUP_LOG_LEVEL,
21
+ DEFAULT_FOUNDATION_LOG_OUTPUT,
22
+ DEFAULT_RATE_LIMIT_ENABLED,
23
+ DEFAULT_RATE_LIMIT_EMIT_WARNINGS,
24
+ DEFAULT_RATE_LIMIT_GLOBAL,
25
+ DEFAULT_RATE_LIMIT_GLOBAL_CAPACITY,
26
+ DEFAULT_RATE_LIMIT_OVERFLOW_POLICY,
27
+ )
28
+ from provide.foundation.config.converters import (
29
+ parse_bool_extended,
30
+ parse_console_formatter,
31
+ parse_float_with_validation,
32
+ parse_foundation_log_output,
33
+ parse_log_level,
34
+ parse_module_levels,
35
+ parse_rate_limits,
36
+ validate_log_level,
37
+ validate_non_negative,
38
+ validate_overflow_policy,
39
+ validate_positive,
40
+ )
18
41
  from provide.foundation.types import (
19
- _VALID_FORMATTER_TUPLE,
20
- _VALID_LOG_LEVEL_TUPLE,
21
42
  ConsoleFormatterStr,
22
43
  LogLevelStr,
23
44
  )
24
45
 
25
46
 
26
47
  @define(slots=True, repr=False)
27
- class LoggingConfig(BaseConfig):
48
+ class LoggingConfig(RuntimeConfig):
28
49
  """Configuration specific to logging behavior within Foundation Telemetry."""
29
50
 
30
51
  default_level: LogLevelStr = field(
31
- default="DEBUG",
52
+ default=DEFAULT_LOG_LEVEL,
32
53
  env_var="PROVIDE_LOG_LEVEL",
54
+ converter=parse_log_level,
55
+ validator=validate_log_level,
33
56
  description="Default logging level",
34
57
  )
35
58
  module_levels: dict[str, LogLevelStr] = field(
36
59
  factory=lambda: {},
37
60
  env_var="PROVIDE_LOG_MODULE_LEVELS",
61
+ converter=parse_module_levels,
38
62
  description="Per-module log levels (format: module1:LEVEL,module2:LEVEL)",
39
63
  )
40
64
  console_formatter: ConsoleFormatterStr = field(
41
- default="key_value",
65
+ default=DEFAULT_CONSOLE_FORMATTER,
42
66
  env_var="PROVIDE_LOG_CONSOLE_FORMATTER",
67
+ converter=parse_console_formatter,
43
68
  description="Console output formatter (key_value or json)",
44
69
  )
45
70
  logger_name_emoji_prefix_enabled: bool = field(
46
- default=True,
71
+ default=DEFAULT_LOGGER_NAME_EMOJI_ENABLED,
47
72
  env_var="PROVIDE_LOG_LOGGER_NAME_EMOJI_ENABLED",
73
+ converter=parse_bool_extended,
48
74
  description="Enable emoji prefixes based on logger names",
49
75
  )
50
76
  das_emoji_prefix_enabled: bool = field(
51
- default=True,
77
+ default=DEFAULT_DAS_EMOJI_ENABLED,
52
78
  env_var="PROVIDE_LOG_DAS_EMOJI_ENABLED",
79
+ converter=parse_bool_extended,
53
80
  description="Enable Domain-Action-Status emoji prefixes",
54
81
  )
55
82
  omit_timestamp: bool = field(
56
- default=False,
83
+ default=DEFAULT_OMIT_TIMESTAMP,
57
84
  env_var="PROVIDE_LOG_OMIT_TIMESTAMP",
85
+ converter=parse_bool_extended,
58
86
  description="Omit timestamps from console output",
59
87
  )
60
- enabled_emoji_sets: list[str] = field(
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[EmojiSetConfig] = 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[EmojiSet] = field(
71
- factory=lambda: [],
72
- env_var="PROVIDE_LOG_USER_DEFINED_EMOJI_SETS",
73
- description="JSON array of user-defined emoji sets",
74
- )
88
+ # Event sets have replaced emoji sets - these fields are deprecated
75
89
  log_file: Path | None = field(
76
- default=None, env_var="PROVIDE_LOG_FILE", description="Path to log file"
90
+ default=None,
91
+ env_var="PROVIDE_LOG_FILE",
92
+ converter=lambda x: Path(x) if x else None,
93
+ description="Path to log file"
77
94
  )
78
95
  foundation_setup_log_level: LogLevelStr = field(
79
- default="INFO",
96
+ default=DEFAULT_FOUNDATION_SETUP_LOG_LEVEL,
80
97
  env_var="FOUNDATION_LOG_LEVEL",
98
+ converter=parse_log_level,
99
+ validator=validate_log_level,
81
100
  description="Log level for Foundation internal setup messages",
82
101
  )
83
102
  foundation_log_output: str = field(
84
- default="stderr",
103
+ default=DEFAULT_FOUNDATION_LOG_OUTPUT,
85
104
  env_var="FOUNDATION_LOG_OUTPUT",
105
+ converter=parse_foundation_log_output,
86
106
  description="Output destination for Foundation internal messages (stderr, stdout, main)",
87
107
  )
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
108
  rate_limit_enabled: bool = field(
94
- default=False,
109
+ default=DEFAULT_RATE_LIMIT_ENABLED,
95
110
  env_var="PROVIDE_LOG_RATE_LIMIT_ENABLED",
111
+ converter=parse_bool_extended,
96
112
  description="Enable rate limiting for log output",
97
113
  )
98
114
  rate_limit_global: float | None = field(
99
115
  default=None,
100
116
  env_var="PROVIDE_LOG_RATE_LIMIT_GLOBAL",
117
+ converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else None,
101
118
  description="Global rate limit (logs per second)",
102
119
  )
103
120
  rate_limit_global_capacity: float | None = field(
104
121
  default=None,
105
122
  env_var="PROVIDE_LOG_RATE_LIMIT_GLOBAL_CAPACITY",
123
+ converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else None,
106
124
  description="Global rate limit burst capacity",
107
125
  )
108
126
  rate_limit_per_logger: dict[str, tuple[float, float]] = field(
109
127
  factory=lambda: {},
110
128
  env_var="PROVIDE_LOG_RATE_LIMIT_PER_LOGGER",
129
+ converter=parse_rate_limits,
111
130
  description="Per-logger rate limits (format: logger1:rate:capacity,logger2:rate:capacity)",
112
131
  )
113
132
  rate_limit_emit_warnings: bool = field(
114
- default=True,
133
+ default=DEFAULT_RATE_LIMIT_EMIT_WARNINGS,
115
134
  env_var="PROVIDE_LOG_RATE_LIMIT_EMIT_WARNINGS",
135
+ converter=parse_bool_extended,
116
136
  description="Emit warnings when logs are rate limited",
117
137
  )
118
138
  rate_limit_summary_interval: float = field(
119
- default=5.0,
139
+ default=DEFAULT_RATE_LIMIT_GLOBAL,
120
140
  env_var="PROVIDE_LOG_RATE_LIMIT_SUMMARY_INTERVAL",
141
+ converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else DEFAULT_RATE_LIMIT_GLOBAL,
142
+ validator=validate_positive,
121
143
  description="Seconds between rate limit summary reports",
122
144
  )
123
145
  rate_limit_max_queue_size: int = field(
124
- default=1000,
146
+ default=DEFAULT_RATE_LIMIT_GLOBAL_CAPACITY,
125
147
  env_var="PROVIDE_LOG_RATE_LIMIT_MAX_QUEUE_SIZE",
148
+ converter=int,
149
+ validator=validate_positive,
126
150
  description="Maximum number of logs to queue when rate limited",
127
151
  )
128
152
  rate_limit_max_memory_mb: float | None = field(
129
153
  default=None,
130
154
  env_var="PROVIDE_LOG_RATE_LIMIT_MAX_MEMORY_MB",
155
+ converter=lambda x: parse_float_with_validation(x, min_val=0.0) if x else None,
131
156
  description="Maximum memory (MB) for queued logs",
132
157
  )
133
158
  rate_limit_overflow_policy: str = field(
134
- default="drop_oldest",
159
+ default=DEFAULT_RATE_LIMIT_OVERFLOW_POLICY,
135
160
  env_var="PROVIDE_LOG_RATE_LIMIT_OVERFLOW_POLICY",
161
+ validator=validate_overflow_policy,
136
162
  description="Policy when queue is full: drop_oldest, drop_newest, or block",
137
163
  )
138
164
 
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
- EmojiSetConfig(**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
- EmojiSet(**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)