remove emoji to resolve encoding error

This commit is contained in:
2025-08-24 23:15:52 +09:00
parent 47779ba471
commit 2125598864
7 changed files with 358 additions and 116 deletions

73
CLAUDE.md Normal file
View File

@@ -0,0 +1,73 @@
# CLAUDE.md - 개발 가이드
## 🚨 인코딩 문제 방지 (Windows cp949 오류)
### 증상
```
'cp949' codec can't encode character '\U0001f4ad' in position 0: illegal multibyte sequence
```
### 해결책
**1. Python 파일 상단 필수 코드**
```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
# UTF-8 강제 설정
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['PYTHONUTF8'] = '1'
# Windows UTF-8 설정
if sys.platform.startswith('win'):
import codecs
if hasattr(sys.stdout, 'reconfigure'):
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
else:
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach(), errors='replace')
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach(), errors='replace')
```
**2. 실행 스크립트 (run_utf8.bat)**
```batch
@echo off
chcp 65001 >nul 2>&1
set PYTHONIOENCODING=utf-8
set PYTHONUTF8=1
python -X utf8 main.py
pause
```
**3. 코딩 규칙**
- ❌ 이모지 사용 금지: `🚀✅❌💡📊⚠️`
- ✅ 텍스트 라벨 사용: `[START][SUCCESS][ERROR][TIP][STATS][WARNING]`
- 파일 I/O 시 `encoding='utf-8'` 필수 명시
**4. 긴급 해결**
```bash
chcp 65001
set PYTHONIOENCODING=utf-8
set PYTHONUTF8=1
python -X utf8 your_script.py
```
### 체크리스트
- [ ] Python 파일 상단에 UTF-8 설정 추가
- [ ] 이모지를 텍스트로 교체
- [ ] run_utf8.bat 스크립트 생성
- [ ] Windows에서 테스트 실행
---
## 📝 추가 가이드 작성 예정
- API 연동 가이드
- 로깅 설정 가이드
- 배포 및 운영 가이드
- 에러 처리 가이드
---
*마지막 업데이트: 2024년 8월 24일*

267
main.py
View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Imagen4 MCP Server with Preview Image Support
@@ -10,13 +11,83 @@ Google Imagen 4를 사용한 AI 이미지 생성 MCP 서버
Run from imagen4 root directory
"""
# ==================== CRITICAL: UTF-8 SETUP MUST BE FIRST ====================
# This must be done before ANY other imports to prevent cp949 codec errors
import os
import sys
import locale
# Force UTF-8 environment variables - set immediately
os.environ['PYTHONIOENCODING'] = 'utf-8'
os.environ['PYTHONUTF8'] = '1'
os.environ['LC_ALL'] = 'C.UTF-8'
# Windows-specific UTF-8 setup
if sys.platform.startswith('win'):
# Set console code page to UTF-8
try:
os.system('chcp 65001 >nul 2>&1')
except Exception:
pass
# Force locale to UTF-8
try:
locale.setlocale(locale.LC_ALL, 'C.UTF-8')
except locale.Error:
try:
locale.setlocale(locale.LC_ALL, '')
except locale.Error:
pass
# Reconfigure stdout/stderr with UTF-8 encoding
import codecs
import io
# Method 1: Try reconfigure (Python 3.7+)
if hasattr(sys.stdout, 'reconfigure'):
try:
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
except Exception:
# Method 2: Replace streams with UTF-8 writers
try:
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach(), errors='replace')
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach(), errors='replace')
except Exception:
# Method 3: Create new UTF-8 streams
try:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
except Exception:
# Final fallback: continue with existing streams
pass
else:
# For older Python versions
try:
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.detach(), errors='replace')
sys.stderr = codecs.getwriter('utf-8')(sys.stderr.detach(), errors='replace')
except Exception:
try:
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
except Exception:
pass
# Verify UTF-8 setup
try:
test_unicode = "Test UTF-8: 한글 테스트 ✓"
print(f"[UTF8-TEST] {test_unicode}")
except UnicodeEncodeError as e:
print(f"[UTF8-ERROR] Unicode test failed: {e}")
# ==================== Regular Imports (after UTF-8 setup) ====================
import asyncio
import base64
import json
import random
import logging
import sys
import os
import io
from typing import Dict, Any, List, Optional
from dataclasses import dataclass
@@ -59,86 +130,116 @@ except ImportError as e:
# ==================== Unicode-Safe Logging Setup ====================
class UnicodeStreamHandler(logging.StreamHandler):
"""Custom stream handler that ensures proper Unicode handling"""
class SafeUnicodeHandler(logging.StreamHandler):
"""Ultra-safe Unicode stream handler that prevents all encoding issues"""
def __init__(self, stream=None):
super().__init__(stream)
# Force UTF-8 encoding for Windows compatibility
if hasattr(self.stream, 'reconfigure'):
try:
self.stream.reconfigure(encoding='utf-8', errors='replace')
except:
pass
self.encoding = 'utf-8'
def emit(self, record):
try:
# Format the record
msg = self.format(record)
# Ensure the message is properly encoded as UTF-8
if isinstance(msg, str):
# For Windows, ensure proper UTF-8 handling
if sys.platform.startswith('win'):
msg = msg.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
# Ultra-safe character replacement for Windows cp949 issues
if sys.platform.startswith('win'):
# Replace ALL potentially problematic Unicode characters
emoji_replacements = {
'💭': '[THOUGHT]', '📊': '[STATS]', '⚠️': '[WARNING]', '💡': '[TIP]',
'': '[SUCCESS]', '': '[ERROR]', '🔄': '[RETRY]', '': '[WAIT]',
'🚀': '[START]', '📦': '[RESPONSE]', '💥': '[FAILED]', '🖼️': '[IMAGE]',
'📁': '[FILES]', '⚙️': '[PARAMS]', '🎨': '[GENERATE]', '📏': '[SIZE]',
'🎯': '[SETTINGS]', '⏱️': '[TIME]', '': '[TIMEOUT]', '🎉': '[COMPLETE]',
'🔍': '[DEBUG]', '🚨': '[CIRCUIT]', '📊': '[STATUS]'
}
for emoji, replacement in emoji_replacements.items():
msg = msg.replace(emoji, replacement)
# Additional safety: encode/decode to remove any remaining problematic characters
msg = msg.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
# Final safety: ASCII-safe fallback for any remaining issues
try:
msg.encode('cp949')
except UnicodeEncodeError:
# If still can't encode to cp949, make it ASCII-safe
msg = msg.encode('ascii', errors='replace').decode('ascii')
# Write the message
stream = self.stream
# Write with explicit UTF-8 encoding
if hasattr(stream, 'buffer'):
stream.buffer.write((msg + self.terminator).encode('utf-8', errors='replace'))
stream.buffer.flush()
else:
stream.write(msg + self.terminator)
if hasattr(stream, 'flush'):
stream.flush()
terminator = getattr(self, 'terminator', '\n')
# Try multiple methods to write safely
try:
if hasattr(stream, 'buffer'):
# Method 1: Write to buffer with UTF-8 encoding
stream.buffer.write((msg + terminator).encode('utf-8', errors='replace'))
stream.buffer.flush()
else:
# Method 2: Write directly to stream
stream.write(msg + terminator)
if hasattr(stream, 'flush'):
stream.flush()
except (UnicodeEncodeError, UnicodeDecodeError):
# Emergency fallback: write ASCII version only
try:
safe_msg = msg.encode('ascii', errors='replace').decode('ascii')
if hasattr(stream, 'buffer'):
stream.buffer.write((safe_msg + terminator).encode('ascii'))
stream.buffer.flush()
else:
stream.write(safe_msg + terminator)
if hasattr(stream, 'flush'):
stream.flush()
except Exception:
# Absolute last resort: just skip this log message
pass
except Exception:
# If all else fails, call the default error handler
self.handleError(record)
# Custom formatter for proper Unicode handling
class UnicodeFormatter(logging.Formatter):
"""Custom formatter that ensures proper Unicode handling in log messages"""
# Emoji-safe formatter
class SafeFormatter(logging.Formatter):
"""Formatter that ensures safe handling of all Unicode characters"""
def format(self, record):
# Ensure all arguments are properly handled for Unicode
if hasattr(record, 'args') and record.args:
safe_args = []
for arg in record.args:
if isinstance(arg, (dict, list)):
# Convert complex objects to string safely
safe_args.append(str(arg))
elif isinstance(arg, str):
# Ensure string is properly encoded
safe_args.append(arg)
else:
safe_args.append(str(arg))
record.args = tuple(safe_args)
return super().format(record)
try:
# Safely handle record arguments
if hasattr(record, 'args') and record.args:
safe_args = []
for arg in record.args:
if isinstance(arg, str):
# Ensure safe encoding for string arguments
safe_arg = arg.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
safe_args.append(safe_arg)
else:
safe_args.append(str(arg))
record.args = tuple(safe_args)
return super().format(record)
except Exception:
# Fallback: return a safe version of the log message
return f"[LOG-ERROR] Could not format log message safely: {record.levelname}"
# Set up UTF-8 encoding for stdout/stderr on Windows
if sys.platform.startswith('win'):
# Force UTF-8 encoding for Windows console
import locale
try:
# Try to set console to UTF-8
os.system('chcp 65001 >nul 2>&1')
# Set environment variables for Python UTF-8 mode
os.environ['PYTHONIOENCODING'] = 'utf-8'
except:
pass
# Logging configuration with Unicode support
unicode_handler = UnicodeStreamHandler(sys.stderr)
unicode_handler.setFormatter(UnicodeFormatter(
# Set up ultra-safe logging
safe_handler = SafeUnicodeHandler(sys.stderr)
safe_handler.setFormatter(SafeFormatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
))
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)
root_logger.handlers.clear() # Remove default handlers
root_logger.addHandler(unicode_handler)
root_logger.handlers.clear() # Remove any existing handlers
root_logger.addHandler(safe_handler)
logger = logging.getLogger("imagen4-mcp-server")
# Test logging
logger.info("[INIT] Imagen4 MCP Server initializing with Unicode-safe logging")
# ==================== Image Processing Utilities ====================
@@ -230,17 +331,17 @@ class ImageGenerationResult:
lines = [self.message]
if self.success and self.preview_images_b64:
lines.append(f"\n🖼️ Preview Images Generated: {len(self.preview_images_b64)} images (512x512 JPEG)")
lines.append(f"\n[IMAGE] Preview Images Generated: {len(self.preview_images_b64)} images (512x512 JPEG)")
for i, preview_b64 in enumerate(self.preview_images_b64):
lines.append(f"Preview {i+1} (base64 JPEG): {preview_b64[:50]}...({len(preview_b64)} chars)")
if self.saved_files:
lines.append(f"\n📁 Files saved:")
lines.append(f"\n[FILES] Files saved:")
for filepath in self.saved_files:
lines.append(f" - {filepath}")
if self.generation_params:
lines.append(f"\n⚙️ Generation Parameters:")
lines.append(f"\n[PARAMS] Generation Parameters:")
for key, value in self.generation_params.items():
if key == 'prompt' and len(str(value)) > 100:
lines.append(f" - {key}: {str(value)[:100]}...")
@@ -466,7 +567,7 @@ class Imagen4ToolHandlers:
# Create result with preview images
result = ImageGenerationResult(
success=True,
message=f" Images have been successfully regenerated from {json_file_path}",
message=f"[SUCCESS] Images have been successfully regenerated from {json_file_path}",
original_images_count=len(response.images_data),
preview_images_b64=preview_images_b64,
saved_files=saved_files,
@@ -542,12 +643,12 @@ class Imagen4ToolHandlers:
logger.info("Starting image generation with enhanced error handling...")
if error_recovery_available:
# API 상태 건강성 확인
# Check API health status
if not health_monitor.is_healthy():
stats = health_monitor.get_stats()
logger.warning(f"⚠️ API 상태 불안정: 성공률 {stats['success_rate']:.1%}, 연속 에러 {stats['consecutive_errors']}")
logger.warning(f"[WARNING] API status unstable: Success rate {stats['success_rate']:.1%}, Consecutive errors {stats['consecutive_errors']}")
# 회로 차단기와 재시도를 사용한 안전한 API 호출
# Safe API call with circuit breaker and retry
async def safe_generate():
return await circuit_breaker.call(self.client.generate_image, request)
@@ -556,17 +657,17 @@ class Imagen4ToolHandlers:
response = await retry_with_backoff(safe_generate, default_retry_config)
execution_time = time.time() - start_time
# 성공 기록
# Record success
health_monitor.record_success(execution_time)
logger.info(f"Image generation completed successfully. Execution time: {execution_time:.1f}s")
except Exception as e:
# 에러 기록
# Record error
from src.connector.imagen4_client import classify_api_error, APIErrorType
error_type, user_message = classify_api_error(e)
health_monitor.record_error(error_type.value)
# 에러를 응답 객체로 변환
# Convert error to response object
execution_time = time.time() - start_time if 'start_time' in locals() else 0
response = type('ErrorResponse', (), {
'success': False,
@@ -578,7 +679,7 @@ class Imagen4ToolHandlers:
logger.error(f"Image generation failed after retries: {user_message}")
else:
# 기본 에러 처리 (fallback)
# Basic error handling fallback
logger.info("Calling client.generate_image()...")
response = await self.client.generate_image(request)
logger.info(f"Image generation completed. Success: {response.success}")
@@ -589,26 +690,26 @@ class Imagen4ToolHandlers:
logger.error(f"Image generation failed: {response.error_message} (Type: {error_type_str}, Time: {execution_time:.1f}s)")
# 에러 타입에 따른 추가 정보 제공
# Provide additional information based on error type
additional_info = ""
if hasattr(response, 'error_type') and response.error_type:
if error_type_str == "quota_exceeded":
additional_info = "\n💡 팁: API 할당량 확인 또는 결제 정보를 점검해보세요."
additional_info = "\n[TIP] Please check your API quota or payment information."
elif error_type_str == "safety_violation":
additional_info = "\n💡 팁: 프롬프트에서 민감한 내용을 제거하거나 다른 표현을 사용해보세요."
additional_info = "\n[TIP] Try removing sensitive content from your prompt or use different expressions."
elif error_type_str == "timeout":
additional_info = f"\n💡 팁: 프롬프트를 단순화하거나 이미지 개수를 줄여보세요. (소요시간: {execution_time:.1f})"
additional_info = f"\n[TIP] Try simplifying your prompt or reducing the number of images. (Took: {execution_time:.1f}s)"
elif error_type_str == "network":
additional_info = "\n💡 팁: 네트워크 연결을 확인하고 잠시 후 다시 시도해보세요."
additional_info = "\n[TIP] Please check your network connection and try again later."
elif error_type_str == "service_unavailable":
additional_info = "\n💡 팁: Google 서비스가 일시적으로 불안정할 수 있습니다. 몇 분 후 재시도해보세요."
additional_info = "\n[TIP] Google service may be temporarily unstable. Please try again in a few minutes."
# API 상태 정보 추가
# Add API status information
if error_recovery_available:
try:
stats = health_monitor.get_stats()
if stats['total_requests'] > 0:
additional_info += f"\n📊 API 상태: 성공률 {stats['success_rate']:.1%} ({stats['success_count']}/{stats['total_requests']})"
additional_info += f"\n[API STATUS] Success rate: {stats['success_rate']:.1%} ({stats['success_count']}/{stats['total_requests']})"
except:
pass
@@ -663,14 +764,14 @@ class Imagen4ToolHandlers:
for file_path in saved_files:
if os.path.exists(file_path):
size = os.path.getsize(file_path)
logger.info(f" Verified: {file_path} ({size} bytes)")
logger.info(f" [OK] Verified: {file_path} ({size} bytes)")
else:
logger.error(f" ❌ Missing: {file_path}")
logger.error(f" [MISSING] File not found: {file_path}")
# Create enhanced result with preview images
result = ImageGenerationResult(
success=True,
message=f" Images have been successfully generated! (⚙️ {response.execution_time:.1f}초 소요)",
message=f"[SUCCESS] Images have been successfully generated! (Took {response.execution_time:.1f}s)",
original_images_count=len(response.images_data),
preview_images_b64=preview_images_b64,
saved_files=saved_files,
@@ -776,11 +877,11 @@ async def main():
mcp_server = Imagen4MCPServer(config)
server = mcp_server.get_server()
# 클라이언트 타임아웃 정보 로그
# Log client timeout information
try:
temp_client = Imagen4Client(config)
timeout_settings = temp_client.get_timeout_settings()
config_timeout_info = f"API: {timeout_settings['api_timeout']}, 진행상황: {timeout_settings['progress_interval']}"
config_timeout_info = f"API: {timeout_settings['api_timeout']}s, Progress: {timeout_settings['progress_interval']}s"
except:
config_timeout_info = "Unknown"

28
run_forced_utf8.bat Normal file
View File

@@ -0,0 +1,28 @@
@echo off
echo Starting Imagen4 MCP Server with forced UTF-8 mode...
echo.
REM Set console code page to UTF-8
chcp 65001 >nul 2>&1
REM Set all UTF-8 environment variables
set PYTHONIOENCODING=utf-8
set PYTHONUTF8=1
set LC_ALL=C.UTF-8
set LANG=C.UTF-8
REM Set console properties
title Imagen4 MCP Server (UTF-8 Forced)
echo Environment Variables Set:
echo PYTHONIOENCODING=%PYTHONIOENCODING%
echo PYTHONUTF8=%PYTHONUTF8%
echo LC_ALL=%LC_ALL%
echo.
REM Run Python with explicit UTF-8 mode flag
python -X utf8 main.py
echo.
echo Server stopped. Press any key to exit...
pause >nul

16
run_utf8.bat Normal file
View File

@@ -0,0 +1,16 @@
@echo off
REM Force UTF-8 encoding for Python and console
chcp 65001 >nul 2>&1
REM Set Python UTF-8 environment variables
set PYTHONIOENCODING=utf-8
set PYTHONUTF8=1
set LC_ALL=C.UTF-8
REM Set console properties
title Imagen4 MCP Server (UTF-8)
REM Run the Python script
python main.py
pause

24
run_utf8_forced.py Normal file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env python3
# Emergency UTF-8 override script
import sys
import os
# Force UTF-8 mode at Python startup
if not hasattr(sys, '_utf8_mode') or not sys._utf8_mode:
os.environ['PYTHONUTF8'] = '1'
os.environ['PYTHONIOENCODING'] = 'utf-8'
# Restart Python with UTF-8 mode
import subprocess
result = subprocess.run([sys.executable, '-X', 'utf8', __file__] + sys.argv[1:])
sys.exit(result.returncode)
# Now run the actual main.py
if __name__ == "__main__":
import importlib.util
import sys
spec = importlib.util.spec_from_file_location("main", "main.py")
main_module = importlib.util.module_from_spec(spec)
sys.modules["main"] = main_module
spec.loader.exec_module(main_module)

View File

@@ -204,18 +204,18 @@ class Imagen4Client:
while True:
await asyncio.sleep(self.PROGRESS_INTERVAL)
elapsed = time.time() - start_time
logger.info(f"🔄 API call in progress... {elapsed:.1f}s elapsed (prompt: '{prompt_preview}')")
logger.info(f"[PROGRESS] API call in progress... {elapsed:.1f}s elapsed (prompt: '{prompt_preview}')")
# Also output to console
try:
print(f" Generating image... {elapsed:.1f}s elapsed")
print(f"[PROGRESS] Generating image... {elapsed:.1f}s elapsed")
except:
pass # Continue even if console output fails
except asyncio.CancelledError:
# Normal cancellation
elapsed = time.time() - start_time
logger.info(f" API call completed (total {elapsed:.1f}s)")
logger.info(f"[COMPLETED] API call completed (total {elapsed:.1f}s)")
def _sync_generate_images(self, request: ImageGenerationRequest) -> any:
"""
@@ -231,7 +231,7 @@ class Imagen4Client:
Various Google API exceptions
"""
try:
logger.info(f"🚀 Starting Google Imagen API call (model: {request.model})")
logger.info(f"[API] Starting Google Imagen API call (model: {request.model})")
response = self._client.models.generate_images(
model=request.model,
@@ -249,11 +249,11 @@ class Imagen4Client:
)
)
logger.info("📦 API response received successfully")
logger.info("[API] API response received successfully")
return response
except Exception as e:
logger.error(f"💥 API call failed: {type(e).__name__}: {str(e)}")
logger.error(f"[ERROR] API call failed: {type(e).__name__}: {str(e)}")
raise # Re-raise exception for upper level handling
async def generate_image(self, request: ImageGenerationRequest) -> ImageGenerationResponse:
@@ -276,35 +276,35 @@ class Imagen4Client:
# Log token count information
from ..utils.token_utils import get_prompt_stats
prompt_stats = get_prompt_stats(request.prompt)
logger.info(f"📊 Prompt analysis: {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']} tokens")
logger.info(f"[STATS] Prompt analysis: {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']} tokens")
if request.negative_prompt:
neg_stats = get_prompt_stats(request.negative_prompt)
logger.info(f"📊 Negative prompt: {neg_stats['estimated_tokens']} tokens")
logger.info(f"[STATS] Negative prompt: {neg_stats['estimated_tokens']} tokens")
# Log with proper Unicode support for Korean/international text
prompt_preview = request.prompt[:50] + "..." if len(request.prompt) > 50 else request.prompt
logger.info(f"🎨 Starting image generation - Prompt: '{prompt_preview}', Seed: {request.seed}")
logger.info(f"[START] Starting image generation - Prompt: '{prompt_preview}', Seed: {request.seed}")
# Console output with UTF-8 handling
try:
print(f"🎯 Settings: {request.aspect_ratio}, {request.number_of_images} images, Model: {request.model}")
print(f"📏 Tokens: {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']}")
print(f"[SETTINGS] {request.aspect_ratio}, {request.number_of_images} images, Model: {request.model}")
print(f"[TOKENS] {prompt_stats['estimated_tokens']}/{prompt_stats['max_tokens']}")
# For Windows, ensure proper UTF-8 encoding
if sys.platform.startswith('win'):
prompt_safe = request.prompt.encode('utf-8', errors='replace').decode('utf-8', errors='replace')
print(f"💭 Prompt: {prompt_safe[:80]}{'...' if len(prompt_safe) > 80 else ''}")
print(f"[PROMPT] {prompt_safe[:80]}{'...' if len(prompt_safe) > 80 else ''}")
else:
print(f"💭 Prompt: {request.prompt[:80]}{'...' if len(request.prompt) > 80 else ''}")
print(f"[PROMPT] {request.prompt[:80]}{'...' if len(request.prompt) > 80 else ''}")
except UnicodeEncodeError:
print(f"💭 Prompt: <Unicode text, {len(request.prompt)} characters>")
print(f"[PROMPT] <Unicode text, {len(request.prompt)} characters>")
# Start progress tracking task
progress_task = asyncio.create_task(self._log_progress(start_time, request))
# API call with shortened timeout
logger.info(f"⏱️ API timeout: {self.API_TIMEOUT} seconds")
logger.info(f"[TIMEOUT] API timeout: {self.API_TIMEOUT} seconds")
try:
response = await asyncio.wait_for(
@@ -313,12 +313,12 @@ class Imagen4Client:
)
execution_time = time.time() - start_time
logger.info(f" API call successful (execution time: {execution_time:.1f}s)")
logger.info(f"[SUCCESS] API call successful (execution time: {execution_time:.1f}s)")
except asyncio.TimeoutError:
execution_time = time.time() - start_time
error_msg = f"API request timed out after {self.API_TIMEOUT} seconds. Please check network status or prompt complexity."
logger.error(f"⏰ Timeout: Stopped after {execution_time:.1f} seconds")
logger.error(f"[TIMEOUT] Stopped after {execution_time:.1f} seconds")
return ImageGenerationResponse(
images_data=[],
@@ -337,15 +337,15 @@ class Imagen4Client:
image_bytes = gen_image.image.image_bytes
if image_bytes:
images_data.append(image_bytes)
logger.info(f"🖼️ Image {i+1} extraction complete (size: {len(image_bytes):,} bytes)")
logger.info(f"[IMAGE] Image {i+1} extraction complete (size: {len(image_bytes):,} bytes)")
else:
logger.warning(f"⚠️ Image {i+1} data is empty.")
logger.warning(f"[WARNING] Image {i+1} data is empty.")
else:
logger.warning(f"⚠️ Cannot find image_bytes in image {i+1}.")
logger.warning(f"[WARNING] Cannot find image_bytes in image {i+1}.")
if not images_data:
error_msg = "Cannot find generated image data. Please check API response."
logger.error(f" {error_msg}")
logger.error(f"[ERROR] {error_msg}")
return ImageGenerationResponse(
images_data=[],
@@ -357,7 +357,7 @@ class Imagen4Client:
)
execution_time = time.time() - start_time
logger.info(f"🎉 Successfully generated {len(images_data)} images (total time: {execution_time:.1f} seconds)")
logger.info(f"[SUCCESS] Successfully generated {len(images_data)} images (total time: {execution_time:.1f} seconds)")
return ImageGenerationResponse(
images_data=images_data,
@@ -370,12 +370,12 @@ class Imagen4Client:
execution_time = time.time() - start_time
error_type, user_message = classify_api_error(e)
logger.error(f"💥 Image generation failed ({execution_time:.1f}s elapsed): {error_type.value}")
logger.error(f"🔍 Original error: {type(e).__name__}: {str(e)}")
logger.error(f"[ERROR] Image generation failed ({execution_time:.1f}s elapsed): {error_type.value}")
logger.error(f"[DEBUG] Original error: {type(e).__name__}: {str(e)}")
# Output error to console as well
try:
print(f" Error occurred: {user_message}")
print(f"[ERROR] Error occurred: {user_message}")
except:
pass

View File

@@ -112,11 +112,11 @@ async def retry_with_backoff(
for attempt in range(config.max_attempts):
try:
logger.info(f"🔄 Attempt {attempt + 1}/{config.max_attempts}")
logger.info(f"[RETRY] Attempt {attempt + 1}/{config.max_attempts}")
result = await func(*args, **kwargs)
if attempt > 0:
logger.info(f" Success on attempt {attempt + 1}!")
logger.info(f"[SUCCESS] Success on attempt {attempt + 1}!")
return result
@@ -128,16 +128,16 @@ async def retry_with_backoff(
error_type, _ = classify_api_error(e)
if error_type not in config.retryable_errors:
logger.info(f" Non-retryable error: {error_type.value}")
logger.info(f"[ERROR] Non-retryable error: {error_type.value}")
raise e
if attempt == config.max_attempts - 1:
logger.error(f"💥 All attempts failed. Last error: {str(e)}")
logger.error(f"[FAILED] All attempts failed. Last error: {str(e)}")
break
# Calculate backoff delay
delay = _calculate_delay(config, attempt)
logger.info(f" Retrying in {delay:.1f}s... (error: {error_type.value})")
logger.info(f"[WAIT] Retrying in {delay:.1f}s... (error: {error_type.value})")
await asyncio.sleep(delay)
@@ -186,7 +186,7 @@ class CircuitBreaker:
if self._state == "open":
if time.time() - self._last_failure_time > self.timeout:
self._state = "half-open"
logger.info("🔄 Circuit breaker: transitioning to half-open state")
logger.info("[CIRCUIT] Circuit breaker: transitioning to half-open state")
else:
remaining = self.timeout - (time.time() - self._last_failure_time)
raise Exception(f"Circuit breaker is open. Please try again in {remaining:.1f} seconds.")
@@ -198,7 +198,7 @@ class CircuitBreaker:
if self._state == "half-open":
self._state = "closed"
self._failure_count = 0
logger.info(" Circuit breaker: restored to closed state")
logger.info("[CIRCUIT] Circuit breaker: restored to closed state")
return result
@@ -208,7 +208,7 @@ class CircuitBreaker:
if self._failure_count >= self.failure_threshold:
self._state = "open"
logger.error(f"🚨 Circuit breaker: transitioning to open state ({self._failure_count} failures)")
logger.error(f"[CIRCUIT] Circuit breaker: transitioning to open state ({self._failure_count} failures)")
raise e