dt-extensions-sdk 1.3.0__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dt_extensions_sdk-1.3.0.dist-info → dt_extensions_sdk-1.4.0.dist-info}/METADATA +4 -3
- {dt_extensions_sdk-1.3.0.dist-info → dt_extensions_sdk-1.4.0.dist-info}/RECORD +9 -9
- {dt_extensions_sdk-1.3.0.dist-info → dt_extensions_sdk-1.4.0.dist-info}/WHEEL +1 -1
- dynatrace_extension/__about__.py +1 -1
- dynatrace_extension/sdk/callback.py +8 -3
- dynatrace_extension/sdk/communication.py +23 -0
- dynatrace_extension/sdk/extension.py +46 -11
- {dt_extensions_sdk-1.3.0.dist-info → dt_extensions_sdk-1.4.0.dist-info}/entry_points.txt +0 -0
- {dt_extensions_sdk-1.3.0.dist-info → dt_extensions_sdk-1.4.0.dist-info}/licenses/LICENSE.txt +0 -0
@@ -1,11 +1,12 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: dt-extensions-sdk
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Project-URL: Documentation, https://github.com/dynatrace-extensions/dt-extensions-python-sdk#readme
|
5
5
|
Project-URL: Issues, https://github.com/dynatrace-extensions/dt-extensions-python-sdk/issues
|
6
6
|
Project-URL: Source, https://github.com/dynatrace-extensions/dt-extensions-python-sdk
|
7
7
|
Author-email: dlopes7 <davidribeirolopes@gmail.com>
|
8
|
-
License: MIT
|
8
|
+
License-Expression: MIT
|
9
|
+
License-File: LICENSE.txt
|
9
10
|
Classifier: Development Status :: 4 - Beta
|
10
11
|
Classifier: Programming Language :: Python
|
11
12
|
Classifier: Programming Language :: Python :: 3.10
|
@@ -1,4 +1,4 @@
|
|
1
|
-
dynatrace_extension/__about__.py,sha256=
|
1
|
+
dynatrace_extension/__about__.py,sha256=0eF7oFPAs4l6aF4bObZHJnLcIaLQ07bZ0ozH7nzz4HY,110
|
2
2
|
dynatrace_extension/__init__.py,sha256=BvQuknmA7ti3WJi3zEXZfY7aAxJrie37VNitWICsUvI,752
|
3
3
|
dynatrace_extension/cli/__init__.py,sha256=HCboY_eJPoqjFmoPDsBL8Jk6aNvank8K7JpkVrgwzUM,123
|
4
4
|
dynatrace_extension/cli/main.py,sha256=Z8gFcp0vIMBkzV6jUd5mwvP1U0JcLqAHoMLJw_6puz4,18260
|
@@ -16,10 +16,10 @@ dynatrace_extension/cli/create/extension_template/extension_name/__init__.py.tem
|
|
16
16
|
dynatrace_extension/cli/create/extension_template/extension_name/__main__.py.template,sha256=NYuZ6BWZ6HOs4TCuboh6S0nuYp3F8MXW2lmjSpdREEA,1205
|
17
17
|
dynatrace_extension/sdk/__init__.py,sha256=RsqQ1heGyCmSK3fhuEKAcxQIRCg4gEK0-eSkIehL5Nc,86
|
18
18
|
dynatrace_extension/sdk/activation.py,sha256=goTbT1tD2kn8xfyXFdTy_cTZNcFPJpgbvQM8HOzKECA,1480
|
19
|
-
dynatrace_extension/sdk/callback.py,sha256=
|
20
|
-
dynatrace_extension/sdk/communication.py,sha256=
|
19
|
+
dynatrace_extension/sdk/callback.py,sha256=eMpC0F3fCI82mWHIFgmy9QmKl7Kq_9dSaanHdV6o7hA,6511
|
20
|
+
dynatrace_extension/sdk/communication.py,sha256=QkJgEBblOen-jmvsb3ZfYZYglYUc7jHbkEgPtRj9l6w,19123
|
21
21
|
dynatrace_extension/sdk/event.py,sha256=J261imbFKpxfuAQ6Nfu3RRcsIQKKivy6fme1nww2g-8,388
|
22
|
-
dynatrace_extension/sdk/extension.py,sha256=
|
22
|
+
dynatrace_extension/sdk/extension.py,sha256=59hg3aUXbrkhvEB7za8jujv1LZQ4sSx4ot9scijadQU,44926
|
23
23
|
dynatrace_extension/sdk/helper.py,sha256=ZNrO9ao2hE3KQ934vAYD74k0fCr6QTG-_bAvbk9-hi8,6562
|
24
24
|
dynatrace_extension/sdk/metric.py,sha256=7VClzJCFJNDCxA-d69uTu1pdPtDZBTwq7fbafs_L6nQ,3690
|
25
25
|
dynatrace_extension/sdk/runtime.py,sha256=jyYsM1x-gMnW68eWq8IoZZZBarHgIcr_nVeGDDgpRDk,2802
|
@@ -28,8 +28,8 @@ dynatrace_extension/sdk/vendor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5
|
|
28
28
|
dynatrace_extension/sdk/vendor/mureq/LICENSE,sha256=8AVcgZgiT_mvK1fOofXtRRr2f1dRXS_K21NuxQgP4VM,671
|
29
29
|
dynatrace_extension/sdk/vendor/mureq/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
30
|
dynatrace_extension/sdk/vendor/mureq/mureq.py,sha256=znF4mvzk5L03CLNozRz8UpK-fMijmSkObDFwlbhwLUg,14656
|
31
|
-
dt_extensions_sdk-1.
|
32
|
-
dt_extensions_sdk-1.
|
33
|
-
dt_extensions_sdk-1.
|
34
|
-
dt_extensions_sdk-1.
|
35
|
-
dt_extensions_sdk-1.
|
31
|
+
dt_extensions_sdk-1.4.0.dist-info/METADATA,sha256=LYTXYtBm33nrBL_jz22oNjpB0mRIhIdr27pFuHgNzao,2685
|
32
|
+
dt_extensions_sdk-1.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
33
|
+
dt_extensions_sdk-1.4.0.dist-info/entry_points.txt,sha256=pweyOCgENGHjOlT6_kXYaBPOrE3p18K0UettqnNlnoE,55
|
34
|
+
dt_extensions_sdk-1.4.0.dist-info/licenses/LICENSE.txt,sha256=3Zihv0lOVYHNfDkJC-tUAU6euP9r2NexsDW4w-zqgVk,1078
|
35
|
+
dt_extensions_sdk-1.4.0.dist-info/RECORD,,
|
dynatrace_extension/__about__.py
CHANGED
@@ -9,7 +9,7 @@ from timeit import default_timer as timer
|
|
9
9
|
from typing import Callable, Dict, Optional, Tuple
|
10
10
|
|
11
11
|
from .activation import ActivationType
|
12
|
-
from .communication import Status, StatusValue
|
12
|
+
from .communication import MultiStatus, Status, StatusValue
|
13
13
|
|
14
14
|
|
15
15
|
class WrappedCallback:
|
@@ -60,8 +60,13 @@ class WrappedCallback:
|
|
60
60
|
start_time = timer()
|
61
61
|
failed = False
|
62
62
|
try:
|
63
|
-
self.callback(*self.callback_args, **self.callback_kwargs)
|
64
|
-
|
63
|
+
ret = self.callback(*self.callback_args, **self.callback_kwargs)
|
64
|
+
if isinstance(ret, Status):
|
65
|
+
self.status = ret
|
66
|
+
elif isinstance(ret, MultiStatus):
|
67
|
+
self.status = ret.build()
|
68
|
+
else:
|
69
|
+
self.status = Status(StatusValue.OK)
|
65
70
|
except Exception as e:
|
66
71
|
failed = True
|
67
72
|
self.logger.exception(f"Error running callback {self}: {e!r}")
|
@@ -59,6 +59,29 @@ class Status:
|
|
59
59
|
return self.status not in (StatusValue.OK, StatusValue.EMPTY)
|
60
60
|
|
61
61
|
|
62
|
+
class MultiStatus:
|
63
|
+
|
64
|
+
def __init__(self):
|
65
|
+
self.statuses = []
|
66
|
+
|
67
|
+
def add_status(self, status: StatusValue, message):
|
68
|
+
self.statuses.append(Status(status, message))
|
69
|
+
|
70
|
+
def build(self) -> Status:
|
71
|
+
ret = Status(StatusValue.OK)
|
72
|
+
if len(self.statuses) == 0:
|
73
|
+
return ret
|
74
|
+
|
75
|
+
messages = []
|
76
|
+
for stored_status in self.statuses:
|
77
|
+
print(stored_status) # noqa: T201
|
78
|
+
if stored_status.is_error():
|
79
|
+
ret.status = stored_status.status
|
80
|
+
messages.append(stored_status.message)
|
81
|
+
ret.message = "\n".join(messages)
|
82
|
+
return ret
|
83
|
+
|
84
|
+
|
62
85
|
class CommunicationClient(ABC):
|
63
86
|
"""
|
64
87
|
Abstract class for extension communication
|
@@ -32,6 +32,7 @@ TIME_DIFF_INTERVAL = timedelta(seconds=60)
|
|
32
32
|
|
33
33
|
CALLBACKS_THREAD_POOL_SIZE = 100
|
34
34
|
INTERNAL_THREAD_POOL_SIZE = 20
|
35
|
+
HEARTBEAT_THREAD_POOL_SIZE = 10
|
35
36
|
|
36
37
|
RFC_3339_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
|
37
38
|
DATASOURCE_TYPE = "python"
|
@@ -214,17 +215,23 @@ class Extension:
|
|
214
215
|
"timediff": datetime.now() + TIME_DIFF_INTERVAL,
|
215
216
|
"heartbeat": datetime.now() + HEARTBEAT_INTERVAL,
|
216
217
|
"metrics": datetime.now() + METRIC_SENDING_INTERVAL,
|
218
|
+
"events": datetime.now() + METRIC_SENDING_INTERVAL,
|
217
219
|
"sfm_metrics": datetime.now() + SFM_METRIC_SENDING_INTERVAL,
|
218
220
|
}
|
219
221
|
|
220
222
|
# Executors for the callbacks and internal methods
|
221
223
|
self._callbacks_executor = ThreadPoolExecutor(max_workers=CALLBACKS_THREAD_POOL_SIZE)
|
222
224
|
self._internal_executor = ThreadPoolExecutor(max_workers=INTERNAL_THREAD_POOL_SIZE)
|
225
|
+
self._heartbeat_executor = ThreadPoolExecutor(max_workers=HEARTBEAT_THREAD_POOL_SIZE)
|
223
226
|
|
224
227
|
# Extension metrics
|
225
228
|
self._metrics_lock = RLock()
|
226
229
|
self._metrics: List[str] = []
|
227
230
|
|
231
|
+
# Extension logs
|
232
|
+
self._logs_lock = RLock()
|
233
|
+
self._logs: List[dict] = []
|
234
|
+
|
228
235
|
# Self monitoring metrics
|
229
236
|
self._sfm_metrics_lock = Lock()
|
230
237
|
self._callbackSfmReport: Dict[str, WrappedCallback] = {}
|
@@ -503,6 +510,7 @@ class Extension:
|
|
503
510
|
properties: Optional[dict] = None,
|
504
511
|
timestamp: Optional[datetime] = None,
|
505
512
|
severity: Union[Severity, str] = Severity.INFO,
|
513
|
+
send_immediately: bool = False,
|
506
514
|
) -> None:
|
507
515
|
"""Report an event using log ingest.
|
508
516
|
|
@@ -512,6 +520,7 @@ class Extension:
|
|
512
520
|
properties: A dictionary of extra event properties
|
513
521
|
timestamp: The timestamp of the event, defaults to the current time
|
514
522
|
severity: The severity of the event, defaults to Severity.INFO
|
523
|
+
send_immediately: Option to directly schedule log to be sent without batching
|
515
524
|
"""
|
516
525
|
if timestamp is None:
|
517
526
|
timestamp = datetime.now(tz=timezone.utc)
|
@@ -528,7 +537,7 @@ class Extension:
|
|
528
537
|
**self._metadata,
|
529
538
|
**properties,
|
530
539
|
}
|
531
|
-
self._send_events(event)
|
540
|
+
self._send_events(event, send_immediately=send_immediately)
|
532
541
|
|
533
542
|
def report_dt_event(
|
534
543
|
self,
|
@@ -633,7 +642,7 @@ class Extension:
|
|
633
642
|
raise ValueError(msg)
|
634
643
|
self._send_dt_event(event)
|
635
644
|
|
636
|
-
def report_log_event(self, log_event: dict):
|
645
|
+
def report_log_event(self, log_event: dict, send_immediately: bool = False):
|
637
646
|
"""Report a custom log event using log ingest.
|
638
647
|
|
639
648
|
Note:
|
@@ -641,25 +650,28 @@ class Extension:
|
|
641
650
|
|
642
651
|
Args:
|
643
652
|
log_event: The log event dictionary.
|
653
|
+
send_immediately: Option to directly schedule log to be sent without batching
|
644
654
|
"""
|
645
|
-
self._send_events(log_event)
|
655
|
+
self._send_events(log_event, send_immediately=send_immediately)
|
646
656
|
|
647
|
-
def report_log_events(self, log_events: List[dict]):
|
657
|
+
def report_log_events(self, log_events: List[dict], send_immediately: bool = False):
|
648
658
|
"""Report a list of custom log events using log ingest.
|
649
659
|
|
650
660
|
Args:
|
651
661
|
log_events: The list of log events
|
662
|
+
send_immediately: Option to directly schedule log to be sent without batching
|
652
663
|
"""
|
653
|
-
self._send_events(log_events)
|
664
|
+
self._send_events(log_events, send_immediately=send_immediately)
|
654
665
|
|
655
|
-
def report_log_lines(self, log_lines: List[Union[str, bytes]]):
|
666
|
+
def report_log_lines(self, log_lines: List[Union[str, bytes]], send_immediately: bool = False):
|
656
667
|
"""Report a list of log lines using log ingest
|
657
668
|
|
658
669
|
Args:
|
659
670
|
log_lines: The list of log lines
|
671
|
+
send_immediately: Option to directly schedule log to be sent without batching
|
660
672
|
"""
|
661
673
|
events = [{"content": line} for line in log_lines]
|
662
|
-
self._send_events(events)
|
674
|
+
self._send_events(events, send_immediately=send_immediately)
|
663
675
|
|
664
676
|
@property
|
665
677
|
def enabled_feature_sets(self) -> dict[str, list[str]]:
|
@@ -817,6 +829,7 @@ class Extension:
|
|
817
829
|
for callback in self._scheduled_callbacks_before_run:
|
818
830
|
self._schedule_callback(callback)
|
819
831
|
self._metrics_iteration()
|
832
|
+
self._events_iteration()
|
820
833
|
self._sfm_metrics_iteration()
|
821
834
|
self._timediff_iteration()
|
822
835
|
self._scheduler.run()
|
@@ -827,7 +840,7 @@ class Extension:
|
|
827
840
|
self._scheduler.enterabs(next_timestamp, 1, self._timediff_iteration)
|
828
841
|
|
829
842
|
def _heartbeat_iteration(self):
|
830
|
-
self.
|
843
|
+
self._heartbeat_executor.submit(self._heartbeat)
|
831
844
|
next_timestamp = self._get_and_set_next_internal_callback_timestamp("heartbeat", HEARTBEAT_INTERVAL)
|
832
845
|
self._scheduler.enterabs(next_timestamp, 2, self._heartbeat_iteration)
|
833
846
|
|
@@ -836,6 +849,11 @@ class Extension:
|
|
836
849
|
next_timestamp = self._get_and_set_next_internal_callback_timestamp("metrics", METRIC_SENDING_INTERVAL)
|
837
850
|
self._scheduler.enterabs(next_timestamp, 1, self._metrics_iteration)
|
838
851
|
|
852
|
+
def _events_iteration(self):
|
853
|
+
self._internal_executor.submit(self._send_buffered_events)
|
854
|
+
next_timestamp = self._get_and_set_next_internal_callback_timestamp("events", METRIC_SENDING_INTERVAL)
|
855
|
+
self._scheduler.enterabs(next_timestamp, 1, self._events_iteration)
|
856
|
+
|
839
857
|
def _sfm_metrics_iteration(self):
|
840
858
|
self._internal_executor.submit(self._send_sfm_metrics)
|
841
859
|
next_timestamp = self._get_and_set_next_internal_callback_timestamp("sfm_metrics", SFM_METRIC_SENDING_INTERVAL)
|
@@ -976,7 +994,9 @@ class Extension:
|
|
976
994
|
if callback.status.is_error():
|
977
995
|
overall_status.status = callback.status.status
|
978
996
|
messages.append(f"{callback}: {callback.status.message}")
|
979
|
-
|
997
|
+
continue
|
998
|
+
if callback.status.message is not None and callback.status.message != "":
|
999
|
+
messages.append(f"{callback}: {callback.status.message}")
|
980
1000
|
overall_status.message = "\n".join(messages)
|
981
1001
|
return overall_status
|
982
1002
|
|
@@ -1040,8 +1060,23 @@ class Extension:
|
|
1040
1060
|
with self._internal_callbacks_results_lock:
|
1041
1061
|
self._internal_callbacks_results[self._send_events.__name__] = Status(StatusValue.GENERIC_ERROR, str(e))
|
1042
1062
|
|
1043
|
-
def _send_events(self, events: Union[dict, List[dict]]):
|
1044
|
-
|
1063
|
+
def _send_events(self, events: Union[dict, List[dict]], send_immediately: bool = False):
|
1064
|
+
if send_immediately:
|
1065
|
+
self._internal_executor.submit(self._send_events_internal, events)
|
1066
|
+
return
|
1067
|
+
with self._logs_lock:
|
1068
|
+
if isinstance(events, dict):
|
1069
|
+
self._logs.append(events)
|
1070
|
+
elif isinstance(events, list):
|
1071
|
+
self._logs.extend(events)
|
1072
|
+
else:
|
1073
|
+
self.logger.error(f"Invalid log format: {events}")
|
1074
|
+
|
1075
|
+
def _send_buffered_events(self):
|
1076
|
+
with self._logs_lock:
|
1077
|
+
if len(self._logs) > 0:
|
1078
|
+
self._send_events_internal(self._logs)
|
1079
|
+
self._logs = []
|
1045
1080
|
|
1046
1081
|
def _send_dt_event(self, event: dict[str, str | int | dict[str, str]]):
|
1047
1082
|
self._client.send_dt_event(event)
|
File without changes
|
{dt_extensions_sdk-1.3.0.dist-info → dt_extensions_sdk-1.4.0.dist-info}/licenses/LICENSE.txt
RENAMED
File without changes
|