ouroboros-ai 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ouroboros-ai might be problematic. Click here for more details.
- ouroboros/__init__.py +15 -0
- ouroboros/__main__.py +9 -0
- ouroboros/bigbang/__init__.py +39 -0
- ouroboros/bigbang/ambiguity.py +464 -0
- ouroboros/bigbang/interview.py +530 -0
- ouroboros/bigbang/seed_generator.py +610 -0
- ouroboros/cli/__init__.py +9 -0
- ouroboros/cli/commands/__init__.py +7 -0
- ouroboros/cli/commands/config.py +79 -0
- ouroboros/cli/commands/init.py +425 -0
- ouroboros/cli/commands/run.py +201 -0
- ouroboros/cli/commands/status.py +85 -0
- ouroboros/cli/formatters/__init__.py +31 -0
- ouroboros/cli/formatters/panels.py +157 -0
- ouroboros/cli/formatters/progress.py +112 -0
- ouroboros/cli/formatters/tables.py +166 -0
- ouroboros/cli/main.py +60 -0
- ouroboros/config/__init__.py +81 -0
- ouroboros/config/loader.py +292 -0
- ouroboros/config/models.py +332 -0
- ouroboros/core/__init__.py +62 -0
- ouroboros/core/ac_tree.py +401 -0
- ouroboros/core/context.py +472 -0
- ouroboros/core/errors.py +246 -0
- ouroboros/core/seed.py +212 -0
- ouroboros/core/types.py +205 -0
- ouroboros/evaluation/__init__.py +110 -0
- ouroboros/evaluation/consensus.py +350 -0
- ouroboros/evaluation/mechanical.py +351 -0
- ouroboros/evaluation/models.py +235 -0
- ouroboros/evaluation/pipeline.py +286 -0
- ouroboros/evaluation/semantic.py +302 -0
- ouroboros/evaluation/trigger.py +278 -0
- ouroboros/events/__init__.py +5 -0
- ouroboros/events/base.py +80 -0
- ouroboros/events/decomposition.py +153 -0
- ouroboros/events/evaluation.py +248 -0
- ouroboros/execution/__init__.py +44 -0
- ouroboros/execution/atomicity.py +451 -0
- ouroboros/execution/decomposition.py +481 -0
- ouroboros/execution/double_diamond.py +1386 -0
- ouroboros/execution/subagent.py +275 -0
- ouroboros/observability/__init__.py +63 -0
- ouroboros/observability/drift.py +383 -0
- ouroboros/observability/logging.py +504 -0
- ouroboros/observability/retrospective.py +338 -0
- ouroboros/orchestrator/__init__.py +78 -0
- ouroboros/orchestrator/adapter.py +391 -0
- ouroboros/orchestrator/events.py +278 -0
- ouroboros/orchestrator/runner.py +597 -0
- ouroboros/orchestrator/session.py +486 -0
- ouroboros/persistence/__init__.py +23 -0
- ouroboros/persistence/checkpoint.py +511 -0
- ouroboros/persistence/event_store.py +183 -0
- ouroboros/persistence/migrations/__init__.py +1 -0
- ouroboros/persistence/migrations/runner.py +100 -0
- ouroboros/persistence/migrations/scripts/001_initial.sql +20 -0
- ouroboros/persistence/schema.py +56 -0
- ouroboros/persistence/uow.py +230 -0
- ouroboros/providers/__init__.py +28 -0
- ouroboros/providers/base.py +133 -0
- ouroboros/providers/claude_code_adapter.py +212 -0
- ouroboros/providers/litellm_adapter.py +316 -0
- ouroboros/py.typed +0 -0
- ouroboros/resilience/__init__.py +67 -0
- ouroboros/resilience/lateral.py +595 -0
- ouroboros/resilience/stagnation.py +727 -0
- ouroboros/routing/__init__.py +60 -0
- ouroboros/routing/complexity.py +272 -0
- ouroboros/routing/downgrade.py +664 -0
- ouroboros/routing/escalation.py +340 -0
- ouroboros/routing/router.py +204 -0
- ouroboros/routing/tiers.py +247 -0
- ouroboros/secondary/__init__.py +40 -0
- ouroboros/secondary/scheduler.py +467 -0
- ouroboros/secondary/todo_registry.py +483 -0
- ouroboros_ai-0.1.0.dist-info/METADATA +607 -0
- ouroboros_ai-0.1.0.dist-info/RECORD +81 -0
- ouroboros_ai-0.1.0.dist-info/WHEEL +4 -0
- ouroboros_ai-0.1.0.dist-info/entry_points.txt +2 -0
- ouroboros_ai-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""Structured logging configuration for Ouroboros.
|
|
2
|
+
|
|
3
|
+
This module configures structlog with standard processors for structured
|
|
4
|
+
logging throughout the application. It supports both development mode
|
|
5
|
+
(human-readable console output) and production mode (JSON output).
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- ISO 8601 timestamps
|
|
9
|
+
- Log level in all entries
|
|
10
|
+
- contextvars integration for cross-async context propagation
|
|
11
|
+
- Daily log rotation with configurable retention
|
|
12
|
+
- Mode selection via environment variable or config
|
|
13
|
+
|
|
14
|
+
Standard log keys:
|
|
15
|
+
- seed_id: Seed identifier
|
|
16
|
+
- ac_id: Atomic Capability identifier
|
|
17
|
+
- depth: Current depth in execution tree
|
|
18
|
+
- iteration: Iteration number
|
|
19
|
+
- tier: PAL routing tier
|
|
20
|
+
|
|
21
|
+
Event naming convention:
|
|
22
|
+
- Use dot.notation (e.g., "ac.execution.started", "ontology.concept.added")
|
|
23
|
+
- Format: domain.entity.verb_past_tense
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
from ouroboros.observability import configure_logging, get_logger, bind_context
|
|
27
|
+
|
|
28
|
+
# Configure at application startup
|
|
29
|
+
configure_logging(LoggingConfig(mode=LogMode.DEV))
|
|
30
|
+
|
|
31
|
+
# Get a logger
|
|
32
|
+
log = get_logger()
|
|
33
|
+
|
|
34
|
+
# Bind context for async propagation
|
|
35
|
+
bind_context(seed_id="seed_123", ac_id="ac_456")
|
|
36
|
+
|
|
37
|
+
# Log with standard keys
|
|
38
|
+
log.info("ac.execution.started", depth=2, iteration=1, tier="mini")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
from enum import Enum
|
|
44
|
+
import logging
|
|
45
|
+
from logging.handlers import TimedRotatingFileHandler
|
|
46
|
+
import os
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
from typing import Any
|
|
49
|
+
|
|
50
|
+
from pydantic import BaseModel, Field
|
|
51
|
+
import structlog
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class LogMode(str, Enum):
|
|
55
|
+
"""Logging output mode."""
|
|
56
|
+
|
|
57
|
+
DEV = "dev"
|
|
58
|
+
PROD = "prod"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class LoggingConfig(BaseModel):
|
|
62
|
+
"""Configuration for structured logging.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
mode: Output mode (dev for human-readable, prod for JSON).
|
|
66
|
+
log_level: Minimum log level to output.
|
|
67
|
+
log_dir: Directory for log files. Defaults to ~/.ouroboros/logs/.
|
|
68
|
+
max_log_days: Number of days to retain log files. Defaults to 7.
|
|
69
|
+
enable_file_logging: Whether to write logs to files.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
mode: LogMode = Field(default=LogMode.DEV)
|
|
73
|
+
log_level: str = Field(default="INFO")
|
|
74
|
+
log_dir: Path = Field(default_factory=lambda: Path.home() / ".ouroboros" / "logs")
|
|
75
|
+
max_log_days: int = Field(default=7, ge=1, le=365)
|
|
76
|
+
enable_file_logging: bool = Field(default=True)
|
|
77
|
+
|
|
78
|
+
model_config = {"frozen": True}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Module-level state for tracking configuration
|
|
82
|
+
_configured: bool = False
|
|
83
|
+
_current_config: LoggingConfig | None = None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _get_mode_from_env() -> LogMode:
|
|
87
|
+
"""Get logging mode from environment variable.
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
LogMode based on OUROBOROS_LOG_MODE environment variable.
|
|
91
|
+
Defaults to DEV if not set or invalid.
|
|
92
|
+
"""
|
|
93
|
+
env_mode = os.environ.get("OUROBOROS_LOG_MODE", "dev").lower()
|
|
94
|
+
if env_mode == "prod":
|
|
95
|
+
return LogMode.PROD
|
|
96
|
+
return LogMode.DEV
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _get_log_level(level_str: str) -> int:
|
|
100
|
+
"""Convert log level string to logging constant.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
level_str: Log level as string (e.g., "INFO", "DEBUG").
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Logging constant (e.g., logging.INFO).
|
|
107
|
+
"""
|
|
108
|
+
level_map = {
|
|
109
|
+
"DEBUG": logging.DEBUG,
|
|
110
|
+
"INFO": logging.INFO,
|
|
111
|
+
"WARNING": logging.WARNING,
|
|
112
|
+
"WARN": logging.WARNING,
|
|
113
|
+
"ERROR": logging.ERROR,
|
|
114
|
+
"CRITICAL": logging.CRITICAL,
|
|
115
|
+
}
|
|
116
|
+
return level_map.get(level_str.upper(), logging.INFO)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _setup_file_handler(config: LoggingConfig) -> TimedRotatingFileHandler | None:
|
|
120
|
+
"""Set up rotating file handler for log output.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
config: Logging configuration.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Configured TimedRotatingFileHandler or None if file logging disabled.
|
|
127
|
+
"""
|
|
128
|
+
if not config.enable_file_logging:
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
# Ensure log directory exists
|
|
132
|
+
config.log_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
|
|
134
|
+
# Create log file path with date
|
|
135
|
+
log_file = config.log_dir / "ouroboros.log"
|
|
136
|
+
|
|
137
|
+
# Configure rotating file handler
|
|
138
|
+
# - when="midnight" for daily rotation
|
|
139
|
+
# - backupCount controls retention
|
|
140
|
+
handler = TimedRotatingFileHandler(
|
|
141
|
+
filename=str(log_file),
|
|
142
|
+
when="midnight",
|
|
143
|
+
interval=1,
|
|
144
|
+
backupCount=config.max_log_days,
|
|
145
|
+
encoding="utf-8",
|
|
146
|
+
utc=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Set formatter based on mode
|
|
150
|
+
if config.mode == LogMode.PROD:
|
|
151
|
+
# JSON format for production - structlog will handle formatting
|
|
152
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
153
|
+
else:
|
|
154
|
+
# Simple format for dev - structlog console renderer handles formatting
|
|
155
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
156
|
+
|
|
157
|
+
handler.setLevel(_get_log_level(config.log_level))
|
|
158
|
+
|
|
159
|
+
return handler
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _get_shared_processors() -> list[Any]:
|
|
163
|
+
"""Get the shared processor chain for structlog.
|
|
164
|
+
|
|
165
|
+
These processors are used for preparing event dicts before final rendering.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
List of structlog processors.
|
|
169
|
+
"""
|
|
170
|
+
return [
|
|
171
|
+
# Merge contextvars into event dict (for cross-async context)
|
|
172
|
+
structlog.contextvars.merge_contextvars,
|
|
173
|
+
# Add log level to all entries
|
|
174
|
+
structlog.processors.add_log_level,
|
|
175
|
+
# Add timestamp in ISO 8601 format
|
|
176
|
+
structlog.processors.TimeStamper(fmt="iso", utc=True),
|
|
177
|
+
# Add stack info for exceptions
|
|
178
|
+
structlog.processors.StackInfoRenderer(),
|
|
179
|
+
# Add caller info (file, line, function) - useful for debugging
|
|
180
|
+
structlog.processors.CallsiteParameterAdder(
|
|
181
|
+
parameters=[
|
|
182
|
+
structlog.processors.CallsiteParameter.FILENAME,
|
|
183
|
+
structlog.processors.CallsiteParameter.LINENO,
|
|
184
|
+
]
|
|
185
|
+
),
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _get_console_processors(mode: LogMode) -> list[Any]:
|
|
190
|
+
"""Get the processor chain for console output.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
mode: Logging output mode.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of structlog processors including renderer.
|
|
197
|
+
"""
|
|
198
|
+
processors = _get_shared_processors()
|
|
199
|
+
|
|
200
|
+
# Format exceptions nicely for console
|
|
201
|
+
processors.append(structlog.processors.format_exc_info)
|
|
202
|
+
|
|
203
|
+
# Add final renderer based on mode
|
|
204
|
+
if mode == LogMode.DEV:
|
|
205
|
+
# Human-readable console output for development
|
|
206
|
+
processors.append(structlog.dev.ConsoleRenderer(colors=True))
|
|
207
|
+
else:
|
|
208
|
+
# JSON output for production
|
|
209
|
+
processors.append(structlog.processors.JSONRenderer())
|
|
210
|
+
|
|
211
|
+
return processors
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _get_file_processors() -> list[Any]:
|
|
215
|
+
"""Get the processor chain for file output.
|
|
216
|
+
|
|
217
|
+
File output always uses JSON format for easy parsing.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of structlog processors for file logging.
|
|
221
|
+
"""
|
|
222
|
+
processors = _get_shared_processors()
|
|
223
|
+
|
|
224
|
+
# Format exceptions for file
|
|
225
|
+
processors.append(structlog.processors.format_exc_info)
|
|
226
|
+
|
|
227
|
+
# Always use JSON for file output (for log aggregation tools)
|
|
228
|
+
processors.append(structlog.processors.JSONRenderer())
|
|
229
|
+
|
|
230
|
+
return processors
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class _FileWritingPrintLogger:
|
|
234
|
+
"""Print logger that also writes to a file handler.
|
|
235
|
+
|
|
236
|
+
This logger writes to stdout (for console output) and optionally
|
|
237
|
+
to a file handler for persistent logging. Supports proper log levels.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
def __init__(self, file_handler: TimedRotatingFileHandler | None = None) -> None:
|
|
241
|
+
"""Initialize the file-writing print logger.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
file_handler: Optional file handler for persistent logging.
|
|
245
|
+
"""
|
|
246
|
+
self._file_handler = file_handler
|
|
247
|
+
|
|
248
|
+
def _log(self, message: str, level: int = logging.INFO) -> None:
|
|
249
|
+
"""Log a message to console and file with proper level.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
message: The message to log.
|
|
253
|
+
level: The log level (e.g., logging.DEBUG, logging.INFO).
|
|
254
|
+
"""
|
|
255
|
+
# Print to console
|
|
256
|
+
print(message)
|
|
257
|
+
|
|
258
|
+
# Write to file if handler exists
|
|
259
|
+
if self._file_handler:
|
|
260
|
+
record = logging.LogRecord(
|
|
261
|
+
name="ouroboros",
|
|
262
|
+
level=level,
|
|
263
|
+
pathname="",
|
|
264
|
+
lineno=0,
|
|
265
|
+
msg=message,
|
|
266
|
+
args=(),
|
|
267
|
+
exc_info=None,
|
|
268
|
+
)
|
|
269
|
+
self._file_handler.emit(record)
|
|
270
|
+
|
|
271
|
+
def msg(self, message: str) -> None:
|
|
272
|
+
"""Log a message to console and file (default INFO level)."""
|
|
273
|
+
self._log(message, logging.INFO)
|
|
274
|
+
|
|
275
|
+
# Alias for structlog compatibility
|
|
276
|
+
def __call__(self, message: str) -> None:
|
|
277
|
+
"""Log a message (alias for msg)."""
|
|
278
|
+
self.msg(message)
|
|
279
|
+
|
|
280
|
+
# Level-specific methods
|
|
281
|
+
def debug(self, message: str) -> None:
|
|
282
|
+
"""Log a debug message."""
|
|
283
|
+
self._log(message, logging.DEBUG)
|
|
284
|
+
|
|
285
|
+
def info(self, message: str) -> None:
|
|
286
|
+
"""Log an info message."""
|
|
287
|
+
self._log(message, logging.INFO)
|
|
288
|
+
|
|
289
|
+
def warning(self, message: str) -> None:
|
|
290
|
+
"""Log a warning message."""
|
|
291
|
+
self._log(message, logging.WARNING)
|
|
292
|
+
|
|
293
|
+
def warn(self, message: str) -> None:
|
|
294
|
+
"""Log a warning message (alias for warning)."""
|
|
295
|
+
self._log(message, logging.WARNING)
|
|
296
|
+
|
|
297
|
+
def error(self, message: str) -> None:
|
|
298
|
+
"""Log an error message."""
|
|
299
|
+
self._log(message, logging.ERROR)
|
|
300
|
+
|
|
301
|
+
def critical(self, message: str) -> None:
|
|
302
|
+
"""Log a critical message."""
|
|
303
|
+
self._log(message, logging.CRITICAL)
|
|
304
|
+
|
|
305
|
+
def fatal(self, message: str) -> None:
|
|
306
|
+
"""Log a fatal message (alias for critical)."""
|
|
307
|
+
self._log(message, logging.CRITICAL)
|
|
308
|
+
|
|
309
|
+
def exception(self, message: str) -> None:
|
|
310
|
+
"""Log an exception message (ERROR level)."""
|
|
311
|
+
self._log(message, logging.ERROR)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class _FileWritingPrintLoggerFactory:
|
|
315
|
+
"""Factory for creating file-writing print loggers."""
|
|
316
|
+
|
|
317
|
+
def __init__(self, file_handler: TimedRotatingFileHandler | None = None) -> None:
|
|
318
|
+
"""Initialize the factory.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
file_handler: Optional file handler for persistent logging.
|
|
322
|
+
"""
|
|
323
|
+
self._file_handler = file_handler
|
|
324
|
+
|
|
325
|
+
def __call__(self, *_args: Any) -> _FileWritingPrintLogger:
|
|
326
|
+
"""Create a new logger instance.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
*_args: Ignored arguments (structlog may pass logger name).
|
|
330
|
+
"""
|
|
331
|
+
return _FileWritingPrintLogger(self._file_handler)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def configure_logging(config: LoggingConfig | None = None) -> None:
|
|
335
|
+
"""Configure structlog for the application.
|
|
336
|
+
|
|
337
|
+
This should be called once at application startup. It configures:
|
|
338
|
+
- structlog processors (log level, timestamp, stack info)
|
|
339
|
+
- Output renderer (console for dev, JSON for prod)
|
|
340
|
+
- File handler with daily rotation (if enabled)
|
|
341
|
+
- contextvars integration for cross-async context
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
config: Logging configuration. If None, uses defaults with
|
|
345
|
+
mode from OUROBOROS_LOG_MODE environment variable.
|
|
346
|
+
|
|
347
|
+
Example:
|
|
348
|
+
# Use environment variable for mode
|
|
349
|
+
configure_logging()
|
|
350
|
+
|
|
351
|
+
# Or specify config explicitly
|
|
352
|
+
configure_logging(LoggingConfig(mode=LogMode.PROD, max_log_days=14))
|
|
353
|
+
"""
|
|
354
|
+
global _configured, _current_config
|
|
355
|
+
|
|
356
|
+
if config is None:
|
|
357
|
+
config = LoggingConfig(mode=_get_mode_from_env())
|
|
358
|
+
|
|
359
|
+
_current_config = config
|
|
360
|
+
|
|
361
|
+
# Set up standard library logging
|
|
362
|
+
log_level = _get_log_level(config.log_level)
|
|
363
|
+
|
|
364
|
+
# Configure root logger
|
|
365
|
+
root_logger = logging.getLogger()
|
|
366
|
+
root_logger.setLevel(log_level)
|
|
367
|
+
|
|
368
|
+
# Remove existing handlers to avoid duplicates on reconfigure
|
|
369
|
+
for handler in root_logger.handlers[:]:
|
|
370
|
+
root_logger.removeHandler(handler)
|
|
371
|
+
|
|
372
|
+
# Add file handler if enabled
|
|
373
|
+
file_handler = _setup_file_handler(config)
|
|
374
|
+
if file_handler:
|
|
375
|
+
root_logger.addHandler(file_handler)
|
|
376
|
+
|
|
377
|
+
# Get processors for console output
|
|
378
|
+
processors = _get_console_processors(config.mode)
|
|
379
|
+
|
|
380
|
+
# Create logger factory that writes to both console and file
|
|
381
|
+
logger_factory = _FileWritingPrintLoggerFactory(file_handler)
|
|
382
|
+
|
|
383
|
+
# Configure structlog
|
|
384
|
+
structlog.configure(
|
|
385
|
+
processors=processors,
|
|
386
|
+
wrapper_class=structlog.make_filtering_bound_logger(log_level),
|
|
387
|
+
context_class=dict,
|
|
388
|
+
logger_factory=logger_factory,
|
|
389
|
+
cache_logger_on_first_use=True,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
_configured = True
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def get_logger(name: str | None = None) -> structlog.stdlib.BoundLogger:
|
|
396
|
+
"""Get a bound logger instance.
|
|
397
|
+
|
|
398
|
+
If logging has not been configured, this will configure it with defaults.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
name: Optional logger name. If not provided, uses the calling module.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
A bound structlog logger.
|
|
405
|
+
|
|
406
|
+
Example:
|
|
407
|
+
log = get_logger()
|
|
408
|
+
log.info("ac.execution.started", seed_id="seed_123", ac_id="ac_456")
|
|
409
|
+
"""
|
|
410
|
+
global _configured
|
|
411
|
+
|
|
412
|
+
if not _configured:
|
|
413
|
+
configure_logging()
|
|
414
|
+
|
|
415
|
+
return structlog.get_logger(name)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def bind_context(**kwargs: Any) -> None:
|
|
419
|
+
"""Bind context variables for cross-async propagation.
|
|
420
|
+
|
|
421
|
+
Context bound here will be included in all subsequent log entries
|
|
422
|
+
within the same async context. This is useful for propagating
|
|
423
|
+
request-scoped data like seed_id, ac_id, etc.
|
|
424
|
+
|
|
425
|
+
Standard keys:
|
|
426
|
+
- seed_id: Seed identifier
|
|
427
|
+
- ac_id: Atomic Capability identifier
|
|
428
|
+
- depth: Current depth in execution tree
|
|
429
|
+
- iteration: Iteration number
|
|
430
|
+
- tier: PAL routing tier
|
|
431
|
+
|
|
432
|
+
IMPORTANT: Never bind sensitive data (API keys, credentials).
|
|
433
|
+
|
|
434
|
+
Args:
|
|
435
|
+
**kwargs: Key-value pairs to bind to the logging context.
|
|
436
|
+
|
|
437
|
+
Example:
|
|
438
|
+
bind_context(seed_id="seed_123", ac_id="ac_456", depth=2)
|
|
439
|
+
|
|
440
|
+
# All subsequent logs will include these keys
|
|
441
|
+
log.info("ac.execution.started") # Will include seed_id, ac_id, depth
|
|
442
|
+
"""
|
|
443
|
+
structlog.contextvars.bind_contextvars(**kwargs)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def unbind_context(*keys: str) -> None:
|
|
447
|
+
"""Remove context variables from the logging context.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
*keys: Keys to remove from the context.
|
|
451
|
+
|
|
452
|
+
Example:
|
|
453
|
+
unbind_context("ac_id", "depth")
|
|
454
|
+
"""
|
|
455
|
+
structlog.contextvars.unbind_contextvars(*keys)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def clear_context() -> None:
|
|
459
|
+
"""Clear all bound context variables.
|
|
460
|
+
|
|
461
|
+
This should be called at the end of a request or execution scope
|
|
462
|
+
to prevent context leakage.
|
|
463
|
+
|
|
464
|
+
Example:
|
|
465
|
+
try:
|
|
466
|
+
bind_context(seed_id="seed_123")
|
|
467
|
+
# ... do work ...
|
|
468
|
+
finally:
|
|
469
|
+
clear_context()
|
|
470
|
+
"""
|
|
471
|
+
structlog.contextvars.clear_contextvars()
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def get_current_config() -> LoggingConfig | None:
|
|
475
|
+
"""Get the current logging configuration.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
The current LoggingConfig or None if not configured.
|
|
479
|
+
"""
|
|
480
|
+
return _current_config
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def is_configured() -> bool:
|
|
484
|
+
"""Check if logging has been configured.
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
True if configure_logging has been called.
|
|
488
|
+
"""
|
|
489
|
+
return _configured
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def reset_logging() -> None:
|
|
493
|
+
"""Reset logging configuration state.
|
|
494
|
+
|
|
495
|
+
This is primarily for testing purposes. It resets the module state
|
|
496
|
+
but does not reconfigure the loggers.
|
|
497
|
+
"""
|
|
498
|
+
global _configured, _current_config
|
|
499
|
+
_configured = False
|
|
500
|
+
_current_config = None
|
|
501
|
+
# Clear any bound context
|
|
502
|
+
structlog.contextvars.clear_contextvars()
|
|
503
|
+
# Reset structlog configuration
|
|
504
|
+
structlog.reset_defaults()
|