@@ -157,7 +157,8 @@ def client_resposne_hook(span, future):
157
157
from functools import partial
158
158
from logging import getLogger
159
159
from time import time_ns
160
- from typing import Collection
160
+ from timeit import default_timer
161
+ from typing import Collection , Dict
161
162
162
163
import tornado .web
163
164
import wrapt
@@ -177,6 +178,8 @@ def client_resposne_hook(span, future):
177
178
http_status_to_status_code ,
178
179
unwrap ,
179
180
)
181
+ from opentelemetry .metrics import get_meter
182
+ from opentelemetry .metrics ._internal .instrument import Histogram
180
183
from opentelemetry .propagators import textmap
181
184
from opentelemetry .semconv .trace import SpanAttributes
182
185
from opentelemetry .trace .status import Status , StatusCode
@@ -197,6 +200,14 @@ def client_resposne_hook(span, future):
197
200
_HANDLER_CONTEXT_KEY = "_otel_trace_context_key"
198
201
_OTEL_PATCHED_KEY = "_otel_patched_key"
199
202
203
+ _START_TIME = "start_time"
204
+ _CLIENT_DURATION_HISTOGRAM = "http.client.duration"
205
+ _CLIENT_REQUEST_SIZE_HISTOGRAM = "http.client.request.size"
206
+ _CLIENT_RESPONSE_SIZE_HISTOGRAM = "http.client.response.size"
207
+ _SERVER_DURATION_HISTOGRAM = "http.server.duration"
208
+ _SERVER_REQUEST_SIZE_HISTOGRAM = "http.server.request.size"
209
+ _SERVER_RESPONSE_SIZE_HISTOGRAM = "http.server.response.size"
210
+ _SERVER_ACTIVE_REQUESTS_HISTOGRAM = "http.server.active_requests"
200
211
201
212
_excluded_urls = get_excluded_urls ("TORNADO" )
202
213
_traced_request_attrs = get_traced_request_attrs ("TORNADO" )
@@ -233,13 +244,21 @@ def _instrument(self, **kwargs):
233
244
tracer_provider = kwargs .get ("tracer_provider" )
234
245
tracer = trace .get_tracer (__name__ , __version__ , tracer_provider )
235
246
247
+ meter_provider = kwargs .get ("meter_provider" )
248
+ meter = get_meter (__name__ , __version__ , meter_provider )
249
+
250
+ client_histograms = _create_client_histograms (meter )
251
+ server_histograms = _create_server_histograms (meter )
252
+
236
253
client_request_hook = kwargs .get ("client_request_hook" , None )
237
254
client_response_hook = kwargs .get ("client_response_hook" , None )
238
255
server_request_hook = kwargs .get ("server_request_hook" , None )
239
256
240
257
def handler_init (init , handler , args , kwargs ):
241
258
cls = handler .__class__
242
- if patch_handler_class (tracer , cls , server_request_hook ):
259
+ if patch_handler_class (
260
+ tracer , server_histograms , cls , server_request_hook
261
+ ):
243
262
self .patched_handlers .append (cls )
244
263
return init (* args , ** kwargs )
245
264
@@ -250,7 +269,13 @@ def handler_init(init, handler, args, kwargs):
250
269
"tornado.httpclient" ,
251
270
"AsyncHTTPClient.fetch" ,
252
271
partial (
253
- fetch_async , tracer , client_request_hook , client_response_hook
272
+ fetch_async ,
273
+ tracer ,
274
+ client_request_hook ,
275
+ client_response_hook ,
276
+ client_histograms [_CLIENT_DURATION_HISTOGRAM ],
277
+ client_histograms [_CLIENT_REQUEST_SIZE_HISTOGRAM ],
278
+ client_histograms [_CLIENT_RESPONSE_SIZE_HISTOGRAM ],
254
279
),
255
280
)
256
281
@@ -262,14 +287,71 @@ def _uninstrument(self, **kwargs):
262
287
self .patched_handlers = []
263
288
264
289
265
- def patch_handler_class (tracer , cls , request_hook = None ):
290
+ def _create_server_histograms (meter ) -> Dict [str , Histogram ]:
291
+ histograms = {
292
+ _SERVER_DURATION_HISTOGRAM : meter .create_histogram (
293
+ name = "http.server.duration" ,
294
+ unit = "ms" ,
295
+ description = "measures the duration outbound HTTP requests" ,
296
+ ),
297
+ _SERVER_REQUEST_SIZE_HISTOGRAM : meter .create_histogram (
298
+ name = "http.server.request.size" ,
299
+ unit = "By" ,
300
+ description = "measures the size of HTTP request messages (compressed)" ,
301
+ ),
302
+ _SERVER_RESPONSE_SIZE_HISTOGRAM : meter .create_histogram (
303
+ name = "http.server.response.size" ,
304
+ unit = "By" ,
305
+ description = "measures the size of HTTP response messages (compressed)" ,
306
+ ),
307
+ _SERVER_ACTIVE_REQUESTS_HISTOGRAM : meter .create_up_down_counter (
308
+ name = "http.server.active_requests" ,
309
+ unit = "requests" ,
310
+ description = "measures the number of concurrent HTTP requests that are currently in-flight" ,
311
+ ),
312
+ }
313
+
314
+ return histograms
315
+
316
+
317
+ def _create_client_histograms (meter ) -> Dict [str , Histogram ]:
318
+ histograms = {
319
+ _CLIENT_DURATION_HISTOGRAM : meter .create_histogram (
320
+ name = "http.client.duration" ,
321
+ unit = "ms" ,
322
+ description = "measures the duration outbound HTTP requests" ,
323
+ ),
324
+ _CLIENT_REQUEST_SIZE_HISTOGRAM : meter .create_histogram (
325
+ name = "http.client.request.size" ,
326
+ unit = "By" ,
327
+ description = "measures the size of HTTP request messages (compressed)" ,
328
+ ),
329
+ _CLIENT_RESPONSE_SIZE_HISTOGRAM : meter .create_histogram (
330
+ name = "http.client.response.size" ,
331
+ unit = "By" ,
332
+ description = "measures the size of HTTP response messages (compressed)" ,
333
+ ),
334
+ }
335
+
336
+ return histograms
337
+
338
+
339
+ def patch_handler_class (tracer , server_histograms , cls , request_hook = None ):
266
340
if getattr (cls , _OTEL_PATCHED_KEY , False ):
267
341
return False
268
342
269
343
setattr (cls , _OTEL_PATCHED_KEY , True )
270
- _wrap (cls , "prepare" , partial (_prepare , tracer , request_hook ))
271
- _wrap (cls , "on_finish" , partial (_on_finish , tracer ))
272
- _wrap (cls , "log_exception" , partial (_log_exception , tracer ))
344
+ _wrap (
345
+ cls ,
346
+ "prepare" ,
347
+ partial (_prepare , tracer , server_histograms , request_hook ),
348
+ )
349
+ _wrap (cls , "on_finish" , partial (_on_finish , tracer , server_histograms ))
350
+ _wrap (
351
+ cls ,
352
+ "log_exception" ,
353
+ partial (_log_exception , tracer , server_histograms ),
354
+ )
273
355
return True
274
356
275
357
@@ -289,28 +371,40 @@ def _wrap(cls, method_name, wrapper):
289
371
wrapt .apply_patch (cls , method_name , wrapper )
290
372
291
373
292
- def _prepare (tracer , request_hook , func , handler , args , kwargs ):
293
- start_time = time_ns ()
374
+ def _prepare (
375
+ tracer , server_histograms , request_hook , func , handler , args , kwargs
376
+ ):
377
+ server_histograms [_START_TIME ] = default_timer ()
378
+
294
379
request = handler .request
295
380
if _excluded_urls .url_disabled (request .uri ):
296
381
return func (* args , ** kwargs )
297
- ctx = _start_span (tracer , handler , start_time )
382
+
383
+ _record_prepare_metrics (server_histograms , handler )
384
+
385
+ ctx = _start_span (tracer , handler )
298
386
if request_hook :
299
387
request_hook (ctx .span , handler )
300
388
return func (* args , ** kwargs )
301
389
302
390
303
- def _on_finish (tracer , func , handler , args , kwargs ):
391
+ def _on_finish (tracer , server_histograms , func , handler , args , kwargs ):
304
392
response = func (* args , ** kwargs )
393
+
394
+ _record_on_finish_metrics (server_histograms , handler )
395
+
305
396
_finish_span (tracer , handler )
397
+
306
398
return response
307
399
308
400
309
- def _log_exception (tracer , func , handler , args , kwargs ):
401
+ def _log_exception (tracer , server_histograms , func , handler , args , kwargs ):
310
402
error = None
311
403
if len (args ) == 3 :
312
404
error = args [1 ]
313
405
406
+ _record_on_finish_metrics (server_histograms , handler , error )
407
+
314
408
_finish_span (tracer , handler , error )
315
409
return func (* args , ** kwargs )
316
410
@@ -377,11 +471,11 @@ def _get_full_handler_name(handler):
377
471
return f"{ klass .__module__ } .{ klass .__qualname__ } "
378
472
379
473
380
- def _start_span (tracer , handler , start_time ) -> _TraceContext :
474
+ def _start_span (tracer , handler ) -> _TraceContext :
381
475
span , token = _start_internal_or_server_span (
382
476
tracer = tracer ,
383
477
span_name = _get_operation_name (handler , handler .request ),
384
- start_time = start_time ,
478
+ start_time = time_ns () ,
385
479
context_carrier = handler .request .headers ,
386
480
context_getter = textmap .default_getter ,
387
481
)
@@ -423,7 +517,7 @@ def _finish_span(tracer, handler, error=None):
423
517
if isinstance (error , tornado .web .HTTPError ):
424
518
status_code = error .status_code
425
519
if not ctx and status_code == 404 :
426
- ctx = _start_span (tracer , handler , time_ns () )
520
+ ctx = _start_span (tracer , handler )
427
521
else :
428
522
status_code = 500
429
523
reason = None
@@ -462,3 +556,65 @@ def _finish_span(tracer, handler, error=None):
462
556
if ctx .token :
463
557
context .detach (ctx .token )
464
558
delattr (handler , _HANDLER_CONTEXT_KEY )
559
+
560
+
561
+ def _record_prepare_metrics (server_histograms , handler ):
562
+ request_size = int (handler .request .headers .get ("Content-Length" , 0 ))
563
+ metric_attributes = _create_metric_attributes (handler )
564
+
565
+ server_histograms [_SERVER_REQUEST_SIZE_HISTOGRAM ].record (
566
+ request_size , attributes = metric_attributes
567
+ )
568
+
569
+ active_requests_attributes = _create_active_requests_attributes (
570
+ handler .request
571
+ )
572
+ server_histograms [_SERVER_ACTIVE_REQUESTS_HISTOGRAM ].add (
573
+ 1 , attributes = active_requests_attributes
574
+ )
575
+
576
+
577
+ def _record_on_finish_metrics (server_histograms , handler , error = None ):
578
+ elapsed_time = round (
579
+ (default_timer () - server_histograms [_START_TIME ]) * 1000
580
+ )
581
+
582
+ response_size = int (handler ._headers .get ("Content-Length" , 0 ))
583
+ metric_attributes = _create_metric_attributes (handler )
584
+
585
+ if isinstance (error , tornado .web .HTTPError ):
586
+ metric_attributes [SpanAttributes .HTTP_STATUS_CODE ] = error .status_code
587
+
588
+ server_histograms [_SERVER_RESPONSE_SIZE_HISTOGRAM ].record (
589
+ response_size , attributes = metric_attributes
590
+ )
591
+
592
+ server_histograms [_SERVER_DURATION_HISTOGRAM ].record (
593
+ elapsed_time , attributes = metric_attributes
594
+ )
595
+
596
+ active_requests_attributes = _create_active_requests_attributes (
597
+ handler .request
598
+ )
599
+ server_histograms [_SERVER_ACTIVE_REQUESTS_HISTOGRAM ].add (
600
+ - 1 , attributes = active_requests_attributes
601
+ )
602
+
603
+
604
+ def _create_active_requests_attributes (request ):
605
+ metric_attributes = {
606
+ SpanAttributes .HTTP_METHOD : request .method ,
607
+ SpanAttributes .HTTP_SCHEME : request .protocol ,
608
+ SpanAttributes .HTTP_FLAVOR : request .version ,
609
+ SpanAttributes .HTTP_HOST : request .host ,
610
+ SpanAttributes .HTTP_TARGET : request .path ,
611
+ }
612
+
613
+ return metric_attributes
614
+
615
+
616
+ def _create_metric_attributes (handler ):
617
+ metric_attributes = _create_active_requests_attributes (handler .request )
618
+ metric_attributes [SpanAttributes .HTTP_STATUS_CODE ] = handler .get_status ()
619
+
620
+ return metric_attributes
0 commit comments