# 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 logging 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.core.config import global_settings from newrelic.hooks.logger_logging import add_nr_linking_metadata from newrelic.packages import six _logger = logging.getLogger(__name__) def loguru_version(): from loguru import __version__ return tuple(int(x) for x in __version__.split(".")) def _nr_log_forwarder(message_instance): transaction = current_transaction() record = message_instance.record message = record.get("_nr_original_message", record["message"]) if transaction: settings = transaction.settings else: settings = global_settings() # Return early if application logging not enabled if settings and settings.application_logging and settings.application_logging.enabled: level = record["level"] level_name = "UNKNOWN" if not level else (level.name or "UNKNOWN") if settings.application_logging.metrics and settings.application_logging.metrics.enabled: if transaction: transaction.record_custom_metric("Logging/lines", {"count": 1}) transaction.record_custom_metric("Logging/lines/%s" % level_name, {"count": 1}) else: application = application_instance(activate=False) 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())) except Exception: pass 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) options[-2] = nr_log_patcher(options[-2]) except Exception as e: _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) def nr_log_patcher(original_patcher=None): def _nr_log_patcher(record): if original_patcher: record = original_patcher(record) transaction = current_transaction() if transaction: settings = transaction.settings else: settings = global_settings() if settings and settings.application_logging and settings.application_logging.enabled: if settings.application_logging.local_decorating and settings.application_logging.local_decorating.enabled: record["_nr_original_message"] = message = record["message"] record["message"] = add_nr_linking_metadata(message) 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. original_patcher = None else: patchers = [] patchers.append(_nr_log_patcher) return patchers else: return _nr_log_patcher def wrap_Logger_init(wrapped, instance, args, kwargs): result = wrapped(*args, **kwargs) patch_loguru_logger(instance) return result def patch_loguru_logger(logger): if hasattr(logger, "_core"): if not hasattr(logger._core, "_nr_instrumented"): logger.add(_nr_log_forwarder, format="{message}") logger._core._nr_instrumented = True elif not hasattr(logger, "_nr_instrumented"): for _, handler in six.iteritems(logger._handlers): if handler._writer is _nr_log_forwarder: logger._nr_instrumented = True return logger.add(_nr_log_forwarder, format="{message}") logger._nr_instrumented = True def instrument_loguru_logger(module): if hasattr(module, "Logger"): wrap_function_wrapper(module, "Logger.__init__", wrap_Logger_init) if hasattr(module.Logger, "_log"): wrap_function_wrapper(module, "Logger._log", wrap_log) def instrument_loguru(module): if hasattr(module, "logger"): patch_loguru_logger(module.logger)