Skip to content

Commit 4930a51

Browse files
authored
SNOW-654808, SNOW-652000: Add field source to basic telemetry data and refactor Telemetry data (#1243)
1 parent 4ccba6a commit 4930a51

File tree

8 files changed

+159
-67
lines changed

8 files changed

+159
-67
lines changed

src/snowflake/connector/connection.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,7 @@
8383
SnowflakeRestful,
8484
)
8585
from .sqlstate import SQLSTATE_CONNECTION_NOT_EXISTS, SQLSTATE_FEATURE_NOT_SUPPORTED
86-
from .telemetry import (
87-
TelemetryClient,
88-
TelemetryData,
89-
TelemetryField,
90-
generate_telemetry_data,
91-
)
86+
from .telemetry import TelemetryClient, TelemetryData, TelemetryField
9287
from .telemetry_oob import TelemetryService
9388
from .time_util import HeartBeatTimer, get_time_millis
9489
from .util_text import construct_hostname, parse_account, split_statements
@@ -1565,16 +1560,12 @@ def _log_telemetry_imported_packages(self) -> None:
15651560
}
15661561
ts = get_time_millis()
15671562
self._log_telemetry(
1568-
TelemetryData(
1569-
generate_telemetry_data(
1570-
from_dict={
1571-
TelemetryField.KEY_TYPE.value: TelemetryField.IMPORTED_PACKAGES.value,
1572-
TelemetryField.KEY_SOURCE.value: self.application
1573-
if self.application
1574-
else CLIENT_NAME,
1575-
TelemetryField.KEY_VALUE.value: str(imported_modules),
1576-
}
1577-
),
1578-
ts,
1563+
TelemetryData.from_telemetry_data_dict(
1564+
from_dict={
1565+
TelemetryField.KEY_TYPE.value: TelemetryField.IMPORTED_PACKAGES.value,
1566+
TelemetryField.KEY_VALUE.value: str(imported_modules),
1567+
},
1568+
timestamp=ts,
1569+
connection=self,
15791570
)
15801571
)

src/snowflake/connector/cursor.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
FileTransferType,
4040
QueryStatus,
4141
)
42-
from .description import CLIENT_NAME
4342
from .errorcode import (
4443
ER_CURSOR_IS_CLOSED,
4544
ER_FAILED_PROCESSING_PYFORMAT,
@@ -62,7 +61,7 @@
6261
from .file_transfer_agent import SnowflakeFileTransferAgent
6362
from .options import installed_pandas, pandas
6463
from .sqlstate import SQLSTATE_FEATURE_NOT_SUPPORTED
65-
from .telemetry import TelemetryData, TelemetryField, generate_telemetry_data
64+
from .telemetry import TelemetryData, TelemetryField
6665
from .time_util import get_time_millis
6766

6867
if TYPE_CHECKING: # pragma: no cover
@@ -1214,18 +1213,14 @@ def _log_telemetry_job_data(
12141213
ts = get_time_millis()
12151214
try:
12161215
self._connection._log_telemetry(
1217-
TelemetryData(
1218-
generate_telemetry_data(
1219-
from_dict={
1220-
TelemetryField.KEY_TYPE.value: telemetry_field.value,
1221-
TelemetryField.KEY_SOURCE.value: self._connection.application
1222-
if self._connection
1223-
else CLIENT_NAME,
1224-
TelemetryField.KEY_SFQID.value: self._sfqid,
1225-
TelemetryField.KEY_VALUE.value: value,
1226-
}
1227-
),
1228-
ts,
1216+
TelemetryData.from_telemetry_data_dict(
1217+
from_dict={
1218+
TelemetryField.KEY_TYPE.value: telemetry_field.value,
1219+
TelemetryField.KEY_SFQID.value: self._sfqid,
1220+
TelemetryField.KEY_VALUE.value: value,
1221+
},
1222+
timestamp=ts,
1223+
connection=self._connection,
12291224
)
12301225
)
12311226
except AttributeError:

src/snowflake/connector/errors.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
from .compat import BASE_EXCEPTION_CLASS
1616
from .secret_detector import SecretDetector
17-
from .telemetry import TelemetryData, TelemetryField, generate_telemetry_data
17+
from .telemetry import TelemetryData, TelemetryField
1818
from .telemetry_oob import TelemetryService
1919
from .time_util import get_time_millis
2020

@@ -119,25 +119,22 @@ def telemetry_msg(self) -> str | None:
119119

120120
def generate_telemetry_exception_data(self) -> dict[str, str]:
121121
"""Generate the data to send through telemetry."""
122-
123-
telemetry_data = generate_telemetry_data(
124-
from_dict={
125-
TelemetryField.KEY_STACKTRACE.value: SecretDetector.mask_secrets(
126-
self.telemetry_traceback
127-
)
128-
}
129-
)
122+
telemetry_data_dict = {
123+
TelemetryField.KEY_STACKTRACE.value: SecretDetector.mask_secrets(
124+
self.telemetry_traceback
125+
)
126+
}
130127
telemetry_msg = self.telemetry_msg()
131128
if self.sfqid:
132-
telemetry_data[TelemetryField.KEY_SFQID.value] = self.sfqid
129+
telemetry_data_dict[TelemetryField.KEY_SFQID.value] = self.sfqid
133130
if self.sqlstate:
134-
telemetry_data[TelemetryField.KEY_SQLSTATE.value] = self.sqlstate
131+
telemetry_data_dict[TelemetryField.KEY_SQLSTATE.value] = self.sqlstate
135132
if telemetry_msg:
136-
telemetry_data[TelemetryField.KEY_REASON.value] = telemetry_msg
133+
telemetry_data_dict[TelemetryField.KEY_REASON.value] = telemetry_msg
137134
if self.errno:
138-
telemetry_data[TelemetryField.KEY_ERROR_NUMBER.value] = str(self.errno)
135+
telemetry_data_dict[TelemetryField.KEY_ERROR_NUMBER.value] = str(self.errno)
139136

140-
return telemetry_data
137+
return telemetry_data_dict
141138

142139
def send_exception_telemetry(
143140
self,
@@ -159,7 +156,11 @@ def send_exception_telemetry(
159156
telemetry_data[TelemetryField.KEY_EXCEPTION.value] = self.__class__.__name__
160157
ts = get_time_millis()
161158
try:
162-
connection._log_telemetry(TelemetryData(telemetry_data, ts))
159+
connection._log_telemetry(
160+
TelemetryData.from_telemetry_data_dict(
161+
from_dict=telemetry_data, timestamp=ts, connection=connection
162+
)
163+
)
163164
except AttributeError:
164165
logger.debug("Cursor failed to log to telemetry.", exc_info=True)
165166
elif connection is None:
@@ -176,13 +177,13 @@ def exception_telemetry(
176177
) -> None:
177178
"""Main method to generate and send telemetry data for exceptions."""
178179
try:
179-
telemetry_data = self.generate_telemetry_exception_data()
180+
telemetry_data_dict = self.generate_telemetry_exception_data()
180181
if cursor is not None:
181-
self.send_exception_telemetry(cursor.connection, telemetry_data)
182+
self.send_exception_telemetry(cursor.connection, telemetry_data_dict)
182183
elif connection is not None:
183-
self.send_exception_telemetry(connection, telemetry_data)
184+
self.send_exception_telemetry(connection, telemetry_data_dict)
184185
else:
185-
self.send_exception_telemetry(None, telemetry_data)
186+
self.send_exception_telemetry(None, telemetry_data_dict)
186187
except Exception:
187188
# Do nothing but log if sending telemetry fails
188189
logger.debug("Sending exception telemetry failed")

src/snowflake/connector/ocsp_snowflake.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@
7070

7171
from . import constants
7272
from .cache import SFDictCache, SFDictFileCache
73-
from .telemetry import TelemetryField
74-
from .telemetry import generate_telemetry_data as generate_telemetry_data_base
73+
from .telemetry import TelemetryField, generate_telemetry_data_dict
7574

7675
try:
7776
OCSP_CACHE: SFDictFileCache[
@@ -225,7 +224,7 @@ def generate_telemetry_data(
225224
self, event_type: str, urgent: bool = False
226225
) -> dict[str, Any]:
227226
_, exception, _ = sys.exc_info()
228-
telemetry_data = generate_telemetry_data_base(
227+
telemetry_data = generate_telemetry_data_dict(
229228
from_dict={
230229
TelemetryField.KEY_OOB_EVENT_TYPE.value: event_type,
231230
TelemetryField.KEY_OOB_EVENT_SUB_TYPE.value: self.event_sub_type,

src/snowflake/connector/telemetry.py

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .test_util import ENABLE_TELEMETRY_LOG, rt_plain_logger
1616

1717
if TYPE_CHECKING:
18+
from .connection import SnowflakeConnection
1819
from .network import SnowflakeRestful
1920

2021
logger = logging.getLogger(__name__)
@@ -90,6 +91,29 @@ def __init__(self, message, timestamp):
9091
self.message = message
9192
self.timestamp = timestamp
9293

94+
@classmethod
95+
def from_telemetry_data_dict(
96+
cls,
97+
from_dict: dict,
98+
timestamp: int,
99+
connection: SnowflakeConnection | None = None,
100+
is_oob_telemetry: bool = False,
101+
):
102+
"""
103+
Generate telemetry data with driver info from given dict and timestamp.
104+
It takes an optional connection object to read data from.
105+
It also takes a boolean is_oob_telemetry to indicate whether it's for out-of-band telemetry, as
106+
naming of keys for driver and version is different from the ones of in-band telemetry.
107+
"""
108+
return cls(
109+
generate_telemetry_data_dict(
110+
from_dict=(from_dict or {}),
111+
connection=connection,
112+
is_oob_telemetry=is_oob_telemetry,
113+
),
114+
timestamp,
115+
)
116+
93117
def to_dict(self):
94118
return {"message": self.message, "timestamp": str(self.timestamp)}
95119

@@ -195,17 +219,25 @@ def buffer_size(self):
195219
return len(self._log_batch)
196220

197221

198-
def generate_telemetry_data(
199-
from_dict: dict | None = None, is_oob_telemetry: bool = False
222+
def generate_telemetry_data_dict(
223+
from_dict: dict | None = None,
224+
connection: SnowflakeConnection | None = None,
225+
is_oob_telemetry: bool = False,
200226
) -> dict[str, Any]:
201227
"""
202-
Generate telemetry data with driver info. The method also takes an optional dict to update from.
228+
Generate telemetry data with driver info.
229+
The method also takes an optional dict to update from and optional connection object to read data from.
230+
It also takes a boolean is_oob_telemetry to indicate whether it's for out-of-band telemetry, as
231+
naming of keys for driver and version is different from the ones of in-band telemetry.
203232
"""
204233
from_dict = from_dict or {}
205234
return (
206235
{
207236
TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
208237
TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
238+
TelemetryField.KEY_SOURCE.value: connection.application
239+
if connection
240+
else CLIENT_NAME,
209241
**from_dict,
210242
}
211243
if not is_oob_telemetry

src/snowflake/connector/telemetry_oob.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .compat import OK
1616
from .description import CLIENT_NAME, SNOWFLAKE_CONNECTOR_VERSION
1717
from .secret_detector import SecretDetector
18-
from .telemetry import TelemetryField, generate_telemetry_data
18+
from .telemetry import TelemetryField, generate_telemetry_data_dict
1919
from .test_util import ENABLE_TELEMETRY_LOG, rt_plain_logger
2020
from .vendored import requests
2121

@@ -388,7 +388,7 @@ def log_http_request_error(
388388
if self.enabled:
389389
response_status_code = -1
390390
# This mimics the output of HttpRequestBase.toString() from JBDC
391-
telemetry_data = generate_telemetry_data(
391+
telemetry_data = generate_telemetry_data_dict(
392392
from_dict={
393393
TelemetryField.KEY_OOB_REQUEST.value: f"{method} {url}",
394394
TelemetryField.KEY_OOB_SQL_STATE.value: sqlstate,

test/unit/test_telemetry.py

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,26 +135,29 @@ def test_telemetry_send_batch_disabled():
135135
assert rest_call.call_count == 0
136136

137137

138-
def test_generate_telemetry_with_driver_info():
139-
assert snowflake.connector.telemetry.generate_telemetry_data() == {
138+
def test_generate_telemetry_data_dict_with_basic_info():
139+
assert snowflake.connector.telemetry.generate_telemetry_data_dict() == {
140140
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
141141
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
142+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: CLIENT_NAME,
142143
}
143144

144-
assert snowflake.connector.telemetry.generate_telemetry_data(from_dict={}) == {
145+
assert snowflake.connector.telemetry.generate_telemetry_data_dict(from_dict={}) == {
145146
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
146147
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
148+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: CLIENT_NAME,
147149
}
148150

149-
assert snowflake.connector.telemetry.generate_telemetry_data(
151+
assert snowflake.connector.telemetry.generate_telemetry_data_dict(
150152
from_dict={"key": "value"}
151153
) == {
152154
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
153155
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
156+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: CLIENT_NAME,
154157
"key": "value",
155158
}
156159

157-
assert snowflake.connector.telemetry.generate_telemetry_data(
160+
assert snowflake.connector.telemetry.generate_telemetry_data_dict(
158161
from_dict={
159162
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: "CUSTOM_CLIENT_NAME",
160163
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: "1.2.3",
@@ -163,5 +166,76 @@ def test_generate_telemetry_with_driver_info():
163166
) == {
164167
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: "CUSTOM_CLIENT_NAME",
165168
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: "1.2.3",
169+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: CLIENT_NAME,
166170
"key": "value",
167171
}
172+
173+
mock_connection = Mock()
174+
mock_connection.application = "test_application"
175+
assert snowflake.connector.telemetry.generate_telemetry_data_dict(
176+
from_dict={
177+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: "CUSTOM_CLIENT_NAME",
178+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: "1.2.3",
179+
"key": "value",
180+
},
181+
connection=mock_connection,
182+
) == {
183+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: "CUSTOM_CLIENT_NAME",
184+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: "1.2.3",
185+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: mock_connection.application,
186+
"key": "value",
187+
}
188+
189+
190+
def test_generate_telemetry_data():
191+
telemetry_data = (
192+
snowflake.connector.telemetry.TelemetryData.from_telemetry_data_dict(
193+
from_dict={}, timestamp=123
194+
)
195+
)
196+
assert (
197+
telemetry_data.message
198+
== {
199+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
200+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
201+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: CLIENT_NAME,
202+
}
203+
and telemetry_data.timestamp == 123
204+
)
205+
206+
mock_connection = Mock()
207+
mock_connection.application = "test_application"
208+
telemetry_data = (
209+
snowflake.connector.telemetry.TelemetryData.from_telemetry_data_dict(
210+
from_dict={},
211+
timestamp=123,
212+
connection=mock_connection,
213+
)
214+
)
215+
assert (
216+
telemetry_data.message
217+
== {
218+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
219+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
220+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: mock_connection.application,
221+
}
222+
and telemetry_data.timestamp == 123
223+
)
224+
225+
telemetry_data = (
226+
snowflake.connector.telemetry.TelemetryData.from_telemetry_data_dict(
227+
from_dict={"key": "value"},
228+
timestamp=123,
229+
connection=mock_connection,
230+
)
231+
)
232+
assert (
233+
telemetry_data.message
234+
== {
235+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_TYPE.value: CLIENT_NAME,
236+
snowflake.connector.telemetry.TelemetryField.KEY_DRIVER_VERSION.value: SNOWFLAKE_CONNECTOR_VERSION,
237+
snowflake.connector.telemetry.TelemetryField.KEY_SOURCE.value: mock_connection.application,
238+
"key": "value",
239+
}
240+
and telemetry_data.timestamp == 123
241+
)

0 commit comments

Comments
 (0)