Repository URL to install this package:
|
Version:
2.68.0.50 ▾
|
""" This module provides a structure to hang the configuration settings. We
use an empty class structure and manually populate it. The global defaults
will be overlaid with any settings from the local agent configuration file.
For a specific application we will then deep copy the global default
settings and then overlay that with application settings obtained from the
server side core application. Finally, to allow for local testing and
debugging, for selected override configuration settings, we will apply back
the global defaults or those from local agent configuration.
"""
import os
import logging
import copy
import socket
from .attribute_filter import AttributeFilter
try:
import urlparse
except ImportError:
import urllib.parse as urlparse
# By default, Transaction Events and Custom Events have the same size
# reservoir. Error Events have a different default size.
DEFAULT_RESERVOIR_SIZE = 1200
ERROR_EVENT_RESERVOIR_SIZE = 100
# settings that should be completely ignored if set server side
IGNORED_SERVER_SIDE_SETTINGS = ['utilization.logical_processors',
'utilization.total_ram_mib', 'utilization.billing_hostname']
class _NullHandler(logging.Handler):
def emit(self, record):
pass
_logger = logging.getLogger(__name__)
_logger.addHandler(_NullHandler())
# The Settings objects and the global default settings. We create a
# distinct type for each sub category of settings that the agent knows
# about so that an error when accessing a non existant setting is more
# descriptive and identifies the category of settings. When applying
# server side configuration we create normal Settings object for new
# sub categories we don't know about.
class Settings(object):
def __repr__(self):
return repr(self.__dict__)
def __iter__(self):
return iter(flatten_settings(self).items())
def __contains__(self, item):
return hasattr(self, item)
class AttributesSettings(Settings): pass
class ThreadProfilerSettings(Settings): pass
class TransactionTracerSettings(Settings): pass
class TransactionTracerAttributesSettings(Settings): pass
class ErrorCollectorSettings(Settings): pass
class ErrorCollectorAttributesSettings(Settings): pass
class BrowserMonitorSettings(Settings): pass
class BrowserMonitorAttributesSettings(Settings): pass
class TransactionNameSettings(Settings): pass
class TransactionMetricsSettings(Settings): pass
class RumSettings(Settings): pass
class SlowSqlSettings(Settings): pass
class AgentLimitsSettings(Settings): pass
class ConsoleSettings(Settings): pass
class DebugSettings(Settings): pass
class CrossApplicationTracerSettings(Settings): pass
class XraySessionSettings(Settings): pass
class TransactionEventsSettings(Settings): pass
class TransactionEventsAttributesSettings(Settings): pass
class CustomInsightsEventsSettings(Settings): pass
class ProcessHostSettings(Settings): pass
class SyntheticsSettings(Settings): pass
class UtilizationSettings(Settings): pass
class StripExceptionMessageSettings(Settings): pass
_settings = Settings()
_settings.attributes = AttributesSettings()
_settings.thread_profiler = ThreadProfilerSettings()
_settings.transaction_tracer = TransactionTracerSettings()
_settings.transaction_tracer.attributes = TransactionTracerAttributesSettings()
_settings.error_collector = ErrorCollectorSettings()
_settings.error_collector.attributes = ErrorCollectorAttributesSettings()
_settings.browser_monitoring = BrowserMonitorSettings()
_settings.browser_monitoring.attributes = BrowserMonitorAttributesSettings()
_settings.transaction_name = TransactionNameSettings()
_settings.transaction_metrics = TransactionMetricsSettings()
_settings.rum = RumSettings()
_settings.slow_sql = SlowSqlSettings()
_settings.agent_limits = AgentLimitsSettings()
_settings.console = ConsoleSettings()
_settings.debug = DebugSettings()
_settings.cross_application_tracer = CrossApplicationTracerSettings()
_settings.xray_session = XraySessionSettings()
_settings.transaction_events = TransactionEventsSettings()
_settings.transaction_events.attributes = TransactionEventsAttributesSettings()
_settings.custom_insights_events = CustomInsightsEventsSettings()
_settings.process_host = ProcessHostSettings()
_settings.synthetics = SyntheticsSettings()
_settings.utilization = UtilizationSettings()
_settings.strip_exception_messages = StripExceptionMessageSettings()
_settings.log_file = os.environ.get('NEW_RELIC_LOG', None)
_settings.audit_log_file = os.environ.get('NEW_RELIC_AUDIT_LOG', None)
def _environ_as_int(name, default=0):
val = os.environ.get(name, default)
try:
return int(val)
except ValueError:
return default
def _environ_as_bool(name, default=False):
flag = os.environ.get(name, default)
if default is None or default:
try:
flag = not flag.lower() in ['off', 'false', '0']
except AttributeError:
pass
else:
try:
flag = flag.lower() in ['on', 'true', '1']
except AttributeError:
pass
return flag
def _environ_as_set(name, default=''):
value = os.environ.get(name, default)
return set(value.split())
def _environ_as_mapping(name, default=''):
result = []
items = os.environ.get(name, default)
# Strip all whitespace and semicolons from the end of the string.
# That way, when we split a valid labels string by ';', the resulting
# list will contain no empty elements. When we loop through the
# elements, if we see one that is empty, or can't be split by ':',
# then we know the string has an invalid format.
items = items.strip('; \t\n\r\f\v')
if not items:
return result
for item in items.split(';'):
try:
key, value = item.split(':')
except ValueError:
_logger.warning('Invalid configuration. Cannot parse: %r.'
'Expected format \'key1:value1;key2:value2 ... \'.',
items)
result = []
break
key = key.strip()
value = value.strip()
if key and value:
result.append((key, value))
else:
_logger.warning('Invalid configuration. Cannot parse: %r.'
'Expected format \'key1:value1;key2:value2 ... \'.',
items)
result = []
break
return result
def _parse_ignore_status_codes(value, target):
items = value.split()
for item in items:
try:
negate = item.startswith('!')
if negate:
item = item[1:]
start, end = item.split('-')
values = set(range(int(start), int(end)+1))
if negate:
target.symmetric_difference_update(values)
else:
target.update(values)
except ValueError:
if negate:
target.remove(int(item))
else:
target.add(int(item))
return target
def _parse_attributes(s):
valid = []
for item in s.split():
if '*' not in item[:-1] and len(item.encode('utf-8')) < 256:
valid.append(item)
else:
_logger.warning('Improperly formatted attribute: %r', item)
return valid
_LOG_LEVEL = {
'CRITICAL': logging.CRITICAL,
'ERROR': logging.ERROR,
'WARNING': logging.WARNING,
'INFO': logging.INFO,
'DEBUG': logging.DEBUG,
}
_settings.enabled = _environ_as_bool('NEW_RELIC_ENABLED', False)
_settings.feature_flag = _environ_as_set('NEW_RELIC_FEATURE_FLAG', '')
_settings.log_level = os.environ.get('NEW_RELIC_LOG_LEVEL', 'INFO').upper()
if _settings.log_level in _LOG_LEVEL:
_settings.log_level = _LOG_LEVEL[_settings.log_level]
else:
_settings.log_level = logging.INFO
_settings.license_key = os.environ.get('NEW_RELIC_LICENSE_KEY', None)
_settings.api_key = os.environ.get('NEW_RELIC_API_KEY', None)
_settings.ssl = _environ_as_bool('NEW_RELIC_SSL', True)
_settings.host = os.environ.get('NEW_RELIC_HOST', 'collector.newrelic.com')
_settings.port = int(os.environ.get('NEW_RELIC_PORT', '0'))
_settings.proxy_scheme = os.environ.get('NEW_RELIC_PROXY_SCHEME', None)
_settings.proxy_host = os.environ.get('NEW_RELIC_PROXY_HOST', None)
_settings.proxy_port = int(os.environ.get('NEW_RELIC_PROXY_PORT', '0'))
_settings.proxy_user = os.environ.get('NEW_RELIC_PROXY_USER', None)
_settings.proxy_pass = os.environ.get('NEW_RELIC_PROXY_PASS', None)
_settings.app_name = os.environ.get('NEW_RELIC_APP_NAME', 'Python Application')
_settings.linked_applications = []
_settings.process_host.display_name = os.environ.get(
'NEW_RELIC_PROCESS_HOST_DISPLAY_NAME', None)
_settings.labels = _environ_as_mapping('NEW_RELIC_LABELS', '')
_settings.monitor_mode = _environ_as_bool('NEW_RELIC_MONITOR_MODE', True)
_settings.developer_mode = _environ_as_bool('NEW_RELIC_DEVELOPER_MODE', False)
_settings.high_security = _environ_as_bool('NEW_RELIC_HIGH_SECURITY', False)
_settings.attribute_filter = None
_settings.collect_errors = True
_settings.collect_error_events = True
_settings.collect_traces = True
_settings.collect_analytics_events = True
_settings.collect_custom_events = True
_settings.apdex_t = 0.5
_settings.web_transactions_apdex = {}
_settings.capture_params = None
_settings.ignored_params = []
_settings.capture_environ = True
_settings.include_environ = ['REQUEST_METHOD', 'HTTP_USER_AGENT',
'HTTP_REFERER', 'CONTENT_TYPE',
'CONTENT_LENGTH', 'HTTP_HOST', 'HTTP_ACCEPT']
_settings.max_stack_trace_lines = 50
_settings.sampling_rate = 0
_settings.startup_timeout = float(
os.environ.get('NEW_RELIC_STARTUP_TIMEOUT', '0.0'))
_settings.shutdown_timeout = float(
os.environ.get('NEW_RELIC_SHUTDOWN_TIMEOUT', '2.5'))
_settings.beacon = None
_settings.error_beacon = None
_settings.application_id = None
_settings.browser_key = None
_settings.episodes_url = None
_settings.js_agent_loader = None
_settings.js_agent_file = None
_settings.url_rules = []
_settings.metric_name_rules = []
_settings.transaction_name_rules = []
_settings.transaction_segment_terms = []
_settings.cross_process_id = None
_settings.trusted_account_ids = []
_settings.encoding_key = None
_settings.attributes.enabled = True
_settings.attributes.exclude = []
_settings.attributes.include = []
_settings.thread_profiler.enabled = True
_settings.cross_application_tracer.enabled = True
_settings.xray_session.enabled = True
_settings.transaction_events.enabled = True
_settings.transaction_events.max_samples_stored = DEFAULT_RESERVOIR_SIZE
_settings.transaction_events.attributes.enabled = True
_settings.transaction_events.attributes.exclude = []
_settings.transaction_events.attributes.include = []
_settings.custom_insights_events.enabled = True
_settings.custom_insights_events.max_samples_stored = DEFAULT_RESERVOIR_SIZE
_settings.transaction_tracer.enabled = True
_settings.transaction_tracer.transaction_threshold = None
_settings.transaction_tracer.record_sql = 'obfuscated'
_settings.transaction_tracer.stack_trace_threshold = 0.5
_settings.transaction_tracer.explain_enabled = True
_settings.transaction_tracer.explain_threshold = 0.5
_settings.transaction_tracer.function_trace = []
_settings.transaction_tracer.generator_trace = []
_settings.transaction_tracer.top_n = 20
_settings.transaction_tracer.attributes.enabled = True
_settings.transaction_tracer.attributes.exclude = []
_settings.transaction_tracer.attributes.include = []
_settings.error_collector.enabled = True
_settings.error_collector.capture_events = True
_settings.error_collector.max_event_samples_stored = ERROR_EVENT_RESERVOIR_SIZE
_settings.error_collector.capture_source = False
_settings.error_collector.ignore_errors = []
_settings.error_collector.ignore_status_codes = _parse_ignore_status_codes(
'100-102 200-208 226 300-308 404', set())
_settings.error_collector.attributes.enabled = True
_settings.error_collector.attributes.exclude = []
_settings.error_collector.attributes.include = []
_settings.browser_monitoring.enabled = True
_settings.browser_monitoring.auto_instrument = True
_settings.browser_monitoring.loader = 'rum' # Valid values: 'full', 'none'
_settings.browser_monitoring.loader_version = None
_settings.browser_monitoring.debug = False
_settings.browser_monitoring.ssl_for_http = None
_settings.browser_monitoring.content_type = ['text/html']
_settings.browser_monitoring.attributes.enabled = False
_settings.browser_monitoring.attributes.exclude = []
_settings.browser_monitoring.attributes.include = []
_settings.transaction_name.limit = None
_settings.transaction_name.naming_scheme = os.environ.get(
'NEW_RELIC_TRANSACTION_NAMING_SCHEME')
_settings.slow_sql.enabled = True
_settings.synthetics.enabled = True
_settings.agent_limits.data_collector_timeout = 30.0
_settings.agent_limits.transaction_traces_nodes = 2000
_settings.agent_limits.sql_query_length_maximum = 16384
_settings.agent_limits.slow_sql_stack_trace = 30
_settings.agent_limits.max_sql_connections = 4
_settings.agent_limits.sql_explain_plans = 30
_settings.agent_limits.sql_explain_plans_per_harvest = 60
_settings.agent_limits.slow_sql_data = 10
_settings.agent_limits.merge_stats_maximum = 5
_settings.agent_limits.errors_per_transaction = 5
_settings.agent_limits.errors_per_harvest = 20
_settings.agent_limits.slow_transaction_dry_harvests = 5
_settings.agent_limits.thread_profiler_nodes = 20000
_settings.agent_limits.xray_transactions = 10
_settings.agent_limits.xray_profile_overhead = 0.05
_settings.agent_limits.xray_profile_maximum = 500
_settings.agent_limits.synthetics_events = 200
_settings.agent_limits.synthetics_transactions = 20
_settings.agent_limits.data_compression_threshold = 64*1024
_settings.agent_limits.data_compression_level = None
_settings.console.listener_socket = None
_settings.console.allow_interpreter_cmd = False
_settings.debug.ignore_all_server_settings = False
_settings.debug.local_settings_overrides = []
_settings.debug.log_agent_initialization = False
_settings.debug.log_data_collector_calls = False
_settings.debug.log_data_collector_payloads = False
_settings.debug.log_malformed_json_data = False
_settings.debug.log_transaction_trace_payload = False
_settings.debug.log_thread_profile_payload = False
_settings.debug.log_normalization_rules = False
_settings.debug.log_raw_metric_data = False
_settings.debug.log_normalized_metric_data = False
_settings.debug.log_explain_plan_queries = False
_settings.debug.log_autorum_middleware = False
_settings.debug.record_transaction_failure = False
_settings.debug.enable_coroutine_profiling = False
_settings.debug.explain_plan_obfuscation = 'simple'
_settings.debug.disable_certificate_validation = False
_settings.utilization.detect_aws = True
_settings.utilization.detect_docker = True
_settings.utilization.logical_processors = _environ_as_int(
'NEW_RELIC_UTILIZATION_LOGICAL_PROCESSORS')
_settings.utilization.total_ram_mib = _environ_as_int(
'NEW_RELIC_UTILIZATION_TOTAL_RAM_MIB')
_settings.utilization.billing_hostname = os.environ.get(
'NEW_RELIC_UTILIZATION_BILLING_HOSTNAME')
_settings.strip_exception_messages.enabled = False
_settings.strip_exception_messages.whitelist = []
def global_settings():
"""This returns the default global settings. Generally only used
directly in test scripts and test harnesses or when applying global
settings from agent configuration file. Making changes to the settings
object returned by this function will not have any effect on any
applications that have already been initialised. This is because when
the settings are obtained from the core application a snapshot of these
settings will be taken.
>>> global_settings = global_settings()
>>> global_settings.browser_monitoring.auto_instrument = False
>>> global_settings.browser_monitoring.auto_instrument
False
"""
return _settings
def flatten_settings(settings):
"""This returns dictionary of settings flattened into a single
key namespace rather than nested hierarchy.
"""
def _flatten(settings, name, object):
for key, value in object.__dict__.items():
if isinstance(value, Settings):
if name:
_flatten(settings, '%s.%s' % (name, key), value)
else:
_flatten(settings, key, value)
else:
if name:
settings['%s.%s' % (name, key)] = value
else:
settings[key] = value
return settings
return _flatten({}, None, settings)
def create_obfuscated_netloc(username, password, hostname, mask):
"""Create a netloc string from hostname, username and password. If the
username and/or password is present, replace them with the obfuscation
mask. Otherwise, leave them out of netloc.
"""
if username:
username = mask
if password:
password = mask
if username and password:
netloc = '%s:%s@%s' % (username, password, hostname)
elif username:
netloc = '%s@%s' % (username, hostname)
else:
netloc = hostname
return netloc
def global_settings_dump(settings_object=None):
"""This returns dictionary of global settings flattened into a single
key namespace rather than nested hierarchy. This is used to send the
global settings configuration back to core application.
"""
if settings_object is None:
settings_object = _settings
settings = flatten_settings(settings_object)
# Strip out any sensitive settings as can be sent unencrypted.
# The license key is being sent already, but no point sending
# it again.
del settings['license_key']
del settings['api_key']
# If proxy credentials are included in the settings, we obfuscate
# them before sending, rather than deleting.
obfuscated = '****'
if settings['proxy_user'] is not None:
settings['proxy_user'] = obfuscated
if settings['proxy_pass'] is not None:
settings['proxy_pass'] = obfuscated
# For the case of proxy_host we have to do a bit more work as it
# could be a URI which includes the username and password within
# it. What we do here is parse the value and if identified as a
# URI, we recompose it with the obfuscated username and password.
proxy_host = settings['proxy_host']
if proxy_host:
components = urlparse.urlparse(proxy_host)
if components.scheme:
netloc = create_obfuscated_netloc(components.username,
components.password, components.hostname, obfuscated)
if components.port:
uri = '%s://%s:%s%s' % (components.scheme, netloc,
components.port, components.path)
else:
uri = '%s://%s%s' % (components.scheme, netloc,
components.path)
settings['proxy_host'] = uri
return settings
# Creation of an application settings object from global default settings
# and any server side configuration settings.
def apply_config_setting(settings_object, name, value):
"""Apply a setting to the settings object where name is a dotted path.
If there is no pre existing settings object for a sub category then
one will be created and added automatically.
>>> name = 'browser_monitoring.auto_instrument'
>>> value = False
>>>
>>> global_settings = global_settings()
>>> _apply_config_setting(global_settings, name, value)
"""
target = settings_object
fields = name.split('.', 1)
while len(fields) > 1:
if not hasattr(target, fields[0]):
setattr(target, fields[0], Settings())
target = getattr(target, fields[0])
fields = fields[1].split('.', 1)
setattr(target, fields[0], value)
def fetch_config_setting(settings_object, name):
"""Fetch a setting from the settings object where name is a dotted path.
>>> name = 'browser_monitoring.auto_instrument'
>>>
>>> global_settings = global_settings()
>>> _fetch_config_setting(global_settings, name)
'browser_monitoring.auto_instrument'
"""
target = settings_object
fields = name.split('.', 1)
target = getattr(target, fields[0])
while len(fields) > 1:
fields = fields[1].split('.', 1)
target = getattr(target, fields[0])
return target
def apply_server_side_settings(server_side_config={}, settings=_settings):
"""Create a snapshot of the global default settings and overlay it
with any server side configuration settings. Any local settings
overrides to take precedence over server side configuration settings
will then be reapplied to the copy. Note that the intention is that
the resulting settings object will be cached for subsequent use
within the application object the settings pertain to.
>>> server_config = { 'browser_monitoring.auto_instrument': False }
>>>
>>> settings_snapshot = apply_server_side_settings(server_config)
"""
settings_snapshot = copy.deepcopy(settings)
# Break out the server side agent config settings which
# are stored under 'agent_config' key.
agent_config = server_side_config.pop('agent_config', {})
# Remap as necessary any server side agent config settings.
if 'transaction_tracer.transaction_threshold' in agent_config:
value = agent_config['transaction_tracer.transaction_threshold']
if value == 'apdex_f':
agent_config['transaction_tracer.transaction_threshold'] = None
# Overlay with global server side configuration settings.
for (name, value) in server_side_config.items():
apply_config_setting(settings_snapshot, name, value)
# Overlay with agent server side configuration settings.
# Assuming for now that agent service side configuration
# can always take precedence over the global server side
# configuration settings.
for (name, value) in agent_config.items():
apply_config_setting(settings_snapshot, name, value)
# Reapply on top any local setting overrides.
for name in _settings.debug.local_settings_overrides:
value = fetch_config_setting(_settings, name)
apply_config_setting(settings_snapshot, name, value)
return settings_snapshot
def finalize_application_settings(server_side_config={}, settings=_settings):
"""Overlay server-side settings and add attribute filter."""
# Remove values from server_config that should not overwrite the
# ones set locally
server_side_config = _remove_ignored_configs(server_side_config)
application_settings = apply_server_side_settings(
server_side_config, settings)
application_settings.attribute_filter = AttributeFilter(
flatten_settings(application_settings))
return application_settings
def _remove_ignored_configs(server_settings):
if not server_settings.get('agent_config'):
return server_settings
# These settings should be ignored completely
for ignored_setting in IGNORED_SERVER_SIDE_SETTINGS:
server_settings['agent_config'].pop(ignored_setting, None)
return server_settings
def ignore_status_code(status):
return status in _settings.error_collector.ignore_status_codes