11
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
+ # pylint: disable=too-many-lines
14
15
15
16
import unittest
16
17
from timeit import default_timer
@@ -310,9 +311,56 @@ def test_metric_uninstrument(self):
310
311
if isinstance (point , NumberDataPoint ):
311
312
self .assertEqual (point .value , 0 )
312
313
314
+ def test_sub_app_fastapi_call (self ):
315
+ """
316
+ This test is to ensure that a span in case of a sub app targeted contains the correct server url
317
+
318
+ As this test case covers manual instrumentation, we won't see any additional spans for the sub app.
319
+ In this case all generated spans might suffice the requirements for the attributes already
320
+ (as the testcase is not setting a root_path for the outer app here)
321
+ """
322
+
323
+ self ._client .get ("/sub/home" )
324
+ spans = self .memory_exporter .get_finished_spans ()
325
+ self .assertEqual (len (spans ), 3 )
326
+ for span in spans :
327
+ # As we are only looking to the "outer" app, we would see only the "GET /sub" spans
328
+ self .assertIn ("GET /sub" , span .name )
329
+
330
+ # We now want to specifically test all spans including the
331
+ # - HTTP_TARGET
332
+ # - HTTP_URL
333
+ # attributes to be populated with the expected values
334
+ spans_with_http_attributes = [
335
+ span
336
+ for span in spans
337
+ if (
338
+ SpanAttributes .HTTP_URL in span .attributes
339
+ or SpanAttributes .HTTP_TARGET in span .attributes
340
+ )
341
+ ]
342
+
343
+ # We expect only one span to have the HTTP attributes set (the SERVER span from the app itself)
344
+ # the sub app is not instrumented with manual instrumentation tests.
345
+ self .assertEqual (1 , len (spans_with_http_attributes ))
346
+
347
+ for span in spans_with_http_attributes :
348
+ self .assertEqual (
349
+ "/sub/home" , span .attributes [SpanAttributes .HTTP_TARGET ]
350
+ )
351
+ self .assertEqual (
352
+ "https://testserver:443/sub/home" ,
353
+ span .attributes [SpanAttributes .HTTP_URL ],
354
+ )
355
+
313
356
@staticmethod
314
357
def _create_fastapi_app ():
315
358
app = fastapi .FastAPI ()
359
+ sub_app = fastapi .FastAPI ()
360
+
361
+ @sub_app .get ("/home" )
362
+ async def _ ():
363
+ return {"message" : "sub hi" }
316
364
317
365
@app .get ("/foobar" )
318
366
async def _ ():
@@ -330,6 +378,8 @@ async def _(param: str):
330
378
async def _ ():
331
379
return {"message" : "ok" }
332
380
381
+ app .mount ("/sub" , app = sub_app )
382
+
333
383
return app
334
384
335
385
@@ -444,6 +494,58 @@ def tearDown(self):
444
494
self ._instrumentor .uninstrument ()
445
495
super ().tearDown ()
446
496
497
+ def test_sub_app_fastapi_call (self ):
498
+ """
499
+ !!! Attention: we need to override this testcase for the auto-instrumented variant
500
+ The reason is, that with auto instrumentation, the sub app is instrumented as well
501
+ and therefore we would see the spans for the sub app as well
502
+
503
+ This test is to ensure that a span in case of a sub app targeted contains the correct server url
504
+ """
505
+
506
+ self ._client .get ("/sub/home" )
507
+ spans = self .memory_exporter .get_finished_spans ()
508
+ self .assertEqual (len (spans ), 6 )
509
+
510
+ for span in spans :
511
+ # As we are only looking to the "outer" app, we would see only the "GET /sub" spans
512
+ # -> the outer app is not aware of the sub_apps internal routes
513
+ sub_in = "GET /sub" in span .name
514
+ # The sub app spans are named GET /home as from the sub app perspective the request targets /home
515
+ # -> the sub app is technically not aware of the /sub prefix
516
+ home_in = "GET /home" in span .name
517
+
518
+ # We expect the spans to be either from the outer app or the sub app
519
+ self .assertTrue (
520
+ sub_in or home_in ,
521
+ f"Span { span .name } does not have /sub or /home in its name" ,
522
+ )
523
+
524
+ # We now want to specifically test all spans including the
525
+ # - HTTP_TARGET
526
+ # - HTTP_URL
527
+ # attributes to be populated with the expected values
528
+ spans_with_http_attributes = [
529
+ span
530
+ for span in spans
531
+ if (
532
+ SpanAttributes .HTTP_URL in span .attributes
533
+ or SpanAttributes .HTTP_TARGET in span .attributes
534
+ )
535
+ ]
536
+
537
+ # We now expect spans with attributes from both the app and its sub app
538
+ self .assertEqual (2 , len (spans_with_http_attributes ))
539
+
540
+ for span in spans_with_http_attributes :
541
+ self .assertEqual (
542
+ "/sub/home" , span .attributes [SpanAttributes .HTTP_TARGET ]
543
+ )
544
+ self .assertEqual (
545
+ "https://testserver:443/sub/home" ,
546
+ span .attributes [SpanAttributes .HTTP_URL ],
547
+ )
548
+
447
549
448
550
class TestAutoInstrumentationHooks (TestFastAPIManualInstrumentationHooks ):
449
551
"""
@@ -485,6 +587,58 @@ def tearDown(self):
485
587
self ._instrumentor .uninstrument ()
486
588
super ().tearDown ()
487
589
590
+ def test_sub_app_fastapi_call (self ):
591
+ """
592
+ !!! Attention: we need to override this testcase for the auto-instrumented variant
593
+ The reason is, that with auto instrumentation, the sub app is instrumented as well
594
+ and therefore we would see the spans for the sub app as well
595
+
596
+ This test is to ensure that a span in case of a sub app targeted contains the correct server url
597
+ """
598
+
599
+ self ._client .get ("/sub/home" )
600
+ spans = self .memory_exporter .get_finished_spans ()
601
+ self .assertEqual (len (spans ), 6 )
602
+
603
+ for span in spans :
604
+ # As we are only looking to the "outer" app, we would see only the "GET /sub" spans
605
+ # -> the outer app is not aware of the sub_apps internal routes
606
+ sub_in = "GET /sub" in span .name
607
+ # The sub app spans are named GET /home as from the sub app perspective the request targets /home
608
+ # -> the sub app is technically not aware of the /sub prefix
609
+ home_in = "GET /home" in span .name
610
+
611
+ # We expect the spans to be either from the outer app or the sub app
612
+ self .assertTrue (
613
+ sub_in or home_in ,
614
+ f"Span { span .name } does not have /sub or /home in its name" ,
615
+ )
616
+
617
+ # We now want to specifically test all spans including the
618
+ # - HTTP_TARGET
619
+ # - HTTP_URL
620
+ # attributes to be populated with the expected values
621
+ spans_with_http_attributes = [
622
+ span
623
+ for span in spans
624
+ if (
625
+ SpanAttributes .HTTP_URL in span .attributes
626
+ or SpanAttributes .HTTP_TARGET in span .attributes
627
+ )
628
+ ]
629
+
630
+ # We now expect spans with attributes from both the app and its sub app
631
+ self .assertEqual (2 , len (spans_with_http_attributes ))
632
+
633
+ for span in spans_with_http_attributes :
634
+ self .assertEqual (
635
+ "/sub/home" , span .attributes [SpanAttributes .HTTP_TARGET ]
636
+ )
637
+ self .assertEqual (
638
+ "https://testserver:443/sub/home" ,
639
+ span .attributes [SpanAttributes .HTTP_URL ],
640
+ )
641
+
488
642
489
643
class TestAutoInstrumentationLogic (unittest .TestCase ):
490
644
def test_instrumentation (self ):
0 commit comments