Skip to content

Commit

Permalink
Add GraphQL Introspection Setting (#783)
Browse files Browse the repository at this point in the history
* Add graphql introspection setting

* Sort settings object hierarchy

* Add test for introspection queries setting

* Expand introspection queries testing

* [Mega-Linter] Apply linters fixes

* Adjust introspection detection for graphql

---------

Co-authored-by: TimPansino <TimPansino@users.noreply.github.com>
  • Loading branch information
TimPansino and TimPansino committed Mar 28, 2023
1 parent 3d8679c commit 36417c4
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 70 deletions.
70 changes: 42 additions & 28 deletions newrelic/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,14 @@ def _can_enable_infinite_tracing():
return True


class InstrumentationSettings(Settings):
pass


class InstrumentationGraphQLSettings(Settings):
pass


class EventHarvestConfigSettings(Settings):
nested = True
_lock = threading.Lock()
Expand All @@ -355,50 +363,52 @@ class EventHarvestConfigHarvestLimitSettings(Settings):


_settings = TopLevelSettings()
_settings.agent_limits = AgentLimitsSettings()
_settings.application_logging = ApplicationLoggingSettings()
_settings.application_logging.forwarding = ApplicationLoggingForwardingSettings()
_settings.application_logging.metrics = ApplicationLoggingMetricsSettings()
_settings.application_logging.local_decorating = ApplicationLoggingLocalDecoratingSettings()
_settings.application_logging.metrics = ApplicationLoggingMetricsSettings()
_settings.attributes = AttributesSettings()
_settings.gc_runtime_metrics = GCRuntimeMetricsSettings()
_settings.code_level_metrics = CodeLevelMetricsSettings()
_settings.thread_profiler = ThreadProfilerSettings()
_settings.transaction_tracer = TransactionTracerSettings()
_settings.transaction_tracer.attributes = TransactionTracerAttributesSettings()
_settings.error_collector = ErrorCollectorSettings()
_settings.error_collector.attributes = ErrorCollectorAttributesSettings()
_settings.browser_monitoring = BrowserMonitorSettings()
_settings.browser_monitoring.attributes = BrowserMonitorAttributesSettings()
_settings.transaction_name = TransactionNameSettings()
_settings.transaction_metrics = TransactionMetricsSettings()
_settings.event_loop_visibility = EventLoopVisibilitySettings()
_settings.rum = RumSettings()
_settings.slow_sql = SlowSqlSettings()
_settings.agent_limits = AgentLimitsSettings()
_settings.code_level_metrics = CodeLevelMetricsSettings()
_settings.console = ConsoleSettings()
_settings.debug = DebugSettings()
_settings.cross_application_tracer = CrossApplicationTracerSettings()
_settings.transaction_events = TransactionEventsSettings()
_settings.transaction_events.attributes = TransactionEventsAttributesSettings()
_settings.custom_insights_events = CustomInsightsEventsSettings()
_settings.process_host = ProcessHostSettings()
_settings.synthetics = SyntheticsSettings()
_settings.message_tracer = MessageTracerSettings()
_settings.utilization = UtilizationSettings()
_settings.strip_exception_messages = StripExceptionMessageSettings()
_settings.datastore_tracer = DatastoreTracerSettings()
_settings.datastore_tracer.instance_reporting = DatastoreTracerInstanceReportingSettings()
_settings.datastore_tracer.database_name_reporting = DatastoreTracerDatabaseNameReportingSettings()
_settings.datastore_tracer.instance_reporting = DatastoreTracerInstanceReportingSettings()
_settings.debug = DebugSettings()
_settings.distributed_tracing = DistributedTracingSettings()
_settings.error_collector = ErrorCollectorSettings()
_settings.error_collector.attributes = ErrorCollectorAttributesSettings()
_settings.event_harvest_config = EventHarvestConfigSettings()
_settings.event_harvest_config.harvest_limits = EventHarvestConfigHarvestLimitSettings()
_settings.event_loop_visibility = EventLoopVisibilitySettings()
_settings.gc_runtime_metrics = GCRuntimeMetricsSettings()
_settings.heroku = HerokuSettings()
_settings.infinite_tracing = InfiniteTracingSettings()
_settings.instrumentation = InstrumentationSettings()
_settings.instrumentation.graphql = InstrumentationGraphQLSettings()
_settings.message_tracer = MessageTracerSettings()
_settings.process_host = ProcessHostSettings()
_settings.rum = RumSettings()
_settings.serverless_mode = ServerlessModeSettings()
_settings.slow_sql = SlowSqlSettings()
_settings.span_events = SpanEventSettings()
_settings.span_events.attributes = SpanEventAttributesSettings()
_settings.strip_exception_messages = StripExceptionMessageSettings()
_settings.synthetics = SyntheticsSettings()
_settings.thread_profiler = ThreadProfilerSettings()
_settings.transaction_events = TransactionEventsSettings()
_settings.transaction_events.attributes = TransactionEventsAttributesSettings()
_settings.transaction_metrics = TransactionMetricsSettings()
_settings.transaction_name = TransactionNameSettings()
_settings.transaction_segments = TransactionSegmentSettings()
_settings.transaction_segments.attributes = TransactionSegmentAttributesSettings()
_settings.distributed_tracing = DistributedTracingSettings()
_settings.serverless_mode = ServerlessModeSettings()
_settings.infinite_tracing = InfiniteTracingSettings()
_settings.event_harvest_config = EventHarvestConfigSettings()
_settings.event_harvest_config.harvest_limits = EventHarvestConfigHarvestLimitSettings()
_settings.transaction_tracer = TransactionTracerSettings()
_settings.transaction_tracer.attributes = TransactionTracerAttributesSettings()
_settings.utilization = UtilizationSettings()


_settings.log_file = os.environ.get("NEW_RELIC_LOG", None)
Expand Down Expand Up @@ -736,6 +746,10 @@ def default_host(license_key):
_settings.infinite_tracing.ssl = True
_settings.infinite_tracing.span_queue_size = _environ_as_int("NEW_RELIC_INFINITE_TRACING_SPAN_QUEUE_SIZE", 10000)

_settings.instrumentation.graphql.capture_introspection_queries = os.environ.get(
"NEW_RELIC_INSTRUMENTATION_GRAPHQL_CAPTURE_INTROSPECTION_QUERIES", False
)

_settings.event_harvest_config.harvest_limits.analytic_event_data = _environ_as_int(
"NEW_RELIC_ANALYTICS_EVENTS_MAX_SAMPLES_STORED", DEFAULT_RESERVOIR_SIZE
)
Expand Down
6 changes: 4 additions & 2 deletions newrelic/hooks/framework_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def bind_operation_v2(exe_context, operation, root_value):
def wrap_execute_operation(wrapped, instance, args, kwargs):
transaction = current_transaction()
trace = current_trace()
settings = transaction.settings

if not transaction:
return wrapped(*args, **kwargs)
Expand Down Expand Up @@ -135,8 +136,9 @@ def wrap_execute_operation(wrapped, instance, args, kwargs):
if operation.selection_set is not None:
fields = operation.selection_set.selections
# Ignore transactions for introspection queries
for field in fields:
if get_node_value(field, "name") in GRAPHQL_INTROSPECTION_FIELDS:
if not settings.instrumentation.graphql.capture_introspection_queries:
# If all selected fields are introspection fields
if all(get_node_value(field, "name") in GRAPHQL_INTROSPECTION_FIELDS for field in fields):
ignore_transaction()

fragments = execution_context.fragments
Expand Down
21 changes: 15 additions & 6 deletions tests/framework_ariadne/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

import pytest
from testing_support.fixtures import dt_enabled
from testing_support.fixtures import dt_enabled, override_application_settings
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_count import (
validate_transaction_count,
Expand Down Expand Up @@ -520,8 +520,17 @@ def _test():
_test()


@validate_transaction_count(0)
@background_task()
def test_ignored_introspection_transactions(app, graphql_run):
ok, response = graphql_run(app, "{ __schema { types { name } } }")
assert ok and not response.get("errors")
@pytest.mark.parametrize("capture_introspection_setting", (True, False))
def test_introspection_transactions(app, graphql_run, capture_introspection_setting):
txn_ct = 1 if capture_introspection_setting else 0

@override_application_settings(
{"instrumentation.graphql.capture_introspection_queries": capture_introspection_setting}
)
@validate_transaction_count(txn_ct)
@background_task()
def _test():
ok, response = graphql_run(app, "{ __schema { types { name } } }")
assert ok and not response.get("errors")

_test()
29 changes: 21 additions & 8 deletions tests/framework_graphene/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@

import pytest
import six
from testing_support.fixtures import dt_enabled
from testing_support.fixtures import dt_enabled, override_application_settings
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_count import (
validate_transaction_count,
)
from testing_support.validators.validate_transaction_errors import validate_transaction_errors
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
from testing_support.validators.validate_transaction_errors import (
validate_transaction_errors,
)
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
)

from newrelic.api.background_task import background_task
from newrelic.common.object_names import callable_name
Expand Down Expand Up @@ -510,8 +514,17 @@ def _test():
_test()


@validate_transaction_count(0)
@background_task()
def test_ignored_introspection_transactions(app, graphql_run):
response = graphql_run(app, "{ __schema { types { name } } }")
assert not response.errors
@pytest.mark.parametrize("capture_introspection_setting", (True, False))
def test_introspection_transactions(app, graphql_run, capture_introspection_setting):
txn_ct = 1 if capture_introspection_setting else 0

@override_application_settings(
{"instrumentation.graphql.capture_introspection_queries": capture_introspection_setting}
)
@validate_transaction_count(txn_ct)
@background_task()
def _test():
response = graphql_run(app, "{ __schema { types { name } } }")
assert not response.errors

_test()
41 changes: 27 additions & 14 deletions tests/framework_graphql/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@
# limitations under the License.

import pytest
from testing_support.fixtures import dt_enabled
from testing_support.fixtures import dt_enabled, override_application_settings
from testing_support.validators.validate_code_level_metrics import (
validate_code_level_metrics,
)
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_count import (
validate_transaction_count,
)
from testing_support.validators.validate_code_level_metrics import validate_code_level_metrics
from testing_support.validators.validate_transaction_errors import validate_transaction_errors
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
from testing_support.validators.validate_transaction_errors import (
validate_transaction_errors,
)
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
)

from newrelic.api.background_task import background_task
from newrelic.common.object_names import callable_name
Expand Down Expand Up @@ -99,9 +105,9 @@ def test_basic(app, graphql_run):
)
@background_task()
def _test():
response = graphql_run(app, '{ hello }')
response = graphql_run(app, "{ hello }")
assert not response.errors

_test()


Expand Down Expand Up @@ -376,9 +382,7 @@ def test_operation_metrics_and_attrs(app, graphql_run):
@validate_span_events(exact_agents=operation_attrs)
@background_task()
def _test():
response = graphql_run(
app, "query MyQuery { library(index: 0) { branch, book { id, name } } }"
)
response = graphql_run(app, "query MyQuery { library(index: 0) { branch, book { id, name } } }")
assert not response.errors

_test()
Expand Down Expand Up @@ -507,8 +511,17 @@ def _test():
_test()


@validate_transaction_count(0)
@background_task()
def test_ignored_introspection_transactions(app, graphql_run):
response = graphql_run(app, "{ __schema { types { name } } }")
assert not response.errors
@pytest.mark.parametrize("capture_introspection_setting", (True, False))
def test_introspection_transactions(app, graphql_run, capture_introspection_setting):
txn_ct = 1 if capture_introspection_setting else 0

@override_application_settings(
{"instrumentation.graphql.capture_introspection_queries": capture_introspection_setting}
)
@validate_transaction_count(txn_ct)
@background_task()
def _test():
response = graphql_run(app, "{ __schema { types { name } } }")
assert not response.errors

_test()
39 changes: 27 additions & 12 deletions tests/framework_strawberry/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
# limitations under the License.

import pytest
from testing_support.fixtures import dt_enabled
from testing_support.fixtures import dt_enabled, override_application_settings
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_errors import validate_transaction_errors
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
from testing_support.validators.validate_transaction_count import validate_transaction_count
from testing_support.validators.validate_transaction_count import (
validate_transaction_count,
)
from testing_support.validators.validate_transaction_errors import (
validate_transaction_errors,
)
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
)

from newrelic.api.background_task import background_task
from newrelic.common.object_names import callable_name
Expand Down Expand Up @@ -61,12 +67,12 @@ def delay_import():
return delay_import


def example_middleware(next, root, info, **args): #pylint: disable=W0622
def example_middleware(next, root, info, **args): # pylint: disable=W0622
return_value = next(root, info, **args)
return return_value


def error_middleware(next, root, info, **args): #pylint: disable=W0622
def error_middleware(next, root, info, **args): # pylint: disable=W0622
raise RuntimeError("Runtime Error!")


Expand Down Expand Up @@ -248,7 +254,7 @@ def test_exception_in_validation(app, graphql_run, is_graphql_2, query, exc_clas
exc_class = callable_name(GraphQLError)

_test_exception_scoped_metrics = [
('GraphQL/operation/Strawberry/<unknown>/<anonymous>/<unknown>', 1),
("GraphQL/operation/Strawberry/<unknown>/<anonymous>/<unknown>", 1),
]
_test_exception_rollup_metrics = [
("Errors/all", 1),
Expand Down Expand Up @@ -434,8 +440,17 @@ def _test():
_test()


@validate_transaction_count(0)
@background_task()
def test_ignored_introspection_transactions(app, graphql_run):
response = graphql_run(app, "{ __schema { types { name } } }")
assert not response.errors
@pytest.mark.parametrize("capture_introspection_setting", (True, False))
def test_introspection_transactions(app, graphql_run, capture_introspection_setting):
txn_ct = 1 if capture_introspection_setting else 0

@override_application_settings(
{"instrumentation.graphql.capture_introspection_queries": capture_introspection_setting}
)
@validate_transaction_count(txn_ct)
@background_task()
def _test():
response = graphql_run(app, "{ __schema { types { name } } }")
assert not response.errors

_test()

0 comments on commit 36417c4

Please sign in to comment.