def test_hooks(self): def server_request_hook(span, scope): span.update_name("name from server hook") def client_request_hook(recieve_span, request): recieve_span.update_name("name from client request hook") def client_response_hook(send_span, response): send_span.set_attribute("attr-from-hook", "value") def update_expected_hook_results(expected): for entry in expected: if entry["kind"] == trace_api.SpanKind.SERVER: entry["name"] = "name from server hook" elif entry["name"] == "/ http receive": entry["name"] = "name from client request hook" elif entry["name"] == "/ http send": entry["attributes"].update({"attr-from-hook": "value"}) return expected app = otel_asgi.OpenTelemetryMiddleware( simple_asgi, server_request_hook=server_request_hook, client_request_hook=client_request_hook, client_response_hook=client_response_hook, ) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs, modifiers=[update_expected_hook_results])
def test_asgi_metrics(self): app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() self.seed_app(app) self.send_default_request() self.seed_app(app) self.send_default_request() metrics_list = self.memory_metrics_reader.get_metrics_data() number_data_point_seen = False histogram_data_point_seen = False self.assertTrue(len(metrics_list.resource_metrics) != 0) for resource_metric in metrics_list.resource_metrics: self.assertTrue(len(resource_metric.scope_metrics) != 0) for scope_metric in resource_metric.scope_metrics: self.assertTrue(len(scope_metric.metrics) != 0) for metric in scope_metric.metrics: self.assertIn(metric.name, _expected_metric_names) data_points = list(metric.data.data_points) self.assertEqual(len(data_points), 1) for point in data_points: if isinstance(point, HistogramDataPoint): self.assertEqual(point.count, 3) histogram_data_point_seen = True if isinstance(point, NumberDataPoint): number_data_point_seen = True for attr in point.attributes: self.assertIn(attr, _recommended_attrs[metric.name]) self.assertTrue(number_data_point_seen and histogram_data_point_seen)
def test_asgi_exc_info(self): """Test that exception information is emitted as expected.""" app = otel_asgi.OpenTelemetryMiddleware(error_asgi) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs, error=ValueError)
def test_basic_asgi_call(self): """Test that spans are emitted as expected.""" app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs)
def test_websocket_custom_response_headers_not_in_span_attributes(self): self.scope = { "type": "websocket", "http_version": "1.1", "scheme": "ws", "path": "/", "query_string": b"", "headers": [], "client": ("127.0.0.1", 32767), "server": ("127.0.0.1", 80), } self.app = otel_asgi.OpenTelemetryMiddleware( websocket_app_with_custom_headers, tracer_provider=self.tracer_provider, ) self.seed_app(self.app) self.send_input({"type": "websocket.connect"}) self.send_input({"type": "websocket.receive", "text": "ping"}) self.send_input({"type": "websocket.disconnect"}) self.get_all_output() span_list = self.exporter.get_finished_spans() not_expected = { "http.response.header.custom_test_header_3": ("test-header-value-3", ), } for span in span_list: if span.kind == SpanKind.SERVER: for key, _ in not_expected.items(): self.assertNotIn(key, span.attributes)
def test_mark_span_internal_in_presence_of_span_from_other_framework(self): tracer_provider, exporter = TestBase.create_tracer_provider() tracer = tracer_provider.get_tracer(__name__) app = otel_asgi.OpenTelemetryMiddleware( simple_asgi, tracer_provider=tracer_provider) # Wrapping the otel intercepted app with server span async def wrapped_app(scope, receive, send): with tracer.start_as_current_span("test", kind=SpanKind.SERVER) as _: await app(scope, receive, send) self.seed_app(wrapped_app) self.send_default_request() span_list = exporter.get_finished_spans() self.assertEqual(SpanKind.INTERNAL, span_list[0].kind) self.assertEqual(SpanKind.INTERNAL, span_list[1].kind) self.assertEqual(SpanKind.INTERNAL, span_list[2].kind) self.assertEqual(trace_api.SpanKind.INTERNAL, span_list[3].kind) # SERVER "test" self.assertEqual(SpanKind.SERVER, span_list[4].kind) # internal span should be child of the test span we have provided self.assertEqual(span_list[4].context.span_id, span_list[3].parent.span_id)
def test_traceresponse_header(self): """Test a traceresponse header is sent when a global propagator is set.""" orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() span = self.memory_exporter.get_finished_spans()[-1] self.assertEqual(trace_api.SpanKind.SERVER, span.kind) response_start, response_body, *_ = self.get_all_output() self.assertEqual(response_body["body"], b"*") self.assertEqual(response_start["status"], 200) traceresponse = "00-{0}-{1}-01".format( format_trace_id(span.get_span_context().trace_id), format_span_id(span.get_span_context().span_id), ) self.assertListEqual( response_start["headers"], [ [b"Content-Type", b"text/plain"], [b"traceresponse", f"{traceresponse}".encode()], [b"access-control-expose-headers", b"traceresponse"], ], ) set_global_response_propagator(orig)
def test_lifespan(self): self.scope["type"] = "lifespan" app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 0)
def test_websocket(self): self.scope = { "type": "websocket", "http_version": "1.1", "scheme": "ws", "path": "/", "query_string": b"", "headers": [], "client": ("127.0.0.1", 32767), "server": ("127.0.0.1", 80), } app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_input({"type": "websocket.connect"}) self.send_input({"type": "websocket.receive", "text": "ping"}) self.send_input({"type": "websocket.disconnect"}) self.get_all_output() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 6) expected = [ "/ asgi.websocket.receive", "/ asgi.websocket.send", "/ asgi.websocket.receive", "/ asgi.websocket.send", "/ asgi.websocket.receive", "/ asgi", ] actual = [span.name for span in span_list] self.assertListEqual(actual, expected)
def test_custom_tracer_provider_otel_asgi(self): resource = resources.Resource.create({"service-test-key": "value"}) result = TestBase.create_tracer_provider(resource=resource) tracer_provider, exporter = result app = otel_asgi.OpenTelemetryMiddleware( simple_asgi, tracer_provider=tracer_provider) self.seed_app(app) self.send_default_request() span_list = exporter.get_finished_spans() for span in span_list: self.assertEqual(span.resource.attributes["service-test-key"], "value")
def test_host_header(self): """Test that host header is converted to http.server_name.""" hostname = b"server_name_1" def update_expected_server(expected): expected[3]["attributes"].update( {SpanAttributes.HTTP_SERVER_NAME: hostname.decode("utf8")}) return expected self.scope["headers"].append([b"host", hostname]) app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs, modifiers=[update_expected_server])
def test_user_agent(self): """Test that host header is converted to http.server_name.""" user_agent = b"test-agent" def update_expected_user_agent(expected): expected[3]["attributes"].update( {SpanAttributes.HTTP_USER_AGENT: user_agent.decode("utf8")}) return expected self.scope["headers"].append([b"user-agent", user_agent]) app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs, modifiers=[update_expected_user_agent])
def test_http_custom_response_headers_not_in_span_attributes(self): self.app = otel_asgi.OpenTelemetryMiddleware( http_app_with_custom_headers, tracer_provider=self.tracer_provider) self.seed_app(self.app) self.send_default_request() self.get_all_output() span_list = self.exporter.get_finished_spans() not_expected = { "http.response.header.custom_test_header_3": ("test-header-value-3", ), } for span in span_list: if span.kind == SpanKind.SERVER: for key, _ in not_expected.items(): self.assertNotIn(key, span.attributes)
def test_asgi_not_recording(self): mock_tracer = mock.Mock() mock_span = mock.Mock() mock_span.is_recording.return_value = False mock_tracer.start_as_current_span.return_value = mock_span mock_tracer.start_as_current_span.return_value.__enter__ = mock_span mock_tracer.start_as_current_span.return_value.__exit__ = mock_span with mock.patch("opentelemetry.trace.get_tracer") as tracer: tracer.return_value = mock_tracer app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() self.assertFalse(mock_span.is_recording()) self.assertTrue(mock_span.is_recording.called) self.assertFalse(mock_span.set_attribute.called) self.assertFalse(mock_span.set_status.called)
def test_behavior_with_scope_server_as_none(self): """Test that middleware is ok when server is none in scope.""" def update_expected_server(expected): expected[3]["attributes"].update({ "http.host": "0.0.0.0", "host.port": 80, "http.url": "http://0.0.0.0/", }) return expected self.scope["server"] = None app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs, modifiers=[update_expected_server])
def test_http_custom_response_headers_in_span_attributes(self): self.app = otel_asgi.OpenTelemetryMiddleware( http_app_with_custom_headers, tracer_provider=self.tracer_provider) self.seed_app(self.app) self.send_default_request() self.get_all_output() span_list = self.exporter.get_finished_spans() expected = { "http.response.header.custom_test_header_1": ("test-header-value-1", ), "http.response.header.custom_test_header_2": ("test-header-value-2", ), } for span in span_list: if span.kind == SpanKind.SERVER: self.assertSpanHasAttributes(span, expected)
def test_override_span_name(self): """Test that span_names can be overwritten by our callback function.""" span_name = "Dymaxion" def get_predefined_span_details(_): return span_name, {} def update_expected_span_name(expected): for entry in expected: entry["name"] = " ".join([span_name] + entry["name"].split(" ")[-1:]) return expected app = otel_asgi.OpenTelemetryMiddleware( simple_asgi, span_details_callback=get_predefined_span_details) self.seed_app(app) self.send_default_request() outputs = self.get_all_output() self.validate_outputs(outputs, modifiers=[update_expected_span_name])
def test_no_metric_for_websockets(self): self.scope = { "type": "websocket", "http_version": "1.1", "scheme": "ws", "path": "/", "query_string": b"", "headers": [], "client": ("127.0.0.1", 32767), "server": ("127.0.0.1", 80), } app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_input({"type": "websocket.connect"}) self.send_input({"type": "websocket.receive", "text": "ping"}) self.send_input({"type": "websocket.disconnect"}) self.get_all_output() metrics_list = self.memory_metrics_reader.get_metrics_data() self.assertEqual(len(metrics_list.resource_metrics[0].scope_metrics), 0)
def test_basic_metric_success(self): app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) start = default_timer() self.send_default_request() duration = max(round((default_timer() - start) * 1000), 0) expected_duration_attributes = { "http.method": "GET", "http.host": "127.0.0.1", "http.scheme": "http", "http.flavor": "1.0", "net.host.port": 80, "http.status_code": 200, } expected_requests_count_attributes = { "http.method": "GET", "http.host": "127.0.0.1", "http.scheme": "http", "http.flavor": "1.0", } metrics_list = self.memory_metrics_reader.get_metrics_data() for resource_metric in metrics_list.resource_metrics: for scope_metrics in resource_metric.scope_metrics: for metric in scope_metrics.metrics: for point in list(metric.data.data_points): if isinstance(point, HistogramDataPoint): self.assertDictEqual( expected_duration_attributes, dict(point.attributes), ) self.assertEqual(point.count, 1) self.assertAlmostEqual(duration, point.sum, delta=5) elif isinstance(point, NumberDataPoint): self.assertDictEqual( expected_requests_count_attributes, dict(point.attributes), ) self.assertEqual(point.value, 0)
def test_websocket_traceresponse_header(self): """Test a traceresponse header is set for websocket messages""" orig = get_global_response_propagator() set_global_response_propagator(TraceResponsePropagator()) self.scope = { "type": "websocket", "http_version": "1.1", "scheme": "ws", "path": "/", "query_string": b"", "headers": [], "client": ("127.0.0.1", 32767), "server": ("127.0.0.1", 80), } app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_input({"type": "websocket.connect"}) self.send_input({"type": "websocket.receive", "text": "ping"}) self.send_input({"type": "websocket.disconnect"}) _, socket_send, *_ = self.get_all_output() span = self.memory_exporter.get_finished_spans()[-1] self.assertEqual(trace_api.SpanKind.SERVER, span.kind) traceresponse = "00-{0}-{1}-01".format( format_trace_id(span.get_span_context().trace_id), format_span_id(span.get_span_context().span_id), ) self.assertListEqual( socket_send["headers"], [ [b"traceresponse", f"{traceresponse}".encode()], [b"access-control-expose-headers", b"traceresponse"], ], ) set_global_response_propagator(orig)
def setUp(self): super().setUp() self.tracer_provider, self.exporter = TestBase.create_tracer_provider() self.tracer = self.tracer_provider.get_tracer(__name__) self.app = otel_asgi.OpenTelemetryMiddleware( simple_asgi, tracer_provider=self.tracer_provider)
def test_websocket(self): self.scope = { "type": "websocket", "http_version": "1.1", "scheme": "ws", "path": "/", "query_string": b"", "headers": [], "client": ("127.0.0.1", 32767), "server": ("127.0.0.1", 80), } app = otel_asgi.OpenTelemetryMiddleware(simple_asgi) self.seed_app(app) self.send_input({"type": "websocket.connect"}) self.send_input({"type": "websocket.receive", "text": "ping"}) self.send_input({"type": "websocket.disconnect"}) self.get_all_output() span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 6) expected = [ { "name": "/ websocket receive", "kind": trace_api.SpanKind.INTERNAL, "attributes": { "type": "websocket.connect" }, }, { "name": "/ websocket send", "kind": trace_api.SpanKind.INTERNAL, "attributes": { "type": "websocket.accept" }, }, { "name": "/ websocket receive", "kind": trace_api.SpanKind.INTERNAL, "attributes": { "type": "websocket.receive", SpanAttributes.HTTP_STATUS_CODE: 200, }, }, { "name": "/ websocket send", "kind": trace_api.SpanKind.INTERNAL, "attributes": { "type": "websocket.send", SpanAttributes.HTTP_STATUS_CODE: 200, }, }, { "name": "/ websocket receive", "kind": trace_api.SpanKind.INTERNAL, "attributes": { "type": "websocket.disconnect" }, }, { "name": "/", "kind": trace_api.SpanKind.SERVER, "attributes": { SpanAttributes.HTTP_SCHEME: self.scope["scheme"], SpanAttributes.NET_HOST_PORT: self.scope["server"][1], SpanAttributes.HTTP_HOST: self.scope["server"][0], SpanAttributes.HTTP_FLAVOR: self.scope["http_version"], SpanAttributes.HTTP_TARGET: self.scope["path"], SpanAttributes.HTTP_URL: f'{self.scope["scheme"]}://{self.scope["server"][0]}{self.scope["path"]}', SpanAttributes.NET_PEER_IP: self.scope["client"][0], SpanAttributes.NET_PEER_PORT: self.scope["client"][1], SpanAttributes.HTTP_STATUS_CODE: 200, }, }, ] for span, expected in zip(span_list, expected): self.assertEqual(span.name, expected["name"]) self.assertEqual(span.kind, expected["kind"]) self.assertDictEqual(dict(span.attributes), expected["attributes"])