1
1
from api_to_dataframe .models .retainer import retry_strategies , Strategies
2
2
from api_to_dataframe .models .get_data import GetData
3
- from api_to_dataframe .utils .logger import logger
4
- from otel_wrapper import OpenObservability
3
+ from api_to_dataframe .utils .logger import logger , telemetry
4
+ import time
5
5
6
6
7
7
class ClientBuilder :
@@ -35,16 +35,40 @@ def __init__( # pylint: disable=too-many-positional-arguments,too-many-argument
35
35
if headers is None :
36
36
headers = {}
37
37
if endpoint == "" :
38
- logger .error ("endpoint cannot be an empty string" )
38
+ error_msg = "endpoint cannot be an empty string"
39
+ logger .error (error_msg )
40
+ telemetry .logs ().new_log (
41
+ msg = error_msg ,
42
+ tags = {"component" : "ClientBuilder" , "method" : "__init__" },
43
+ level = 40 # ERROR level
44
+ )
39
45
raise ValueError
40
46
if not isinstance (retries , int ) or retries < 0 :
41
- logger .error ("retries must be a non-negative integer" )
47
+ error_msg = "retries must be a non-negative integer"
48
+ logger .error (error_msg )
49
+ telemetry .logs ().new_log (
50
+ msg = error_msg ,
51
+ tags = {"component" : "ClientBuilder" , "method" : "__init__" },
52
+ level = 40 # ERROR level
53
+ )
42
54
raise ValueError
43
55
if not isinstance (initial_delay , int ) or initial_delay < 0 :
44
- logger .error ("initial_delay must be a non-negative integer" )
56
+ error_msg = "initial_delay must be a non-negative integer"
57
+ logger .error (error_msg )
58
+ telemetry .logs ().new_log (
59
+ msg = error_msg ,
60
+ tags = {"component" : "ClientBuilder" , "method" : "__init__" },
61
+ level = 40 # ERROR level
62
+ )
45
63
raise ValueError
46
64
if not isinstance (connection_timeout , int ) or connection_timeout < 0 :
47
- logger .error ("connection_timeout must be a non-negative integer" )
65
+ error_msg = "connection_timeout must be a non-negative integer"
66
+ logger .error (error_msg )
67
+ telemetry .logs ().new_log (
68
+ msg = error_msg ,
69
+ tags = {"component" : "ClientBuilder" , "method" : "__init__" },
70
+ level = 40 # ERROR level
71
+ )
48
72
raise ValueError
49
73
50
74
self .endpoint = endpoint
@@ -53,9 +77,28 @@ def __init__( # pylint: disable=too-many-positional-arguments,too-many-argument
53
77
self .headers = headers
54
78
self .retries = retries
55
79
self .delay = initial_delay
56
- self ._o11y_wrapper = OpenObservability (application_name = "api-to-dataframe" ).get_wrapper ()
57
- self ._traces = self ._o11y_wrapper .traces ()
58
- self ._tracer = self ._traces .get_tracer ()
80
+
81
+ # Record client initialization metric
82
+ telemetry .metrics ().metric_increment (
83
+ name = "client.initialization" ,
84
+ tags = {
85
+ "endpoint" : endpoint ,
86
+ "retry_strategy" : retry_strategy .name ,
87
+ "connection_timeout" : str (connection_timeout )
88
+ }
89
+ )
90
+
91
+ # Log initialization
92
+ telemetry .logs ().new_log (
93
+ msg = f"ClientBuilder initialized with endpoint { endpoint } " ,
94
+ tags = {
95
+ "endpoint" : endpoint ,
96
+ "retry_strategy" : retry_strategy .name ,
97
+ "connection_timeout" : str (connection_timeout ),
98
+ "component" : "ClientBuilder"
99
+ },
100
+ level = 20 # INFO level
101
+ )
59
102
60
103
@retry_strategies
61
104
def get_api_data (self ):
@@ -69,16 +112,62 @@ def get_api_data(self):
69
112
Returns:
70
113
dict: The JSON response from the API as a dictionary.
71
114
"""
72
-
73
- with self ._tracer .start_as_current_span ("get_last_quote" ) as span :
115
+ # Use the telemetry spans with context manager
116
+ with telemetry .traces ().span_in_context ("get_api_data" ) as (span , _ ):
117
+ # Add span attributes
74
118
span .set_attribute ("endpoint" , self .endpoint )
75
-
119
+ span .set_attribute ("retry_strategy" , self .retry_strategy .name )
120
+ span .set_attribute ("connection_timeout" , self .connection_timeout )
121
+
122
+ # Log the API request
123
+ telemetry .logs ().new_log (
124
+ msg = f"Making API request to { self .endpoint } " ,
125
+ tags = {
126
+ "endpoint" : self .endpoint ,
127
+ "component" : "ClientBuilder" ,
128
+ "method" : "get_api_data"
129
+ },
130
+ level = 20 # INFO level
131
+ )
132
+
133
+ # Record the start time for response time measurement
134
+ start_time = time .time ()
135
+
136
+ # Make the API request
76
137
response = GetData .get_response (
77
138
endpoint = self .endpoint ,
78
139
headers = self .headers ,
79
140
connection_timeout = self .connection_timeout ,
80
141
)
81
-
142
+
143
+ # Calculate response time
144
+ response_time = time .time () - start_time
145
+
146
+ # Record response time as histogram
147
+ telemetry .metrics ().record_histogram (
148
+ name = "api.response_time" ,
149
+ tags = {"endpoint" : self .endpoint },
150
+ value = response_time
151
+ )
152
+
153
+ # Record successful request metric
154
+ telemetry .metrics ().metric_increment (
155
+ name = "api.request.success" ,
156
+ tags = {"endpoint" : self .endpoint }
157
+ )
158
+
159
+ # Log success
160
+ telemetry .logs ().new_log (
161
+ msg = f"API request to { self .endpoint } successful" ,
162
+ tags = {
163
+ "endpoint" : self .endpoint ,
164
+ "response_status" : response .status_code ,
165
+ "response_time" : response_time ,
166
+ "component" : "ClientBuilder" ,
167
+ "method" : "get_api_data"
168
+ },
169
+ level = 20 # INFO level
170
+ )
82
171
83
172
return response .json ()
84
173
@@ -97,7 +186,66 @@ def api_to_dataframe(response: dict):
97
186
Returns:
98
187
DataFrame: A pandas DataFrame containing the data from the API response.
99
188
"""
100
-
101
- df = GetData .to_dataframe (response )
102
-
103
- return df
189
+ # Use telemetry for this operation
190
+ with telemetry .traces ().span_in_context ("api_to_dataframe" ) as (span , _ ):
191
+ response_size = len (response ) if isinstance (response , list ) else 1
192
+ span .set_attribute ("response_size" , response_size )
193
+
194
+ # Log conversion start
195
+ telemetry .logs ().new_log (
196
+ msg = "Converting API response to DataFrame" ,
197
+ tags = {
198
+ "response_size" : response_size ,
199
+ "response_type" : type (response ).__name__ ,
200
+ "component" : "ClientBuilder" ,
201
+ "method" : "api_to_dataframe"
202
+ },
203
+ level = 20 # INFO level
204
+ )
205
+
206
+ try :
207
+ # Convert to dataframe
208
+ df = GetData .to_dataframe (response )
209
+
210
+ # Record metrics
211
+ telemetry .metrics ().metric_increment (
212
+ name = "dataframe.conversion.success" ,
213
+ tags = {"size" : len (df )}
214
+ )
215
+
216
+ # Log success
217
+ telemetry .logs ().new_log (
218
+ msg = "Successfully converted API response to DataFrame" ,
219
+ tags = {
220
+ "dataframe_rows" : len (df ),
221
+ "dataframe_columns" : len (df .columns ),
222
+ "component" : "ClientBuilder" ,
223
+ "method" : "api_to_dataframe"
224
+ },
225
+ level = 20 # INFO level
226
+ )
227
+
228
+ return df
229
+
230
+ except Exception as e :
231
+ # Record failure metric
232
+ telemetry .metrics ().metric_increment (
233
+ name = "dataframe.conversion.failure" ,
234
+ tags = {"error_type" : type (e ).__name__ }
235
+ )
236
+
237
+ # Log error
238
+ error_msg = f"Failed to convert API response to DataFrame: { str (e )} "
239
+ telemetry .logs ().new_log (
240
+ msg = error_msg ,
241
+ tags = {
242
+ "error" : str (e ),
243
+ "error_type" : type (e ).__name__ ,
244
+ "component" : "ClientBuilder" ,
245
+ "method" : "api_to_dataframe"
246
+ },
247
+ level = 40 # ERROR level
248
+ )
249
+
250
+ # Re-raise the exception
251
+ raise
0 commit comments