Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions newrelic/api/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1640,7 +1640,7 @@ def record_custom_event(self, event_type, params):
if not settings.custom_insights_events.enabled:
return

event = create_custom_event(event_type, params)
event = create_custom_event(event_type, params, settings=settings)
if event:
self._custom_events.add(event, priority=self.priority)

Expand All @@ -1653,7 +1653,7 @@ def record_ml_event(self, event_type, params):
if not settings.ml_insights_events.enabled:
return

event = create_custom_event(event_type, params, is_ml_event=True)
event = create_custom_event(event_type, params, settings=settings, is_ml_event=True)
if event:
self._ml_events.add(event, priority=self.priority)

Expand Down
32 changes: 18 additions & 14 deletions newrelic/common/package_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

import sys
import warnings

try:
from functools import cache as _cache_package_versions
Expand Down Expand Up @@ -110,6 +111,23 @@ def _get_package_version(name):
module = sys.modules.get(name, None)
version = None

with warnings.catch_warnings(record=True):
for attr in VERSION_ATTRS:
try:
version = getattr(module, attr, None)

# In certain cases like importlib_metadata.version, version is a callable
# function.
if callable(version):
continue

# Cast any version specified as a list into a tuple.
version = tuple(version) if isinstance(version, list) else version
if version not in NULL_VERSIONS:
return version
except Exception:
pass

# importlib was introduced into the standard library starting in Python3.8.
if "importlib" in sys.modules and hasattr(sys.modules["importlib"], "metadata"):
try:
Expand All @@ -126,20 +144,6 @@ def _get_package_version(name):
except Exception:
pass

for attr in VERSION_ATTRS:
try:
version = getattr(module, attr, None)
# In certain cases like importlib_metadata.version, version is a callable
# function.
if callable(version):
continue
# Cast any version specified as a list into a tuple.
version = tuple(version) if isinstance(version, list) else version
if version not in NULL_VERSIONS:
return version
except Exception:
pass

if "pkg_resources" in sys.modules:
try:
version = sys.modules["pkg_resources"].get_distribution(name).version
Expand Down
2 changes: 2 additions & 0 deletions newrelic/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
from newrelic.common.log_file import initialize_logging
from newrelic.common.object_names import expand_builtin_exception_name
from newrelic.core import trace_cache
from newrelic.core.attribute import MAX_ATTRIBUTE_LENGTH
from newrelic.core.config import (
Settings,
apply_config_setting,
Expand Down Expand Up @@ -443,6 +444,7 @@ def _process_configuration(section):
)
_process_setting(section, "custom_insights_events.enabled", "getboolean", None)
_process_setting(section, "custom_insights_events.max_samples_stored", "getint", None)
_process_setting(section, "custom_insights_events.max_attribute_value", "getint", MAX_ATTRIBUTE_LENGTH)
_process_setting(section, "ml_insights_events.enabled", "getboolean", None)
_process_setting(section, "distributed_tracing.enabled", "getboolean", None)
_process_setting(section, "distributed_tracing.exclude_newrelic_header", "getboolean", None)
Expand Down
8 changes: 5 additions & 3 deletions newrelic/core/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,7 @@ def record_custom_event(self, event_type, params):
if settings is None or not settings.custom_insights_events.enabled:
return

event = create_custom_event(event_type, params)
event = create_custom_event(event_type, params, settings=settings)

if event:
with self._stats_custom_lock:
Expand All @@ -932,7 +932,7 @@ def record_ml_event(self, event_type, params):
if settings is None or not settings.ml_insights_events.enabled:
return

event = create_custom_event(event_type, params, is_ml_event=True)
event = create_custom_event(event_type, params, settings=settings, is_ml_event=True)

if event:
with self._stats_custom_lock:
Expand Down Expand Up @@ -1506,7 +1506,9 @@ def harvest(self, shutdown=False, flexible=False):
# Send metrics
self._active_session.send_metric_data(self._period_start, period_end, metric_data)
if dimensional_metric_data:
self._active_session.send_dimensional_metric_data(self._period_start, period_end, dimensional_metric_data)
self._active_session.send_dimensional_metric_data(
self._period_start, period_end, dimensional_metric_data
)

_logger.debug("Done sending data for harvest of %r.", self._app_name)

Expand Down
15 changes: 15 additions & 0 deletions newrelic/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

import newrelic.packages.six as six
from newrelic.common.object_names import parse_exc_info
from newrelic.core.attribute import MAX_ATTRIBUTE_LENGTH
from newrelic.core.attribute_filter import AttributeFilter

try:
Expand Down Expand Up @@ -717,6 +718,7 @@ def default_otlp_host(host):
_settings.transaction_events.attributes.include = []

_settings.custom_insights_events.enabled = True
_settings.custom_insights_events.max_attribute_value = MAX_ATTRIBUTE_LENGTH
_settings.ml_insights_events.enabled = False

_settings.distributed_tracing.enabled = _environ_as_bool("NEW_RELIC_DISTRIBUTED_TRACING_ENABLED", default=True)
Expand Down Expand Up @@ -810,6 +812,10 @@ def default_otlp_host(host):
"NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED", CUSTOM_EVENT_RESERVOIR_SIZE
)

_settings.custom_insights_events.max_attribute_value = _environ_as_int(
"NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_ATTRIBUTE_VALUE", MAX_ATTRIBUTE_LENGTH
)

_settings.event_harvest_config.harvest_limits.ml_event_data = _environ_as_int(
"NEW_RELIC_ML_INSIGHTS_EVENTS_MAX_SAMPLES_STORED", ML_EVENT_RESERVOIR_SIZE
)
Expand Down Expand Up @@ -898,6 +904,7 @@ def default_otlp_host(host):
_settings.machine_learning.inference_events_value.enabled = _environ_as_bool(
"NEW_RELIC_MACHINE_LEARNING_INFERENCE_EVENT_VALUE_ENABLED", default=False
)
_settings.ml_insights_events.enabled = _environ_as_bool("NEW_RELIC_ML_INSIGHTS_EVENTS_ENABLED", default=False)


def global_settings():
Expand Down Expand Up @@ -1170,6 +1177,14 @@ def apply_server_side_settings(server_side_config=None, settings=_settings):
settings_snapshot.event_harvest_config.harvest_limits.ml_event_data / 12,
)

# Since the server does not override this setting we must override it here manually
# by caping it at the max value of 4095.
apply_config_setting(
settings_snapshot,
"custom_insights_events.max_attribute_value",
min(settings_snapshot.custom_insights_events.max_attribute_value, 4095),
)

# This will be removed at some future point
# Special case for account_id which will be sent instead of
# cross_process_id in the future
Expand Down
60 changes: 37 additions & 23 deletions newrelic/core/custom_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,37 @@
# 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
import re
import time

from newrelic.core.attribute import (check_name_is_string, check_name_length,
process_user_attribute, NameIsNotStringException, NameTooLongException,
MAX_NUM_USER_ATTRIBUTES, MAX_ML_ATTRIBUTE_LENGTH, MAX_NUM_ML_USER_ATTRIBUTES, MAX_ATTRIBUTE_LENGTH)

from newrelic.core.attribute import (
MAX_ML_ATTRIBUTE_LENGTH,
MAX_NUM_ML_USER_ATTRIBUTES,
MAX_NUM_USER_ATTRIBUTES,
NameIsNotStringException,
NameTooLongException,
check_name_is_string,
check_name_length,
process_user_attribute,
)
from newrelic.core.config import global_settings

_logger = logging.getLogger(__name__)

EVENT_TYPE_VALID_CHARS_REGEX = re.compile(r'^[a-zA-Z0-9:_ ]+$')
EVENT_TYPE_VALID_CHARS_REGEX = re.compile(r"^[a-zA-Z0-9:_ ]+$")


class NameInvalidCharactersException(Exception):
pass

class NameInvalidCharactersException(Exception): pass

def check_event_type_valid_chars(name):
regex = EVENT_TYPE_VALID_CHARS_REGEX
if not regex.match(name):
raise NameInvalidCharactersException()


def process_event_type(name):
"""Perform all necessary validation on a potential event type.

Expand All @@ -55,25 +65,22 @@ def process_event_type(name):
check_event_type_valid_chars(name)

except NameIsNotStringException:
_logger.debug('Event type must be a string. Dropping '
'event: %r', name)
_logger.debug("Event type must be a string. Dropping event: %r", name)
return FAILED_RESULT

except NameTooLongException:
_logger.debug('Event type exceeds maximum length. Dropping '
'event: %r', name)
_logger.debug("Event type exceeds maximum length. Dropping event: %r", name)
return FAILED_RESULT

except NameInvalidCharactersException:
_logger.debug('Event type has invalid characters. Dropping '
'event: %r', name)
_logger.debug("Event type has invalid characters. Dropping event: %r", name)
return FAILED_RESULT

else:
return name


def create_custom_event(event_type, params, is_ml_event=False):
def create_custom_event(event_type, params, settings=None, is_ml_event=False):
"""Creates a valid custom event.

Ensures that the custom event has a valid name, and also checks
Expand All @@ -84,6 +91,7 @@ def create_custom_event(event_type, params, is_ml_event=False):
Args:
event_type (str): The type (name) of the custom event.
params (dict): Attributes to add to the event.
settings: Optional config settings.
is_ml_event (bool): Boolean indicating whether create_custom_event was called from
record_ml_event for truncation purposes

Expand All @@ -92,6 +100,7 @@ def create_custom_event(event_type, params, is_ml_event=False):
None, if not successful.

"""
settings = settings or global_settings()

name = process_event_type(event_type)

Expand All @@ -106,25 +115,30 @@ def create_custom_event(event_type, params, is_ml_event=False):
max_length = MAX_ML_ATTRIBUTE_LENGTH
max_num_attrs = MAX_NUM_ML_USER_ATTRIBUTES
else:
max_length = MAX_ATTRIBUTE_LENGTH
max_length = settings.custom_insights_events.max_attribute_value
max_num_attrs = MAX_NUM_USER_ATTRIBUTES
key, value = process_user_attribute(k, v, max_length=max_length)
if key:
if len(attributes) >= max_num_attrs:
_logger.debug('Maximum number of attributes already '
'added to event %r. Dropping attribute: %r=%r',
name, key, value)
_logger.debug(
"Maximum number of attributes already added to event %r. Dropping attribute: %r=%r",
name,
key,
value,
)
else:
attributes[key] = value
except Exception:
_logger.debug('Attributes failed to validate for unknown reason. '
'Check traceback for clues. Dropping event: %r.', name,
exc_info=True)
_logger.debug(
"Attributes failed to validate for unknown reason. Check traceback for clues. Dropping event: %r.",
name,
exc_info=True,
)
return None

intrinsics = {
'type': name,
'timestamp': int(1000.0 * time.time()),
"type": name,
"timestamp": int(1000.0 * time.time()),
}

event = [intrinsics, attributes]
Expand Down
Loading