1
1
"""Iterative Telemetry."""
2
-
2
+ import contextlib
3
+ import dataclasses
3
4
import json
4
5
import logging
5
6
import os
6
7
import platform
7
8
import subprocess
8
9
import sys
9
10
import uuid
10
- from functools import lru_cache
11
+ from functools import lru_cache , wraps
11
12
from pathlib import Path
12
13
from threading import Thread
13
- from typing import Any , Callable , Dict , Union
14
+ from typing import Any , Callable , Dict , Iterator , Optional , Union
14
15
15
16
import distro
16
17
import requests
28
29
DO_NOT_TRACK_VALUE = "do-not-track"
29
30
30
31
32
+ @dataclasses .dataclass
33
+ class TelemetryEvent :
34
+ interface : str
35
+ action : str
36
+ error : Optional [str ] = None
37
+ kwargs : Dict [str , Any ] = dataclasses .field (default_factory = dict )
38
+
39
+
31
40
class IterativeTelemetryLogger :
32
41
def __init__ (
33
42
self ,
@@ -47,6 +56,57 @@ def __init__(
47
56
if self .debug :
48
57
logger .setLevel (logging .DEBUG )
49
58
logger .debug ("IterativeTelemetryLogger is in debug mode" )
59
+ self ._current_event : Optional [TelemetryEvent ] = None
60
+
61
+ def log_param (self , key : str , value ):
62
+ if self ._current_event :
63
+ self ._current_event .kwargs [key ] = value
64
+
65
+ @contextlib .contextmanager
66
+ def event_scope (
67
+ self , interface : str , action : str
68
+ ) -> Iterator [TelemetryEvent ]:
69
+ event = TelemetryEvent (interface = interface , action = action )
70
+ tmp = self ._current_event
71
+ self ._current_event = event
72
+ try :
73
+ yield event
74
+ finally :
75
+ self ._current_event = tmp
76
+
77
+ def log (
78
+ self ,
79
+ interface : str ,
80
+ action : str = None ,
81
+ skip : Union [bool , Callable [[TelemetryEvent ], bool ]] = None ,
82
+ ):
83
+ def decorator (f ):
84
+ @wraps (f )
85
+ def inner (* args , ** kwargs ):
86
+ with self .event_scope (
87
+ interface , action or f .__name__
88
+ ) as event :
89
+ try :
90
+ return f (* args , ** kwargs )
91
+ except Exception as e :
92
+ event .error = e .__class__ .__name__
93
+ raise
94
+ finally :
95
+ if (
96
+ skip is None
97
+ or (callable (skip ) and not skip (event ))
98
+ or not skip
99
+ ):
100
+ self .send_event (
101
+ event .interface ,
102
+ event .action ,
103
+ event .error ,
104
+ ** event .kwargs ,
105
+ )
106
+
107
+ return inner
108
+
109
+ return decorator
50
110
51
111
def send_cli_call (self , cmd_name : str , error : str = None , ** kwargs ):
52
112
self .send_event ("cli" , cmd_name , error = error , ** kwargs )
0 commit comments