-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathtelemetry.py
More file actions
123 lines (94 loc) · 4.06 KB
/
Copy pathtelemetry.py
File metadata and controls
123 lines (94 loc) · 4.06 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
"""Databricks Labs telemetry for CoDA.
Follows the DQX pattern: piggybacks telemetry on the Databricks SDK's
User-Agent header. Each log_telemetry() call creates a throwaway
WorkspaceClient, augments the User-Agent with key-value data, and fires
clusters.select_spark_version() to transmit the header to Databricks
servers where it's recorded.
All telemetry runs in background daemon threads -- never blocks the
Flask request path or terminal I/O.
Reference: https://github.com/databrickslabs/dqx/blob/main/src/databricks/labs/dqx/telemetry.py
"""
import functools
import logging
import os
import threading
try:
import tomllib
except ModuleNotFoundError: # Python 3.10 compatibility
import tomli as tomllib
logger = logging.getLogger(__name__)
_version_cache = None
def _get_version():
"""Get CoDA version from pyproject.toml (cached after first call)."""
global _version_cache
if _version_cache is not None:
return _version_cache
try:
pyproject = os.path.join(os.path.dirname(__file__), "pyproject.toml")
with open(pyproject, "rb") as f:
_version_cache = tomllib.load(f)["project"]["version"]
except Exception:
_version_cache = "0.0.0"
return _version_cache
def set_product_info(ws):
"""Set CoDA product info on a WorkspaceClient for telemetry attribution.
Call this on any WorkspaceClient so all SDK API calls carry the 'coda'
product identifier in the User-Agent header.
"""
product_info = getattr(ws.config, "_product_info", None)
if product_info is None or product_info[0] != "coda":
setattr(ws.config, "_product_info", ("coda", _get_version()))
def _telemetry_disabled() -> bool:
"""True when the operator has opted out of CoDA telemetry.
Enterprise security teams (banks, regulated retail, etc.) require an
inventory of every data flow that leaves their workspace boundary.
`CODA_TELEMETRY_DISABLED=true` in app.yaml makes log_telemetry() a
no-op so deployments can pass third-party-risk review with no outbound
telemetry to disclose.
Telemetry is on by default for backward compatibility; opt-out is
operator-controlled per-deployment.
"""
return os.environ.get("CODA_TELEMETRY_DISABLED", "").strip().lower() in (
"true", "1", "yes", "on"
)
def log_telemetry(key, value):
"""Send a telemetry key-value pair via the Databricks SDK User-Agent header.
Creates a throwaway WorkspaceClient from ~/.databrickscfg, adds the
key-value to the User-Agent, and fires clusters.select_spark_version()
to transmit. Runs in a background daemon thread. Errors are caught and
logged, never raised.
No-op if `CODA_TELEMETRY_DISABLED` is set to a truthy value — the
enterprise opt-out path.
"""
if _telemetry_disabled():
return
def _send():
try:
from databricks.sdk import WorkspaceClient
from databricks.sdk.errors import DatabricksError
ws = WorkspaceClient()
set_product_info(ws)
new_config = ws.config.copy().with_user_agent_extra(key, value)
temp_ws = WorkspaceClient(config=new_config)
try:
temp_ws.clusters.select_spark_version()
except DatabricksError as e:
logger.debug(f"Telemetry transmit failed: {e}")
except Exception as e:
logger.debug(f"Telemetry error ({key}={value}): {e}")
threading.Thread(target=_send, daemon=True, name=f"telemetry-{key}").start()
def telemetry_logger(key, value):
"""Decorator that fires telemetry before executing the wrapped function.
Works on standalone functions and class methods alike. Creates its own
WorkspaceClient from ~/.databrickscfg -- no self.ws required.
"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
log_telemetry(key, value)
except Exception:
pass # Telemetry must never break the wrapped function
return func(*args, **kwargs)
return wrapper
return decorator