Skip to content

Commit

Permalink
Merge branch 'main' into increase-custom-event-limit
Browse files Browse the repository at this point in the history
  • Loading branch information
hmstepanek committed Nov 14, 2023
2 parents 30d8fae + f939014 commit ef0834b
Show file tree
Hide file tree
Showing 18 changed files with 133 additions and 157 deletions.
8 changes: 8 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,12 @@ RUN mkdir -p ${HOME} && \
groupadd --gid ${GID} vscode && \
useradd --uid ${UID} --gid ${GID} --home ${HOME} vscode && \
chown -R ${UID}:${GID} /home/vscode

# Move pyenv installation
ENV PYENV_ROOT="${HOME}/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:${PATH}"
RUN mv /root/.pyenv /home/vscode/.pyenv && \
chown -R vscode:vscode /home/vscode/.pyenv

# Set user
USER ${UID}:${GID}
32 changes: 18 additions & 14 deletions newrelic/common/package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import sys
import warnings

try:
from functools import cache as _cache_package_versions
Expand Down Expand Up @@ -110,6 +111,23 @@ def _get_package_version(name):
module = sys.modules.get(name, None)
version = None

with warnings.catch_warnings(record=True):
for attr in VERSION_ATTRS:
try:
version = getattr(module, attr, None)

# In certain cases like importlib_metadata.version, version is a callable
# function.
if callable(version):
continue

# Cast any version specified as a list into a tuple.
version = tuple(version) if isinstance(version, list) else version
if version not in NULL_VERSIONS:
return version
except Exception:
pass

# importlib was introduced into the standard library starting in Python3.8.
if "importlib" in sys.modules and hasattr(sys.modules["importlib"], "metadata"):
try:
Expand All @@ -126,20 +144,6 @@ def _get_package_version(name):
except Exception:
pass

for attr in VERSION_ATTRS:
try:
version = getattr(module, attr, None)
# In certain cases like importlib_metadata.version, version is a callable
# function.
if callable(version):
continue
# Cast any version specified as a list into a tuple.
version = tuple(version) if isinstance(version, list) else version
if version not in NULL_VERSIONS:
return version
except Exception:
pass

if "pkg_resources" in sys.modules:
try:
version = sys.modules["pkg_resources"].get_distribution(name).version
Expand Down
5 changes: 0 additions & 5 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2450,11 +2450,6 @@ def _process_module_builtin_defaults():
"newrelic.hooks.messagebroker_kafkapython",
"instrument_kafka_heartbeat",
)
_process_module_definition(
"kafka.consumer.group",
"newrelic.hooks.messagebroker_kafkapython",
"instrument_kafka_consumer_group",
)

_process_module_definition(
"logging",
Expand Down
68 changes: 25 additions & 43 deletions newrelic/hooks/external_botocore.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from newrelic.api.message_trace import message_trace
from newrelic.api.datastore_trace import datastore_trace
from newrelic.api.external_trace import ExternalTrace
from newrelic.api.message_trace import message_trace
from newrelic.common.object_wrapper import wrap_function_wrapper


def extract_sqs(*args, **kwargs):
queue_value = kwargs.get('QueueUrl', 'Unknown')
return queue_value.rsplit('/', 1)[-1]
queue_value = kwargs.get("QueueUrl", "Unknown")
return queue_value.rsplit("/", 1)[-1]


def extract(argument_names, default=None):
Expand All @@ -41,42 +41,27 @@ def extractor_string(*args, **kwargs):


CUSTOM_TRACE_POINTS = {
('sns', 'publish'): message_trace(
'SNS', 'Produce', 'Topic',
extract(('TopicArn', 'TargetArn'), 'PhoneNumber')),
('dynamodb', 'put_item'): datastore_trace(
'DynamoDB', extract('TableName'), 'put_item'),
('dynamodb', 'get_item'): datastore_trace(
'DynamoDB', extract('TableName'), 'get_item'),
('dynamodb', 'update_item'): datastore_trace(
'DynamoDB', extract('TableName'), 'update_item'),
('dynamodb', 'delete_item'): datastore_trace(
'DynamoDB', extract('TableName'), 'delete_item'),
('dynamodb', 'create_table'): datastore_trace(
'DynamoDB', extract('TableName'), 'create_table'),
('dynamodb', 'delete_table'): datastore_trace(
'DynamoDB', extract('TableName'), 'delete_table'),
('dynamodb', 'query'): datastore_trace(
'DynamoDB', extract('TableName'), 'query'),
('dynamodb', 'scan'): datastore_trace(
'DynamoDB', extract('TableName'), 'scan'),
('sqs', 'send_message'): message_trace(
'SQS', 'Produce', 'Queue', extract_sqs),
('sqs', 'send_message_batch'): message_trace(
'SQS', 'Produce', 'Queue', extract_sqs),
('sqs', 'receive_message'): message_trace(
'SQS', 'Consume', 'Queue', extract_sqs),
("sns", "publish"): message_trace("SNS", "Produce", "Topic", extract(("TopicArn", "TargetArn"), "PhoneNumber")),
("dynamodb", "put_item"): datastore_trace("DynamoDB", extract("TableName"), "put_item"),
("dynamodb", "get_item"): datastore_trace("DynamoDB", extract("TableName"), "get_item"),
("dynamodb", "update_item"): datastore_trace("DynamoDB", extract("TableName"), "update_item"),
("dynamodb", "delete_item"): datastore_trace("DynamoDB", extract("TableName"), "delete_item"),
("dynamodb", "create_table"): datastore_trace("DynamoDB", extract("TableName"), "create_table"),
("dynamodb", "delete_table"): datastore_trace("DynamoDB", extract("TableName"), "delete_table"),
("dynamodb", "query"): datastore_trace("DynamoDB", extract("TableName"), "query"),
("dynamodb", "scan"): datastore_trace("DynamoDB", extract("TableName"), "scan"),
("sqs", "send_message"): message_trace("SQS", "Produce", "Queue", extract_sqs),
("sqs", "send_message_batch"): message_trace("SQS", "Produce", "Queue", extract_sqs),
("sqs", "receive_message"): message_trace("SQS", "Consume", "Queue", extract_sqs),
}


def bind__create_api_method(py_operation_name, operation_name, service_model,
*args, **kwargs):
def bind__create_api_method(py_operation_name, operation_name, service_model, *args, **kwargs):
return (py_operation_name, service_model)


def _nr_clientcreator__create_api_method_(wrapped, instance, args, kwargs):
(py_operation_name, service_model) = \
bind__create_api_method(*args, **kwargs)
(py_operation_name, service_model) = bind__create_api_method(*args, **kwargs)

service_name = service_model.service_name.lower()
tracer = CUSTOM_TRACE_POINTS.get((service_name, py_operation_name))
Expand All @@ -95,30 +80,27 @@ def _bind_make_request_params(operation_model, request_dict, *args, **kwargs):

def _nr_endpoint_make_request_(wrapped, instance, args, kwargs):
operation_model, request_dict = _bind_make_request_params(*args, **kwargs)
url = request_dict.get('url', '')
method = request_dict.get('method', None)

with ExternalTrace(library='botocore', url=url, method=method, source=wrapped) as trace:
url = request_dict.get("url", "")
method = request_dict.get("method", None)

with ExternalTrace(library="botocore", url=url, method=method, source=wrapped) as trace:
try:
trace._add_agent_attribute('aws.operation', operation_model.name)
trace._add_agent_attribute("aws.operation", operation_model.name)
except:
pass

result = wrapped(*args, **kwargs)
try:
request_id = result[1]['ResponseMetadata']['RequestId']
trace._add_agent_attribute('aws.requestId', request_id)
request_id = result[1]["ResponseMetadata"]["RequestId"]
trace._add_agent_attribute("aws.requestId", request_id)
except:
pass
return result


def instrument_botocore_endpoint(module):
wrap_function_wrapper(module, 'Endpoint.make_request',
_nr_endpoint_make_request_)
wrap_function_wrapper(module, "Endpoint.make_request", _nr_endpoint_make_request_)


def instrument_botocore_client(module):
wrap_function_wrapper(module, 'ClientCreator._create_api_method',
_nr_clientcreator__create_api_method_)
wrap_function_wrapper(module, "ClientCreator._create_api_method", _nr_clientcreator__create_api_method_)
41 changes: 28 additions & 13 deletions tests/agent_unittests/test_package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
# limitations under the License.

import sys
import warnings

import pytest
import six
from testing_support.validators.validate_function_called import validate_function_called

from newrelic.common.package_version_utils import (
Expand Down Expand Up @@ -66,30 +68,26 @@ def cleared_package_version_cache():
("version_tuple", [3, 1, "0b2"], "3.1.0b2"),
),
)
def test_get_package_version(attr, value, expected_value):
def test_get_package_version(monkeypatch, attr, value, expected_value):
# There is no file/module here, so we monkeypatch
# pytest instead for our purposes
setattr(pytest, attr, value)
monkeypatch.setattr(pytest, attr, value, raising=False)
version = get_package_version("pytest")
assert version == expected_value
delattr(pytest, attr)


# This test only works on Python 3.7
@SKIP_IF_IMPORTLIB_METADATA
def test_skips_version_callables():
def test_skips_version_callables(monkeypatch):
# There is no file/module here, so we monkeypatch
# pytest instead for our purposes
setattr(pytest, "version", lambda x: "1.2.3.4")
setattr(pytest, "version_tuple", [3, 1, "0b2"])
monkeypatch.setattr(pytest, "version", lambda x: "1.2.3.4", raising=False)
monkeypatch.setattr(pytest, "version_tuple", [3, 1, "0b2"], raising=False)

version = get_package_version("pytest")

assert version == "3.1.0b2"

delattr(pytest, "version")
delattr(pytest, "version_tuple")


# This test only works on Python 3.7
@SKIP_IF_IMPORTLIB_METADATA
Expand All @@ -102,13 +100,12 @@ def test_skips_version_callables():
("version_tuple", [3, 1, "0b2"], (3, 1, "0b2")),
),
)
def test_get_package_version_tuple(attr, value, expected_value):
def test_get_package_version_tuple(monkeypatch, attr, value, expected_value):
# There is no file/module here, so we monkeypatch
# pytest instead for our purposes
setattr(pytest, attr, value)
monkeypatch.setattr(pytest, attr, value, raising=False)
version = get_package_version_tuple("pytest")
assert version == expected_value
delattr(pytest, attr)


@SKIP_IF_NOT_IMPORTLIB_METADATA
Expand All @@ -132,10 +129,28 @@ def test_pkg_resources_metadata():
assert version not in NULL_VERSIONS, version


def _getattr_deprecation_warning(attr):
if attr == "__version__":
warnings.warn("Testing deprecation warnings.", DeprecationWarning)
return "3.2.1"
else:
raise NotImplementedError()


@pytest.mark.skipif(six.PY2, reason="Can't add Deprecation in __version__ in Python 2.")
def test_deprecation_warning_suppression(monkeypatch, recwarn):
# Add fake module to be deleted later
monkeypatch.setattr(pytest, "__getattr__", _getattr_deprecation_warning, raising=False)

assert get_package_version("pytest") == "3.2.1"

assert not recwarn.list, "Warnings not suppressed."


def test_version_caching(monkeypatch):
# Add fake module to be deleted later
sys.modules["mymodule"] = sys.modules["pytest"]
setattr(pytest, "__version__", "1.0.0")
monkeypatch.setattr(pytest, "__version__", "1.0.0", raising=False)
version = get_package_version("mymodule")
assert version not in NULL_VERSIONS, version

Expand Down
30 changes: 0 additions & 30 deletions tests/external_boto3/conftest.py

This file was deleted.

22 changes: 11 additions & 11 deletions tests/external_botocore/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

from testing_support.fixtures import collector_agent_registration_fixture, collector_available_fixture # noqa: F401; pylint: disable=W0611

from testing_support.fixtures import ( # noqa: F401; pylint: disable=W0611
collector_agent_registration_fixture,
collector_available_fixture,
)

_default_settings = {
'transaction_tracer.explain_threshold': 0.0,
'transaction_tracer.transaction_threshold': 0.0,
'transaction_tracer.stack_trace_threshold': 0.0,
'debug.log_data_collector_payloads': True,
'debug.record_transaction_failure': True,
"transaction_tracer.explain_threshold": 0.0,
"transaction_tracer.transaction_threshold": 0.0,
"transaction_tracer.stack_trace_threshold": 0.0,
"debug.log_data_collector_payloads": True,
"debug.record_transaction_failure": True,
}

collector_agent_registration = collector_agent_registration_fixture(
app_name='Python Agent Test (external_botocore)',
default_settings=_default_settings)
app_name="Python Agent Test (external_botocore)", default_settings=_default_settings
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import boto3
import moto
from testing_support.fixtures import override_application_settings
from testing_support.fixtures import dt_enabled
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
Expand Down Expand Up @@ -53,7 +53,7 @@
]


@override_application_settings({"distributed_tracing.enabled": True})
@dt_enabled
@validate_span_events(exact_agents={"http.url": "https://iam.amazonaws.com/"}, count=3)
@validate_span_events(expected_agents=("aws.requestId",), count=3)
@validate_span_events(exact_agents={"aws.operation": "CreateUser"}, count=1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import boto3
import botocore
import moto
from testing_support.fixtures import override_application_settings
from testing_support.fixtures import dt_enabled
from testing_support.validators.validate_span_events import validate_span_events
from testing_support.validators.validate_transaction_metrics import (
validate_transaction_metrics,
Expand Down Expand Up @@ -73,7 +73,7 @@
]


@override_application_settings({"distributed_tracing.enabled": True})
@dt_enabled
@validate_span_events(exact_agents={"aws.operation": "CreateBucket"}, count=1)
@validate_span_events(exact_agents={"aws.operation": "PutObject"}, count=1)
@validate_span_events(exact_agents={"aws.operation": "ListObjects"}, count=1)
Expand Down
Loading

0 comments on commit ef0834b

Please sign in to comment.