Skip to content

Commit 8968f30

Browse files
committed
Implement event scopes
1 parent c5ac5ee commit 8968f30

File tree

1 file changed

+63
-3
lines changed

1 file changed

+63
-3
lines changed

src/iterative_telemetry/__init__.py

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
"""Iterative Telemetry."""
2-
2+
import contextlib
3+
import dataclasses
34
import json
45
import logging
56
import os
67
import platform
78
import subprocess
89
import sys
910
import uuid
10-
from functools import lru_cache
11+
from functools import lru_cache, wraps
1112
from pathlib import Path
1213
from threading import Thread
13-
from typing import Any, Callable, Dict, Union
14+
from typing import Any, Callable, Dict, Iterator, Optional, Union
1415

1516
import distro
1617
import requests
@@ -28,6 +29,14 @@
2829
DO_NOT_TRACK_VALUE = "do-not-track"
2930

3031

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+
3140
class IterativeTelemetryLogger:
3241
def __init__(
3342
self,
@@ -47,6 +56,57 @@ def __init__(
4756
if self.debug:
4857
logger.setLevel(logging.DEBUG)
4958
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
50110

51111
def send_cli_call(self, cmd_name: str, error: str = None, **kwargs):
52112
self.send_event("cli", cmd_name, error=error, **kwargs)

0 commit comments

Comments
 (0)