Base code

This commit is contained in:
Kunthawat Greethong
2026-01-08 22:39:53 +07:00
parent 697115c61a
commit c35fa52117
2169 changed files with 626670 additions and 0 deletions

View File

@@ -0,0 +1,332 @@
"""
Intelligent Logging Middleware for AI SEO Tools
Provides structured logging, file saving, and monitoring capabilities
for all SEO tool operations with performance tracking.
"""
import json
import asyncio
import aiofiles
from datetime import datetime
from functools import wraps
from typing import Dict, Any, Callable
from pathlib import Path
from loguru import logger
import os
import time
# Logging configuration
LOG_BASE_DIR = "logs"
os.makedirs(LOG_BASE_DIR, exist_ok=True)
# Ensure subdirectories exist
for subdir in ["seo_tools", "api_calls", "errors", "performance"]:
os.makedirs(f"{LOG_BASE_DIR}/{subdir}", exist_ok=True)
class PerformanceLogger:
"""Performance monitoring and logging for SEO operations"""
def __init__(self):
self.performance_data = {}
async def log_performance(self, operation: str, duration: float, metadata: Dict[str, Any] = None):
"""Log performance metrics for operations"""
performance_log = {
"operation": operation,
"duration_seconds": duration,
"timestamp": datetime.utcnow().isoformat(),
"metadata": metadata or {}
}
await save_to_file(f"{LOG_BASE_DIR}/performance/metrics.jsonl", performance_log)
# Log performance warnings for slow operations
if duration > 30: # More than 30 seconds
logger.warning(f"Slow operation detected: {operation} took {duration:.2f} seconds")
elif duration > 10: # More than 10 seconds
logger.info(f"Operation {operation} took {duration:.2f} seconds")
performance_logger = PerformanceLogger()
async def save_to_file(filepath: str, data: Dict[str, Any]) -> None:
"""
Asynchronously save structured data to a JSONL file
Args:
filepath: Path to the log file
data: Dictionary data to save
"""
try:
# Ensure directory exists
Path(filepath).parent.mkdir(parents=True, exist_ok=True)
# Convert data to JSON string
json_line = json.dumps(data, default=str) + "\n"
# Write asynchronously
async with aiofiles.open(filepath, "a", encoding="utf-8") as file:
await file.write(json_line)
except Exception as e:
logger.error(f"Failed to save log to {filepath}: {e}")
def log_api_call(func: Callable) -> Callable:
"""
Decorator for logging API calls with performance tracking
Automatically logs request/response data, timing, and errors
for SEO tool endpoints.
"""
@wraps(func)
async def wrapper(*args, **kwargs):
start_time = time.time()
operation_name = func.__name__
# Extract request data
request_data = {}
for arg in args:
if hasattr(arg, 'dict'): # Pydantic model
request_data.update(arg.dict())
# Log API call start
call_log = {
"operation": operation_name,
"timestamp": datetime.utcnow().isoformat(),
"request_data": request_data,
"status": "started"
}
logger.info(f"API Call Started: {operation_name}")
try:
# Execute the function
result = await func(*args, **kwargs)
execution_time = time.time() - start_time
# Log successful completion
call_log.update({
"status": "completed",
"execution_time": execution_time,
"success": getattr(result, 'success', True),
"completion_timestamp": datetime.utcnow().isoformat()
})
await save_to_file(f"{LOG_BASE_DIR}/api_calls/successful.jsonl", call_log)
await performance_logger.log_performance(operation_name, execution_time, request_data)
logger.info(f"API Call Completed: {operation_name} in {execution_time:.2f}s")
return result
except Exception as e:
execution_time = time.time() - start_time
# Log error
error_log = call_log.copy()
error_log.update({
"status": "failed",
"execution_time": execution_time,
"error_type": type(e).__name__,
"error_message": str(e),
"completion_timestamp": datetime.utcnow().isoformat()
})
await save_to_file(f"{LOG_BASE_DIR}/api_calls/failed.jsonl", error_log)
logger.error(f"API Call Failed: {operation_name} after {execution_time:.2f}s - {e}")
# Re-raise the exception
raise
return wrapper
class SEOToolsLogger:
"""Centralized logger for SEO tools with intelligent categorization"""
@staticmethod
async def log_tool_usage(tool_name: str, input_data: Dict[str, Any],
output_data: Dict[str, Any], success: bool = True):
"""Log SEO tool usage with input/output tracking"""
usage_log = {
"tool": tool_name,
"timestamp": datetime.utcnow().isoformat(),
"input_data": input_data,
"output_data": output_data,
"success": success,
"input_size": len(str(input_data)),
"output_size": len(str(output_data))
}
await save_to_file(f"{LOG_BASE_DIR}/seo_tools/usage.jsonl", usage_log)
@staticmethod
async def log_ai_analysis(tool_name: str, prompt: str, response: str,
model_used: str, tokens_used: int = None):
"""Log AI analysis operations with token tracking"""
ai_log = {
"tool": tool_name,
"timestamp": datetime.utcnow().isoformat(),
"model": model_used,
"prompt_length": len(prompt),
"response_length": len(response),
"tokens_used": tokens_used,
"prompt_preview": prompt[:200] + "..." if len(prompt) > 200 else prompt,
"response_preview": response[:200] + "..." if len(response) > 200 else response
}
await save_to_file(f"{LOG_BASE_DIR}/seo_tools/ai_analysis.jsonl", ai_log)
@staticmethod
async def log_external_api_call(api_name: str, endpoint: str, response_code: int,
response_time: float, request_data: Dict[str, Any] = None):
"""Log external API calls (PageSpeed, etc.)"""
api_log = {
"api": api_name,
"endpoint": endpoint,
"response_code": response_code,
"response_time": response_time,
"timestamp": datetime.utcnow().isoformat(),
"request_data": request_data or {},
"success": 200 <= response_code < 300
}
await save_to_file(f"{LOG_BASE_DIR}/seo_tools/external_apis.jsonl", api_log)
@staticmethod
async def log_crawling_operation(url: str, pages_crawled: int, errors_found: int,
crawl_depth: int, duration: float):
"""Log web crawling operations"""
crawl_log = {
"url": url,
"pages_crawled": pages_crawled,
"errors_found": errors_found,
"crawl_depth": crawl_depth,
"duration": duration,
"timestamp": datetime.utcnow().isoformat(),
"pages_per_second": pages_crawled / duration if duration > 0 else 0
}
await save_to_file(f"{LOG_BASE_DIR}/seo_tools/crawling.jsonl", crawl_log)
class LogAnalyzer:
"""Analyze logs to provide insights and monitoring"""
@staticmethod
async def get_performance_summary(hours: int = 24) -> Dict[str, Any]:
"""Get performance summary for the last N hours"""
try:
performance_file = f"{LOG_BASE_DIR}/performance/metrics.jsonl"
if not os.path.exists(performance_file):
return {"error": "No performance data available"}
# Read recent performance data
cutoff_time = datetime.utcnow().timestamp() - (hours * 3600)
operations = []
async with aiofiles.open(performance_file, "r") as file:
async for line in file:
try:
data = json.loads(line.strip())
log_time = datetime.fromisoformat(data["timestamp"]).timestamp()
if log_time >= cutoff_time:
operations.append(data)
except (json.JSONDecodeError, KeyError):
continue
if not operations:
return {"message": f"No operations in the last {hours} hours"}
# Calculate statistics
durations = [op["duration_seconds"] for op in operations]
operation_counts = {}
for op in operations:
op_name = op["operation"]
operation_counts[op_name] = operation_counts.get(op_name, 0) + 1
return {
"total_operations": len(operations),
"average_duration": sum(durations) / len(durations),
"max_duration": max(durations),
"min_duration": min(durations),
"operations_by_type": operation_counts,
"time_period_hours": hours
}
except Exception as e:
logger.error(f"Error analyzing performance logs: {e}")
return {"error": str(e)}
@staticmethod
async def get_error_summary(hours: int = 24) -> Dict[str, Any]:
"""Get error summary for the last N hours"""
try:
error_file = f"{LOG_BASE_DIR}/seo_tools/errors.jsonl"
if not os.path.exists(error_file):
return {"message": "No errors recorded"}
cutoff_time = datetime.utcnow().timestamp() - (hours * 3600)
errors = []
async with aiofiles.open(error_file, "r") as file:
async for line in file:
try:
data = json.loads(line.strip())
log_time = datetime.fromisoformat(data["timestamp"]).timestamp()
if log_time >= cutoff_time:
errors.append(data)
except (json.JSONDecodeError, KeyError):
continue
if not errors:
return {"message": f"No errors in the last {hours} hours"}
# Analyze errors
error_types = {}
functions_with_errors = {}
for error in errors:
error_type = error.get("error_type", "Unknown")
function = error.get("function", "Unknown")
error_types[error_type] = error_types.get(error_type, 0) + 1
functions_with_errors[function] = functions_with_errors.get(function, 0) + 1
return {
"total_errors": len(errors),
"error_types": error_types,
"functions_with_errors": functions_with_errors,
"recent_errors": errors[-5:], # Last 5 errors
"time_period_hours": hours
}
except Exception as e:
logger.error(f"Error analyzing error logs: {e}")
return {"error": str(e)}
# Initialize global logger instance
seo_logger = SEOToolsLogger()
log_analyzer = LogAnalyzer()
# Configure loguru for structured logging
# Commented out to prevent conflicts with main logging configuration
# logger.add(
# f"{LOG_BASE_DIR}/application.log",
# rotation="1 day",
# retention="30 days",
# level="INFO",
# format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} | {message}",
# serialize=True
# )
# logger.add(
# f"{LOG_BASE_DIR}/errors.log",
# rotation="1 day",
# retention="30 days",
# level="ERROR",
# format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {name}:{function}:{line} | {message}",
# serialize=True
# )
logger.info("Logging middleware initialized successfully")