opentelemetry-instrumentation-tornado 0.55b1__tar.gz → 0.56b0__tar.gz

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.
Files changed (13) hide show
  1. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/PKG-INFO +4 -4
  2. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/pyproject.toml +3 -3
  3. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/src/opentelemetry/instrumentation/tornado/__init__.py +53 -24
  4. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/src/opentelemetry/instrumentation/tornado/client.py +12 -8
  5. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/src/opentelemetry/instrumentation/tornado/version.py +1 -1
  6. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/tests/test_instrumentation.py +140 -88
  7. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/tests/tornado_test_app.py +12 -0
  8. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/.gitignore +0 -0
  9. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/LICENSE +0 -0
  10. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/README.rst +0 -0
  11. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/src/opentelemetry/instrumentation/tornado/package.py +0 -0
  12. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/tests/__init__.py +0 -0
  13. {opentelemetry_instrumentation_tornado-0.55b1 → opentelemetry_instrumentation_tornado-0.56b0}/tests/test_metrics_instrumentation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opentelemetry-instrumentation-tornado
3
- Version: 0.55b1
3
+ Version: 0.56b0
4
4
  Summary: Tornado instrumentation for OpenTelemetry
5
5
  Project-URL: Homepage, https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-tornado
6
6
  Project-URL: Repository, https://github.com/open-telemetry/opentelemetry-python-contrib
@@ -18,9 +18,9 @@ Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
19
  Requires-Python: >=3.9
20
20
  Requires-Dist: opentelemetry-api~=1.12
21
- Requires-Dist: opentelemetry-instrumentation==0.55b1
22
- Requires-Dist: opentelemetry-semantic-conventions==0.55b1
23
- Requires-Dist: opentelemetry-util-http==0.55b1
21
+ Requires-Dist: opentelemetry-instrumentation==0.56b0
22
+ Requires-Dist: opentelemetry-semantic-conventions==0.56b0
23
+ Requires-Dist: opentelemetry-util-http==0.56b0
24
24
  Provides-Extra: instruments
25
25
  Requires-Dist: tornado>=5.1.1; extra == 'instruments'
26
26
  Description-Content-Type: text/x-rst
@@ -25,9 +25,9 @@ classifiers = [
25
25
  ]
26
26
  dependencies = [
27
27
  "opentelemetry-api ~= 1.12",
28
- "opentelemetry-instrumentation == 0.55b1",
29
- "opentelemetry-semantic-conventions == 0.55b1",
30
- "opentelemetry-util-http == 0.55b1",
28
+ "opentelemetry-instrumentation == 0.56b0",
29
+ "opentelemetry-semantic-conventions == 0.56b0",
30
+ "opentelemetry-util-http == 0.56b0",
31
31
  ]
32
32
 
33
33
  [project.optional-dependencies]
@@ -162,6 +162,7 @@ from timeit import default_timer
162
162
  from typing import Collection, Dict
163
163
 
164
164
  import tornado.web
165
+ import tornado.websocket
165
166
  import wrapt
166
167
  from wrapt import wrap_function_wrapper
167
168
 
@@ -182,8 +183,19 @@ from opentelemetry.instrumentation.utils import (
182
183
  from opentelemetry.metrics import get_meter
183
184
  from opentelemetry.metrics._internal.instrument import Histogram
184
185
  from opentelemetry.propagators import textmap
186
+ from opentelemetry.semconv._incubating.attributes.http_attributes import (
187
+ HTTP_CLIENT_IP,
188
+ HTTP_FLAVOR,
189
+ HTTP_HOST,
190
+ HTTP_METHOD,
191
+ HTTP_SCHEME,
192
+ HTTP_STATUS_CODE,
193
+ HTTP_TARGET,
194
+ )
195
+ from opentelemetry.semconv._incubating.attributes.net_attributes import (
196
+ NET_PEER_IP,
197
+ )
185
198
  from opentelemetry.semconv.metrics import MetricInstruments
186
- from opentelemetry.semconv.trace import SpanAttributes
187
199
  from opentelemetry.trace.status import Status, StatusCode
188
200
  from opentelemetry.util.http import (
189
201
  OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
@@ -351,12 +363,20 @@ def patch_handler_class(tracer, server_histograms, cls, request_hook=None):
351
363
  "prepare",
352
364
  partial(_prepare, tracer, server_histograms, request_hook),
353
365
  )
354
- _wrap(cls, "on_finish", partial(_on_finish, tracer, server_histograms))
355
366
  _wrap(
356
367
  cls,
357
368
  "log_exception",
358
369
  partial(_log_exception, tracer, server_histograms),
359
370
  )
371
+
372
+ if issubclass(cls, tornado.websocket.WebSocketHandler):
373
+ _wrap(
374
+ cls,
375
+ "on_close",
376
+ partial(_websockethandler_on_close, tracer, server_histograms),
377
+ )
378
+ else:
379
+ _wrap(cls, "on_finish", partial(_on_finish, tracer, server_histograms))
360
380
  return True
361
381
 
362
382
 
@@ -365,8 +385,11 @@ def unpatch_handler_class(cls):
365
385
  return
366
386
 
367
387
  unwrap(cls, "prepare")
368
- unwrap(cls, "on_finish")
369
388
  unwrap(cls, "log_exception")
389
+ if issubclass(cls, tornado.websocket.WebSocketHandler):
390
+ unwrap(cls, "on_close")
391
+ else:
392
+ unwrap(cls, "on_finish")
370
393
  delattr(cls, _OTEL_PATCHED_KEY)
371
394
 
372
395
 
@@ -394,13 +417,21 @@ def _prepare(
394
417
 
395
418
 
396
419
  def _on_finish(tracer, server_histograms, func, handler, args, kwargs):
397
- response = func(*args, **kwargs)
398
-
399
- _record_on_finish_metrics(server_histograms, handler)
420
+ try:
421
+ return func(*args, **kwargs)
422
+ finally:
423
+ _record_on_finish_metrics(server_histograms, handler)
424
+ _finish_span(tracer, handler)
400
425
 
401
- _finish_span(tracer, handler)
402
426
 
403
- return response
427
+ def _websockethandler_on_close(
428
+ tracer, server_histograms, func, handler, args, kwargs
429
+ ):
430
+ try:
431
+ func()
432
+ finally:
433
+ _record_on_finish_metrics(server_histograms, handler)
434
+ _finish_span(tracer, handler)
404
435
 
405
436
 
406
437
  def _log_exception(tracer, server_histograms, func, handler, args, kwargs):
@@ -442,23 +473,21 @@ def _collect_custom_response_headers_attributes(response_headers):
442
473
 
443
474
  def _get_attributes_from_request(request):
444
475
  attrs = {
445
- SpanAttributes.HTTP_METHOD: request.method,
446
- SpanAttributes.HTTP_SCHEME: request.protocol,
447
- SpanAttributes.HTTP_HOST: request.host,
448
- SpanAttributes.HTTP_TARGET: request.path,
476
+ HTTP_METHOD: request.method,
477
+ HTTP_SCHEME: request.protocol,
478
+ HTTP_HOST: request.host,
479
+ HTTP_TARGET: request.path,
449
480
  }
450
481
 
451
482
  if request.remote_ip:
452
483
  # NET_PEER_IP is the address of the network peer
453
484
  # HTTP_CLIENT_IP is the address of the client, which might be different
454
485
  # if Tornado is set to trust X-Forwarded-For headers (xheaders=True)
455
- attrs[SpanAttributes.HTTP_CLIENT_IP] = request.remote_ip
486
+ attrs[HTTP_CLIENT_IP] = request.remote_ip
456
487
  if hasattr(request.connection, "context") and getattr(
457
488
  request.connection.context, "_orig_remote_ip", None
458
489
  ):
459
- attrs[SpanAttributes.NET_PEER_IP] = (
460
- request.connection.context._orig_remote_ip
461
- )
490
+ attrs[NET_PEER_IP] = request.connection.context._orig_remote_ip
462
491
 
463
492
  return extract_attributes_from_object(
464
493
  request, _traced_request_attrs, attrs
@@ -550,7 +579,7 @@ def _finish_span(tracer, handler, error=None):
550
579
  return
551
580
 
552
581
  if ctx.span.is_recording():
553
- ctx.span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
582
+ ctx.span.set_attribute(HTTP_STATUS_CODE, status_code)
554
583
  otel_status_code = http_status_to_status_code(
555
584
  status_code, server_span=True
556
585
  )
@@ -601,7 +630,7 @@ def _record_on_finish_metrics(server_histograms, handler, error=None):
601
630
  metric_attributes = _create_metric_attributes(handler)
602
631
 
603
632
  if isinstance(error, tornado.web.HTTPError):
604
- metric_attributes[SpanAttributes.HTTP_STATUS_CODE] = error.status_code
633
+ metric_attributes[HTTP_STATUS_CODE] = error.status_code
605
634
 
606
635
  server_histograms[MetricInstruments.HTTP_SERVER_RESPONSE_SIZE].record(
607
636
  response_size, attributes=metric_attributes
@@ -621,11 +650,11 @@ def _record_on_finish_metrics(server_histograms, handler, error=None):
621
650
 
622
651
  def _create_active_requests_attributes(request):
623
652
  metric_attributes = {
624
- SpanAttributes.HTTP_METHOD: request.method,
625
- SpanAttributes.HTTP_SCHEME: request.protocol,
626
- SpanAttributes.HTTP_FLAVOR: request.version,
627
- SpanAttributes.HTTP_HOST: request.host,
628
- SpanAttributes.HTTP_TARGET: request.path,
653
+ HTTP_METHOD: request.method,
654
+ HTTP_SCHEME: request.protocol,
655
+ HTTP_FLAVOR: request.version,
656
+ HTTP_HOST: request.host,
657
+ HTTP_TARGET: request.path,
629
658
  }
630
659
 
631
660
  return metric_attributes
@@ -633,6 +662,6 @@ def _create_active_requests_attributes(request):
633
662
 
634
663
  def _create_metric_attributes(handler):
635
664
  metric_attributes = _create_active_requests_attributes(handler.request)
636
- metric_attributes[SpanAttributes.HTTP_STATUS_CODE] = handler.get_status()
665
+ metric_attributes[HTTP_STATUS_CODE] = handler.get_status()
637
666
 
638
667
  return metric_attributes
@@ -20,9 +20,13 @@ from tornado.httpclient import HTTPError, HTTPRequest
20
20
  from opentelemetry import trace
21
21
  from opentelemetry.instrumentation.utils import http_status_to_status_code
22
22
  from opentelemetry.propagate import inject
23
- from opentelemetry.semconv.trace import SpanAttributes
23
+ from opentelemetry.semconv._incubating.attributes.http_attributes import (
24
+ HTTP_METHOD,
25
+ HTTP_STATUS_CODE,
26
+ HTTP_URL,
27
+ )
24
28
  from opentelemetry.trace.status import Status, StatusCode
25
- from opentelemetry.util.http import remove_url_credentials
29
+ from opentelemetry.util.http import redact_url
26
30
 
27
31
 
28
32
  def _normalize_request(args, kwargs):
@@ -75,8 +79,8 @@ def fetch_async(
75
79
 
76
80
  if span.is_recording():
77
81
  attributes = {
78
- SpanAttributes.HTTP_URL: remove_url_credentials(request.url),
79
- SpanAttributes.HTTP_METHOD: request.method,
82
+ HTTP_URL: redact_url(request.url),
83
+ HTTP_METHOD: request.method,
80
84
  }
81
85
  for key, value in attributes.items():
82
86
  span.set_attribute(key, value)
@@ -135,7 +139,7 @@ def _finish_tracing_callback(
135
139
  )
136
140
 
137
141
  if status_code is not None:
138
- span.set_attribute(SpanAttributes.HTTP_STATUS_CODE, status_code)
142
+ span.set_attribute(HTTP_STATUS_CODE, status_code)
139
143
  span.set_status(status)
140
144
 
141
145
  if response is not None:
@@ -160,9 +164,9 @@ def _finish_tracing_callback(
160
164
 
161
165
  def _create_metric_attributes(response):
162
166
  metric_attributes = {
163
- SpanAttributes.HTTP_STATUS_CODE: response.code,
164
- SpanAttributes.HTTP_URL: remove_url_credentials(response.request.url),
165
- SpanAttributes.HTTP_METHOD: response.request.method,
167
+ HTTP_STATUS_CODE: response.code,
168
+ HTTP_URL: redact_url(response.request.url),
169
+ HTTP_METHOD: response.request.method,
166
170
  }
167
171
 
168
172
  return metric_attributes
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.55b1"
15
+ __version__ = "0.56b0"
@@ -13,8 +13,10 @@
13
13
  # limitations under the License.
14
14
 
15
15
 
16
+ import asyncio
16
17
  from unittest.mock import Mock, patch
17
18
 
19
+ import tornado.websocket
18
20
  from http_server_mock import HttpServerMock
19
21
  from tornado.httpclient import HTTPClientError
20
22
  from tornado.testing import AsyncHTTPTestCase
@@ -30,7 +32,18 @@ from opentelemetry.instrumentation.tornado import (
30
32
  patch_handler_class,
31
33
  unpatch_handler_class,
32
34
  )
33
- from opentelemetry.semconv.trace import SpanAttributes
35
+ from opentelemetry.semconv._incubating.attributes.http_attributes import (
36
+ HTTP_CLIENT_IP,
37
+ HTTP_HOST,
38
+ HTTP_METHOD,
39
+ HTTP_SCHEME,
40
+ HTTP_STATUS_CODE,
41
+ HTTP_TARGET,
42
+ HTTP_URL,
43
+ )
44
+ from opentelemetry.semconv._incubating.attributes.net_attributes import (
45
+ NET_PEER_IP,
46
+ )
34
47
  from opentelemetry.test.test_base import TestBase
35
48
  from opentelemetry.test.wsgitestutil import WsgiTestBase
36
49
  from opentelemetry.trace import SpanKind, StatusCode
@@ -146,13 +159,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
146
159
  self.assertSpanHasAttributes(
147
160
  server,
148
161
  {
149
- SpanAttributes.HTTP_METHOD: method,
150
- SpanAttributes.HTTP_SCHEME: "http",
151
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
152
- + str(self.get_http_port()),
153
- SpanAttributes.HTTP_TARGET: "/",
154
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
155
- SpanAttributes.HTTP_STATUS_CODE: 201,
162
+ HTTP_METHOD: method,
163
+ HTTP_SCHEME: "http",
164
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
165
+ HTTP_TARGET: "/",
166
+ HTTP_CLIENT_IP: "127.0.0.1",
167
+ HTTP_STATUS_CODE: 201,
156
168
  "tornado.handler": "tests.tornado_test_app.MainHandler",
157
169
  },
158
170
  )
@@ -164,9 +176,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
164
176
  self.assertSpanHasAttributes(
165
177
  client,
166
178
  {
167
- SpanAttributes.HTTP_URL: self.get_url("/"),
168
- SpanAttributes.HTTP_METHOD: method,
169
- SpanAttributes.HTTP_STATUS_CODE: 201,
179
+ HTTP_URL: self.get_url("/"),
180
+ HTTP_METHOD: method,
181
+ HTTP_STATUS_CODE: 201,
170
182
  },
171
183
  )
172
184
 
@@ -224,13 +236,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
224
236
  self.assertSpanHasAttributes(
225
237
  server,
226
238
  {
227
- SpanAttributes.HTTP_METHOD: "GET",
228
- SpanAttributes.HTTP_SCHEME: "http",
229
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
230
- + str(self.get_http_port()),
231
- SpanAttributes.HTTP_TARGET: url,
232
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
233
- SpanAttributes.HTTP_STATUS_CODE: 201,
239
+ HTTP_METHOD: "GET",
240
+ HTTP_SCHEME: "http",
241
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
242
+ HTTP_TARGET: url,
243
+ HTTP_CLIENT_IP: "127.0.0.1",
244
+ HTTP_STATUS_CODE: 201,
234
245
  "tornado.handler": f"tests.tornado_test_app.{handler_name}",
235
246
  },
236
247
  )
@@ -242,9 +253,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
242
253
  self.assertSpanHasAttributes(
243
254
  client,
244
255
  {
245
- SpanAttributes.HTTP_URL: self.get_url(url),
246
- SpanAttributes.HTTP_METHOD: "GET",
247
- SpanAttributes.HTTP_STATUS_CODE: 201,
256
+ HTTP_URL: self.get_url(url),
257
+ HTTP_METHOD: "GET",
258
+ HTTP_STATUS_CODE: 201,
248
259
  },
249
260
  )
250
261
 
@@ -263,13 +274,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
263
274
  self.assertSpanHasAttributes(
264
275
  server,
265
276
  {
266
- SpanAttributes.HTTP_METHOD: "GET",
267
- SpanAttributes.HTTP_SCHEME: "http",
268
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
269
- + str(self.get_http_port()),
270
- SpanAttributes.HTTP_TARGET: "/error",
271
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
272
- SpanAttributes.HTTP_STATUS_CODE: 500,
277
+ HTTP_METHOD: "GET",
278
+ HTTP_SCHEME: "http",
279
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
280
+ HTTP_TARGET: "/error",
281
+ HTTP_CLIENT_IP: "127.0.0.1",
282
+ HTTP_STATUS_CODE: 500,
273
283
  "tornado.handler": "tests.tornado_test_app.BadHandler",
274
284
  },
275
285
  )
@@ -279,9 +289,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
279
289
  self.assertSpanHasAttributes(
280
290
  client,
281
291
  {
282
- SpanAttributes.HTTP_URL: self.get_url("/error"),
283
- SpanAttributes.HTTP_METHOD: "GET",
284
- SpanAttributes.HTTP_STATUS_CODE: 500,
292
+ HTTP_URL: self.get_url("/error"),
293
+ HTTP_METHOD: "GET",
294
+ HTTP_STATUS_CODE: 500,
285
295
  },
286
296
  )
287
297
 
@@ -298,13 +308,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
298
308
  self.assertSpanHasAttributes(
299
309
  server,
300
310
  {
301
- SpanAttributes.HTTP_METHOD: "GET",
302
- SpanAttributes.HTTP_SCHEME: "http",
303
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
304
- + str(self.get_http_port()),
305
- SpanAttributes.HTTP_TARGET: "/missing-url",
306
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
307
- SpanAttributes.HTTP_STATUS_CODE: 404,
311
+ HTTP_METHOD: "GET",
312
+ HTTP_SCHEME: "http",
313
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
314
+ HTTP_TARGET: "/missing-url",
315
+ HTTP_CLIENT_IP: "127.0.0.1",
316
+ HTTP_STATUS_CODE: 404,
308
317
  "tornado.handler": "tornado.web.ErrorHandler",
309
318
  },
310
319
  )
@@ -314,9 +323,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
314
323
  self.assertSpanHasAttributes(
315
324
  client,
316
325
  {
317
- SpanAttributes.HTTP_URL: self.get_url("/missing-url"),
318
- SpanAttributes.HTTP_METHOD: "GET",
319
- SpanAttributes.HTTP_STATUS_CODE: 404,
326
+ HTTP_URL: self.get_url("/missing-url"),
327
+ HTTP_METHOD: "GET",
328
+ HTTP_STATUS_CODE: 404,
320
329
  },
321
330
  )
322
331
 
@@ -333,13 +342,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
333
342
  self.assertSpanHasAttributes(
334
343
  server,
335
344
  {
336
- SpanAttributes.HTTP_METHOD: "GET",
337
- SpanAttributes.HTTP_SCHEME: "http",
338
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
339
- + str(self.get_http_port()),
340
- SpanAttributes.HTTP_TARGET: "/raise_403",
341
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
342
- SpanAttributes.HTTP_STATUS_CODE: 403,
345
+ HTTP_METHOD: "GET",
346
+ HTTP_SCHEME: "http",
347
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
348
+ HTTP_TARGET: "/raise_403",
349
+ HTTP_CLIENT_IP: "127.0.0.1",
350
+ HTTP_STATUS_CODE: 403,
343
351
  "tornado.handler": "tests.tornado_test_app.RaiseHTTPErrorHandler",
344
352
  },
345
353
  )
@@ -349,9 +357,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
349
357
  self.assertSpanHasAttributes(
350
358
  client,
351
359
  {
352
- SpanAttributes.HTTP_URL: self.get_url("/raise_403"),
353
- SpanAttributes.HTTP_METHOD: "GET",
354
- SpanAttributes.HTTP_STATUS_CODE: 403,
360
+ HTTP_URL: self.get_url("/raise_403"),
361
+ HTTP_METHOD: "GET",
362
+ HTTP_STATUS_CODE: 403,
355
363
  },
356
364
  )
357
365
 
@@ -378,13 +386,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
378
386
  self.assertSpanHasAttributes(
379
387
  server,
380
388
  {
381
- SpanAttributes.HTTP_METHOD: "GET",
382
- SpanAttributes.HTTP_SCHEME: "http",
383
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
384
- + str(self.get_http_port()),
385
- SpanAttributes.HTTP_TARGET: "/dyna",
386
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
387
- SpanAttributes.HTTP_STATUS_CODE: 202,
389
+ HTTP_METHOD: "GET",
390
+ HTTP_SCHEME: "http",
391
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
392
+ HTTP_TARGET: "/dyna",
393
+ HTTP_CLIENT_IP: "127.0.0.1",
394
+ HTTP_STATUS_CODE: 202,
388
395
  "tornado.handler": "tests.tornado_test_app.DynamicHandler",
389
396
  },
390
397
  )
@@ -396,9 +403,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
396
403
  self.assertSpanHasAttributes(
397
404
  client,
398
405
  {
399
- SpanAttributes.HTTP_URL: self.get_url("/dyna"),
400
- SpanAttributes.HTTP_METHOD: "GET",
401
- SpanAttributes.HTTP_STATUS_CODE: 202,
406
+ HTTP_URL: self.get_url("/dyna"),
407
+ HTTP_METHOD: "GET",
408
+ HTTP_STATUS_CODE: 202,
402
409
  },
403
410
  )
404
411
 
@@ -419,13 +426,12 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
419
426
  self.assertSpanHasAttributes(
420
427
  server,
421
428
  {
422
- SpanAttributes.HTTP_METHOD: "GET",
423
- SpanAttributes.HTTP_SCHEME: "http",
424
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
425
- + str(self.get_http_port()),
426
- SpanAttributes.HTTP_TARGET: "/on_finish",
427
- SpanAttributes.HTTP_CLIENT_IP: "127.0.0.1",
428
- SpanAttributes.HTTP_STATUS_CODE: 200,
429
+ HTTP_METHOD: "GET",
430
+ HTTP_SCHEME: "http",
431
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
432
+ HTTP_TARGET: "/on_finish",
433
+ HTTP_CLIENT_IP: "127.0.0.1",
434
+ HTTP_STATUS_CODE: 200,
429
435
  "tornado.handler": "tests.tornado_test_app.FinishedHandler",
430
436
  },
431
437
  )
@@ -437,9 +443,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
437
443
  self.assertSpanHasAttributes(
438
444
  client,
439
445
  {
440
- SpanAttributes.HTTP_URL: self.get_url("/on_finish"),
441
- SpanAttributes.HTTP_METHOD: "GET",
442
- SpanAttributes.HTTP_STATUS_CODE: 200,
446
+ HTTP_URL: self.get_url("/on_finish"),
447
+ HTTP_METHOD: "GET",
448
+ HTTP_STATUS_CODE: 200,
443
449
  },
444
450
  )
445
451
 
@@ -450,6 +456,53 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
450
456
 
451
457
  self.assertEqual(auditor.kind, SpanKind.INTERNAL)
452
458
 
459
+ @tornado.testing.gen_test()
460
+ async def test_websockethandler(self):
461
+ ws_client = await tornado.websocket.websocket_connect(
462
+ f"ws://127.0.0.1:{self.get_http_port()}/echo_socket"
463
+ )
464
+
465
+ await ws_client.write_message("world")
466
+ resp = await ws_client.read_message()
467
+ self.assertEqual(resp, "hello world")
468
+
469
+ ws_client.close()
470
+ await asyncio.sleep(0.5)
471
+
472
+ spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
473
+ self.assertEqual(len(spans), 3)
474
+ close_span, msg_span, req_span = spans
475
+
476
+ self.assertEqual(req_span.name, "GET /echo_socket")
477
+ self.assertEqual(req_span.context.trace_id, msg_span.context.trace_id)
478
+ self.assertIsNone(req_span.parent)
479
+ self.assertEqual(req_span.kind, SpanKind.SERVER)
480
+ self.assertSpanHasAttributes(
481
+ req_span,
482
+ {
483
+ HTTP_METHOD: "GET",
484
+ HTTP_SCHEME: "http",
485
+ HTTP_HOST: f"127.0.0.1:{self.get_http_port()}",
486
+ HTTP_TARGET: "/echo_socket",
487
+ HTTP_CLIENT_IP: "127.0.0.1",
488
+ HTTP_STATUS_CODE: 101,
489
+ "tornado.handler": "tests.tornado_test_app.EchoWebSocketHandler",
490
+ },
491
+ )
492
+
493
+ self.assertEqual(msg_span.name, "audit_message")
494
+ self.assertFalse(msg_span.context.is_remote)
495
+ self.assertEqual(msg_span.kind, SpanKind.INTERNAL)
496
+ self.assertEqual(msg_span.parent.span_id, req_span.context.span_id)
497
+
498
+ self.assertEqual(close_span.name, "audit_on_close")
499
+ self.assertFalse(close_span.context.is_remote)
500
+ self.assertEqual(close_span.parent.span_id, req_span.context.span_id)
501
+ self.assertEqual(
502
+ close_span.context.trace_id, msg_span.context.trace_id
503
+ )
504
+ self.assertEqual(close_span.kind, SpanKind.INTERNAL)
505
+
453
506
  def test_exclude_lists(self):
454
507
  def test_excluded(path):
455
508
  self.fetch(path)
@@ -463,9 +516,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
463
516
  self.assertSpanHasAttributes(
464
517
  client,
465
518
  {
466
- SpanAttributes.HTTP_URL: self.get_url(path),
467
- SpanAttributes.HTTP_METHOD: "GET",
468
- SpanAttributes.HTTP_STATUS_CODE: 200,
519
+ HTTP_URL: self.get_url(path),
520
+ HTTP_METHOD: "GET",
521
+ HTTP_STATUS_CODE: 200,
469
522
  },
470
523
  )
471
524
  self.memory_exporter.clear()
@@ -496,8 +549,8 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
496
549
 
497
550
  set_global_response_propagator(orig)
498
551
 
499
- def test_credential_removal(self):
500
- app = HttpServerMock("test_credential_removal")
552
+ def test_remove_sensitive_params(self):
553
+ app = HttpServerMock("test_remove_sensitive_params")
501
554
 
502
555
  @app.route("/status/200")
503
556
  def index():
@@ -505,7 +558,7 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
505
558
 
506
559
  with app.run("localhost", 5000):
507
560
  response = self.fetch(
508
- "http://username:password@localhost:5000/status/200"
561
+ "http://username:password@localhost:5000/status/200?Signature=secret"
509
562
  )
510
563
  self.assertEqual(response.code, 200)
511
564
 
@@ -518,9 +571,9 @@ class TestTornadoInstrumentation(TornadoTest, WsgiTestBase):
518
571
  self.assertSpanHasAttributes(
519
572
  client,
520
573
  {
521
- SpanAttributes.HTTP_URL: "http://localhost:5000/status/200",
522
- SpanAttributes.HTTP_METHOD: "GET",
523
- SpanAttributes.HTTP_STATUS_CODE: 200,
574
+ HTTP_URL: "http://REDACTED:REDACTED@localhost:5000/status/200?Signature=REDACTED",
575
+ HTTP_METHOD: "GET",
576
+ HTTP_STATUS_CODE: 200,
524
577
  },
525
578
  )
526
579
 
@@ -538,14 +591,13 @@ class TestTornadoInstrumentationWithXHeaders(TornadoTest):
538
591
  self.assertSpanHasAttributes(
539
592
  spans.by_name("GET /"),
540
593
  {
541
- SpanAttributes.HTTP_METHOD: "GET",
542
- SpanAttributes.HTTP_SCHEME: "http",
543
- SpanAttributes.HTTP_HOST: "127.0.0.1:"
544
- + str(self.get_http_port()),
545
- SpanAttributes.HTTP_TARGET: "/",
546
- SpanAttributes.HTTP_CLIENT_IP: "12.34.56.78",
547
- SpanAttributes.HTTP_STATUS_CODE: 201,
548
- SpanAttributes.NET_PEER_IP: "127.0.0.1",
594
+ HTTP_METHOD: "GET",
595
+ HTTP_SCHEME: "http",
596
+ HTTP_HOST: "127.0.0.1:" + str(self.get_http_port()),
597
+ HTTP_TARGET: "/",
598
+ HTTP_CLIENT_IP: "12.34.56.78",
599
+ HTTP_STATUS_CODE: 201,
600
+ NET_PEER_IP: "127.0.0.1",
549
601
  "tornado.handler": "tests.tornado_test_app.MainHandler",
550
602
  },
551
603
  )
@@ -2,6 +2,7 @@
2
2
  import time
3
3
 
4
4
  import tornado.web
5
+ import tornado.websocket
5
6
  from tornado import gen
6
7
 
7
8
 
@@ -110,6 +111,16 @@ class RaiseHTTPErrorHandler(tornado.web.RequestHandler):
110
111
  raise tornado.web.HTTPError(403)
111
112
 
112
113
 
114
+ class EchoWebSocketHandler(tornado.websocket.WebSocketHandler):
115
+ async def on_message(self, message):
116
+ with self.application.tracer.start_as_current_span("audit_message"):
117
+ self.write_message(f"hello {message}")
118
+
119
+ def on_close(self):
120
+ with self.application.tracer.start_as_current_span("audit_on_close"):
121
+ time.sleep(0.05)
122
+
123
+
113
124
  def make_app(tracer):
114
125
  app = tornado.web.Application(
115
126
  [
@@ -122,6 +133,7 @@ def make_app(tracer):
122
133
  (r"/ping", HealthCheckHandler),
123
134
  (r"/test_custom_response_headers", CustomResponseHeaderHandler),
124
135
  (r"/raise_403", RaiseHTTPErrorHandler),
136
+ (r"/echo_socket", EchoWebSocketHandler),
125
137
  ]
126
138
  )
127
139
  app.tracer = tracer