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

Allow custom event's attribute values to have a configurable size #1612 #1683

Merged
merged 4 commits into from
Jan 10, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import com.newrelic.agent.Agent;
import com.newrelic.agent.Transaction;
import com.newrelic.agent.config.ConfigConstant;
import com.newrelic.agent.service.logging.LogSenderServiceImpl;

import java.math.BigDecimal;
import java.math.BigInteger;
Expand Down Expand Up @@ -161,19 +160,13 @@ private boolean validateAndLogKeyLength(String key, String methodCalled) {
return true;
}

private String truncateValue(String key, String value, String methodCalled) {
String truncatedVal;
if (methodCalled.equals(LogSenderServiceImpl.METHOD)) {
truncatedVal = truncateString(value, ConfigConstant.MAX_LOG_EVENT_ATTRIBUTE_SIZE);
logTruncatedValue(key, value, truncatedVal, methodCalled, ConfigConstant.MAX_LOG_EVENT_ATTRIBUTE_SIZE);
} else {
truncatedVal = truncateString(value, ConfigConstant.MAX_USER_ATTRIBUTE_SIZE);
logTruncatedValue(key, value, truncatedVal, methodCalled, ConfigConstant.MAX_USER_ATTRIBUTE_SIZE);
}
protected String truncateValue(String key, String value, String methodCalled) {
String truncatedVal = truncateString(value, ConfigConstant.MAX_USER_ATTRIBUTE_SIZE);
logTruncatedValue(key, value, truncatedVal, methodCalled, ConfigConstant.MAX_USER_ATTRIBUTE_SIZE);
return truncatedVal;
}

private void logTruncatedValue(String key, String value, String truncatedVal, String methodCalled, int maxAttributeSize) {
protected void logTruncatedValue(String key, String value, String truncatedVal, String methodCalled, int maxAttributeSize) {
if (!value.equals(truncatedVal)) {
Agent.LOG.log(Level.FINER,
"{0} was invoked with a value longer than {2} bytes for key \"{3}\". The value will be shortened to the first {4} characters.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
*
* * Copyright 2023 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.attributes;

import com.newrelic.agent.service.ServiceFactory;

/**
* Attribute validator with truncation rules specific to custom events.
*/
public class CustomEventAttributeValidator extends AttributeValidator{
private static final int MAX_CUSTOM_EVENT_ATTRIBUTE_SIZE = ServiceFactory.getConfigService().getDefaultAgentConfig().getInsightsConfig().getMaxAttributeValue();

public CustomEventAttributeValidator(String attributeType) {
super(attributeType);
}

@Override
protected String truncateValue(String key, String value, String methodCalled) {
String truncatedVal = truncateString(value, MAX_CUSTOM_EVENT_ATTRIBUTE_SIZE);
logTruncatedValue(key, value, truncatedVal, methodCalled, MAX_CUSTOM_EVENT_ATTRIBUTE_SIZE);
return truncatedVal;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
*
* * Copyright 2023 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.attributes;

import com.newrelic.agent.config.ConfigConstant;

/**
* Attribute validator with truncation rules specific to log events.
*/
public class LogAttributeValidator extends AttributeValidator{
public LogAttributeValidator(String attributeType) {
super(attributeType);
}

@Override
protected String truncateValue(String key, String value, String methodCalled) {
String truncatedVal = truncateString(value, ConfigConstant.MAX_LOG_EVENT_ATTRIBUTE_SIZE);
logTruncatedValue(key, value, truncatedVal, methodCalled, ConfigConstant.MAX_LOG_EVENT_ATTRIBUTE_SIZE);
return truncatedVal;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ public interface InsightsConfig {

int getMaxSamplesStored();

/**
* Returns the max attribute size.
* @since 9.0.0
*/
int getMaxAttributeValue();

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,37 @@

package com.newrelic.agent.config;

import com.newrelic.api.agent.NewRelic;

import java.util.Collections;
import java.util.Map;
import java.util.logging.Level;

public class InsightsConfigImpl extends BaseConfig implements InsightsConfig {
public static final String MAX_SAMPLES_STORED_PROP = "max_samples_stored";
public static final int DEFAULT_MAX_SAMPLES_STORED = 30000;
public static final String MAX_ATTRIBUTE_VALUE = "max_attribute_value";
public static final int DEFAULT_MAX_ATTRIBUTE_VALUE = 255;
public static final int MAX_MAX_ATTRIBUTE_VALUE = 4095;

public static final String ENABLED_PROP = "enabled";
public static final boolean DEFAULT_ENABLED = true;
public static final String SYSTEM_PROPERTY_ROOT = "newrelic.config.custom_insights_events.";
public static final String ENABLED = SYSTEM_PROPERTY_ROOT + ENABLED_PROP;
public static final String COLLECT_CUSTOM_EVENTS = "collect_custom_events";

public final int maxSamplesStored;
public final boolean isEnabled;
private final int maxSamplesStored;
private final boolean isEnabled;
private final int maxAttributeValue;

public InsightsConfigImpl(Map<String, Object> pProps, boolean highSecurity) {
super(pProps, SYSTEM_PROPERTY_ROOT);
maxSamplesStored = getProperty(MAX_SAMPLES_STORED_PROP, DEFAULT_MAX_SAMPLES_STORED);
isEnabled = !highSecurity && initEnabled();
this.maxAttributeValue = initMaxAttributeValue();
}

public boolean initEnabled() {
private boolean initEnabled() {
boolean storedMoreThan0 = maxSamplesStored > 0;
Boolean configEnabled = getProperty(ENABLED_PROP, DEFAULT_ENABLED);
/*
Expand All @@ -39,6 +48,18 @@ public boolean initEnabled() {
return storedMoreThan0 && configEnabled && featureGateEnabled;
}

private int initMaxAttributeValue() {
int maxAttributeValue = getProperty(MAX_ATTRIBUTE_VALUE, DEFAULT_MAX_ATTRIBUTE_VALUE);
if (maxAttributeValue > MAX_MAX_ATTRIBUTE_VALUE) {
NewRelic.getAgent().getLogger().log(Level.WARNING,
"The value for custom_insights_events.max_attribute_value was too large {0}, we will use maximum allowed {1}",
maxAttributeValue,
DEFAULT_MAX_SAMPLES_STORED);
maxAttributeValue = MAX_MAX_ATTRIBUTE_VALUE;
}
return maxAttributeValue;
}

static InsightsConfigImpl createInsightsConfig(Map<String, Object> settings, boolean highSecurity) {
if (settings == null) {
settings = Collections.emptyMap();
Expand All @@ -54,4 +75,8 @@ public int getMaxSamplesStored() {
return maxSamplesStored;
}

@Override
public int getMaxAttributeValue() {
return maxAttributeValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import com.newrelic.agent.Transaction;
import com.newrelic.agent.TransactionData;
import com.newrelic.agent.attributes.AttributeSender;
import com.newrelic.agent.attributes.AttributeValidator;
import com.newrelic.agent.attributes.CustomEventAttributeValidator;
import com.newrelic.agent.config.AgentConfig;
import com.newrelic.agent.config.AgentConfigListener;
import com.newrelic.agent.model.AnalyticsEvent;
Expand Down Expand Up @@ -389,7 +389,7 @@ private static CustomInsightsEvent createValidatedEvent(String eventType, Map<St
continue;
}

mapInternString(key);
key = mapInternString(key);

if (value instanceof String) {
sender.addAttribute(key, mapInternString((String) value), method);
Expand All @@ -413,7 +413,7 @@ private static class CustomEventAttributeSender extends AttributeSender {
private final Map<String, Object> userAttributes;

public CustomEventAttributeSender(Map<String, Object> userAttributes) {
super(new AttributeValidator(ATTRIBUTE_TYPE));
super(new CustomEventAttributeValidator(ATTRIBUTE_TYPE));
this.userAttributes = userAttributes;
setTransactional(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,15 @@
import com.newrelic.agent.Transaction;
import com.newrelic.agent.TransactionData;
import com.newrelic.agent.attributes.AttributeSender;
import com.newrelic.agent.attributes.AttributeValidator;
import com.newrelic.agent.attributes.DisabledExcludeIncludeFilter;
import com.newrelic.agent.attributes.ExcludeIncludeFilter;
import com.newrelic.agent.attributes.ExcludeIncludeFilterImpl;
import com.newrelic.agent.attributes.LogAttributeValidator;
import com.newrelic.agent.bridge.logging.LogAttributeKey;
import com.newrelic.agent.bridge.logging.LogAttributeType;
import com.newrelic.agent.config.AgentConfig;
import com.newrelic.agent.config.AgentConfigListener;
import com.newrelic.agent.config.ApplicationLoggingConfig;
import com.newrelic.agent.config.ApplicationLoggingContextDataConfig;
import com.newrelic.agent.config.ApplicationLoggingForwardingConfig;
import com.newrelic.agent.model.LogEvent;
import com.newrelic.agent.service.AbstractService;
import com.newrelic.agent.service.ServiceFactory;
Expand Down Expand Up @@ -560,7 +558,7 @@ private static class LogEventAttributeSender extends AttributeSender {
private final Map<String, Object> logEventAttributes;

public LogEventAttributeSender(Map<String, Object> logEventAttributes) {
super(new AttributeValidator(ATTRIBUTE_TYPE));
super(new LogAttributeValidator(ATTRIBUTE_TYPE));
this.logEventAttributes = logEventAttributes;
setTransactional(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,25 +187,6 @@ public void testVerifyTruncatedValue() {
assertEquals(expected, result);
}

@Test
public void testVerifyTruncatedValueForLogEventData() {
Map<String, Object> input = new HashMap<>();
String longValue = Strings.padEnd("", 33000, 'e');
String longExpectedValue = Strings.padEnd("", 32767, 'e');
input.put("key", longValue);
input.put("apple", "pie");
input.put("sugar", "cream");

Map<String, Object> expected = ImmutableMap.<String, Object>of("apple", "pie", "sugar", "cream", "key", longExpectedValue);

AttributeValidator attributeValidator = new AttributeValidator(ATTRIBUTE_TYPE);

attributeValidator.setTransactional(false);
Map<String, Object> result = attributeValidator.verifyParametersAndReturnValues(input, LogSenderServiceImpl.METHOD);

assertEquals(expected, result);
}

@Test
public void testVerifySendOutsideTxn() {
String methodCalled = "noticeError";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
*
* * Copyright 2023 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.attributes;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.newrelic.agent.MockConfigService;
import com.newrelic.agent.MockServiceManager;
import com.newrelic.agent.config.AgentConfig;
import com.newrelic.agent.service.ServiceFactory;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class CustomEventAttributeValidatorTest {

final String methodCalled = "method";
final static String ATTRIBUTE_TYPE = "custom";
final static int ATTR_SIZE = 1024;

@BeforeClass
public static void beforeClass() {
MockServiceManager sm = new MockServiceManager();
ServiceFactory.setServiceManager(sm);
AgentConfig agentConfig = mock(AgentConfig.class, RETURNS_DEEP_STUBS);
MockConfigService configService = new MockConfigService(agentConfig);
sm.setConfigService(configService);

when(agentConfig.getInsightsConfig().getMaxAttributeValue())
.thenReturn(ATTR_SIZE);
}

@Test
public void testVerifyTruncatedValue() {
Map<String, Object> input = new HashMap<>();
String longValue = Strings.repeat("e", ATTR_SIZE + 42);
String longExpectedValue = Strings.repeat("e", ATTR_SIZE);
input.put("key", longValue);
input.put("apple", "pie");
input.put("sugar", "cream");

Map<String, Object> expected = ImmutableMap.of("apple", "pie", "sugar", "cream", "key", longExpectedValue);

AttributeValidator attributeValidator = new CustomEventAttributeValidator(ATTRIBUTE_TYPE);
attributeValidator.setTransactional(false);
Map<String, Object> result = attributeValidator.verifyParametersAndReturnValues(input, methodCalled);

assertEquals(expected, result);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
*
* * Copyright 2023 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.attributes;

import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.newrelic.agent.MockServiceManager;
import com.newrelic.agent.Transaction;
import com.newrelic.agent.service.ServiceFactory;
import com.newrelic.agent.service.logging.LogSenderServiceImpl;
import com.newrelic.agent.tracers.ClassMethodSignature;
import com.newrelic.agent.tracers.servlet.BasicRequestRootTracer;
import com.newrelic.agent.tracers.servlet.MockHttpRequest;
import com.newrelic.agent.tracers.servlet.MockHttpResponse;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class LogAttributeValidatorTest {

final static String ATTRIBUTE_TYPE = "custom";

@BeforeClass
public static void beforeClass() {
MockServiceManager sm = new MockServiceManager();
ServiceFactory.setServiceManager(sm);
}

@Test
public void testVerifyTruncatedValue() {
Map<String, Object> input = new HashMap<>();
String longValue = Strings.padEnd("", 33000, 'e');
String longExpectedValue = Strings.padEnd("", 32767, 'e');
input.put("key", longValue);
input.put("apple", "pie");
input.put("sugar", "cream");

Map<String, Object> expected = ImmutableMap.<String, Object>of("apple", "pie", "sugar", "cream", "key", longExpectedValue);

AttributeValidator attributeValidator = new LogAttributeValidator(ATTRIBUTE_TYPE);
attributeValidator.setTransactional(false);

Map<String, Object> result = attributeValidator.verifyParametersAndReturnValues(input, LogSenderServiceImpl.METHOD);

assertEquals(expected, result);
}
}