def test_third_party_instrumentor(self, telemetry: TelemetryFixture): import requests from telemetry.api.listeners.span import LabelAttributes, InstrumentorSpanListener RequestsInstrumentor().instrument() telemetry.initialize() telemetry.add_span_processor(InstrumentorSpanListener( LabelAttributes('component', 'http.status_code', 'http.method'), 'requests')) responses.add_passthru('http://localhost:1234/does_not_exist') with telemetry.span('test_category', 'span1', attributes={TestAttributes.LABEL1: 'l1'}) as span: try: with requests.get('http://localhost:1234/does_not_exist') as response: pass except: pass telemetry.collect() assert telemetry.get_value_recorder(name='trace.duration', labels={'component': 'http', 'http.method': 'GET', TestAttributes.LABEL1.name: 'l1', Attributes.TRACE_CATEGORY.name: 'requests', Attributes.TRACE_NAME.name: 'requests.HTTP GET', Attributes.TRACE_STATUS.name: 'ERROR'}).count == 1
def test_up_down_counter(self, telemetry: TelemetryFixture): telemetry.up_down_counter("category1", "counter1", 5) telemetry.up_down_counter("category1", "counter1", -10) telemetry.collect() assert telemetry.get_up_down_counter('category1.counter1').value == -5
def test_environment_attributes(self, monkeypatch, telemetry: TelemetryFixture): monkeypatch.setenv('METRICS_LABEL_label1', 'label1_value') monkeypatch.setenv('METRICS_LABEL_label2', 'label2_value') monkeypatch.setenv('METRICS_ATTRIBUTE_ATTRIB1', 'attrib1_value') monkeypatch.setenv('METRICS_ATTRIBUTE_ATTRIB2', 'attrib2_value') # need to initialize again after environment is updated telemetry.initialize() with telemetry.span("category1", 'span1') as span: logging.info("In span") telemetry.collect() assert len( telemetry.get_finished_spans( name_filter=lambda name: name == 'category1.span1', attribute_filter=lambda a: a.get('attrib1') == 'attrib1_value' and a.get('attrib2') == 'attrib2_value')) == 1 assert telemetry.get_value_recorder('trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'category1', Attributes.TRACE_NAME.name: 'category1.span1', Attributes.TRACE_STATUS.name: 'OK', 'label1': 'label1_value', 'label2': 'label2_value' }).count == 1
def test_decorator_argument_labelging_none(self, telemetry: TelemetryFixture, caplog): telemetry.enable_log_record_capture(caplog) example = DecoratorExample() example.method_trace_custom(arg1='foo', arg2=None) telemetry.collect() assert telemetry.get_value_recorder( name='trace.duration', labels={ 'arg1': 'foo', 'label1': 't1', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'custom_category', Attributes.TRACE_NAME.name: 'custom_category.method_trace_custom' }).count == 1 rec = telemetry.caplog.get_record( lambda rec: rec['message'] == 'method_trace_custom log') assert rec['attributes']['label1'] == 't1' assert rec['attributes']['attrib1'] == 'a1' assert rec['attributes']['arg1'] == 'foo'
def test_instrumentors(self, telemetry: TelemetryFixture): telemetry.record_value("category1", "value1", 1) with telemetry.span("span_category1", "span1") as span: pass telemetry.collect() assert len(telemetry.get_metrics()) == 2 assert len( telemetry.get_metrics( instrumentor_filter=lambda name: name == "default")) == 2
def test_decorator_invalid_argument_label(self, telemetry: TelemetryFixture, caplog): telemetry.enable_log_record_capture(caplog) example = DecoratorExample() example.method_invalid_argument_label(arg1='arg1_value') telemetry.collect() telemetry.caplog.assert_log_contains( "@trace decorator refers to an argument 'arg4' that was not found in the signature for DecoratorExample.method_invalid_argument_label", 'WARNING')
def test_span_inheritance(self, telemetry: TelemetryFixture): with telemetry.span('test', 'span1', attributes={TestAttributes.ATTRIB1: 'attrib1', TestAttributes.LABEL1: 'label1'}) as span1: telemetry.counter('test', 'counter1') with telemetry.span('test', 'span2', attributes={TestAttributes.ATTRIB2: 'attrib2', TestAttributes.LABEL2: 'label2'}) as span2: telemetry.counter('test', 'counter2') with telemetry.span('test', 'span3') as span3: span3.set_label('label3', 'label3') span3.set_attribute('attrib3', 'attrib3') telemetry.counter('test', 'counter3', labels={'counter_label': 'counter_label'}) assert span3.attributes == {'attrib1': 'attrib1', 'attrib2': 'attrib2', 'attrib3': 'attrib3', 'label1': 'label1', 'label2': 'label2', 'label3': 'label3', Attributes.TRACE_ID.name: str(span3.context.trace_id), Attributes.TRACE_SPAN_ID.name: str(span3.context.span_id), Attributes.TRACE_IS_REMOTE.name: False, Attributes.TRACE_CATEGORY.name: 'test', Attributes.TRACE_NAME.name: 'test.span3' } assert span3.labels == {Attributes.TRACE_CATEGORY.name: 'test', Attributes.TRACE_NAME.name: 'test.span3', 'label1': 'label1', 'label2': 'label2', 'label3': 'label3'} assert telemetry.current_span.qname == 'test.span3' assert span3.qname == 'test.span3' telemetry.collect() assert telemetry.get_counter('test.counter1', labels={'label1': 'label1', Attributes.TRACE_CATEGORY.name: 'test', Attributes.TRACE_NAME.name: 'test.span1'}).value == 1 assert telemetry.get_counter('test.counter2', labels={'label1': 'label1', 'label2': 'label2', Attributes.TRACE_CATEGORY.name: 'test', Attributes.TRACE_NAME.name: 'test.span2'}).value == 1 assert telemetry.get_counter('test.counter3', labels={'label1': 'label1', 'label2': 'label2', 'label3': 'label3', 'counter_label': 'counter_label', Attributes.TRACE_CATEGORY.name: 'test', Attributes.TRACE_NAME.name: 'test.span3'}).value == 1 assert len(telemetry.get_finished_spans()) == 3
def test_decorator_default(self, telemetry: TelemetryFixture): example = DecoratorExample() example.method_trace_default(arg1='arg1_value') telemetry.collect() assert example.telemetry_category == 'tests.test_decorator.DecoratorExample' assert telemetry.get_value_recorder( 'trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'tests.test_decorator.DecoratorExample', Attributes.TRACE_NAME.name: 'tests.test_decorator.DecoratorExample.method_trace_default', Attributes.TRACE_STATUS.name: 'OK' }).count == 1
def test_decorator_complex_argument_label(self, telemetry: TelemetryFixture): example = DecoratorExample() example.method_complex_argument_label(arg1=ComplexValue('foo', 10)) telemetry.collect() assert telemetry.get_value_recorder( name='trace.duration', labels={ Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.test_decorator.DecoratorExample', Attributes.TRACE_NAME.name: 'tests.test_decorator.DecoratorExample.method_complex_argument_label' }).count == 1
def test_value_recorder(self, telemetry: TelemetryFixture): telemetry.record_value("category1", "value1", 1) telemetry.record_value("category1", "value2", 1.0) telemetry.record_value("category1", "value2", 1.2) telemetry.record_value("category1", "value2", 1.4) telemetry.collect() assert telemetry.get_value_recorder('category1.value1').count == 1 assert telemetry.get_value_recorder('category1.value1').sum == 1 assert telemetry.get_value_recorder('category1.value1').min == 1 assert telemetry.get_value_recorder('category1.value1').max == 1 assert telemetry.get_value_recorder('category1.value2').count == 3 assert telemetry.get_value_recorder('category1.value2').sum == 3.6 assert telemetry.get_value_recorder('category1.value2').min == 1.0 assert telemetry.get_value_recorder('category1.value2').max == 1.4
def test_decorator_local_def(self, telemetry: TelemetryFixture): @trace(extractor=extract_args("arg")) def foo(arg: str): time.sleep(.1) return "value" foo('arg1') telemetry.collect() assert telemetry.get_value_recorder('trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'tests.test_decorator', Attributes.TRACE_NAME.name: 'tests.test_decorator.foo', Attributes.TRACE_STATUS.name: 'OK' }).count == 1
def test_counter(self, telemetry: TelemetryFixture): telemetry.counter("category1", "counter1", 1) telemetry.counter("category1", "counter2", 2) telemetry.counter("category1", "counter3", 2) telemetry.counter("category1", "counter3", 1) telemetry.counter("category1", "counter4", 1, labels={'label1': 'label1'}) telemetry.collect() assert telemetry.get_counter('category1.counter1').value == 1 assert telemetry.get_counter('category1.counter2').value == 2 assert telemetry.get_counter('category1.counter3').value == 3 assert telemetry.get_counter('category1.counter4', labels={ 'label1': 'label1' }).value == 1
def test_json_log_format(self, telemetry: TelemetryFixture, caplog): telemetry.enable_log_record_capture(caplog) example = ExampleClass() example.method1() telemetry.collect() method1_span = telemetry.get_finished_spans(name_filter=lambda name: name == 'tests.example.ExampleClass.method1')[0] method2_span = telemetry.get_finished_spans(name_filter=lambda name: name == 'tests.example.ExampleClass.method2')[0] log_record = telemetry.caplog.get_record(lambda l: l['message'] == 'method1 log') assert log_record['attributes'] == {TestAttributes.ATTRIB1.name: 'value1', TestAttributes.LABEL1.name: 'value1', Attributes.TRACE_ID.name: method1_span.context.trace_id, Attributes.TRACE_SPAN_ID.name: method1_span.context.span_id, Attributes.TRACE_IS_REMOTE.name: False, Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'} log_record = telemetry.caplog.get_record(lambda l: l['message'] == 'method2 log') assert log_record['attributes'] == {TestAttributes.ATTRIB1.name: 'value1', TestAttributes.ATTRIB2.name: 'value2', Attributes.TRACE_ID.name: method2_span.context.trace_id, Attributes.TRACE_SPAN_ID.name: method2_span.context.span_id, Attributes.TRACE_IS_REMOTE.name: False, Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2', 'label1': 'value1', 'label2': 'value2'} telemetry.collect() assert example.telemetry_category == 'tests.example.ExampleClass' assert telemetry.get_value_recorder(name='trace.duration', labels={Attributes.TRACE_STATUS.name: 'OK', 'label1': 'value1', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).count == 1 assert telemetry.get_value_recorder(name='trace.duration', labels={Attributes.TRACE_STATUS.name: 'OK', 'label1': 'value1', 'label2': 'value2', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).count == 1 assert len(telemetry.get_value_recorders()) == 2
def test_environment_attributes_override(self, monkeypatch, telemetry: TelemetryFixture): monkeypatch.setenv('METRICS_LABEL_label1', 'label1_value') monkeypatch.setenv('METRICS_LABEL_label2', 'label2_value') monkeypatch.setenv('METRICS_ATTRIBUTE_ATTRIB1', 'attrib1_value') monkeypatch.setenv('METRICS_ATTRIBUTE_ATTRIB2', 'attrib2_value') # need to initialize again after environment is updated telemetry.initialize() # environment labels should win over any locally-specified labels to preserve ops behavior with telemetry.span("category1", 'span1', attributes={ TestAttributes.ATTRIB1: 'attrib1_override', TestAttributes.LABEL1: 'label1_value' }) as span: pass telemetry.collect() spans = telemetry.get_finished_spans() assert len( telemetry.get_finished_spans( name_filter=lambda name: name == 'category1.span1', attribute_filter=lambda a: a.get('attrib1' ) == 'attrib1_override')) == 1 assert telemetry.get_value_recorder('trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'category1', Attributes.TRACE_NAME.name: 'category1.span1', Attributes.TRACE_STATUS.name: 'OK', TestAttributes.LABEL1.name: 'label1_value', TestAttributes.LABEL2.name: 'label2_value' }).count == 1 Environment._clear()
def test_span_listener(self, telemetry: TelemetryFixture): from opentelemetry.sdk.trace import SpanProcessor class Customlabelger(SpanProcessor): def on_start(self, span: "Span", parent_context: Optional[context_api.Context] = None) -> None: wrapped = Span(span) wrapped.set_attribute('hostname', 'localhost') wrapped.set_label('env', 'test') telemetry.add_span_processor(Customlabelger()) with telemetry.span("category1", "span1") as span: assert span.labels['env'] == 'test' telemetry.collect() assert telemetry.get_value_recorder(name='trace.duration', labels={'env': 'test', Attributes.TRACE_CATEGORY.name: 'category1', Attributes.TRACE_NAME.name: 'category1.span1', Attributes.TRACE_STATUS.name: 'OK'}).count == 1
def test_labelger_empty(self, monkeypatch, telemetry: TelemetryFixture): # need to initialize again after environment is updated telemetry.initialize() with telemetry.span("category1", 'span1') as span: pass telemetry.collect() assert telemetry.get_value_recorder('trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'category1', Attributes.TRACE_NAME.name: 'category1.span1', Attributes.TRACE_STATUS.name: 'OK' }).count == 1 Environment._clear()
def test_gauge(self, telemetry: TelemetryFixture): telemetry.gauge( "category1", "gauge1", lambda observer: observer.observe(10, {'label1': 'label1'})) telemetry.gauge( "category1", "gauge2", lambda observer: observer.observe(1.2, {'label1': 'label1'})) telemetry.collect() assert telemetry.get_gauge('category1.gauge1', { 'label1': 'label1' }).count == 1 assert telemetry.get_gauge('category1.gauge1', { 'label1': 'label1' }).min == 10 assert telemetry.get_gauge('category1.gauge1', { 'label1': 'label1' }).max == 10 assert telemetry.get_gauge('category1.gauge1', { 'label1': 'label1' }).last == 10 assert telemetry.get_gauge('category1.gauge1', { 'label1': 'label1' }).sum == 10 assert telemetry.get_gauge('category1.gauge2', { 'label1': 'label1' }).count == 1 assert telemetry.get_gauge('category1.gauge2', { 'label1': 'label1' }).min == 1.2 assert telemetry.get_gauge('category1.gauge2', { 'label1': 'label1' }).max == 1.2 assert telemetry.get_gauge('category1.gauge2', { 'label1': 'label1' }).last == 1.2 assert telemetry.get_gauge('category1.gauge2', { 'label1': 'label1' }).sum == 1.2
def test_decorator_global_method(self, telemetry: TelemetryFixture, caplog): telemetry.enable_log_record_capture(caplog) global_method() telemetry.collect() assert telemetry.get_value_recorder('trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'tests.example', Attributes.TRACE_NAME.name: 'tests.example.global_method', Attributes.TRACE_STATUS.name: 'OK' }).count == 1 log_record = telemetry.caplog.get_record( lambda l: l['message'] == 'global_method log') assert log_record['attributes']['trace.id']
def test_decorator_extractor_mapping(self, telemetry: TelemetryFixture): @trace(extractor=extract_args(arg1='arg1_renamed', arg2=Label('arg2_renamed'), arg3=Attribute, arg4=Label)) def foo(arg1: str, arg2: str, arg3: str, arg4: str): time.sleep(.1) return "value" foo('arg1', 'arg2', 'arg3', 'arg4') telemetry.collect() assert telemetry.get_value_recorder('trace.duration', labels={ Attributes.TRACE_CATEGORY.name: 'tests.test_decorator', Attributes.TRACE_NAME.name: 'tests.test_decorator.foo', Attributes.TRACE_STATUS.name: 'OK', 'arg2_renamed': 'arg2', 'arg4': 'arg4' }).count == 1
def test_metrics_labelged_without_span(self, monkeypatch, telemetry: TelemetryFixture): monkeypatch.setenv('METRICS_LABEL_label1', 'label1_value') monkeypatch.setenv('METRICS_LABEL_label2', 'label2_value') monkeypatch.setenv('METRICS_ATTRIBUTE_ATTRIB1', 'attrib1_value') monkeypatch.setenv('METRICS_ATTRIBUTE_ATTRIB2', 'attrib2_value') # test that we include the environment labelger by default telemetry.initialize() # environment labels should win over any locally-specified labels to preserve ops behavior telemetry.counter('category1', 'counter1', 1, labels={'label1': 'label1_override'}) telemetry.collect() assert telemetry.get_counter('category1.counter1', labels={ 'label1': 'label1_value', 'label2': 'label2_value' }).value == 1 Environment._clear()
def test_http_server(self, monkeypatch, telemetry: TelemetryFixture): address = 'localhost:19102' monkeypatch.setenv('METRICS_EXPORTERS', 'prometheus') monkeypatch.setenv('METRICS_PROMETHEUS_PREFIX', 'test_prefix') monkeypatch.setenv('METRICS_INTERVAL', '5') monkeypatch.setenv('METRICS_PROMETHEUS_BIND_ADDRESS', address) telemetry.initialize() http = urllib3.PoolManager() with telemetry.span("category1", "span1", attributes={ TestAttributes.ATTRIB1: "attrib1", TestAttributes.LABEL1: 'label1' }) as span: time.sleep(.5) telemetry.counter("category1", "counter1", 2.0) def gauge(obv: Observer): obv.observe(1, {Attributes.ENV: 'test'}) telemetry.gauge("category1", "gauge", gauge) # wait for Prometheus collection interval to pass (METRICS_INTERVAL) time.sleep(5) telemetry.collect() response = http.request('GET', 'http://localhost:19102/metrics') def fetch_metric(name: str, labels: dict = {}): response = http.request('GET', 'http://localhost:19102/metrics') lines = response.data.decode('utf8').split('\n') matches = list( filter(lambda line: not line.startswith("#") and name in line, lines)) if len(matches) == 0: pytest.fail(f"Metric not found: {name}") elif len(matches) == 1: return float(matches[0].split(' ')[1]) else: pytest.fail(f"More than one match for metric: {name}") assert fetch_metric('test_prefix_trace_duration_count') == 1.0 assert fetch_metric('test_prefix_trace_duration_sum') >= 500 assert fetch_metric('test_prefix_category1_gauge') == 1.0 # double-check that metrics continue to be returned on duplicate fetches assert fetch_metric('test_prefix_trace_duration_count') == 1.0 assert fetch_metric('test_prefix_trace_duration_sum') >= 500 with telemetry.span("category1", "span1", attributes={ TestAttributes.ATTRIB1: "attrib1", TestAttributes.LABEL1: 'label1' }) as span: time.sleep(.5) # wait for Prometheus collection interval to pass (METRICS_INTERVAL) time.sleep(2) telemetry.collect() assert fetch_metric('test_prefix_trace_duration_count') == 2.0 assert fetch_metric('test_prefix_trace_duration_sum') >= 1000 telemetry.shutdown()
def test_mixin(self, telemetry: TelemetryFixture, caplog): example = ExampleClass() example.method1() example.method2() try: example.error() # raises exception except: pass telemetry.collect() assert example.telemetry_category == 'tests.example.ExampleClass' # method1 (direct) assert telemetry.get_counter('tests.example.ExampleClass.method1_counter').value == 1 # method1 (direct, inside span) assert telemetry.get_counter('tests.example.ExampleClass.method1_counter_inside_span', labels={'label1': 'value1', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).value == 1 # method2 (direct) assert telemetry.get_counter('tests.example.ExampleClass.method2_counter').value == 1 # method2 (direct, inside span) assert telemetry.get_counter('tests.example.ExampleClass.method2_counter_inside_span', labels={'label2': 'value2', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).value == 1 # method2 (inside span) # method2 (indirect) assert telemetry.get_counter('tests.example.ExampleClass.method2_counter', labels={'label1': 'value1', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).value == 1 # method2 (indirect, inside span) assert telemetry.get_counter('tests.example.ExampleClass.method2_counter_inside_span', labels={'label1': 'value1', 'label2': 'value2', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).value == 1 assert len(telemetry.get_counters()) == 7 # method1 (direct) assert telemetry.get_value_recorder('trace.duration', labels={'label1': 'value1', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).count == 1 assert telemetry.get_value_recorder('trace.duration', labels={'label1': 'value1', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).sum >= 100 assert telemetry.get_value_recorder('trace.duration', labels={'label1': 'value1', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).min >= 100 assert telemetry.get_value_recorder('trace.duration', labels={'label1': 'value1', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method1'}).max >= 100 # method2 (direct) assert telemetry.get_value_recorder('trace.duration', labels={'label2': 'value2', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).count == 1 assert telemetry.get_value_recorder('trace.duration', labels={'label2': 'value2', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).sum >= 100 assert telemetry.get_value_recorder('trace.duration', labels={'label2': 'value2', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).min >= 100 assert telemetry.get_value_recorder('trace.duration', labels={'label2': 'value2', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).max >= 100 # method2 (indirect) assert telemetry.get_value_recorder('trace.duration', labels={'label1': 'value1', 'label2': 'value2', Attributes.TRACE_STATUS.name: 'OK', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.method2'}).count == 1 # error (direct) assert telemetry.get_value_recorder('trace.duration', labels={Attributes.TRACE_STATUS.name: 'ERROR', Attributes.TRACE_CATEGORY.name: 'tests.example.ExampleClass', Attributes.TRACE_NAME.name: 'tests.example.ExampleClass.error'}).count == 1 assert len(telemetry.get_value_recorders()) == 4