From 65246e7f37cedc2897adb399fdab24b0a9dcafbb Mon Sep 17 00:00:00 2001 From: Timothy Pansino <11214426+TimPansino@users.noreply.github.com> Date: Mon, 10 Oct 2022 10:35:00 -0700 Subject: [PATCH] Fix Trace Finalizer Crashes (#652) * Patch crashes in various traces with None settings * Add tests for graphql trace types to unittests * Add test to ensure traces don't crash in finalizer * [Mega-Linter] Apply linters fixes * Bump tests Co-authored-by: TimPansino Co-authored-by: Lalleh Rafeei <84813886+lrafeei@users.noreply.github.com> --- newrelic/api/database_trace.py | 5 +- newrelic/api/graphql_trace.py | 7 +- newrelic/api/transaction.py | 36 +- tests/agent_features/test_span_events.py | 735 +++++++++--------- tests/agent_features/test_time_trace.py | 37 +- .../test_transaction_trace_segments.py | 166 ++-- 6 files changed, 548 insertions(+), 438 deletions(-) diff --git a/newrelic/api/database_trace.py b/newrelic/api/database_trace.py index 09dfa1e11b..2bc4976887 100644 --- a/newrelic/api/database_trace.py +++ b/newrelic/api/database_trace.py @@ -127,6 +127,7 @@ def _log_async_warning(self): def finalize_data(self, transaction, exc=None, value=None, tb=None): self.stack_trace = None + self.sql_format = "off" connect_params = None cursor_params = None @@ -206,8 +207,8 @@ def finalize_data(self, transaction, exc=None, value=None, tb=None): transaction._explain_plan_count += 1 self.sql_format = ( - tt.record_sql if tt.record_sql else "" - ) # If tt.record_sql is None, then the empty string will default to sql being obfuscated + tt.record_sql if tt.record_sql else "off" + ) # If tt.record_sql is None, then default to sql being off self.connect_params = connect_params self.cursor_params = cursor_params self.sql_parameters = sql_parameters diff --git a/newrelic/api/graphql_trace.py b/newrelic/api/graphql_trace.py index 0fef99c700..7a2c9ec02f 100644 --- a/newrelic/api/graphql_trace.py +++ b/newrelic/api/graphql_trace.py @@ -69,8 +69,13 @@ def finalize_data(self, transaction, exc=None, value=None, tb=None): self._add_agent_attribute("graphql.operation.type", self.operation_type) self._add_agent_attribute("graphql.operation.name", self.operation_name) + settings = transaction.settings + if settings and settings.agent_limits and settings.agent_limits.sql_query_length_maximum: + limit = transaction.settings.agent_limits.sql_query_length_maximum + else: + limit = 0 + # Attach formatted graphql - limit = transaction.settings.agent_limits.sql_query_length_maximum self.graphql = graphql = self.formatted[:limit] self._add_agent_attribute("graphql.operation.query", graphql) diff --git a/newrelic/api/transaction.py b/newrelic/api/transaction.py index f486989b4b..f7d7595cb1 100644 --- a/newrelic/api/transaction.py +++ b/newrelic/api/transaction.py @@ -25,13 +25,12 @@ import weakref from collections import OrderedDict -from newrelic.api.application import application_instance import newrelic.core.database_node import newrelic.core.error_node -from newrelic.core.log_event_node import LogEventNode import newrelic.core.root_node import newrelic.core.transaction_node import newrelic.packages.six as six +from newrelic.api.application import application_instance from newrelic.api.time_trace import TimeTrace, get_linking_metadata from newrelic.common.encoding_utils import ( DistributedTracePayload, @@ -63,6 +62,7 @@ ) from newrelic.core.config import DEFAULT_RESERVOIR_SIZE, LOG_EVENT_RESERVOIR_SIZE from newrelic.core.custom_event import create_custom_event +from newrelic.core.log_event_node import LogEventNode from newrelic.core.stack_trace import exception_stack from newrelic.core.stats_engine import CustomMetrics, SampledDataSet from newrelic.core.thread_utilization import utilization_tracker @@ -324,8 +324,12 @@ def __init__(self, application, enabled=None, source=None): self.enabled = True if self._settings: - self._custom_events = SampledDataSet(capacity=self._settings.event_harvest_config.harvest_limits.custom_event_data) - self._log_events = SampledDataSet(capacity=self._settings.event_harvest_config.harvest_limits.log_event_data) + self._custom_events = SampledDataSet( + capacity=self._settings.event_harvest_config.harvest_limits.custom_event_data + ) + self._log_events = SampledDataSet( + capacity=self._settings.event_harvest_config.harvest_limits.log_event_data + ) else: self._custom_events = SampledDataSet(capacity=DEFAULT_RESERVOIR_SIZE) self._log_events = SampledDataSet(capacity=LOG_EVENT_RESERVOIR_SIZE) @@ -1473,31 +1477,35 @@ def set_transaction_name(self, name, group=None, priority=None): self._group = group self._name = name - def record_log_event(self, message, level=None, timestamp=None, priority=None): settings = self.settings - if not (settings and settings.application_logging and settings.application_logging.enabled and settings.application_logging.forwarding and settings.application_logging.forwarding.enabled): + if not ( + settings + and settings.application_logging + and settings.application_logging.enabled + and settings.application_logging.forwarding + and settings.application_logging.forwarding.enabled + ): return - + timestamp = timestamp if timestamp is not None else time.time() level = str(level) if level is not None else "UNKNOWN" - + if not message or message.isspace(): _logger.debug("record_log_event called where message was missing. No log event will be sent.") return - + message = truncate(message, MAX_LOG_MESSAGE_LENGTH) event = LogEventNode( timestamp=timestamp, level=level, message=message, - attributes=get_linking_metadata(), + attributes=get_linking_metadata(), ) self._log_events.add(event, priority=priority) - def record_exception(self, exc=None, value=None, tb=None, params=None, ignore_errors=None): # Deprecation Warning warnings.warn( @@ -1603,6 +1611,8 @@ def _process_node(self, node): if type(node) is newrelic.core.database_node.DatabaseNode: settings = self._settings + if not settings: + return if not settings.collect_traces: return if not settings.slow_sql.enabled and not settings.transaction_tracer.explain_enabled: @@ -1869,7 +1879,9 @@ def record_log_event(message, level=None, timestamp=None, application=None, prio "record_log_event has been called but no transaction or application was running. As a result, " "the following event has not been recorded. message: %r level: %r timestamp %r. To correct " "this problem, supply an application object as a parameter to this record_log_event call.", - message, level, timestamp, + message, + level, + timestamp, ) elif application.enabled: application.record_log_event(message, level, timestamp, priority=priority) diff --git a/tests/agent_features/test_span_events.py b/tests/agent_features/test_span_events.py index 0024c1b8b8..4fda858c05 100644 --- a/tests/agent_features/test_span_events.py +++ b/tests/agent_features/test_span_events.py @@ -12,97 +12,95 @@ # See the License for the specific language governing permissions and # limitations under the License. -import pytest import sys -from newrelic.api.transaction import current_transaction -from newrelic.api.time_trace import (current_trace, - add_custom_span_attribute, notice_error) -from newrelic.api.background_task import background_task -from newrelic.common.object_names import callable_name +import pytest +from testing_support.fixtures import ( + dt_enabled, + function_not_called, + override_application_settings, + validate_transaction_event_attributes, + validate_transaction_metrics, + validate_tt_segment_params, +) +from testing_support.validators.validate_span_events import validate_span_events +from newrelic.api.background_task import background_task from newrelic.api.database_trace import DatabaseTrace from newrelic.api.datastore_trace import DatastoreTrace from newrelic.api.external_trace import ExternalTrace from newrelic.api.function_trace import FunctionTrace, function_trace +from newrelic.api.graphql_trace import GraphQLOperationTrace, GraphQLResolverTrace from newrelic.api.memcache_trace import MemcacheTrace from newrelic.api.message_trace import MessageTrace from newrelic.api.solr_trace import SolrTrace - -from testing_support.fixtures import (override_application_settings, - function_not_called, validate_tt_segment_params, - validate_transaction_metrics, dt_enabled, - validate_transaction_event_attributes) -from testing_support.validators.validate_span_events import ( - validate_span_events) +from newrelic.api.time_trace import ( + add_custom_span_attribute, + current_trace, + notice_error, +) +from newrelic.api.transaction import current_transaction +from newrelic.common.object_names import callable_name ERROR = ValueError("whoops") ERROR_NAME = callable_name(ERROR) -@pytest.mark.parametrize('dt_enabled', (True, False)) -@pytest.mark.parametrize('span_events_enabled', (True, False)) -@pytest.mark.parametrize('txn_sampled', (True, False)) +@pytest.mark.parametrize("dt_enabled", (True, False)) +@pytest.mark.parametrize("span_events_enabled", (True, False)) +@pytest.mark.parametrize("txn_sampled", (True, False)) def test_span_events(dt_enabled, span_events_enabled, txn_sampled): - guid = 'dbb536c53b749e0b' - sentinel_guid = '0687e0c371ea2c4e' - function_guid = '482439c52de807ee' - transaction_name = 'OtherTransaction/Function/transaction' + guid = "dbb536c53b749e0b" + sentinel_guid = "0687e0c371ea2c4e" + function_guid = "482439c52de807ee" + transaction_name = "OtherTransaction/Function/transaction" priority = 0.5 - @function_trace(name='child') + @function_trace(name="child") def child(): pass - @function_trace(name='function') + @function_trace(name="function") def function(): current_trace().guid = function_guid child() - _settings = { - 'distributed_tracing.enabled': dt_enabled, - 'span_events.enabled': span_events_enabled - } + _settings = {"distributed_tracing.enabled": dt_enabled, "span_events.enabled": span_events_enabled} count = 0 if dt_enabled and span_events_enabled and txn_sampled: count = 1 exact_intrinsics_common = { - 'type': 'Span', - 'transactionId': guid, - 'sampled': txn_sampled, - 'priority': priority, - 'category': 'generic', + "type": "Span", + "transactionId": guid, + "sampled": txn_sampled, + "priority": priority, + "category": "generic", } - expected_intrinsics = ('timestamp', 'duration') + expected_intrinsics = ("timestamp", "duration") exact_intrinsics_root = exact_intrinsics_common.copy() - exact_intrinsics_root['name'] = 'Function/transaction' - exact_intrinsics_root['transaction.name'] = transaction_name - exact_intrinsics_root['nr.entryPoint'] = True + exact_intrinsics_root["name"] = "Function/transaction" + exact_intrinsics_root["transaction.name"] = transaction_name + exact_intrinsics_root["nr.entryPoint"] = True exact_intrinsics_function = exact_intrinsics_common.copy() - exact_intrinsics_function['name'] = 'Function/function' - exact_intrinsics_function['parentId'] = sentinel_guid + exact_intrinsics_function["name"] = "Function/function" + exact_intrinsics_function["parentId"] = sentinel_guid exact_intrinsics_child = exact_intrinsics_common.copy() - exact_intrinsics_child['name'] = 'Function/child' - exact_intrinsics_child['parentId'] = function_guid - - @validate_span_events(count=count, - expected_intrinsics=['nr.entryPoint']) - @validate_span_events(count=count, - exact_intrinsics=exact_intrinsics_root, - expected_intrinsics=expected_intrinsics) - @validate_span_events(count=count, - exact_intrinsics=exact_intrinsics_function, - expected_intrinsics=expected_intrinsics) - @validate_span_events(count=count, - exact_intrinsics=exact_intrinsics_child, - expected_intrinsics=expected_intrinsics) + exact_intrinsics_child["name"] = "Function/child" + exact_intrinsics_child["parentId"] = function_guid + + @validate_span_events(count=count, expected_intrinsics=["nr.entryPoint"]) + @validate_span_events(count=count, exact_intrinsics=exact_intrinsics_root, expected_intrinsics=expected_intrinsics) + @validate_span_events( + count=count, exact_intrinsics=exact_intrinsics_function, expected_intrinsics=expected_intrinsics + ) + @validate_span_events(count=count, exact_intrinsics=exact_intrinsics_child, expected_intrinsics=expected_intrinsics) @override_application_settings(_settings) - @background_task(name='transaction') + @background_task(name="transaction") def _test(): # Force intrinsics txn = current_transaction() @@ -116,22 +114,29 @@ def _test(): _test() -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), -)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (GraphQLOperationTrace, ()), + (GraphQLResolverTrace, ()), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + ), +) def test_each_span_type(trace_type, args): @validate_span_events(count=2) - @override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, - }) - @background_task(name='test_each_span_type') + @override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + } + ) + @background_task(name="test_each_span_type") def _test(): transaction = current_transaction() @@ -143,37 +148,37 @@ def _test(): _test() -@pytest.mark.parametrize('sql,sql_format,expected', ( - pytest.param( - 'a' * 2001, - 'raw', - ''.join(['a'] * 1997 + ['...']), - id='truncate'), - pytest.param( - 'a' * 2000, - 'raw', - ''.join(['a'] * 2000), - id='no_truncate'), - pytest.param( - 'select * from %s' % ''.join(['?'] * 2000), - 'obfuscated', - 'select * from %s...' % ( - ''.join(['?'] * (2000 - len('select * from ') - 3))), - id='truncate_obfuscated'), - pytest.param('select 1', 'off', ''), - pytest.param('select 1', 'raw', 'select 1'), - pytest.param('select 1', 'obfuscated', 'select ?'), -)) +@pytest.mark.parametrize( + "sql,sql_format,expected", + ( + pytest.param("a" * 2001, "raw", "".join(["a"] * 1997 + ["..."]), id="truncate"), + pytest.param("a" * 2000, "raw", "".join(["a"] * 2000), id="no_truncate"), + pytest.param( + "select * from %s" % "".join(["?"] * 2000), + "obfuscated", + "select * from %s..." % ("".join(["?"] * (2000 - len("select * from ") - 3))), + id="truncate_obfuscated", + ), + pytest.param("select 1", "off", ""), + pytest.param("select 1", "raw", "select 1"), + pytest.param("select 1", "obfuscated", "select ?"), + ), +) def test_database_db_statement_format(sql, sql_format, expected): - @validate_span_events(count=1, exact_agents={ - 'db.statement': expected, - }) - @override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, - 'transaction_tracer.record_sql': sql_format, - }) - @background_task(name='test_database_db_statement_format') + @validate_span_events( + count=1, + exact_agents={ + "db.statement": expected, + }, + ) + @override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + "transaction_tracer.record_sql": sql_format, + } + ) + @background_task(name="test_database_db_statement_format") def _test(): transaction = current_transaction() transaction._sampled = True @@ -186,115 +191,130 @@ def _test(): @validate_span_events( count=1, - exact_intrinsics={'category': 'datastore'}, - unexpected_agents=['db.statement'], + exact_intrinsics={"category": "datastore"}, + unexpected_agents=["db.statement"], +) +@override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + "span_events.attributes.exclude": ["db.statement"], + } ) -@override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, - 'span_events.attributes.exclude': ['db.statement'], -}) -@background_task(name='test_database_db_statement_exclude') +@background_task(name="test_database_db_statement_exclude") def test_database_db_statement_exclude(): transaction = current_transaction() transaction._sampled = True - with DatabaseTrace('select 1'): + with DatabaseTrace("select 1"): pass -@pytest.mark.parametrize('trace_type,args,attrs', ( - (DatastoreTrace, ('db_product', 'db_target', 'db_operation'), {"db.collection": "db_target", "db.operation": "db_operation"}), - (DatabaseTrace, ("select 1 from db_table",), {"db.collection": "db_table", "db.statement": "select ? from db_table"}), -)) +@pytest.mark.parametrize( + "trace_type,args,attrs", + ( + ( + DatastoreTrace, + ("db_product", "db_target", "db_operation"), + {"db.collection": "db_target", "db.operation": "db_operation"}, + ), + ( + DatabaseTrace, + ("select 1 from db_table",), + {"db.collection": "db_table", "db.statement": "select ? from db_table"}, + ), + ), +) def test_datastore_database_trace_attrs(trace_type, args, attrs): @validate_span_events( count=1, exact_agents=attrs, ) - @override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, - }) - @background_task(name='test_database_db_statement_exclude') + @override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + } + ) + @background_task(name="test_database_db_statement_exclude") def test(): transaction = current_transaction() transaction._sampled = True with trace_type(*args): pass - + test() -@pytest.mark.parametrize('exclude_url', (True, False)) +@pytest.mark.parametrize("exclude_url", (True, False)) def test_external_spans(exclude_url): override_settings = { - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, + "distributed_tracing.enabled": True, + "span_events.enabled": True, } if exclude_url: - override_settings['span_events.attributes.exclude'] = ['http.url'] + override_settings["span_events.attributes.exclude"] = ["http.url"] exact_agents = {} - unexpected_agents = ['http.url'] + unexpected_agents = ["http.url"] else: - exact_agents = {'http.url': 'http://example.com/foo'} + exact_agents = {"http.url": "http://example.com/foo"} unexpected_agents = [] @validate_span_events( count=1, exact_intrinsics={ - 'name': 'External/example.com/library/get', - 'type': 'Span', - 'sampled': True, - - 'category': 'http', - 'span.kind': 'client', - 'component': 'library', - 'http.method': 'get', + "name": "External/example.com/library/get", + "type": "Span", + "sampled": True, + "category": "http", + "span.kind": "client", + "component": "library", + "http.method": "get", }, exact_agents=exact_agents, unexpected_agents=unexpected_agents, - expected_intrinsics=('priority',), + expected_intrinsics=("priority",), ) @override_application_settings(override_settings) - @background_task(name='test_external_spans') + @background_task(name="test_external_spans") def _test(): transaction = current_transaction() transaction._sampled = True - with ExternalTrace( - library='library', - url='http://example.com/foo?secret=123', - method='get'): + with ExternalTrace(library="library", url="http://example.com/foo?secret=123", method="get"): pass _test() -@pytest.mark.parametrize('kwarg_override,attr_override', ( - ({'url': 'a' * 256}, {'http.url': 'a' * 255}), - ({'library': 'a' * 256}, {'component': 'a' * 255}), - ({'method': 'a' * 256}, {'http.method': 'a' * 255}), -)) -@override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, -}) +@pytest.mark.parametrize( + "kwarg_override,attr_override", + ( + ({"url": "a" * 256}, {"http.url": "a" * 255}), + ({"library": "a" * 256}, {"component": "a" * 255}), + ({"method": "a" * 256}, {"http.method": "a" * 255}), + ), +) +@override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + } +) def test_external_span_limits(kwarg_override, attr_override): exact_intrinsics = { - 'type': 'Span', - 'sampled': True, - - 'category': 'http', - 'span.kind': 'client', - 'component': 'library', - 'http.method': 'get', + "type": "Span", + "sampled": True, + "category": "http", + "span.kind": "client", + "component": "library", + "http.method": "get", } exact_agents = { - 'http.url': 'http://example.com/foo', + "http.url": "http://example.com/foo", } for attr_name, attr_value in attr_override.items(): if attr_name in exact_agents: @@ -303,9 +323,9 @@ def test_external_span_limits(kwarg_override, attr_override): exact_intrinsics[attr_name] = attr_value kwargs = { - 'library': 'library', - 'url': 'http://example.com/foo?secret=123', - 'method': 'get', + "library": "library", + "url": "http://example.com/foo?secret=123", + "method": "get", } kwargs.update(kwarg_override) @@ -313,9 +333,9 @@ def test_external_span_limits(kwarg_override, attr_override): count=1, exact_intrinsics=exact_intrinsics, exact_agents=exact_agents, - expected_intrinsics=('priority',), + expected_intrinsics=("priority",), ) - @background_task(name='test_external_spans') + @background_task(name="test_external_spans") def _test(): transaction = current_transaction() transaction._sampled = True @@ -326,32 +346,34 @@ def _test(): _test() -@pytest.mark.parametrize('kwarg_override,attribute_override', ( - ({'host': 'a' * 256}, - {'peer.hostname': 'a' * 255, 'peer.address': 'a' * 255}), - ({'port_path_or_id': 'a' * 256, 'host': 'a'}, - {'peer.hostname': 'a', 'peer.address': 'a:' + 'a' * 253}), - ({'database_name': 'a' * 256}, {'db.instance': 'a' * 255}), -)) -@override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, -}) +@pytest.mark.parametrize( + "kwarg_override,attribute_override", + ( + ({"host": "a" * 256}, {"peer.hostname": "a" * 255, "peer.address": "a" * 255}), + ({"port_path_or_id": "a" * 256, "host": "a"}, {"peer.hostname": "a", "peer.address": "a:" + "a" * 253}), + ({"database_name": "a" * 256}, {"db.instance": "a" * 255}), + ), +) +@override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + } +) def test_datastore_span_limits(kwarg_override, attribute_override): exact_intrinsics = { - 'type': 'Span', - 'sampled': True, - - 'category': 'datastore', - 'span.kind': 'client', - 'component': 'library', + "type": "Span", + "sampled": True, + "category": "datastore", + "span.kind": "client", + "component": "library", } exact_agents = { - 'db.instance': 'db', - 'peer.hostname': 'foo', - 'peer.address': 'foo:1234', + "db.instance": "db", + "peer.hostname": "foo", + "peer.address": "foo:1234", } for k, v in attribute_override.items(): @@ -361,22 +383,22 @@ def test_datastore_span_limits(kwarg_override, attribute_override): exact_intrinsics[k] = v kwargs = { - 'product': 'library', - 'target': 'table', - 'operation': 'operation', - 'host': 'foo', - 'port_path_or_id': 1234, - 'database_name': 'db', + "product": "library", + "target": "table", + "operation": "operation", + "host": "foo", + "port_path_or_id": 1234, + "database_name": "db", } kwargs.update(kwarg_override) @validate_span_events( count=1, exact_intrinsics=exact_intrinsics, - expected_intrinsics=('priority',), + expected_intrinsics=("priority",), exact_agents=exact_agents, ) - @background_task(name='test_external_spans') + @background_task(name="test_external_spans") def _test(): transaction = current_transaction() transaction._sampled = True @@ -387,10 +409,9 @@ def _test(): _test() -@pytest.mark.parametrize('collect_span_events', (False, True)) -@pytest.mark.parametrize('span_events_enabled', (False, True)) -def test_collect_span_events_override(collect_span_events, - span_events_enabled): +@pytest.mark.parametrize("collect_span_events", (False, True)) +@pytest.mark.parametrize("span_events_enabled", (False, True)) +def test_collect_span_events_override(collect_span_events, span_events_enabled): if collect_span_events and span_events_enabled: spans_expected = True @@ -400,63 +421,64 @@ def test_collect_span_events_override(collect_span_events, span_count = 2 if spans_expected else 0 @validate_span_events(count=span_count) - @override_application_settings({ - 'transaction_tracer.enabled': False, - 'distributed_tracing.enabled': True, - 'span_events.enabled': span_events_enabled, - 'collect_span_events': collect_span_events - }) - @background_task(name='test_collect_span_events_override') + @override_application_settings( + { + "transaction_tracer.enabled": False, + "distributed_tracing.enabled": True, + "span_events.enabled": span_events_enabled, + "collect_span_events": collect_span_events, + } + ) + @background_task(name="test_collect_span_events_override") def _test(): transaction = current_transaction() transaction._sampled = True - with FunctionTrace('span_generator'): + with FunctionTrace("span_generator"): pass if not spans_expected: - _test = function_not_called( - 'newrelic.core.attribute', - 'resolve_agent_attributes')(_test) + _test = function_not_called("newrelic.core.attribute", "resolve_agent_attributes")(_test) _test() -@pytest.mark.parametrize('include_attribues', (True, False)) +@pytest.mark.parametrize("include_attribues", (True, False)) def test_span_event_agent_attributes(include_attribues): override_settings = { - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, + "distributed_tracing.enabled": True, + "span_events.enabled": True, } if include_attribues: count = 1 - override_settings['attributes.include'] = ['*'] + override_settings["attributes.include"] = ["*"] else: count = 0 @override_application_settings(override_settings) + @validate_span_events(count=count, expected_agents=["webfrontend.queue.seconds"]) @validate_span_events( - count=count, expected_agents=['webfrontend.queue.seconds']) - @validate_span_events( - count=count, - exact_agents={'trace1_a': 'foobar', 'trace1_b': 'barbaz'}, - unexpected_agents=['trace2_a', 'trace2_b']) + count=count, + exact_agents={"trace1_a": "foobar", "trace1_b": "barbaz"}, + unexpected_agents=["trace2_a", "trace2_b"], + ) @validate_span_events( - count=count, - exact_agents={'trace2_a': 'foobar', 'trace2_b': 'barbaz'}, - unexpected_agents=['trace1_a', 'trace1_b']) - @background_task(name='test_span_event_agent_attributes') + count=count, + exact_agents={"trace2_a": "foobar", "trace2_b": "barbaz"}, + unexpected_agents=["trace1_a", "trace1_b"], + ) + @background_task(name="test_span_event_agent_attributes") def _test(): transaction = current_transaction() transaction.queue_start = 1.0 transaction._sampled = True - with FunctionTrace('trace1') as trace_1: - trace_1._add_agent_attribute('trace1_a', 'foobar') - trace_1._add_agent_attribute('trace1_b', 'barbaz') - with FunctionTrace('trace2') as trace_2: - trace_2._add_agent_attribute('trace2_a', 'foobar') - trace_2._add_agent_attribute('trace2_b', 'barbaz') + with FunctionTrace("trace1") as trace_1: + trace_1._add_agent_attribute("trace1_a", "foobar") + trace_1._add_agent_attribute("trace1_b", "barbaz") + with FunctionTrace("trace2") as trace_2: + trace_2._add_agent_attribute("trace2_a", "foobar") + trace_2._add_agent_attribute("trace2_b", "barbaz") _test() @@ -469,31 +491,36 @@ def __exit__(self, *args): pass -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), - (FakeTrace, ()), -)) -@pytest.mark.parametrize('exclude_attributes', (True, False)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + (FakeTrace, ()), + ), +) +@pytest.mark.parametrize("exclude_attributes", (True, False)) def test_span_event_user_attributes(trace_type, args, exclude_attributes): _settings = { - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, + "distributed_tracing.enabled": True, + "span_events.enabled": True, } - forgone_params = ['invalid_value', ] - expected_params = {'trace1_a': 'foobar', 'trace1_b': 'barbaz'} + forgone_params = [ + "invalid_value", + ] + expected_params = {"trace1_a": "foobar", "trace1_b": "barbaz"} # We expect user_attributes to be included by default if exclude_attributes: count = 0 - _settings['attributes.exclude'] = ['*'] - forgone_params.extend(('trace1_a', 'trace1_b')) + _settings["attributes.exclude"] = ["*"] + forgone_params.extend(("trace1_a", "trace1_b")) expected_trace_params = {} else: expected_trace_params = expected_params @@ -503,44 +530,44 @@ def test_span_event_user_attributes(trace_type, args, exclude_attributes): @validate_span_events( count=count, exact_users=expected_params, - unexpected_users=forgone_params,) - @validate_tt_segment_params(exact_params=expected_trace_params, - forgone_params=forgone_params) - @background_task(name='test_span_event_user_attributes') + unexpected_users=forgone_params, + ) + @validate_tt_segment_params(exact_params=expected_trace_params, forgone_params=forgone_params) + @background_task(name="test_span_event_user_attributes") def _test(): transaction = current_transaction() transaction._sampled = True with trace_type(*args): - add_custom_span_attribute('trace1_a', 'foobar') - add_custom_span_attribute('trace1_b', 'barbaz') - add_custom_span_attribute('invalid_value', sys.maxsize + 1) + add_custom_span_attribute("trace1_a", "foobar") + add_custom_span_attribute("trace1_b", "barbaz") + add_custom_span_attribute("invalid_value", sys.maxsize + 1) _test() -@validate_span_events(count=1, exact_users={'foo': 'b'}) +@validate_span_events(count=1, exact_users={"foo": "b"}) @dt_enabled -@background_task(name='test_span_user_attribute_overrides_transaction_attribute') +@background_task(name="test_span_user_attribute_overrides_transaction_attribute") def test_span_user_attribute_overrides_transaction_attribute(): transaction = current_transaction() - transaction.add_custom_parameter('foo', 'a') - add_custom_span_attribute('foo', 'b') - transaction.add_custom_parameter('foo', 'c') + transaction.add_custom_parameter("foo", "a") + add_custom_span_attribute("foo", "b") + transaction.add_custom_parameter("foo", "c") -@override_application_settings({'attributes.include': '*'}) -@validate_span_events(count=1, exact_agents={'foo': 'b'}) +@override_application_settings({"attributes.include": "*"}) +@validate_span_events(count=1, exact_agents={"foo": "b"}) @dt_enabled -@background_task(name='test_span_agent_attribute_overrides_transaction_attribute') +@background_task(name="test_span_agent_attribute_overrides_transaction_attribute") def test_span_agent_attribute_overrides_transaction_attribute(): transaction = current_transaction() trace = current_trace() - transaction._add_agent_attribute('foo', 'a') - trace._add_agent_attribute('foo', 'b') - transaction._add_agent_attribute('foo', 'c') + transaction._add_agent_attribute("foo", "a") + trace._add_agent_attribute("foo", "b") + transaction._add_agent_attribute("foo", "c") def test_span_custom_attribute_limit(): @@ -555,71 +582,71 @@ def test_span_custom_attribute_limit(): for i in range(128): if i < 64: - span_custom_attrs.append('span_attr%i' % i) - txn_custom_attrs.append('txn_attr%i' % i) + span_custom_attrs.append("span_attr%i" % i) + txn_custom_attrs.append("txn_attr%i" % i) unexpected_txn_attrs.extend(span_custom_attrs) span_custom_attrs.extend(txn_custom_attrs[:64]) - expected_txn_attrs = {'user': txn_custom_attrs, 'agent': [], - 'intrinsic': []} - expected_absent_txn_attrs = {'agent': [], - 'user': unexpected_txn_attrs, - 'intrinsic': []} - - @override_application_settings({'attributes.include': '*'}) - @validate_transaction_event_attributes(expected_txn_attrs, - expected_absent_txn_attrs) - @validate_span_events(count=1, - expected_users=span_custom_attrs, - unexpected_users=txn_custom_attrs[64:]) + expected_txn_attrs = {"user": txn_custom_attrs, "agent": [], "intrinsic": []} + expected_absent_txn_attrs = {"agent": [], "user": unexpected_txn_attrs, "intrinsic": []} + + @override_application_settings({"attributes.include": "*"}) + @validate_transaction_event_attributes(expected_txn_attrs, expected_absent_txn_attrs) + @validate_span_events(count=1, expected_users=span_custom_attrs, unexpected_users=txn_custom_attrs[64:]) @dt_enabled - @background_task(name='test_span_attribute_limit') + @background_task(name="test_span_attribute_limit") def _test(): transaction = current_transaction() for i in range(128): - transaction.add_custom_parameter('txn_attr%i' % i, 'txnValue') + transaction.add_custom_parameter("txn_attr%i" % i, "txnValue") if i < 64: - add_custom_span_attribute('span_attr%i' % i, 'spanValue') + add_custom_span_attribute("span_attr%i" % i, "spanValue") + _test() _span_event_metrics = [("Supportability/SpanEvent/Errors/Dropped", None)] -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), - (FakeTrace, ()), -)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (GraphQLOperationTrace, ()), + (GraphQLResolverTrace, ()), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + (FakeTrace, ()), + ), +) def test_span_event_error_attributes_notice_error(trace_type, args): _settings = { - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, + "distributed_tracing.enabled": True, + "span_events.enabled": True, } error = ValueError("whoops") exact_agents = { - 'error.class': callable_name(error), - 'error.message': 'whoops', + "error.class": callable_name(error), + "error.message": "whoops", } @override_application_settings(_settings) @validate_transaction_metrics( - 'test_span_event_error_attributes_notice_error', - background_task=True, - rollup_metrics=_span_event_metrics) + "test_span_event_error_attributes_notice_error", background_task=True, rollup_metrics=_span_event_metrics + ) @validate_span_events( count=1, - exact_agents=exact_agents,) - @background_task(name='test_span_event_error_attributes_notice_error') + exact_agents=exact_agents, + ) + @background_task(name="test_span_event_error_attributes_notice_error") def _test(): transaction = current_transaction() transaction._sampled = True @@ -633,36 +660,41 @@ def _test(): _test() -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), -)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (GraphQLOperationTrace, ()), + (GraphQLResolverTrace, ()), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + ), +) def test_span_event_error_attributes_observed(trace_type, args): error = ValueError("whoops") exact_agents = { - 'error.class': callable_name(error), - 'error.message': 'whoops', + "error.class": callable_name(error), + "error.message": "whoops", } # Verify errors are not recorded since notice_error is not called - rollups = [('Errors/all', None)] + _span_event_metrics + rollups = [("Errors/all", None)] + _span_event_metrics @dt_enabled @validate_transaction_metrics( - 'test_span_event_error_attributes_observed', - background_task=True, - rollup_metrics=rollups) + "test_span_event_error_attributes_observed", background_task=True, rollup_metrics=rollups + ) @validate_span_events( count=1, - exact_agents=exact_agents,) - @background_task(name='test_span_event_error_attributes_observed') + exact_agents=exact_agents, + ) + @background_task(name="test_span_event_error_attributes_observed") def _test(): try: with trace_type(*args): @@ -673,20 +705,24 @@ def _test(): _test() -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), - (FakeTrace, ()), -)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (GraphQLOperationTrace, ()), + (GraphQLResolverTrace, ()), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + (FakeTrace, ()), + ), +) @dt_enabled -@validate_span_events(count=1, - exact_agents={'error.class': ERROR_NAME, 'error.message': 'whoops'}) -@background_task(name='test_span_event_notice_error_overrides_observed') +@validate_span_events(count=1, exact_agents={"error.class": ERROR_NAME, "error.message": "whoops"}) +@background_task(name="test_span_event_notice_error_overrides_observed") def test_span_event_notice_error_overrides_observed(trace_type, args): try: with trace_type(*args): @@ -699,21 +735,24 @@ def test_span_event_notice_error_overrides_observed(trace_type, args): pass -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), - (FakeTrace, ()), -)) -@override_application_settings({'error_collector.enabled': False}) -@validate_span_events(count=0, expected_agents=['error.class']) -@validate_span_events(count=0, expected_agents=['error.message']) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + (FakeTrace, ()), + ), +) +@override_application_settings({"error_collector.enabled": False}) +@validate_span_events(count=0, expected_agents=["error.class"]) +@validate_span_events(count=0, expected_agents=["error.message"]) @dt_enabled -@background_task(name='test_span_event_errors_disabled') +@background_task(name="test_span_event_errors_disabled") def test_span_event_errors_disabled(trace_type, args): with trace_type(*args): try: @@ -725,32 +764,34 @@ def test_span_event_errors_disabled(trace_type, args): _metrics = [("Supportability/SpanEvent/Errors/Dropped", 2)] -@pytest.mark.parametrize('trace_type,args', ( - (FunctionTrace, ('name', )), - (FakeTrace, ()), -)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (FunctionTrace, ("name",)), + (FakeTrace, ()), + ), +) def test_span_event_multiple_errors(trace_type, args): _settings = { - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, + "distributed_tracing.enabled": True, + "span_events.enabled": True, } error = ValueError("whoops") exact_agents = { - 'error.class': callable_name(error), - 'error.message': 'whoops', + "error.class": callable_name(error), + "error.message": "whoops", "error.expected": False, } @override_application_settings(_settings) @validate_span_events( count=1, - exact_agents=exact_agents,) - @validate_transaction_metrics("test_span_event_multiple_errors", - background_task=True, - rollup_metrics=_metrics) - @background_task(name='test_span_event_multiple_errors') + exact_agents=exact_agents, + ) + @validate_transaction_metrics("test_span_event_multiple_errors", background_task=True, rollup_metrics=_metrics) + @background_task(name="test_span_event_multiple_errors") def _test(): transaction = current_transaction() transaction._sampled = True diff --git a/tests/agent_features/test_time_trace.py b/tests/agent_features/test_time_trace.py index f81e8750d7..449b7dc97d 100644 --- a/tests/agent_features/test_time_trace.py +++ b/tests/agent_features/test_time_trace.py @@ -14,11 +14,19 @@ import logging +import pytest from testing_support.fixtures import validate_transaction_metrics from newrelic.api.background_task import background_task +from newrelic.api.database_trace import DatabaseTrace +from newrelic.api.datastore_trace import DatastoreTrace +from newrelic.api.external_trace import ExternalTrace from newrelic.api.function_trace import FunctionTrace -from newrelic.api.transaction import end_of_transaction +from newrelic.api.graphql_trace import GraphQLOperationTrace, GraphQLResolverTrace +from newrelic.api.memcache_trace import MemcacheTrace +from newrelic.api.message_trace import MessageTrace +from newrelic.api.solr_trace import SolrTrace +from newrelic.api.transaction import current_transaction, end_of_transaction @validate_transaction_metrics( @@ -34,3 +42,30 @@ def test_trace_after_end_of_transaction(caplog): error_messages = [record for record in caplog.records if record.levelno >= logging.ERROR] assert not error_messages + + +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (GraphQLOperationTrace, ()), + (GraphQLResolverTrace, ()), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + ), +) +@background_task() +def test_trace_finalizes_with_transaction_missing_settings(monkeypatch, trace_type, args): + txn = current_transaction() + try: + with trace_type(*args): + # Validate no errors are raised when finalizing trace with no settings + monkeypatch.setattr(txn, "_settings", None) + finally: + # Ensure transaction still has settings when it exits to prevent other crashes making errors hard to read + monkeypatch.undo() + assert txn.settings diff --git a/tests/agent_features/test_transaction_trace_segments.py b/tests/agent_features/test_transaction_trace_segments.py index ad4d02b18a..b205afc3ce 100644 --- a/tests/agent_features/test_transaction_trace_segments.py +++ b/tests/agent_features/test_transaction_trace_segments.py @@ -13,142 +13,158 @@ # limitations under the License. import pytest +from testing_support.fixtures import ( + override_application_settings, + validate_tt_segment_params, +) -from newrelic.api.transaction import current_transaction from newrelic.api.background_task import background_task - from newrelic.api.database_trace import DatabaseTrace from newrelic.api.datastore_trace import DatastoreTrace -from newrelic.api.external_trace import external_trace, ExternalTrace +from newrelic.api.external_trace import ExternalTrace, external_trace from newrelic.api.function_trace import FunctionTrace +from newrelic.api.graphql_trace import GraphQLOperationTrace, GraphQLResolverTrace from newrelic.api.memcache_trace import MemcacheTrace from newrelic.api.message_trace import MessageTrace from newrelic.api.solr_trace import SolrTrace - -from testing_support.fixtures import (override_application_settings, - validate_tt_segment_params) +from newrelic.api.transaction import current_transaction -@external_trace('lib', 'https://example.com/path?q=q#frag') +@external_trace("lib", "https://example.com/path?q=q#frag") def external(): pass -@validate_tt_segment_params(present_params=('http.url',)) -@background_task(name='test_external_segment_attributes_default') +@validate_tt_segment_params(present_params=("http.url",)) +@background_task(name="test_external_segment_attributes_default") def test_external_segment_attributes_default(): external() -@override_application_settings({ - 'transaction_segments.attributes.exclude': ['http.url'], -}) -@validate_tt_segment_params(forgone_params=('http.url',)) -@background_task(name='test_external_segment_attributes_disabled') +@override_application_settings( + { + "transaction_segments.attributes.exclude": ["http.url"], + } +) +@validate_tt_segment_params(forgone_params=("http.url",)) +@background_task(name="test_external_segment_attributes_disabled") def test_external_segment_attributes_disabled(): external() -@validate_tt_segment_params(exact_params={'http.url': 'http://example.org'}) -@background_task(name='test_external_user_params_override_url') +@validate_tt_segment_params(exact_params={"http.url": "http://example.org"}) +@background_task(name="test_external_user_params_override_url") def test_external_user_params_override_url(): - with ExternalTrace('lib', 'http://example.com') as t: + with ExternalTrace("lib", "http://example.com") as t: # Pretend like this is a user attribute and it's legal to do this - t.params['http.url'] = 'http://example.org' + t.params["http.url"] = "http://example.org" -@validate_tt_segment_params(exact_params={'db.instance': 'a' * 255}) -@background_task(name='test_datastore_db_instance_truncation') +@validate_tt_segment_params(exact_params={"db.instance": "a" * 255}) +@background_task(name="test_datastore_db_instance_truncation") def test_datastore_db_instance_truncation(): - with DatastoreTrace('db_product', 'db_target', 'db_operation', - database_name='a' * 256): + with DatastoreTrace("db_product", "db_target", "db_operation", database_name="a" * 256): pass -@validate_tt_segment_params(exact_params={'db.instance': 'a' * 255}) -@background_task(name='test_database_db_instance_truncation') +@validate_tt_segment_params(exact_params={"db.instance": "a" * 255}) +@background_task(name="test_database_db_instance_truncation") def test_database_db_instance_truncation(): - with DatabaseTrace('select * from foo', - database_name='a' * 256): + with DatabaseTrace("select * from foo", database_name="a" * 256): pass -@override_application_settings({ - 'transaction_tracer.record_sql': 'raw', -}) -@validate_tt_segment_params(exact_params={'db.statement': 'select 1'}) -@background_task(name='test_database_db_statement') +@override_application_settings( + { + "transaction_tracer.record_sql": "raw", + } +) +@validate_tt_segment_params(exact_params={"db.statement": "select 1"}) +@background_task(name="test_database_db_statement") def test_database_db_statement_default_enabled(): - with DatabaseTrace('select 1'): + with DatabaseTrace("select 1"): pass -@override_application_settings({ - 'transaction_tracer.record_sql': 'raw', - 'agent_limits.sql_query_length_maximum': 1, -}) -@validate_tt_segment_params(exact_params={'db.statement': 'a'}) -@background_task(name='test_database_db_statement_truncation') +@override_application_settings( + { + "transaction_tracer.record_sql": "raw", + "agent_limits.sql_query_length_maximum": 1, + } +) +@validate_tt_segment_params(exact_params={"db.statement": "a"}) +@background_task(name="test_database_db_statement_truncation") def test_database_db_statement_truncation(): - with DatabaseTrace('a' * 2): + with DatabaseTrace("a" * 2): pass -@override_application_settings({ - 'transaction_segments.attributes.exclude': ['db.*'], -}) -@validate_tt_segment_params(forgone_params=('db.instance', 'db.statement')) -@background_task(name='test_database_segment_attributes_disabled') +@override_application_settings( + { + "transaction_segments.attributes.exclude": ["db.*"], + } +) +@validate_tt_segment_params(forgone_params=("db.instance", "db.statement")) +@background_task(name="test_database_segment_attributes_disabled") def test_database_segment_attributes_disabled(): transaction = current_transaction() - with DatabaseTrace('select 1', database_name='foo'): + with DatabaseTrace("select 1", database_name="foo"): pass -@pytest.mark.parametrize('trace_type,args', ( - (DatabaseTrace, ('select * from foo', )), - (DatastoreTrace, ('db_product', 'db_target', 'db_operation')), - (ExternalTrace, ('lib', 'url')), - (FunctionTrace, ('name', )), - (MemcacheTrace, ('command', )), - (MessageTrace, ('lib', 'operation', 'dst_type', 'dst_name')), - (SolrTrace, ('lib', 'command')), -)) +@pytest.mark.parametrize( + "trace_type,args", + ( + (DatabaseTrace, ("select * from foo",)), + (DatastoreTrace, ("db_product", "db_target", "db_operation")), + (ExternalTrace, ("lib", "url")), + (FunctionTrace, ("name",)), + (GraphQLOperationTrace, ()), + (GraphQLResolverTrace, ()), + (MemcacheTrace, ("command",)), + (MessageTrace, ("lib", "operation", "dst_type", "dst_name")), + (SolrTrace, ("lib", "command")), + ), +) def test_each_segment_type(trace_type, args): - @validate_tt_segment_params(exact_params={'blah': 'bloo'}) - @override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, - 'attributes.include': ['blah'], - }) - @background_task(name='test_each_segment_type') + @validate_tt_segment_params(exact_params={"blah": "bloo"}) + @override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + "attributes.include": ["blah"], + } + ) + @background_task(name="test_each_segment_type") def _test(): transaction = current_transaction() transaction._sampled = True with trace_type(*args) as trace: - trace._add_agent_attribute('blah', 'bloo') + trace._add_agent_attribute("blah", "bloo") _test() -@override_application_settings({ - 'distributed_tracing.enabled': True, - 'span_events.enabled': True, - 'attributes.include': ['*'], -}) -@background_task(name='test_attribute_overrides') +@override_application_settings( + { + "distributed_tracing.enabled": True, + "span_events.enabled": True, + "attributes.include": ["*"], + } +) +@background_task(name="test_attribute_overrides") def test_attribute_overrides(): - with FunctionTrace('test_attribute_overrides_trace') as trace: + with FunctionTrace("test_attribute_overrides_trace") as trace: trace.exclusive = 0.1 - trace._add_agent_attribute('exclusive_duration_millis', 0.2) - trace._add_agent_attribute('test_attr', 'a') - trace.add_custom_attribute('exclusive_duration_millis', 0.3) - trace.add_custom_attribute('test_attr', 'b') + trace._add_agent_attribute("exclusive_duration_millis", 0.2) + trace._add_agent_attribute("test_attr", "a") + trace.add_custom_attribute("exclusive_duration_millis", 0.3) + trace.add_custom_attribute("test_attr", "b") node = trace.create_node() params = node.get_trace_segment_params(current_transaction().settings) - assert params['exclusive_duration_millis'] == 100 - assert params['test_attr'] == 'b' + assert params["exclusive_duration_millis"] == 100 + assert params["test_attr"] == "b"