diff --git a/newrelic/common/signature.py b/newrelic/common/signature.py new file mode 100644 index 000000000..314998196 --- /dev/null +++ b/newrelic/common/signature.py @@ -0,0 +1,31 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from newrelic.packages import six + +if six.PY3: + from inspect import Signature + + def bind_args(func, args, kwargs): + """Bind arguments and apply defaults to missing arugments for a callable.""" + bound_args = Signature.from_callable(func).bind(*args, **kwargs) + bound_args.apply_defaults() + return bound_args.arguments + +else: + from inspect import getcallargs + + def bind_args(func, args, kwargs): + """Bind arguments and apply defaults to missing arugments for a callable.""" + return getcallargs(func, *args, **kwargs) diff --git a/newrelic/hooks/logger_loguru.py b/newrelic/hooks/logger_loguru.py index 801a1c8cd..9e7ed3eae 100644 --- a/newrelic/hooks/logger_loguru.py +++ b/newrelic/hooks/logger_loguru.py @@ -18,15 +18,18 @@ from newrelic.api.application import application_instance from newrelic.api.transaction import current_transaction, record_log_event from newrelic.common.object_wrapper import wrap_function_wrapper +from newrelic.common.signature import bind_args from newrelic.core.config import global_settings from newrelic.hooks.logger_logging import add_nr_linking_metadata from newrelic.packages import six -_logger = logging.getLogger(__name__) +_logger = logging.getLogger(__name__) is_pypy = hasattr(sys, "pypy_version_info") + def loguru_version(): from loguru import __version__ + return tuple(int(x) for x in __version__.split(".")) @@ -54,7 +57,7 @@ def _nr_log_forwarder(message_instance): if application and application.enabled: application.record_custom_metric("Logging/lines", {"count": 1}) application.record_custom_metric("Logging/lines/%s" % level_name, {"count": 1}) - + if settings.application_logging.forwarding and settings.application_logging.forwarding.enabled: try: record_log_event(message, level_name, int(record["time"].timestamp())) @@ -64,14 +67,13 @@ def _nr_log_forwarder(message_instance): ALLOWED_LOGURU_OPTIONS_LENGTHS = frozenset((8, 9)) -def bind_log(level_id, static_level_no, from_decorator, options, message, args, kwargs): - assert len(options) in ALLOWED_LOGURU_OPTIONS_LENGTHS # Assert the options signature we expect - return level_id, static_level_no, from_decorator, list(options), message, args, kwargs - def wrap_log(wrapped, instance, args, kwargs): try: - level_id, static_level_no, from_decorator, options, message, subargs, subkwargs = bind_log(*args, **kwargs) + bound_args = bind_args(wrapped, args, kwargs) + options = bound_args["options"] = list(bound_args["options"]) + assert len(options) in ALLOWED_LOGURU_OPTIONS_LENGTHS # Assert the options signature we expect + options[-2] = nr_log_patcher(options[-2]) # Loguru looks into the stack trace to find the caller's module and function names. # options[1] tells loguru how far up to look in the stack trace to find the caller. @@ -87,14 +89,14 @@ def wrap_log(wrapped, instance, args, kwargs): _logger.debug("Exception in loguru handling: %s" % str(e)) return wrapped(*args, **kwargs) else: - return wrapped(level_id, static_level_no, from_decorator, options, message, subargs, subkwargs) + return wrapped(**bound_args) def nr_log_patcher(original_patcher=None): def _nr_log_patcher(record): if original_patcher: record = original_patcher(record) - + transaction = current_transaction() if transaction: diff --git a/tests/agent_unittests/test_signature.py b/tests/agent_unittests/test_signature.py new file mode 100644 index 000000000..8d44896f3 --- /dev/null +++ b/tests/agent_unittests/test_signature.py @@ -0,0 +1,31 @@ +# Copyright 2010 New Relic, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from newrelic.common.signature import bind_args + + +@pytest.mark.parametrize( + "func,args,kwargs,expected", + [ + (lambda x, y: None, (1,), {"y": 2}, {"x": 1, "y": 2}), + (lambda x=1, y=2: None, (1,), {"y": 2}, {"x": 1, "y": 2}), + (lambda x=1: None, (), {}, {"x": 1}), + ], + ids=("posargs", "kwargs", "defaults"), +) +def test_signature_binding(func, args, kwargs, expected): + bound_args = bind_args(func, args, kwargs) + assert bound_args == expected