-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathlogging_wrapper.py
More file actions
203 lines (170 loc) · 7.99 KB
/
Copy pathlogging_wrapper.py
File metadata and controls
203 lines (170 loc) · 7.99 KB
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
import functools
import logging
import time
import inspect
import sys
from typing import Callable
from functools import wraps
from typing import Callable, Any, Type, Optional
from pydantic import BaseModel, ValidationError
from fastapi import HTTPException, status
import asyncio
import functools
import logging
import time
import asyncio
from typing import Callable, Any, Type, Optional, List, get_origin, get_args
from pydantic import BaseModel, ValidationError
from fastapi import HTTPException, status
def log_and_validate(
logger: logging.Logger,
validate_output: bool = False,
output_model: Optional[Type[BaseModel]] = None,
):
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
async def async_wrapper(*args, **kwargs):
start_time = time.time()
func_name = func.__name__
# Log the arguments
args_repr = [repr(a)[:500] for a in args]
kwargs_repr = [f"{k}={v!r}"[:500] for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
logger.info(f"{func_name} called with args: {signature}")
try:
result = await func(*args, **kwargs)
if validate_output and output_model:
validation_start = time.time()
try:
origin = get_origin(output_model)
if origin is list or origin is List:
# Validate each item in the list
item_model = get_args(output_model)[0]
for item in result:
item_model.model_validate(item)
elif issubclass(output_model, BaseModel):
output_model.model_validate(result)
else:
raise ValueError("Unsupported output_model type")
validation_time = time.time() - validation_start
logger.info(
f"{func_name}: Output validation successful. Time: {validation_time:.4f} seconds"
)
except ValidationError as ve:
logger.error(f"{func_name}: Output validation failed: {ve}")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Output validation failed",
) from ve
total_time = time.time() - start_time
logger.info(
f"{func_name}: Function executed successfully. Total time: {total_time:.4f} seconds"
)
return result
except HTTPException as http_exc:
total_time = time.time() - start_time
logger.error(
f"{func_name}: HTTP error: {http_exc.detail}. Status code: {http_exc.status_code}. Total time: {total_time:.4f} seconds"
)
raise http_exc
except Exception as e:
total_time = time.time() - start_time
logger.exception(
f"{func_name}: Unexpected error: {str(e)}. Total time: {total_time:.4f} seconds"
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"An unexpected error occurred: {str(e)}",
) from e
@functools.wraps(func)
def sync_wrapper(*args, **kwargs):
start_time = time.time()
func_name = func.__name__
# Log the arguments
args_repr = [repr(a)[:500] for a in args]
kwargs_repr = [f"{k}={v!r}"[:500] for k, v in kwargs.items()]
signature = ", ".join(args_repr + kwargs_repr)
logger.info(f"{func_name} called with args: {signature}")
try:
result = func(*args, **kwargs)
if validate_output and output_model:
validation_start = time.time()
try:
origin = get_origin(output_model)
if origin is List:
# Validate each item in the list
item_model = get_args(output_model)[0]
for item in result:
item_model.model_validate(item)
elif issubclass(output_model, BaseModel):
output_model.model_validate(result)
else:
raise ValueError("Unsupported output_model type")
validation_time = time.time() - validation_start
logger.info(
f"{func_name}: Output validation successful. Time: {validation_time:.4f} seconds"
)
except ValidationError as ve:
logger.error(f"{func_name}: Output validation failed: {ve}")
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="Output validation failed",
) from ve
total_time = time.time() - start_time
logger.info(
f"{func_name}: Function executed successfully. Total time: {total_time:.4f} seconds"
)
return result
except HTTPException as http_exc:
total_time = time.time() - start_time
logger.error(
f"{func_name}: HTTP error: {http_exc.detail}. Status code: {http_exc.status_code}. Total time: {total_time:.4f} seconds"
)
raise http_exc
except Exception as e:
total_time = time.time() - start_time
logger.exception(
f"{func_name}: Unexpected error: {str(e)}. Total time: {total_time:.4f} seconds"
)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"An unexpected error occurred: {str(e)}",
) from e
return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
return decorator
def create_log_and_validate_decorator(logger):
def decorator_factory(validate_output=False):
return log_and_validate(logger, validate_output=validate_output)
return decorator_factory
def preserve_validate_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._is_decorated = True
wrapper._validate_output = True
return wrapper
def apply_decorator_to_module(logger):
def wrapper(module):
if isinstance(module, str):
module_name = module
module_obj = sys.modules[module]
else:
module_name = module.__name__
module_obj = module
for name, obj in inspect.getmembers(module_obj):
if inspect.isfunction(obj) and obj.__module__ == module_name:
# Check if the function is already decorated with preserve_validate_decorator
if hasattr(obj, "_is_decorated") and obj._is_decorated:
if getattr(obj, "_validate_output", False):
continue # Skip this function as it's already properly decorated
# Determine whether to validate output
validate_output = getattr(obj, "_validate_output", False)
# Apply the log_and_validate decorator
new_func = log_and_validate(logger, validate_output=validate_output)(
obj
)
# Preserve the _is_decorated and _validate_output attributes
new_func._is_decorated = True
new_func._validate_output = validate_output
setattr(module_obj, name, new_func)
return wrapper