-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathcore.py
More file actions
114 lines (99 loc) · 3.95 KB
/
Copy pathcore.py
File metadata and controls
114 lines (99 loc) · 3.95 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
import time
from functools import update_wrapper
from typing import Any, Callable, TypeVar, Generic
from pyquerytracker.config import get_config
from pyquerytracker.utils.logger import QueryLogger
from pyquerytracker.exporter.manager import ExporterManager
from pyquerytracker.exporter.base import NullExporter
logger = QueryLogger.get_logger()
T = TypeVar("T")
class TrackQuery(Generic[T]):
"""
Class-based decorator to track and log the execution time of functions or methods.
Logs include:
- Function name
- Class name (if method)
- Execution time (ms)
- Arguments
- Errors (if any)
Usage:
@TrackQuery()
def my_function():
...
"""
def __init__(self) -> None:
self.config = get_config()
if self.config.export_type and self.config.export_path:
exporter = ExporterManager.create_exporter(self.config)
ExporterManager.set(exporter)
self.exporter = exporter
else:
self.exporter = NullExporter()
def __call__(self, func: Callable[..., T]) -> Callable[..., T]:
def wrapped(*args: Any, **kwargs: Any) -> T:
start = time.perf_counter()
class_name = None
# Try to detect if this is an instance or class method
if args:
possible_self_or_cls = args[0]
if hasattr(possible_self_or_cls, "__class__"):
if isinstance(possible_self_or_cls, type):
class_name = possible_self_or_cls.__name__
else:
class_name = possible_self_or_cls.__class__.__name__
try:
result = func(*args, **kwargs)
duration = (time.perf_counter() - start) * 1000
log_data = {
"event": (
"slow_execution"
if duration > self.config.slow_log_threshold_ms
else "normal_execution"
),
"function_name": func.__name__,
"class_name": class_name,
"duration_ms": duration,
"func_args": repr(args),
"func_kwargs": repr(kwargs),
}
if duration > self.config.slow_log_threshold_ms:
logger.log(
self.config.slow_log_level,
f"{class_name}.{func.__name__} -> "
f"Slow execution: took {duration:.2f}ms",
)
else:
logger.info(
"Function %s%s executed successfully in %.2fms",
f"{class_name}." if class_name else "",
func.__name__,
duration,
extra=log_data,
)
self.exporter.append(log_data)
return result
except Exception as e:
duration = (time.perf_counter() - start) * 1000
log_data = {
"event": "error",
"function_name": func.__name__,
"class_name": class_name,
"duration_ms": duration,
"func_args": repr(args),
"func_kwargs": repr(kwargs),
"error": str(e),
}
logger.error(
"Function %s%s failed after %.2fms: %s",
f"{class_name}." if class_name else "",
func.__name__,
duration,
str(e),
exc_info=True,
extra=log_data,
)
self.exporter.append(log_data)
# Exceptions are handled internally by the decorator,
# allowing the program to proceed smoothly
return None
return update_wrapper(wrapped, func)