10
10
from abc import ABC , abstractmethod
11
11
from collections .abc import Generator , Sequence
12
12
from dataclasses import dataclass
13
- from enum import Enum
14
13
from pathlib import Path
15
- from threading import RLock
16
14
from typing import Any , TypeVar
17
15
16
+ from .status import Status
18
17
from .vendor .mureq .mureq import HTTPException , Response , request
19
18
20
19
CONTENT_TYPE_JSON = "application/json;charset=utf-8"
30
29
HTTP_BAD_REQUEST = 400
31
30
32
31
33
- class StatusValue (Enum ):
34
- EMPTY = ""
35
- OK = "OK"
36
- GENERIC_ERROR = "GENERIC_ERROR"
37
- INVALID_ARGS_ERROR = "INVALID_ARGS_ERROR"
38
- EEC_CONNECTION_ERROR = "EEC_CONNECTION_ERROR"
39
- INVALID_CONFIG_ERROR = "INVALID_CONFIG_ERROR"
40
- AUTHENTICATION_ERROR = "AUTHENTICATION_ERROR"
41
- DEVICE_CONNECTION_ERROR = "DEVICE_CONNECTION_ERROR"
42
- WARNING = "WARNING"
43
- UNKNOWN_ERROR = "UNKNOWN_ERROR"
44
-
45
-
46
- class IgnoreStatus :
47
- pass
48
-
49
-
50
- class Status :
51
- def __init__ (self , status : StatusValue = StatusValue .EMPTY , message : str = "" , timestamp : int | None = None ):
52
- self .status = status
53
- self .message = message
54
- self .timestamp = timestamp
55
-
56
- def to_json (self ) -> dict :
57
- status = {"status" : self .status .value , "message" : self .message }
58
- if self .timestamp :
59
- status ["timestamp" ] = self .timestamp # type: ignore
60
- return status
61
-
62
- def __repr__ (self ):
63
- return json .dumps (self .to_json ())
64
-
65
- def is_error (self ) -> bool :
66
- # WARNING is treated as an error
67
- return self .status not in (StatusValue .OK , StatusValue .EMPTY )
68
-
69
- def is_warning (self ) -> bool :
70
- return self .status == StatusValue .WARNING
71
-
72
-
73
- class MultiStatus :
74
- def __init__ (self ):
75
- self .statuses : list [Status ] = []
76
-
77
- def add_status (self , status : StatusValue , message ):
78
- self .statuses .append (Status (status , message ))
79
-
80
- def build (self ) -> Status :
81
- ret = Status (StatusValue .OK )
82
- if len (self .statuses ) == 0 :
83
- return ret
84
-
85
- messages = []
86
- all_ok = True
87
- all_err = True
88
- any_warning = False
89
-
90
- for stored_status in self .statuses :
91
- if stored_status .message != "" :
92
- messages .append (stored_status .message )
93
-
94
- if stored_status .is_warning ():
95
- any_warning = True
96
-
97
- if stored_status .is_error ():
98
- all_ok = False
99
- else :
100
- all_err = False
101
-
102
- ret .message = ", " .join (messages )
103
-
104
- if any_warning :
105
- ret .status = StatusValue .WARNING
106
- elif all_ok :
107
- ret .status = StatusValue .OK
108
- elif all_err :
109
- ret .status = StatusValue .GENERIC_ERROR
110
- else :
111
- ret .status = StatusValue .WARNING
112
-
113
- return ret
114
-
115
-
116
- class EndpointStatus :
117
- def __init__ (self , endpoint_hint : str , short_status : StatusValue , message : str ):
118
- self .endpoint = endpoint_hint
119
- self .status : StatusValue = short_status
120
- self .message = message
121
-
122
- def __str__ (self ):
123
- return str (self .__dict__ )
124
-
125
-
126
- class EndpointStatuses :
127
- class TooManyEndpointStatusesError (Exception ):
128
- pass
129
-
130
- class MergeConflictError (Exception ):
131
- def __init__ (self , first : EndpointStatus , second : EndpointStatus ):
132
- super ().__init__ (f"Endpoint Statuses conflict while merging - first: { first } ; second: { second } " )
133
-
134
- def __init__ (self , total_endpoints_number : int ):
135
- self ._lock = RLock ()
136
- self ._faulty_endpoints : dict [str , EndpointStatus ] = {}
137
- self ._num_endpoints = total_endpoints_number
138
-
139
- def add_endpoint_status (self , status : EndpointStatus ):
140
- with self ._lock :
141
- if status .status == StatusValue .OK :
142
- self .clear_endpoint_error (status .endpoint )
143
- else :
144
- if len (self ._faulty_endpoints ) == self ._num_endpoints :
145
- message = "Cannot add another endpoint status. \
146
- The number of reported statuses already has reached preconfigured maximum of {self._num_endpoints} endpoints."
147
- raise EndpointStatuses .TooManyEndpointStatusesError (message )
148
-
149
- self ._faulty_endpoints [status .endpoint ] = status
150
-
151
- def clear_endpoint_error (self , endpoint_hint : str ):
152
- with self ._lock :
153
- try :
154
- del self ._faulty_endpoints [endpoint_hint ]
155
- except KeyError :
156
- pass
157
-
158
- def merge (self , other : EndpointStatuses ):
159
- with self ._lock :
160
- with other ._lock :
161
- self ._num_endpoints += other ._num_endpoints
162
-
163
- for endpoint , status in other ._faulty_endpoints .items ():
164
- if endpoint not in self ._faulty_endpoints .keys ():
165
- self ._faulty_endpoints [endpoint ] = status
166
- else :
167
- self ._num_endpoints -= 1
168
- raise EndpointStatuses .MergeConflictError (
169
- self ._faulty_endpoints [endpoint ], other ._faulty_endpoints [endpoint ]
170
- )
171
-
172
- def build_common_status (self ) -> Status :
173
- with self ._lock :
174
- ok_count = self ._num_endpoints - len (self ._faulty_endpoints )
175
- nok_count = len (self ._faulty_endpoints )
176
-
177
- if nok_count == 0 :
178
- return Status (StatusValue .OK , f"Endpoints OK: { self ._num_endpoints } NOK: 0" )
179
-
180
- error_messages = []
181
- for ep_status in self ._faulty_endpoints .values ():
182
- error_messages .append (f"{ ep_status .endpoint } - { ep_status .status .value } { ep_status .message } " )
183
- common_msg = ", " .join (error_messages )
184
-
185
- # Determine status value
186
- all_endpoints_faulty = nok_count == self ._num_endpoints
187
- has_warning_status = StatusValue .WARNING in [
188
- ep_status .status for ep_status in self ._faulty_endpoints .values ()
189
- ]
190
-
191
- if all_endpoints_faulty and not has_warning_status :
192
- status_value = StatusValue .GENERIC_ERROR
193
- else :
194
- status_value = StatusValue .WARNING
195
-
196
- message = f"Endpoints OK: { ok_count } NOK: { nok_count } NOK_reported_errors: { common_msg } "
197
- return Status (status = status_value , message = message )
198
-
199
-
200
32
class CommunicationClient (ABC ):
201
33
"""
202
34
Abstract class for extension communication
@@ -250,6 +82,10 @@ def get_cluster_time_diff(self) -> int:
250
82
def send_dt_event (self , event : dict ) -> None :
251
83
pass
252
84
85
+ @abstractmethod
86
+ def send_sfm_logs (self , sfm_logs : dict | list [dict ]) -> list [dict | None ]:
87
+ pass
88
+
253
89
254
90
class HttpClient (CommunicationClient ):
255
91
"""
@@ -261,6 +97,7 @@ def __init__(self, base_url: str, datasource_id: str, id_token_file_path: str, l
261
97
self ._extension_config_url = f"{ base_url } /extconfig/{ datasource_id } "
262
98
self ._metric_url = f"{ base_url } /mint/{ datasource_id } "
263
99
self ._sfm_url = f"{ base_url } /sfm/{ datasource_id } "
100
+ self ._sfm_logs_url = f"{ base_url } /sfmlogs/{ datasource_id } "
264
101
self ._keep_alive_url = f"{ base_url } /alive/{ datasource_id } "
265
102
self ._timediff_url = f"{ base_url } /timediffms"
266
103
self ._events_url = f"{ base_url } /logs/{ datasource_id } "
@@ -417,7 +254,13 @@ def send_metrics(self, mint_lines: list[str]) -> list[MintResponse]:
417
254
418
255
def send_events (self , events : dict | list [dict ], eec_enrichment : bool = True ) -> list [dict | None ]:
419
256
self .logger .debug (f"Sending log events: { events } " )
257
+ return self ._send_events (self ._events_url , events , eec_enrichment )
258
+
259
+ def send_sfm_logs (self , sfm_logs : dict | list [dict ]):
260
+ self .logger .debug (f"Sending SFM logs: { sfm_logs } " )
261
+ return self ._send_events (self ._sfm_logs_url , sfm_logs )
420
262
263
+ def _send_events (self , url , events : dict | list [dict ], eec_enrichment : bool = True ) -> list [dict | None ]:
421
264
responses = []
422
265
if isinstance (events , dict ):
423
266
events = [events ]
@@ -426,7 +269,7 @@ def send_events(self, events: dict | list[dict], eec_enrichment: bool = True) ->
426
269
for batch in batches :
427
270
try :
428
271
eec_response = self ._make_request (
429
- self . _events_url ,
272
+ url ,
430
273
"POST" ,
431
274
batch ,
432
275
extra_headers = {"Content-Type" : CONTENT_TYPE_JSON , "eec-enrichment" : str (eec_enrichment ).lower ()},
@@ -583,6 +426,17 @@ def replace_secrets_in_activation_config(self, secrets: dict, activation_config_
583
426
584
427
return activation_config_string
585
428
429
+ def send_sfm_logs (self , sfm_logs : dict | list [dict ]) -> list [dict | None ]:
430
+ if isinstance (sfm_logs , dict ):
431
+ sfm_logs = [sfm_logs ]
432
+
433
+ self .logger .info (f"send_sfm_logs: { len (sfm_logs )} logs" )
434
+
435
+ if self .print_metrics :
436
+ for log in sfm_logs :
437
+ self .logger .info (f"send_sfm_log: { log } " )
438
+ return []
439
+
586
440
587
441
def divide_into_batches (
588
442
items : Sequence [dict | str ], max_size_bytes : int , join_with : str | None = None
0 commit comments