provide-foundation 0.0.0.dev0__py3-none-any.whl → 0.0.0.dev1__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 (92) hide show
  1. provide/foundation/__init__.py +12 -20
  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 +336 -0
  7. provide/foundation/archive/tar.py +164 -0
  8. provide/foundation/archive/zip.py +203 -0
  9. provide/foundation/config/base.py +2 -2
  10. provide/foundation/config/sync.py +19 -4
  11. provide/foundation/core.py +1 -2
  12. provide/foundation/crypto/__init__.py +2 -0
  13. provide/foundation/crypto/certificates/__init__.py +34 -0
  14. provide/foundation/crypto/certificates/base.py +173 -0
  15. provide/foundation/crypto/certificates/certificate.py +290 -0
  16. provide/foundation/crypto/certificates/factory.py +213 -0
  17. provide/foundation/crypto/certificates/generator.py +138 -0
  18. provide/foundation/crypto/certificates/loader.py +130 -0
  19. provide/foundation/crypto/certificates/operations.py +198 -0
  20. provide/foundation/crypto/certificates/trust.py +107 -0
  21. provide/foundation/eventsets/__init__.py +0 -0
  22. provide/foundation/eventsets/display.py +84 -0
  23. provide/foundation/eventsets/registry.py +160 -0
  24. provide/foundation/eventsets/resolver.py +192 -0
  25. provide/foundation/eventsets/sets/das.py +128 -0
  26. provide/foundation/eventsets/sets/database.py +125 -0
  27. provide/foundation/eventsets/sets/http.py +153 -0
  28. provide/foundation/eventsets/sets/llm.py +139 -0
  29. provide/foundation/eventsets/sets/task_queue.py +107 -0
  30. provide/foundation/eventsets/types.py +70 -0
  31. provide/foundation/hub/components.py +7 -133
  32. provide/foundation/logger/__init__.py +3 -10
  33. provide/foundation/logger/config/logging.py +6 -6
  34. provide/foundation/logger/core.py +0 -2
  35. provide/foundation/logger/custom_processors.py +1 -0
  36. provide/foundation/logger/factories.py +11 -2
  37. provide/foundation/logger/processors/main.py +20 -84
  38. provide/foundation/logger/setup/__init__.py +5 -1
  39. provide/foundation/logger/setup/coordinator.py +75 -23
  40. provide/foundation/logger/setup/processors.py +2 -9
  41. provide/foundation/logger/trace.py +27 -0
  42. provide/foundation/metrics/otel.py +10 -10
  43. provide/foundation/process/lifecycle.py +82 -26
  44. provide/foundation/testing/__init__.py +77 -0
  45. provide/foundation/testing/archive/__init__.py +24 -0
  46. provide/foundation/testing/archive/fixtures.py +217 -0
  47. provide/foundation/testing/common/__init__.py +34 -0
  48. provide/foundation/testing/common/fixtures.py +263 -0
  49. provide/foundation/testing/file/__init__.py +40 -0
  50. provide/foundation/testing/file/fixtures.py +523 -0
  51. provide/foundation/testing/logger.py +41 -11
  52. provide/foundation/testing/mocking/__init__.py +46 -0
  53. provide/foundation/testing/mocking/fixtures.py +331 -0
  54. provide/foundation/testing/process/__init__.py +48 -0
  55. provide/foundation/testing/process/fixtures.py +577 -0
  56. provide/foundation/testing/threading/__init__.py +38 -0
  57. provide/foundation/testing/threading/fixtures.py +520 -0
  58. provide/foundation/testing/time/__init__.py +32 -0
  59. provide/foundation/testing/time/fixtures.py +409 -0
  60. provide/foundation/testing/transport/__init__.py +30 -0
  61. provide/foundation/testing/transport/fixtures.py +280 -0
  62. provide/foundation/tools/__init__.py +58 -0
  63. provide/foundation/tools/base.py +348 -0
  64. provide/foundation/tools/cache.py +266 -0
  65. provide/foundation/tools/downloader.py +213 -0
  66. provide/foundation/tools/installer.py +254 -0
  67. provide/foundation/tools/registry.py +223 -0
  68. provide/foundation/tools/resolver.py +321 -0
  69. provide/foundation/tools/verifier.py +186 -0
  70. provide/foundation/tracer/otel.py +7 -11
  71. provide/foundation/transport/__init__.py +155 -0
  72. provide/foundation/transport/base.py +171 -0
  73. provide/foundation/transport/client.py +266 -0
  74. provide/foundation/transport/config.py +209 -0
  75. provide/foundation/transport/errors.py +79 -0
  76. provide/foundation/transport/http.py +232 -0
  77. provide/foundation/transport/middleware.py +366 -0
  78. provide/foundation/transport/registry.py +167 -0
  79. provide/foundation/transport/types.py +45 -0
  80. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/METADATA +5 -28
  81. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/RECORD +85 -34
  82. provide/foundation/cli/commands/logs/generate_old.py +0 -569
  83. provide/foundation/crypto/certificates.py +0 -896
  84. provide/foundation/logger/emoji/__init__.py +0 -44
  85. provide/foundation/logger/emoji/matrix.py +0 -209
  86. provide/foundation/logger/emoji/sets.py +0 -458
  87. provide/foundation/logger/emoji/types.py +0 -56
  88. provide/foundation/logger/setup/emoji_resolver.py +0 -64
  89. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/WHEEL +0 -0
  90. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/entry_points.txt +0 -0
  91. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
  92. {provide_foundation-0.0.0.dev0.dist-info → provide_foundation-0.0.0.dev1.dist-info}/top_level.txt +0 -0
@@ -16,7 +16,7 @@ from attrs import define, field
16
16
 
17
17
  from provide.foundation.hub.registry import Registry, RegistryEntry
18
18
  from provide.foundation.logger import get_logger
19
- from provide.foundation.logger.emoji.types import EmojiSet
19
+ from provide.foundation.eventsets.types import EventMapping
20
20
 
21
21
  log = get_logger(__name__)
22
22
 
@@ -40,12 +40,16 @@ class ComponentInfo:
40
40
  class ComponentCategory(Enum):
41
41
  """Predefined component categories for Foundation."""
42
42
 
43
- EMOJI_SET = "emoji_set"
44
43
  CONFIG_SOURCE = "config_source"
45
44
  PROCESSOR = "processor"
46
45
  ERROR_HANDLER = "error_handler"
47
46
  FORMATTER = "formatter"
48
47
  FILTER = "filter"
48
+ TRANSPORT = "transport"
49
+ TRANSPORT_MIDDLEWARE = "transport.middleware"
50
+ TRANSPORT_AUTH = "transport.auth"
51
+ TRANSPORT_CACHE = "transport.cache"
52
+ EVENT_SET = "eventset"
49
53
 
50
54
 
51
55
  class ComponentLifecycle(Protocol):
@@ -71,119 +75,6 @@ def get_component_registry() -> Registry:
71
75
  return _component_registry
72
76
 
73
77
 
74
- def find_emoji_set_for_domain(domain: str) -> EmojiSet | None:
75
- """Find the best emoji set for a given domain."""
76
- with _registry_lock:
77
- registry = get_component_registry()
78
-
79
- # Get all emoji sets for this domain
80
- all_entries = list(registry)
81
- domain_sets = [
82
- entry
83
- for entry in all_entries
84
- if (
85
- entry.dimension == ComponentCategory.EMOJI_SET.value
86
- and entry.metadata.get("domain") == domain
87
- )
88
- ]
89
-
90
- if not domain_sets:
91
- return get_default_emoji_set()
92
-
93
- # Sort by priority (highest first)
94
- domain_sets.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
95
- return domain_sets[0].value
96
-
97
-
98
- def get_default_emoji_set() -> EmojiSet:
99
- """Get the default emoji set for unknown domains."""
100
- with _registry_lock:
101
- registry = get_component_registry()
102
-
103
- # Look for default emoji set
104
- all_entries = list(registry)
105
- default_sets = [
106
- entry
107
- for entry in all_entries
108
- if (
109
- entry.dimension == ComponentCategory.EMOJI_SET.value
110
- and entry.metadata.get("default", False)
111
- )
112
- ]
113
-
114
- if default_sets:
115
- return default_sets[0].value
116
-
117
- # Return built-in default
118
- return EmojiSet(
119
- name="default",
120
- emojis={
121
- "success": "✅",
122
- "error": "❌",
123
- "info": "ℹ️",
124
- "warning": "⚠️",
125
- "debug": "🔍",
126
- },
127
- )
128
-
129
-
130
- def resolve_emoji_for_domain(domain: str, action: str) -> str:
131
- """Resolve emoji for domain and action with priority ordering."""
132
- with _registry_lock:
133
- registry = get_component_registry()
134
-
135
- # Get all emoji sets for this domain
136
- all_entries = list(registry)
137
- domain_sets = [
138
- entry
139
- for entry in all_entries
140
- if (
141
- entry.dimension == ComponentCategory.EMOJI_SET.value
142
- and entry.metadata.get("domain") == domain
143
- )
144
- ]
145
-
146
- # Sort by priority (highest first)
147
- domain_sets.sort(key=lambda e: e.metadata.get("priority", 0), reverse=True)
148
-
149
- # Try each set in priority order
150
- for entry in domain_sets:
151
- emoji_set = entry.value
152
- if action in emoji_set.emojis:
153
- return emoji_set.emojis[action]
154
-
155
- # Fall back to default
156
- default_set = get_default_emoji_set()
157
- return default_set.emojis.get(action, "📝")
158
-
159
-
160
- def get_composed_emoji_set(domain: str) -> EmojiSet:
161
- """Get composed emoji set combining all sets for a domain."""
162
- with _registry_lock:
163
- registry = get_component_registry()
164
-
165
- # Get all emoji sets for this domain
166
- all_entries = list(registry)
167
- domain_sets = [
168
- entry
169
- for entry in all_entries
170
- if (
171
- entry.dimension == ComponentCategory.EMOJI_SET.value
172
- and entry.metadata.get("domain") == domain
173
- )
174
- ]
175
-
176
- # Sort by priority (lowest first for composition)
177
- domain_sets.sort(key=lambda e: e.metadata.get("priority", 0))
178
-
179
- # Compose emojis
180
- composed_emojis = {}
181
- for entry in domain_sets:
182
- emoji_set = entry.value
183
- composed_emojis.update(emoji_set.emojis)
184
-
185
- return EmojiSet(name=f"composed_{domain}", emojis=composed_emojis)
186
-
187
78
 
188
79
  def resolve_config_value(key: str) -> Any:
189
80
  """Resolve configuration value using priority-ordered sources."""
@@ -532,24 +423,7 @@ def bootstrap_foundation() -> None:
532
423
  """Bootstrap Foundation with core registry components."""
533
424
  registry = get_component_registry()
534
425
 
535
- # Register default emoji set
536
- default_emoji_set = EmojiSet(
537
- name="foundation_default",
538
- emojis={
539
- "success": "✅",
540
- "error": "❌",
541
- "info": "ℹ️",
542
- "warning": "⚠️",
543
- "debug": "🔍",
544
- },
545
- )
546
-
547
- registry.register(
548
- name="default",
549
- value=default_emoji_set,
550
- dimension=ComponentCategory.EMOJI_SET.value,
551
- metadata={"default": True, "priority": 1},
552
- )
426
+ # Event sets are now managed by the eventsets module
553
427
 
554
428
  # Config sources would be registered here when implemented
555
429
 
@@ -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
  # 🐍📝
@@ -14,7 +14,7 @@ from attrs import define
14
14
  from provide.foundation.config import BaseConfig, field
15
15
  from provide.foundation.config.types import ConfigSource
16
16
  from provide.foundation.logger.config.base import get_config_logger
17
- from provide.foundation.logger.emoji.types import EmojiSet, EmojiSetConfig
17
+ from provide.foundation.eventsets.types import EventMapping, EventSet
18
18
  from provide.foundation.types import (
19
19
  _VALID_FORMATTER_TUPLE,
20
20
  _VALID_LOG_LEVEL_TUPLE,
@@ -28,7 +28,7 @@ class LoggingConfig(BaseConfig):
28
28
  """Configuration specific to logging behavior within Foundation Telemetry."""
29
29
 
30
30
  default_level: LogLevelStr = field(
31
- default="DEBUG",
31
+ default="WARNING",
32
32
  env_var="PROVIDE_LOG_LEVEL",
33
33
  description="Default logging level",
34
34
  )
@@ -62,12 +62,12 @@ class LoggingConfig(BaseConfig):
62
62
  env_var="PROVIDE_LOG_ENABLED_EMOJI_SETS",
63
63
  description="Comma-separated list of emoji sets to enable",
64
64
  )
65
- custom_emoji_sets: list[EmojiSetConfig] = field(
65
+ custom_emoji_sets: list[EventSet] = field(
66
66
  factory=lambda: [],
67
67
  env_var="PROVIDE_LOG_CUSTOM_EMOJI_SETS",
68
68
  description="JSON array of custom emoji set configurations",
69
69
  )
70
- user_defined_emoji_sets: list[EmojiSet] = field(
70
+ user_defined_emoji_sets: list[EventMapping] = field(
71
71
  factory=lambda: [],
72
72
  env_var="PROVIDE_LOG_USER_DEFINED_EMOJI_SETS",
73
73
  description="JSON array of user-defined emoji sets",
@@ -342,7 +342,7 @@ class LoggingConfig(BaseConfig):
342
342
  parsed = json.loads(custom_sets)
343
343
  if isinstance(parsed, list):
344
344
  config_dict["custom_emoji_sets"] = [
345
- EmojiSetConfig(**item) if isinstance(item, dict) else item
345
+ EventSet(**item) if isinstance(item, dict) else item
346
346
  for item in parsed
347
347
  ]
348
348
  except json.JSONDecodeError as e:
@@ -369,7 +369,7 @@ class LoggingConfig(BaseConfig):
369
369
  parsed = json.loads(user_sets)
370
370
  if isinstance(parsed, list):
371
371
  config_dict["user_defined_emoji_sets"] = [
372
- EmojiSet(**item) if isinstance(item, dict) else item
372
+ EventMapping(**item) if isinstance(item, dict) else item
373
373
  for item in parsed
374
374
  ]
375
375
  except json.JSONDecodeError as e:
@@ -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:
@@ -131,6 +131,7 @@ _EMOJI_CACHE_SIZE_LIMIT: int = 1000
131
131
 
132
132
 
133
133
  def _compute_emoji_for_logger_name(logger_name: str) -> str:
134
+ # Original keyword-based system
134
135
  for keyword in _SORTED_LOGGER_NAME_EMOJI_KEYWORDS:
135
136
  if keyword == "default":
136
137
  continue
@@ -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(name: str | None = None) -> Any:
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 _config_create_emoji_processors(
75
- logging_config: LoggingConfig, resolved_emoji_config: "ResolvedEmojiConfig"
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
- # FIX: Create the processor as a closure with the resolved config
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
- # This inner function now has access to the resolved config from its closure scope
88
- from provide.foundation.logger.emoji.matrix import (
89
- PRIMARY_EMOJI,
90
- SECONDARY_EMOJI,
91
- TERTIARY_EMOJI,
92
- )
93
-
94
- final_das_prefix_parts: list[str] = []
95
-
96
- if resolved_field_definitions: # New Layered Emoji System is active
97
- for field_def in resolved_field_definitions:
98
- value_from_event = event_dict.get(field_def.log_key)
99
- if value_from_event is not None and field_def.emoji_set_name:
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, resolved_emoji_config: "ResolvedEmojiConfig"
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(_config_create_emoji_processors(log_cfg, resolved_emoji_config))
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
- create_core_setup_logger,
182
+ create_foundation_internal_logger,
247
183
  )
248
184
 
249
- setup_logger = create_core_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 internal_setup
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 the Foundation log level from LoggingConfig, checking only once."""
34
+ """Get Foundation log level for setup phase, safely."""
37
35
  global _FOUNDATION_LOG_LEVEL
38
36
  if _FOUNDATION_LOG_LEVEL is None:
39
- # Use the proper config system to get the Foundation setup log level
40
- logging_config = LoggingConfig.from_env(strict=False)
41
- level_str = logging_config.foundation_setup_log_level.upper()
42
- _FOUNDATION_LOG_LEVEL = getattr(
43
- stdlib_logging,
44
- level_str,
45
- stdlib_logging.INFO, # Default fallback
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 create_core_setup_logger(globally_disabled: bool = False) -> Any:
51
- """Create a structlog logger for core setup messages."""
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(
@@ -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 = create_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, resolved_emoji_config, get_log_stream()
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, resolved_emoji_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, resolved_emoji_config, log_stream
65
+ config, log_stream
73
66
  )
74
67
  apply_structlog_configuration(processors, log_stream)
75
68