Fix circular import in __init__.py (#35)

This commit is contained in:
2026-03-14 09:24:31 +00:00
parent 1b116108a6
commit 0f62267806
25 changed files with 517 additions and 137 deletions
+131 -47
View File
@@ -1,61 +1,145 @@
"""Logging configuration for Opus.
"""Structured Logging for Opus Orchestrator.
Structured logging with levels, formats, and handlers.
Provides JSON-formatted logging for production environments.
"""
import json
import logging
import sys
from pathlib import Path
from datetime import datetime
from typing import Any, Optional
from enum import Enum
def setup_logging(
level: str = "INFO",
log_file: str = None,
format: str = "%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
) -> logging.Logger:
"""Setup structured logging for Opus.
class LogLevel(str, Enum):
"""Log levels."""
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
CRITICAL = "CRITICAL"
class StructuredFormatter(logging.Formatter):
"""JSON formatter for structured logging."""
def format(self, record: logging.LogRecord) -> str:
"""Format log record as JSON."""
log_data = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"function": record.funcName,
"line": record.lineno,
}
# Add exception info if present
if record.exc_info:
log_data["exception"] = self.formatException(record.exc_info)
# Add extra fields
if hasattr(record, "extra"):
log_data.update(record.extra)
return json.dumps(log_data)
class OpusLogger:
"""Structured logger for Opus."""
def __init__(self, name: str, level: str = "INFO"):
"""Initialize logger.
Args:
name: Logger name (usually module name)
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
"""
self.logger = logging.getLogger(name)
self.logger.setLevel(getattr(logging, level.upper()))
# Only add handler once
if not self.logger.handlers:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(StructuredFormatter())
self.logger.addHandler(handler)
def debug(self, message: str, **kwargs: Any) -> None:
"""Log debug message."""
self.logger.debug(message, extra={"extra": kwargs} if kwargs else {})
def info(self, message: str, **kwargs: Any) -> None:
"""Log info message."""
self.logger.info(message, extra={"extra": kwargs} if kwargs else {})
def warning(self, message: str, **kwargs: Any) -> None:
"""Log warning message."""
self.logger.warning(message, extra={"extra": kwargs} if kwargs else {})
def error(self, message: str, **kwargs: Any) -> None:
"""Log error message."""
self.logger.error(message, extra={"extra": kwargs} if kwargs else {})
def critical(self, message: str, **kwargs: Any) -> None:
"""Log critical message."""
self.logger.critical(message, extra={"extra": kwargs} if kwargs else {})
def log_request(self, method: str, path: str, status_code: int, duration_ms: float) -> None:
"""Log HTTP request."""
self.info(
"HTTP Request",
method=method,
path=path,
status_code=status_code,
duration_ms=duration_ms,
)
def log_llm_request(self, provider: str, model: str, duration_ms: float, success: bool) -> None:
"""Log LLM API request."""
self.info(
"LLM Request",
provider=provider,
model=model,
duration_ms=duration_ms,
success=success,
)
def log_generation(self, book_type: str, genre: str, word_count: int, duration_s: float) -> None:
"""Log book generation."""
self.info(
"Book Generation",
book_type=book_type,
genre=genre,
word_count=word_count,
duration_s=duration_s,
)
def get_logger(name: str, level: Optional[str] = None) -> OpusLogger:
"""Get a structured logger.
Args:
level: DEBUG, INFO, WARNING, ERROR
log_file: Optional file path
format: Log message format
name: Logger name
level: Optional log level override
Returns:
Configured logger
OpusLogger instance
"""
# Create logger
logger = logging.getLogger("opus")
logger.setLevel(getattr(logging, level.upper()))
# Clear existing handlers
logger.handlers.clear()
# Console handler
console = logging.StreamHandler(sys.stdout)
console.setLevel(getattr(logging, level.upper()))
console.setFormatter(logging.Formatter(format))
logger.addHandler(console)
# File handler (optional)
if log_file:
log_path = Path(log_file)
log_path.parent.mkdir(parents=True, exist_ok=True)
file_handler = logging.FileHandler(log_file)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(logging.Formatter(
"%(asctime)s | %(levelname)-8s | %(name)s:%(lineno)d | %(message)s"
))
logger.addHandler(file_handler)
return logger
import os
env_level = os.environ.get("OPUS_LOG_LEVEL", level or "INFO")
return OpusLogger(name, env_level)
# Default logger
logger = setup_logging()
# Usage in modules:
# from opus_orchestrator.logging import logger
# logger.info("Starting generation")
# logger.error(f"Failed: {e}")
# Convenience function for quick logging
def log(name: str, level: str, message: str, **kwargs: Any) -> None:
"""Quick logging function.
Args:
name: Logger name
level: Log level
message: Log message
**kwargs: Additional context
"""
logger = get_logger(name)
getattr(logger, level.lower())(message, **kwargs)