dataquery-sdk 0.0.7__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.
- dataquery/__init__.py +215 -0
- dataquery/auth.py +380 -0
- dataquery/auto_download.py +448 -0
- dataquery/cli.py +244 -0
- dataquery/client.py +2825 -0
- dataquery/config.py +571 -0
- dataquery/connection_pool.py +538 -0
- dataquery/dataquery.py +2467 -0
- dataquery/exceptions.py +169 -0
- dataquery/logging_config.py +415 -0
- dataquery/models.py +1303 -0
- dataquery/py.typed +2 -0
- dataquery/rate_limiter.py +520 -0
- dataquery/retry.py +406 -0
- dataquery/utils.py +525 -0
- dataquery_sdk-0.0.7.dist-info/METADATA +996 -0
- dataquery_sdk-0.0.7.dist-info/RECORD +21 -0
- dataquery_sdk-0.0.7.dist-info/WHEEL +5 -0
- dataquery_sdk-0.0.7.dist-info/entry_points.txt +2 -0
- dataquery_sdk-0.0.7.dist-info/licenses/LICENSE +201 -0
- dataquery_sdk-0.0.7.dist-info/top_level.txt +1 -0
dataquery/exceptions.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for the DATAQUERY SDK.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DataQueryError(Exception):
|
|
9
|
+
"""Base exception for all DATAQUERY SDK errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
|
|
12
|
+
self.message = message
|
|
13
|
+
self.details = details or {}
|
|
14
|
+
super().__init__(self.message)
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
if self.details:
|
|
18
|
+
return f"{self.message} - Details: {self.details}"
|
|
19
|
+
return self.message
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthenticationError(DataQueryError):
|
|
23
|
+
"""Raised when authentication fails."""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
message: str = "Authentication failed",
|
|
28
|
+
details: Optional[Dict[str, Any]] = None,
|
|
29
|
+
):
|
|
30
|
+
super().__init__(message, details)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ValidationError(DataQueryError):
|
|
34
|
+
"""Raised when input validation fails."""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
message: str = "Validation failed",
|
|
39
|
+
details: Optional[Dict[str, Any]] = None,
|
|
40
|
+
):
|
|
41
|
+
super().__init__(message, details)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class NotFoundError(DataQueryError):
|
|
45
|
+
"""Raised when a requested resource is not found."""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self, resource_type: str, resource_id: str, message: Optional[str] = None
|
|
49
|
+
):
|
|
50
|
+
if message is None:
|
|
51
|
+
message = f"{resource_type} not found: {resource_id}"
|
|
52
|
+
super().__init__(
|
|
53
|
+
message, {"resource_type": resource_type, "resource_id": resource_id}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class RateLimitError(DataQueryError):
|
|
58
|
+
"""Raised when rate limits are exceeded."""
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self, message: str = "Rate limit exceeded", retry_after: Optional[int] = None
|
|
62
|
+
):
|
|
63
|
+
details = {"retry_after": retry_after} if retry_after else {}
|
|
64
|
+
super().__init__(message, details)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class NetworkError(DataQueryError):
|
|
68
|
+
"""Raised when network-related errors occur."""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self, message: str = "Network error occurred", status_code: Optional[int] = None
|
|
72
|
+
):
|
|
73
|
+
details = {"status_code": status_code} if status_code else {}
|
|
74
|
+
super().__init__(message, details)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ConfigurationError(DataQueryError):
|
|
78
|
+
"""Raised when configuration is invalid or missing."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
message: str = "Configuration error",
|
|
83
|
+
details: Optional[Dict[str, Any]] = None,
|
|
84
|
+
):
|
|
85
|
+
super().__init__(message, details)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class DownloadError(DataQueryError):
|
|
89
|
+
"""Raised when file download fails."""
|
|
90
|
+
|
|
91
|
+
def __init__(
|
|
92
|
+
self,
|
|
93
|
+
file_group_id: str,
|
|
94
|
+
group_id: str,
|
|
95
|
+
message: str = "Download failed",
|
|
96
|
+
details: Optional[Dict[str, Any]] = None,
|
|
97
|
+
):
|
|
98
|
+
if details is None:
|
|
99
|
+
details = {}
|
|
100
|
+
details.update({"file_group_id": file_group_id, "group_id": group_id})
|
|
101
|
+
super().__init__(message, details)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class AvailabilityError(DataQueryError):
|
|
105
|
+
"""Raised when checking file availability fails."""
|
|
106
|
+
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
file_group_id: str,
|
|
110
|
+
group_id: str,
|
|
111
|
+
message: str = "Availability check failed",
|
|
112
|
+
details: Optional[Dict[str, Any]] = None,
|
|
113
|
+
):
|
|
114
|
+
if details is None:
|
|
115
|
+
details = {}
|
|
116
|
+
details.update({"file_group_id": file_group_id, "group_id": group_id})
|
|
117
|
+
super().__init__(message, details)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class GroupNotFoundError(NotFoundError):
|
|
121
|
+
"""Raised when a group is not found."""
|
|
122
|
+
|
|
123
|
+
def __init__(self, group_id: str):
|
|
124
|
+
super().__init__("Group", group_id)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class FileNotFoundError(NotFoundError):
|
|
128
|
+
"""Raised when a file is not found."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, file_group_id: str, group_id: str):
|
|
131
|
+
super().__init__(
|
|
132
|
+
"File", file_group_id, f"File {file_group_id} not found in group {group_id}"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DateRangeError(ValidationError):
|
|
137
|
+
"""Raised when date range validation fails."""
|
|
138
|
+
|
|
139
|
+
def __init__(self, start_date: str, end_date: str, message: Optional[str] = None):
|
|
140
|
+
if message is None:
|
|
141
|
+
message = f"Invalid date range: {start_date} to {end_date}"
|
|
142
|
+
super().__init__(message, {"start_date": start_date, "end_date": end_date})
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class FileTypeError(ValidationError):
|
|
146
|
+
"""Raised when file type validation fails."""
|
|
147
|
+
|
|
148
|
+
def __init__(self, file_type: str, allowed_types: Optional[list] = None):
|
|
149
|
+
message = f"Invalid file type: {file_type}"
|
|
150
|
+
if allowed_types:
|
|
151
|
+
message += f". Allowed types: {', '.join(allowed_types)}"
|
|
152
|
+
super().__init__(
|
|
153
|
+
message, {"file_type": file_type, "allowed_types": allowed_types}
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class WorkflowError(DataQueryError):
|
|
158
|
+
"""Raised when workflow operations fail."""
|
|
159
|
+
|
|
160
|
+
def __init__(
|
|
161
|
+
self,
|
|
162
|
+
workflow_name: str,
|
|
163
|
+
message: str = "Workflow failed",
|
|
164
|
+
details: Optional[Dict[str, Any]] = None,
|
|
165
|
+
):
|
|
166
|
+
if details is None:
|
|
167
|
+
details = {}
|
|
168
|
+
details["workflow_name"] = workflow_name
|
|
169
|
+
super().__init__(message, details)
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Enhanced logging configuration for the DATAQUERY SDK.
|
|
3
|
+
|
|
4
|
+
Provides structured logging, performance metrics, request/response logging,
|
|
5
|
+
and configurable log levels and formats.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, Optional
|
|
14
|
+
|
|
15
|
+
import structlog
|
|
16
|
+
|
|
17
|
+
logger = structlog.get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LogLevel(str, Enum):
|
|
21
|
+
"""Log levels."""
|
|
22
|
+
|
|
23
|
+
DEBUG = "DEBUG"
|
|
24
|
+
INFO = "INFO"
|
|
25
|
+
WARNING = "WARNING"
|
|
26
|
+
ERROR = "ERROR"
|
|
27
|
+
CRITICAL = "CRITICAL"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LogFormat(str, Enum):
|
|
31
|
+
"""Log formats."""
|
|
32
|
+
|
|
33
|
+
JSON = "json"
|
|
34
|
+
CONSOLE = "console"
|
|
35
|
+
SIMPLE = "simple"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class LoggingConfig:
|
|
40
|
+
"""Configuration for logging."""
|
|
41
|
+
|
|
42
|
+
level: LogLevel = LogLevel.INFO
|
|
43
|
+
format: LogFormat = LogFormat.JSON
|
|
44
|
+
enable_console: bool = True
|
|
45
|
+
enable_file: bool = False
|
|
46
|
+
log_file: Optional[Path] = None
|
|
47
|
+
max_file_size: int = 10 * 1024 * 1024 # 10MB
|
|
48
|
+
backup_count: int = 5
|
|
49
|
+
enable_request_logging: bool = False
|
|
50
|
+
enable_performance_logging: bool = True
|
|
51
|
+
enable_metrics: bool = True
|
|
52
|
+
include_timestamps: bool = True
|
|
53
|
+
include_process_info: bool = True
|
|
54
|
+
include_thread_info: bool = True
|
|
55
|
+
custom_processors: list = field(default_factory=list)
|
|
56
|
+
log_correlation_id: bool = True
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class RequestResponseLogger:
|
|
60
|
+
"""Log HTTP requests and responses."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, config: LoggingConfig):
|
|
63
|
+
self.config = config
|
|
64
|
+
self.logger = structlog.get_logger(__name__)
|
|
65
|
+
|
|
66
|
+
def log_request(
|
|
67
|
+
self,
|
|
68
|
+
method: str,
|
|
69
|
+
url: str,
|
|
70
|
+
headers: Dict[str, str],
|
|
71
|
+
body: Optional[str] = None,
|
|
72
|
+
correlation_id: Optional[str] = None,
|
|
73
|
+
):
|
|
74
|
+
"""Log HTTP request."""
|
|
75
|
+
if not self.config.enable_request_logging:
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
log_data = {
|
|
79
|
+
"event_type": "http_request",
|
|
80
|
+
"method": method,
|
|
81
|
+
"url": url,
|
|
82
|
+
"headers": self._sanitize_headers(headers),
|
|
83
|
+
"timestamp": datetime.now().isoformat(),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if body:
|
|
87
|
+
log_data["body"] = self._truncate_body(body)
|
|
88
|
+
|
|
89
|
+
if correlation_id:
|
|
90
|
+
log_data["correlation_id"] = correlation_id
|
|
91
|
+
|
|
92
|
+
self.logger.info("HTTP Request", **log_data)
|
|
93
|
+
|
|
94
|
+
def log_response(
|
|
95
|
+
self,
|
|
96
|
+
status_code: int,
|
|
97
|
+
headers: Dict[str, str],
|
|
98
|
+
body: Optional[str] = None,
|
|
99
|
+
duration: float = 0.0,
|
|
100
|
+
correlation_id: Optional[str] = None,
|
|
101
|
+
):
|
|
102
|
+
"""Log HTTP response."""
|
|
103
|
+
if not self.config.enable_request_logging:
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
log_data = {
|
|
107
|
+
"event_type": "http_response",
|
|
108
|
+
"status_code": status_code,
|
|
109
|
+
"headers": self._sanitize_headers(headers),
|
|
110
|
+
"duration_ms": round(duration * 1000, 2),
|
|
111
|
+
"timestamp": datetime.now().isoformat(),
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if body:
|
|
115
|
+
log_data["body"] = self._truncate_body(body)
|
|
116
|
+
|
|
117
|
+
if correlation_id:
|
|
118
|
+
log_data["correlation_id"] = correlation_id
|
|
119
|
+
|
|
120
|
+
# Use appropriate log level based on status code
|
|
121
|
+
if status_code >= 500:
|
|
122
|
+
self.logger.error("HTTP Response", **log_data)
|
|
123
|
+
elif status_code >= 400:
|
|
124
|
+
self.logger.warning("HTTP Response", **log_data)
|
|
125
|
+
else:
|
|
126
|
+
self.logger.info("HTTP Response", **log_data)
|
|
127
|
+
|
|
128
|
+
def _sanitize_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
|
|
129
|
+
"""Sanitize headers for logging."""
|
|
130
|
+
sanitized = {}
|
|
131
|
+
sensitive_headers = {"authorization", "cookie", "x-api-key"}
|
|
132
|
+
|
|
133
|
+
for key, value in headers.items():
|
|
134
|
+
if key.lower() in sensitive_headers:
|
|
135
|
+
sanitized[key] = "***"
|
|
136
|
+
else:
|
|
137
|
+
sanitized[key] = value
|
|
138
|
+
|
|
139
|
+
return sanitized
|
|
140
|
+
|
|
141
|
+
def _truncate_body(self, body: str, max_length: int = 1000) -> str:
|
|
142
|
+
"""Truncate body for logging."""
|
|
143
|
+
if len(body) <= max_length:
|
|
144
|
+
return body
|
|
145
|
+
return body[:max_length] + "... [truncated]"
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class PerformanceLogger:
|
|
149
|
+
"""Log performance metrics."""
|
|
150
|
+
|
|
151
|
+
def __init__(self, config: LoggingConfig):
|
|
152
|
+
self.config = config
|
|
153
|
+
self.logger = structlog.get_logger(__name__)
|
|
154
|
+
self.metrics: Dict[str, Any] = {}
|
|
155
|
+
|
|
156
|
+
def log_operation_start(self, operation: str, **kwargs):
|
|
157
|
+
"""Log start of operation."""
|
|
158
|
+
if not self.config.enable_performance_logging:
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
self.metrics[operation] = {"start_time": datetime.now(), "kwargs": kwargs}
|
|
162
|
+
|
|
163
|
+
self.logger.debug("Operation started", operation=operation, **kwargs)
|
|
164
|
+
|
|
165
|
+
def log_operation_end(
|
|
166
|
+
self, operation: str, duration: float, success: bool = True, **kwargs
|
|
167
|
+
):
|
|
168
|
+
"""Log end of operation."""
|
|
169
|
+
if not self.config.enable_performance_logging:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if operation in self.metrics:
|
|
173
|
+
start_time = self.metrics[operation]["start_time"]
|
|
174
|
+
total_duration = (datetime.now() - start_time).total_seconds()
|
|
175
|
+
|
|
176
|
+
log_data = {
|
|
177
|
+
"operation": operation,
|
|
178
|
+
"duration_ms": round(duration * 1000, 2),
|
|
179
|
+
"total_duration_ms": round(total_duration * 1000, 2),
|
|
180
|
+
"success": success,
|
|
181
|
+
**kwargs,
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if success:
|
|
185
|
+
self.logger.info("Operation completed", **log_data)
|
|
186
|
+
else:
|
|
187
|
+
self.logger.warning("Operation failed", **log_data)
|
|
188
|
+
|
|
189
|
+
# Clean up
|
|
190
|
+
del self.metrics[operation]
|
|
191
|
+
|
|
192
|
+
def log_metric(self, name: str, value: float, unit: str = "", **kwargs):
|
|
193
|
+
"""Log a performance metric."""
|
|
194
|
+
if not self.config.enable_metrics:
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
self.logger.info(
|
|
198
|
+
"Performance metric", metric_name=name, value=value, unit=unit, **kwargs
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class StructuredLogger:
|
|
203
|
+
"""Enhanced structured logger with correlation IDs and context."""
|
|
204
|
+
|
|
205
|
+
def __init__(self, config: LoggingConfig):
|
|
206
|
+
self.config = config
|
|
207
|
+
self._setup_logging()
|
|
208
|
+
|
|
209
|
+
def _setup_logging(self):
|
|
210
|
+
"""Setup structured logging configuration."""
|
|
211
|
+
processors = [
|
|
212
|
+
*self.config.custom_processors,
|
|
213
|
+
structlog.stdlib.filter_by_level,
|
|
214
|
+
structlog.stdlib.add_logger_name,
|
|
215
|
+
structlog.stdlib.add_log_level,
|
|
216
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
if self.config.include_timestamps:
|
|
220
|
+
processors.append(structlog.processors.TimeStamper(fmt="iso"))
|
|
221
|
+
|
|
222
|
+
# Minimal and safe error processors
|
|
223
|
+
processors.extend(
|
|
224
|
+
[
|
|
225
|
+
structlog.processors.StackInfoRenderer(),
|
|
226
|
+
structlog.processors.format_exc_info,
|
|
227
|
+
structlog.processors.UnicodeDecoder(),
|
|
228
|
+
]
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
if self.config.log_correlation_id:
|
|
232
|
+
processors.append(self._add_correlation_id)
|
|
233
|
+
|
|
234
|
+
if self.config.format == LogFormat.JSON:
|
|
235
|
+
processors.append(structlog.processors.JSONRenderer())
|
|
236
|
+
elif self.config.format == LogFormat.CONSOLE:
|
|
237
|
+
processors.append(structlog.dev.ConsoleRenderer())
|
|
238
|
+
else:
|
|
239
|
+
processors.append(structlog.dev.ConsoleRenderer(colors=False))
|
|
240
|
+
|
|
241
|
+
structlog.configure(
|
|
242
|
+
processors=processors,
|
|
243
|
+
context_class=dict,
|
|
244
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
245
|
+
wrapper_class=structlog.stdlib.BoundLogger,
|
|
246
|
+
cache_logger_on_first_use=True,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
def _add_correlation_id(self, logger, method_name, event_dict):
|
|
250
|
+
"""Add correlation ID to log entries."""
|
|
251
|
+
# This would be implemented to add correlation IDs
|
|
252
|
+
# from context or generate new ones
|
|
253
|
+
return event_dict
|
|
254
|
+
|
|
255
|
+
def get_logger(self, name: Optional[str] = None) -> structlog.BoundLogger:
|
|
256
|
+
"""Get a structured logger."""
|
|
257
|
+
return structlog.get_logger(name)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class LoggingManager:
|
|
261
|
+
"""Main logging manager for the DATAQUERY SDK."""
|
|
262
|
+
|
|
263
|
+
def __init__(self, config: LoggingConfig):
|
|
264
|
+
self.config = config
|
|
265
|
+
self.structured_logger = StructuredLogger(config)
|
|
266
|
+
self.request_logger = RequestResponseLogger(config)
|
|
267
|
+
self.performance_logger = PerformanceLogger(config)
|
|
268
|
+
|
|
269
|
+
# Setup file logging if enabled
|
|
270
|
+
if config.enable_file and config.log_file:
|
|
271
|
+
self._setup_file_logging()
|
|
272
|
+
|
|
273
|
+
logger.info(
|
|
274
|
+
"Logging manager initialized",
|
|
275
|
+
level=config.level.value,
|
|
276
|
+
format=config.format.value,
|
|
277
|
+
enable_request_logging=config.enable_request_logging,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def _setup_file_logging(self):
|
|
281
|
+
"""Setup file logging with rotation."""
|
|
282
|
+
try:
|
|
283
|
+
from logging.handlers import RotatingFileHandler
|
|
284
|
+
|
|
285
|
+
# Check if log_file is set
|
|
286
|
+
if not self.config.log_file:
|
|
287
|
+
logger.warning("File logging enabled but no log file specified")
|
|
288
|
+
return
|
|
289
|
+
|
|
290
|
+
# Ensure log directory exists
|
|
291
|
+
self.config.log_file.parent.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
|
|
293
|
+
# Create rotating file handler
|
|
294
|
+
handler = RotatingFileHandler(
|
|
295
|
+
str(self.config.log_file),
|
|
296
|
+
maxBytes=self.config.max_file_size,
|
|
297
|
+
backupCount=self.config.backup_count,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Set formatter
|
|
301
|
+
if self.config.format == LogFormat.JSON:
|
|
302
|
+
formatter = logging.Formatter("%(message)s")
|
|
303
|
+
else:
|
|
304
|
+
formatter = logging.Formatter(
|
|
305
|
+
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
handler.setFormatter(formatter)
|
|
309
|
+
|
|
310
|
+
# Add to root logger
|
|
311
|
+
root_logger = logging.getLogger()
|
|
312
|
+
root_logger.addHandler(handler)
|
|
313
|
+
root_logger.setLevel(getattr(logging, self.config.level.value))
|
|
314
|
+
|
|
315
|
+
logger.info(
|
|
316
|
+
"File logging configured",
|
|
317
|
+
log_file=str(self.config.log_file),
|
|
318
|
+
max_size=self.config.max_file_size,
|
|
319
|
+
backup_count=self.config.backup_count,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
except Exception as e:
|
|
323
|
+
logger.warning("Failed to setup file logging", error=str(e))
|
|
324
|
+
|
|
325
|
+
def get_logger(self, name: Optional[str] = None) -> structlog.BoundLogger:
|
|
326
|
+
"""Get a logger instance."""
|
|
327
|
+
return self.structured_logger.get_logger(name)
|
|
328
|
+
|
|
329
|
+
def log_request(
|
|
330
|
+
self,
|
|
331
|
+
method: str,
|
|
332
|
+
url: str,
|
|
333
|
+
headers: Dict[str, str],
|
|
334
|
+
body: Optional[str] = None,
|
|
335
|
+
correlation_id: Optional[str] = None,
|
|
336
|
+
):
|
|
337
|
+
"""Log HTTP request."""
|
|
338
|
+
self.request_logger.log_request(method, url, headers, body, correlation_id)
|
|
339
|
+
|
|
340
|
+
def log_response(
|
|
341
|
+
self,
|
|
342
|
+
status_code: int,
|
|
343
|
+
headers: Dict[str, str],
|
|
344
|
+
body: Optional[str] = None,
|
|
345
|
+
duration: float = 0.0,
|
|
346
|
+
correlation_id: Optional[str] = None,
|
|
347
|
+
):
|
|
348
|
+
"""Log HTTP response."""
|
|
349
|
+
self.request_logger.log_response(
|
|
350
|
+
status_code, headers, body, duration, correlation_id
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def log_operation_start(self, operation: str, **kwargs):
|
|
354
|
+
"""Log operation start."""
|
|
355
|
+
self.performance_logger.log_operation_start(operation, **kwargs)
|
|
356
|
+
|
|
357
|
+
def log_operation_end(
|
|
358
|
+
self, operation: str, duration: float, success: bool = True, **kwargs
|
|
359
|
+
):
|
|
360
|
+
"""Log operation end."""
|
|
361
|
+
self.performance_logger.log_operation_end(
|
|
362
|
+
operation, duration, success, **kwargs
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def log_metric(self, name: str, value: float, unit: str = "", **kwargs):
|
|
366
|
+
"""Log performance metric."""
|
|
367
|
+
self.performance_logger.log_metric(name, value, unit, **kwargs)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def create_logging_config(
|
|
371
|
+
level: LogLevel = LogLevel.INFO,
|
|
372
|
+
format: LogFormat = LogFormat.JSON,
|
|
373
|
+
enable_console: bool = True,
|
|
374
|
+
enable_file: bool = False,
|
|
375
|
+
log_file: Optional[Path] = None,
|
|
376
|
+
enable_request_logging: bool = False,
|
|
377
|
+
enable_performance_logging: bool = True,
|
|
378
|
+
) -> LoggingConfig:
|
|
379
|
+
"""
|
|
380
|
+
Create logging configuration.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
level: Log level
|
|
384
|
+
format: Log format
|
|
385
|
+
enable_console: Whether to enable console logging
|
|
386
|
+
enable_file: Whether to enable file logging
|
|
387
|
+
log_file: Path to log file
|
|
388
|
+
enable_request_logging: Whether to log HTTP requests/responses
|
|
389
|
+
enable_performance_logging: Whether to log performance metrics
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Logging configuration
|
|
393
|
+
"""
|
|
394
|
+
return LoggingConfig(
|
|
395
|
+
level=level,
|
|
396
|
+
format=format,
|
|
397
|
+
enable_console=enable_console,
|
|
398
|
+
enable_file=enable_file,
|
|
399
|
+
log_file=log_file,
|
|
400
|
+
enable_request_logging=enable_request_logging,
|
|
401
|
+
enable_performance_logging=enable_performance_logging,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def create_logging_manager(config: LoggingConfig) -> LoggingManager:
|
|
406
|
+
"""
|
|
407
|
+
Create a logging manager with the specified configuration.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
config: Logging configuration
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
Configured logging manager
|
|
414
|
+
"""
|
|
415
|
+
return LoggingManager(config)
|