Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public final TelemetryPolicy validate(SourceWrapper source) {
}
SourceFormat format = source.getFormat();
switch (format) {
case JSON: // transitional: same payload shape as JSONKEYVALUE until JSON is removed
case JSONKEYVALUE:
return validateJsonSource(((JsonSourceWrapper) source).asJsonNode());
case KEYVALUE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
* <p>The file format supports JSON and key-value lines:
*
* <ul>
* <li><b>JSON Objects:</b> Lines starting with <code>{</code> are treated as JSON objects and
* validated against the registered {@link PolicyValidator}s.
* <li><b>JSON Objects:</b> Lines starting with <code>{</code> are treated as JSONKEYVALUE objects
* and validated against the registered {@link PolicyValidator}s.
Comment thread
jackshirazi marked this conversation as resolved.
Outdated
* <li><b>Key-Value:</b> Lines containing <code>=</code> are treated as key-value policy lines and
* validated against the registered {@link PolicyValidator}s.
* </ul>
Expand Down Expand Up @@ -60,7 +60,7 @@ public List<TelemetryPolicy> fetchPolicies() throws IOException {

SourceFormat format;
if (trimmedLine.startsWith("{")) {
format = SourceFormat.JSON;
format = SourceFormat.JSONKEYVALUE;
} else if (trimmedLine.indexOf('=') >= 0) {
format = SourceFormat.KEYVALUE;
} else {
Expand All @@ -74,12 +74,18 @@ public List<TelemetryPolicy> fetchPolicies() throws IOException {
}

SourceWrapper parsedSource = parsedSources.get(0);
String policyType = parsedSource.getPolicyType();
if (policyType == null || policyType.isEmpty()) {
logger.info("Policy type not found in line: " + trimmedLine);
return;
}
TelemetryPolicy policy = null;
for (PolicyValidator validator : validators) {
policy = validator.validate(parsedSource);
if (policy != null) {
break;
if (!policyType.equals(validator.getPolicyType())) {
continue;
}
policy = validator.validate(parsedSource);
break;
}
if (policy == null) {
logger.info("Validator not found or rejected for line: " + trimmedLine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,83 +5,24 @@

package io.opentelemetry.contrib.dynamic.policy;

import io.opentelemetry.contrib.dynamic.policy.source.JsonSourceWrapper;
import io.opentelemetry.contrib.dynamic.policy.source.KeyValueSourceWrapper;
import io.opentelemetry.contrib.dynamic.policy.source.SourceWrapper;
import javax.annotation.Nullable;

public interface PolicyValidator {
/**
* Validates a policy configuration provided as a JSON string.
*
* @param json The JSON string containing the policy configuration.
* @return The validated {@link TelemetryPolicy}, or {@code null} if the JSON does not contain a
* valid policy for this validator.
*/
@Nullable
TelemetryPolicy validate(String json);

/**
* Validates a parsed policy configuration source.
*
* <p>This is a transitional API: by default it delegates to {@link #validate(String)} and/or
* {@link #validateAlias(String, String)} where possible.
*
* @param source parsed source wrapper containing the format and payload
* @return The validated {@link TelemetryPolicy}, or {@code null} if the source does not contain a
* valid policy for this validator.
*/
@Nullable
default TelemetryPolicy validate(SourceWrapper source) {
if (source == null) {
return null;
}
if (source instanceof JsonSourceWrapper) {
return validate(((JsonSourceWrapper) source).asJsonNode().toString());
}
if (source instanceof KeyValueSourceWrapper) {
KeyValueSourceWrapper kv = (KeyValueSourceWrapper) source;
String alias = getAlias();
if (alias != null && alias.equals(kv.getKey().trim())) {
return validateAlias(kv.getKey().trim(), kv.getValue());
}
}
return null;
}
TelemetryPolicy validate(SourceWrapper source);

/**
* Returns the type of the policy this validator handles.
*
* @return The policy type string (e.g., "trace-sampling").
*/
String getPolicyType();

/**
* Validates a policy configuration provided as a key-value pair (alias).
*
* <p>This is intended for simple configuration cases where a full JSON object is not necessary.
*
* @param key The alias key (e.g., "trace-sampling.probability").
* @param value The value associated with the key.
* @return The validated {@link TelemetryPolicy}, or {@code null} if the key/value pair is invalid
* or not handled by this validator.
*/
@Nullable
default TelemetryPolicy validateAlias(String key, String value) {
throw new UnsupportedOperationException(
"Alias validation is not supported by validator "
+ getClass().getName()
+ " for key "
+ key);
}

/**
* Returns the alias key supported by this validator, if any.
*
* @return The alias key string, or {@code null} if aliases are not supported.
*/
@Nullable
default String getAlias() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import java.util.Objects;
import javax.annotation.Nullable;

/** JSON-backed source wrapper for a single-policy object. */
/** JSONKEYVALUE-backed source wrapper for a single-policy object. */
public final class JsonSourceWrapper implements SourceWrapper {
private static final ObjectMapper MAPPER = new ObjectMapper();
private final JsonNode source;
Expand All @@ -25,7 +25,7 @@ public JsonSourceWrapper(JsonNode source) {

@Override
public SourceFormat getFormat() {
return SourceFormat.JSON;
return SourceFormat.JSONKEYVALUE;
}

@Override
Expand All @@ -43,11 +43,11 @@ public JsonNode asJsonNode() {
}

/**
* Parses JSON source into one wrapper per top-level policy object.
* Parses JSONKEYVALUE source into one wrapper per top-level policy object.
*
* @return an empty list if the source is an empty JSON array; a non-empty list of wrappers if the
* source is a valid single-policy object or array thereof; or {@code null} if the shape is
* unsupported or the source is not valid JSON.
* @return an empty list if the source is an empty JSONKEYVALUE array; a non-empty list of
* wrappers if the source is a valid single-policy object or array thereof; or {@code null} if
* the shape is unsupported or the source is not valid JSONKEYVALUE.
Comment thread
jackshirazi marked this conversation as resolved.
Outdated
* @throws NullPointerException if source is null
*/
@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
/** Supported source formats and their parser dispatch. */
public enum SourceFormat {
KEYVALUE("keyvalue", KeyValueSourceWrapper::parse),
JSON("json", JsonSourceWrapper::parse), // Will remove
JSONKEYVALUE("jsonkeyvalue", JsonSourceWrapper::parse);

private final String configValue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,63 @@

package io.opentelemetry.contrib.dynamic.policy.tracesampling;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.contrib.dynamic.policy.PolicyValidator;
import io.opentelemetry.contrib.dynamic.policy.AbstractSourcePolicyValidator;
import io.opentelemetry.contrib.dynamic.policy.TelemetryPolicy;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
* Validator for trace sampling policies.
*
* <p>This validator handles the "trace-sampling" policy type and supports the
* "trace-sampling.probability" alias.
* <p>This validator handles the "trace-sampling" policy type.
*/
public final class TraceSamplingValidator implements PolicyValidator {
public final class TraceSamplingValidator extends AbstractSourcePolicyValidator {
private static final Logger logger = Logger.getLogger(TraceSamplingValidator.class.getName());
private static final ObjectMapper MAPPER = new ObjectMapper();

@Override
public String getPolicyType() {
return TraceSamplingRatePolicy.TYPE;
}

@Override
public String getAlias() {
return "trace-sampling.probability";
@Nullable
protected TelemetryPolicy validateJsonValue(JsonNode valueNode) {
JsonNode probabilityNode = valueNode;
if (valueNode.isObject()) {
probabilityNode = valueNode.get("probability");
if (probabilityNode == null) {
return null;
}
}
Double probability = parseDouble(probabilityNode);
if (probability == null) {
return null;
}
return createPolicy(probability);
}

@Override
@Nullable
public TelemetryPolicy validate(String json) {
try {
JsonNode node = MAPPER.readTree(json);
if (node.has(getPolicyType())) {
JsonNode spec = node.get(getPolicyType());
if (spec.has("probability")) {
JsonNode probNode = spec.get("probability");
if (probNode.isNumber()) {
double d = probNode.asDouble();
if (d >= 0.0 && d <= 1.0) {
return new TraceSamplingRatePolicy(d);
}
}
}
}
} catch (JsonProcessingException e) {
// Not valid JSON for this validator
protected TelemetryPolicy validateKeyValueValue(String value) {
Double probability = parseDouble(value);
if (probability == null) {
return null;
}
logger.info("Invalid trace-sampling JSON: " + json);
return null;
return createPolicy(probability);
}

@Override
@Nullable
public TelemetryPolicy validateAlias(String key, String value) {
if (getAlias() != null && getAlias().equals(key)) {
try {
double d = Double.parseDouble(value);
if (d >= 0.0 && d <= 1.0) {
return new TraceSamplingRatePolicy(d);
}
} catch (NumberFormatException e) {
// invalid
}
logger.info("Ignoring invalid trace-sampling.probability value: " + value);
private static TelemetryPolicy createPolicy(double probability) {
try {
return new TraceSamplingRatePolicy(probability);
} catch (IllegalArgumentException e) {
logger.info(
"Invalid trace-sampling probability '"
+ probability
+ "' will be ignored: "
+ e.getMessage());
return null;
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.contrib.dynamic.policy.source.SourceFormat;
import io.opentelemetry.contrib.dynamic.policy.source.SourceWrapper;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
Expand All @@ -19,7 +21,6 @@
class LinePerPolicyFileProviderTest {

private static final String TRACE_SAMPLING_TYPE = "trace-sampling";
private static final String TRACE_SAMPLING_ALIAS = "trace-sampling.probability";

@TempDir Path tempDir;

Expand All @@ -36,7 +37,7 @@ void fetchPoliciesReturnsEmptyWhenFileMissing() throws Exception {

@Test
void fetchPoliciesParsesJsonLines() throws Exception {
Path file = writeLines("{\"trace-sampling\": {\"probability\": 0.5}}");
Path file = writeLines("{\"trace-sampling\": 0.5}");
LinePerPolicyFileProvider provider =
new LinePerPolicyFileProvider(file, Collections.singletonList(acceptingValidator()));

Expand All @@ -48,7 +49,7 @@ void fetchPoliciesParsesJsonLines() throws Exception {

@Test
void fetchPoliciesParsesAliasLines() throws Exception {
Path file = writeLines("trace-sampling.probability=0.5");
Path file = writeLines("trace-sampling=0.5");
Comment thread
jackshirazi marked this conversation as resolved.
LinePerPolicyFileProvider provider =
new LinePerPolicyFileProvider(file, Collections.singletonList(acceptingValidator()));

Expand All @@ -60,7 +61,7 @@ void fetchPoliciesParsesAliasLines() throws Exception {

@Test
void fetchPoliciesSkipsBlankLinesAndComments() throws Exception {
Path file = writeLines("", " ", "# comment line", "trace-sampling.probability=0.25");
Path file = writeLines("", " ", "# comment line", "trace-sampling=0.25");
LinePerPolicyFileProvider provider =
new LinePerPolicyFileProvider(file, Collections.singletonList(acceptingValidator()));

Expand All @@ -74,11 +75,7 @@ void fetchPoliciesSkipsBlankLinesAndComments() throws Exception {
void fetchPoliciesSkipsUnknownOrRejectedPolicies() throws Exception {
PolicyValidator rejectingValidator =
new TestPolicyValidator(/* acceptJson= */ false, /* acceptAlias= */ false);
Path file =
writeLines(
"{\"trace-sampling\": {\"probability\": 0.5}}",
"{\"other-policy\": {\"probability\": 0.5}}",
"other.key=1");
Path file = writeLines("{\"trace-sampling\": 0.5}", "{\"other-policy\": 0.5}", "other.key=1");
LinePerPolicyFileProvider provider =
new LinePerPolicyFileProvider(file, Collections.singletonList(rejectingValidator));

Expand Down Expand Up @@ -106,30 +103,26 @@ private TestPolicyValidator(boolean acceptJson, boolean acceptAlias) {
this.acceptAlias = acceptAlias;
}

@Override
public TelemetryPolicy validate(String json) {
if (!acceptJson) {
return null;
}
return new TelemetryPolicy(TRACE_SAMPLING_TYPE);
}

@Override
public String getPolicyType() {
return TRACE_SAMPLING_TYPE;
}

@Override
public TelemetryPolicy validateAlias(String key, String value) {
if (!acceptAlias) {
return null;
public TelemetryPolicy validate(SourceWrapper source) {
if (source.getFormat() == SourceFormat.JSONKEYVALUE) {
if (!acceptJson) {
return null;
}
return new TelemetryPolicy(TRACE_SAMPLING_TYPE);
}
return new TelemetryPolicy(TRACE_SAMPLING_TYPE);
}

@Override
public String getAlias() {
return TRACE_SAMPLING_ALIAS;
if (source.getFormat() == SourceFormat.KEYVALUE) {
if (!acceptAlias) {
return null;
}
return new TelemetryPolicy(TRACE_SAMPLING_TYPE);
}
return null;
}
}
}
Loading
Loading