Skip to content

Commit fcd898f

Browse files
authored
Logging Handler for APM Log Forwarding (#548)
* Add log fowarding handler API. * Fix pypy import
1 parent 34fe114 commit fcd898f

File tree

2 files changed

+95
-2
lines changed

2 files changed

+95
-2
lines changed

newrelic/api/log.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@
1515
import json
1616
import logging
1717
import re
18+
import warnings
1819
from logging import Formatter, LogRecord
1920

2021
from newrelic.api.time_trace import get_linking_metadata
21-
from newrelic.api.transaction import current_transaction
22+
from newrelic.api.transaction import current_transaction, record_log_event
2223
from newrelic.common import agent_http
2324
from newrelic.common.object_names import parse_exc_info
2425
from newrelic.core.attribute import truncate
@@ -85,8 +86,25 @@ def safe_str(object, *args, **kwargs):
8586
return json.dumps(self.log_record_to_dict(record), default=safe_str, separators=(",", ":"))
8687

8788

89+
class NewRelicLogForwardingHandler(logging.Handler):
90+
def emit(self, record):
91+
try:
92+
# Avoid getting local log decorated message
93+
if hasattr(record, "_nr_original_message"):
94+
message = record._nr_original_message()
95+
else:
96+
message = record.getMessage()
97+
98+
record_log_event(message, record.levelname, int(record.created * 1000))
99+
except Exception:
100+
self.handleError(record)
101+
102+
88103
class NewRelicLogHandler(logging.Handler):
89-
"""This is an experimental log handler provided by the community. Use with caution."""
104+
"""
105+
Deprecated: Please use NewRelicLogForwardingHandler instead.
106+
This is an experimental log handler provided by the community. Use with caution.
107+
"""
90108

91109
PATH = "/log/v1"
92110

@@ -104,6 +122,13 @@ def __init__(
104122
ca_bundle_path=None,
105123
disable_certificate_validation=False,
106124
):
125+
warnings.warn(
126+
"The contributed NewRelicLogHandler has been superseded by automatic instrumentation for "
127+
"logging in the standard lib. If for some reason you need to manually configure a handler, "
128+
"please use newrelic.api.log.NewRelicLogForwardingHandler to take advantage of all the "
129+
"features included in application log forwarding such as proper batching.",
130+
DeprecationWarning
131+
)
107132
super(NewRelicLogHandler, self).__init__(level=level)
108133
self.license_key = license_key or self.settings.license_key
109134
self.host = host or self.settings.host or self.default_host(self.license_key)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Copyright 2010 New Relic, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from conftest import logger as conf_logger
16+
import logging
17+
import pytest
18+
19+
from newrelic.api.background_task import background_task
20+
from newrelic.api.log import NewRelicLogForwardingHandler
21+
from testing_support.fixtures import reset_core_stats_engine
22+
from testing_support.validators.validate_log_event_count import validate_log_event_count
23+
from testing_support.validators.validate_log_event_count_outside_transaction import validate_log_event_count_outside_transaction
24+
from testing_support.validators.validate_function_called import validate_function_called
25+
26+
27+
28+
29+
@pytest.fixture(scope="function")
30+
def uninstrument_logging():
31+
instrumented = logging.Logger.callHandlers
32+
while hasattr(logging.Logger.callHandlers, "__wrapped__"):
33+
logging.Logger.callHandlers = logging.Logger.callHandlers.__wrapped__
34+
yield
35+
logging.Logger.callHandlers = instrumented
36+
37+
38+
@pytest.fixture(scope="function")
39+
def logger(conf_logger, uninstrument_logging):
40+
handler = NewRelicLogForwardingHandler()
41+
conf_logger.addHandler(handler)
42+
yield conf_logger
43+
conf_logger.removeHandler(handler)
44+
45+
46+
def exercise_logging(logger):
47+
logger.warning("C")
48+
assert len(logger.caplog.records) == 1
49+
50+
51+
def test_handler_inside_transaction(logger):
52+
@validate_log_event_count(1)
53+
@validate_function_called("newrelic.api.log", "NewRelicLogForwardingHandler.emit")
54+
@background_task()
55+
def test():
56+
exercise_logging(logger)
57+
58+
test()
59+
60+
61+
@reset_core_stats_engine()
62+
def test_handler_outside_transaction(logger):
63+
@validate_log_event_count_outside_transaction(1)
64+
@validate_function_called("newrelic.api.log", "NewRelicLogForwardingHandler.emit")
65+
def test():
66+
exercise_logging(logger)
67+
68+
test()

0 commit comments

Comments
 (0)