File size: 6,346 Bytes
b0b150b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207

import logging
import json
import time
from datetime import datetime
from typing import Optional, Dict, Any
from functools import wraps
from fastapi import Request
import threading

# Configure structured logging
class JSONFormatter(logging.Formatter):
    """Custom JSON formatter for structured logging."""
    
    def format(self, record):
        log_data = {
            'timestamp': datetime.utcnow().isoformat(),
            'level': record.levelname,
            'logger': record.name,
            'message': record.getMessage(),
            'module': record.module,
            'function': record.funcName,
            'line': record.lineno
        }
        
        # Add extra fields if present
        if hasattr(record, 'extra'):
            log_data.update(record.extra)
        
        return json.dumps(log_data)


def setup_logging(json_format: bool = False):
    """Setup logging configuration."""
    logger = logging.getLogger('mexar')
    logger.setLevel(logging.INFO)
    
    # Console handler
    handler = logging.StreamHandler()
    
    if json_format:
        handler.setFormatter(JSONFormatter())
    else:
        handler.setFormatter(logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        ))
    
    logger.addHandler(handler)
    return logger


# Analytics tracker
class AnalyticsTracker:
    """
    Simple in-memory analytics for tracking usage patterns.
    """
    
    def __init__(self):
        self._metrics = {
            'api_calls': {},
            'chat_messages': 0,
            'compilations': 0,
            'errors': [],
            'response_times': []
        }
        self._lock = threading.RLock()
    
    def track_api_call(self, endpoint: str, method: str, status_code: int, duration_ms: float):
        """Track an API call."""
        with self._lock:
            key = f"{method}:{endpoint}"
            if key not in self._metrics['api_calls']:
                self._metrics['api_calls'][key] = {
                    'count': 0,
                    'success': 0,
                    'errors': 0,
                    'avg_duration_ms': 0
                }
            
            self._metrics['api_calls'][key]['count'] += 1
            
            if 200 <= status_code < 400:
                self._metrics['api_calls'][key]['success'] += 1
            else:
                self._metrics['api_calls'][key]['errors'] += 1
            
            # Update rolling average
            current = self._metrics['api_calls'][key]
            current['avg_duration_ms'] = (
                (current['avg_duration_ms'] * (current['count'] - 1) + duration_ms) 
                / current['count']
            )
    
    def track_chat(self):
        """Track a chat message."""
        with self._lock:
            self._metrics['chat_messages'] += 1
    
    def track_compilation(self):
        """Track a compilation."""
        with self._lock:
            self._metrics['compilations'] += 1
    
    def track_error(self, error: str, endpoint: str = None):
        """Track an error."""
        with self._lock:
            self._metrics['errors'].append({
                'timestamp': datetime.utcnow().isoformat(),
                'error': error,
                'endpoint': endpoint
            })
            # Keep only last 100 errors
            if len(self._metrics['errors']) > 100:
                self._metrics['errors'] = self._metrics['errors'][-100:]
    
    def get_stats(self) -> dict:
        """Get current analytics stats."""
        with self._lock:
            total_calls = sum(v['count'] for v in self._metrics['api_calls'].values())
            total_errors = sum(v['errors'] for v in self._metrics['api_calls'].values())
            
            return {
                'total_api_calls': total_calls,
                'total_errors': total_errors,
                'error_rate': total_errors / total_calls if total_calls > 0 else 0,
                'chat_messages': self._metrics['chat_messages'],
                'compilations': self._metrics['compilations'],
                'endpoints': self._metrics['api_calls'],
                'recent_errors': self._metrics['errors'][-10:]
            }
    
    def reset(self):
        """Reset all metrics."""
        with self._lock:
            self._metrics = {
                'api_calls': {},
                'chat_messages': 0,
                'compilations': 0,
                'errors': [],
                'response_times': []
            }


# Singleton instance
analytics = AnalyticsTracker()
logger = setup_logging()


# Middleware for request logging and analytics
async def logging_middleware(request: Request, call_next):
    """Log and track all requests."""
    start_time = time.time()
    
    # Process request
    response = await call_next(request)
    
    # Calculate duration
    duration_ms = (time.time() - start_time) * 1000
    
    # Track in analytics
    analytics.track_api_call(
        endpoint=request.url.path,
        method=request.method,
        status_code=response.status_code,
        duration_ms=duration_ms
    )
    
    # Log request
    logger.info(
        f"{request.method} {request.url.path} - {response.status_code} - {duration_ms:.2f}ms"
    )
    
    return response


# Decorator for function-level logging
def log_function(func):
    """Decorator to log function calls."""
    @wraps(func)
    def wrapper(*args, **kwargs):
        logger.info(f"Calling {func.__name__}")
        try:
            result = func(*args, **kwargs)
            logger.info(f"{func.__name__} completed successfully")
            return result
        except Exception as e:
            logger.error(f"{func.__name__} failed: {str(e)}")
            analytics.track_error(str(e))
            raise
    return wrapper


async def async_log_function(func):
    """Decorator for async function logging."""
    @wraps(func)
    async def wrapper(*args, **kwargs):
        logger.info(f"Calling {func.__name__}")
        try:
            result = await func(*args, **kwargs)
            logger.info(f"{func.__name__} completed successfully")
            return result
        except Exception as e:
            logger.error(f"{func.__name__} failed: {str(e)}")
            analytics.track_error(str(e))
            raise
    return wrapper