Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loguru Attribute Instrumentation #1025

Merged
merged 25 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4d9c357
Add tests for logging's json logging
TimPansino Dec 7, 2023
a7d4208
Upgrade record_log_event to handle dict logging
TimPansino Dec 11, 2023
e9c2753
Update logging to capture dict messages
TimPansino Dec 11, 2023
808811a
Add attributes for dict log messages
TimPansino Dec 20, 2023
42d4aa4
Implementation of JSON message filtering
TimPansino Dec 21, 2023
8faf043
Correct attributes only log behavior
TimPansino Dec 22, 2023
0791206
Testing for logging attributes
TimPansino Dec 22, 2023
9409bed
Add logging context test for py2
TimPansino Dec 22, 2023
3006415
Logically separate attribute tests
TimPansino Dec 22, 2023
0e920cf
Clean out imports
TimPansino Dec 22, 2023
3bd9ab8
Fix failing tests
TimPansino Dec 22, 2023
a4905f3
Structlog cleanup
TimPansino Dec 28, 2023
c20caa3
Attempting list instrumentation
TimPansino Dec 28, 2023
9837634
Structlog attributes support
TimPansino Dec 29, 2023
bb3abf8
Loguru instrumentation refactor
TimPansino Dec 29, 2023
55a35b3
New attribute testing
TimPansino Dec 29, 2023
0a58f1e
Move exception settings
TimPansino Dec 30, 2023
dff566c
Clean up testing
TimPansino Dec 30, 2023
860eb13
Remove unneeded option
TimPansino Dec 30, 2023
5974d7b
Merge branch 'develop-logging-attributes' into feature-loguru-attrs
TimPansino Jan 8, 2024
e19c95b
Remove other framework changes
TimPansino Jan 8, 2024
a8ef21a
[Mega-Linter] Apply linters fixes
TimPansino Jan 8, 2024
3d33f45
Bump tests
TimPansino Jan 8, 2024
49967f8
Merge branch 'develop-logging-attributes' into feature-loguru-attrs
TimPansino Jan 11, 2024
fd975bd
Merge branch 'develop-logging-attributes' into feature-loguru-attrs
mergify[bot] Jan 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions newrelic/hooks/logger_loguru.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,24 @@
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.package_version_utils import get_package_version_tuple
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__)
is_pypy = hasattr(sys, "pypy_version_info")

IS_PYPY = hasattr(sys, "pypy_version_info")
LOGURU_VERSION = get_package_version_tuple("loguru")
LOGURU_FILTERED_RECORD_ATTRS = {"extra", "message", "time", "level", "_nr_original_message", "record"}
ALLOWED_LOGURU_OPTIONS_LENGTHS = frozenset((8, 9))

def loguru_version():
from loguru import __version__

return tuple(int(x) for x in __version__.split("."))
def _filter_record_attributes(record):
attrs = {k: v for k, v in record.items() if k not in LOGURU_FILTERED_RECORD_ATTRS}
extra_attrs = dict(record.get("extra", {}))
attrs.update({"extra.%s" % k: v for k, v in extra_attrs.items()})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting a backwards incompatibility here:
This means that these attributes are named differently than before since they are now being prefixed with extra.. This makes sense to me but we should have a note in the release notes about this so customers aren't confused.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There weren't attributes in any published releases yet. This is changed from an older unreleased PR that was included on the develop branch.

return attrs


def _nr_log_forwarder(message_instance):
Expand Down Expand Up @@ -59,17 +64,17 @@ def _nr_log_forwarder(message_instance):
application.record_custom_metric("Logging/lines/%s" % level_name, {"count": 1})

if settings.application_logging.forwarding and settings.application_logging.forwarding.enabled:
attrs = dict(record.get("extra", {}))
attrs = _filter_record_attributes(record)

try:
record_log_event(message, level_name, int(record["time"].timestamp()), attributes=attrs)
time = record.get("time", None)
if time:
time = int(time.timestamp())
record_log_event(message, level_name, time, attributes=attrs)
except Exception:
pass


ALLOWED_LOGURU_OPTIONS_LENGTHS = frozenset((8, 9))


def wrap_log(wrapped, instance, args, kwargs):
try:
bound_args = bind_args(wrapped, args, kwargs)
Expand All @@ -80,7 +85,7 @@ def wrap_log(wrapped, instance, args, kwargs):
# 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.
# Because wrap_log is an extra call in the stack trace, loguru needs to look 1 level higher.
if not is_pypy:
if not IS_PYPY:
options[1] += 1
else:
# PyPy inspection requires an additional frame of offset, as the wrapt internals seem to
Expand Down Expand Up @@ -111,7 +116,7 @@ def _nr_log_patcher(record):
record["_nr_original_message"] = message = record["message"]
record["message"] = add_nr_linking_metadata(message)

if loguru_version() > (0, 6, 0):
if LOGURU_VERSION > (0, 6, 0):
if original_patcher is not None:
patchers = [p for p in original_patcher] # Consumer iterable into list so we can modify
# Wipe out reference so patchers aren't called twice, as the framework will handle calling other patchers.
Expand All @@ -137,7 +142,7 @@ def patch_loguru_logger(logger):
logger.add(_nr_log_forwarder, format="{message}")
logger._core._nr_instrumented = True
elif not hasattr(logger, "_nr_instrumented"): # pragma: no cover
for _, handler in six.iteritems(logger._handlers):
for _, handler in logger._handlers.items():
if handler._writer is _nr_log_forwarder:
logger._nr_instrumented = True
return
Expand Down
4 changes: 3 additions & 1 deletion tests/logger_loguru/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"application_logging.forwarding.enabled": True,
"application_logging.metrics.enabled": True,
"application_logging.local_decorating.enabled": True,
"application_logging.forwarding.context_data.enabled": True,
"event_harvest_config.harvest_limits.log_event_data": 100000,
}

Expand Down Expand Up @@ -58,7 +59,8 @@ def logger():
import loguru

_logger = loguru.logger
_logger.configure(extra={"global_extra": "global_value"})
_logger.configure(extra={"global_extra": 3})
_logger = _logger.opt(record=True)

caplog = CaplogHandler()
handler_id = _logger.add(caplog, level="WARNING", format="{message}")
Expand Down
65 changes: 0 additions & 65 deletions tests/logger_loguru/test_attribute_forwarding.py

This file was deleted.

70 changes: 70 additions & 0 deletions tests/logger_loguru/test_attributes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 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 testing_support.validators.validate_log_event_count import validate_log_event_count
from testing_support.validators.validate_log_events import validate_log_events

from newrelic.api.background_task import background_task


@validate_log_events(
[
{ # Fixed attributes
"message": "context_attrs: arg1",
"context.file": "(name='%s', path='%s')" % ("test_attributes.py", str(__file__)),
"context.function": "test_loguru_default_context_attributes",
"context.extra.bound_attr": 1,
"context.extra.contextual_attr": 2,
"context.extra.global_extra": 3,
"context.extra.kwarg_attr": 4,
"context.patched_attr": 5,
"context.module": "test_attributes",
"context.name": "test_attributes",
}
],
required_attrs=[ # Variable attributes
"context.elapsed",
"context.line",
"context.process",
"context.thread",
],
)
@validate_log_event_count(1)
@background_task()
def test_loguru_default_context_attributes(logger):
def _patcher(d):
d["patched_attr"] = 5
return d

bound_logger = logger.bind(bound_attr=1)
bound_logger = bound_logger.patch(_patcher)
with bound_logger.contextualize(contextual_attr=2):
bound_logger.error("context_attrs: {}", "arg1", kwarg_attr=4)


@validate_log_events([{"message": "exc_info"}], required_attrs=["context.exception"])
@validate_log_event_count(1)
@background_task()
def test_loguru_exception_context_attributes(logger):
try:
raise RuntimeError("Oops")
except Exception:
logger.error("exc_info")


@validate_log_events([{"context.extra.attr": 1}])
@validate_log_event_count(1)
@background_task()
def test_loguru_attributes_only(logger):
logger.error("", attr=1)
56 changes: 0 additions & 56 deletions tests/logger_loguru/test_stack_inspection.py

This file was deleted.

Loading