Skip to content

Commit 029ff17

Browse files
committed
refactor: Improve tests cases and adhere to style guide
- Move config test to resource files - Adhere to contribution style guide - On CEL expression syntax error include return the error message - Enhance README to clarify the configuration usage
1 parent 776abf3 commit 029ff17

21 files changed

+518
-236
lines changed

samplers/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ tracer_provider:
5050
rule_based_routing:
5151
# Fallback to the always_on sampler if the criteria is not met.
5252
fallback_sampler:
53+
# A fallback must be specified.
5354
always_on:
5455
# Only apply to SERVER spans.
5556
span_kind: SERVER
@@ -77,6 +78,7 @@ Schema for `cel_based` sampler:
7778
fallback_sampler:
7879
always_on:
7980
# List of CEL expressions to evaluate. Expressions are evaluated in order.
81+
# The first expression that evaluates to true has its action taken, and subsequent expressions are not evaluated.
8082
expressions:
8183
# The action to take when the expression evaluates to true. Must be one of: DROP, RECORD_AND_SAMPLE.
8284
- action: DROP

samplers/src/main/java/io/opentelemetry/contrib/sampler/CelBasedSampler.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
import java.util.Map;
2727
import java.util.logging.Level;
2828
import java.util.logging.Logger;
29-
import java.util.regex.Pattern;
3029

3130
/**
3231
* This sampler accepts a list of {@link CelBasedSamplingExpression}s and tries to match every
3332
* proposed span against those rules. Every rule describes a span's attribute, a pattern against
3433
* which to match attribute's value, and a sampler that will make a decision about given span if
3534
* match was successful.
3635
*
37-
* <p>Matching is performed by {@link Pattern}.
36+
* <p>Matching is performed by CEL expression evaluation.
3837
*
3938
* <p>Provided span kind is checked first and if differs from the one given to {@link
4039
* #builder(Sampler)}, the default fallback sampler will make a decision.
@@ -48,7 +47,7 @@ public final class CelBasedSampler implements Sampler {
4847

4948
private static final Logger logger = Logger.getLogger(CelBasedSampler.class.getName());
5049

51-
public static final CelCompiler celCompiler =
50+
static final CelCompiler celCompiler =
5251
CelCompilerFactory.standardCelCompilerBuilder()
5352
.addVar("name", SimpleType.STRING)
5453
.addVar("traceId", SimpleType.STRING)
@@ -57,24 +56,35 @@ public final class CelBasedSampler implements Sampler {
5756
.setResultType(SimpleType.BOOL)
5857
.build();
5958

60-
final CelRuntime celRuntime;
61-
59+
private final CelRuntime celRuntime;
6260
private final List<CelBasedSamplingExpression> expressions;
6361
private final Sampler fallback;
6462

63+
/**
64+
* Creates a new CEL-based sampler.
65+
*
66+
* @param expressions The list of CEL expressions to evaluate
67+
* @param fallback The fallback sampler to use when no expressions match
68+
*/
6569
public CelBasedSampler(List<CelBasedSamplingExpression> expressions, Sampler fallback) {
6670
this.expressions = requireNonNull(expressions, "expressions must not be null");
6771
this.expressions.forEach(
6872
expr -> {
69-
if (!expr.abstractSyntaxTree.isChecked()) {
73+
if (!expr.getAbstractSyntaxTree().isChecked()) {
7074
throw new IllegalArgumentException(
71-
"Expression and its AST is not checked: " + expr.expression);
75+
"Expression and its AST is not checked: " + expr.getExpression());
7276
}
7377
});
74-
this.fallback = requireNonNull(fallback);
78+
this.fallback = requireNonNull(fallback, "fallback must not be null");
7579
this.celRuntime = CelRuntimeFactory.standardCelRuntimeBuilder().build();
7680
}
7781

82+
/**
83+
* Creates a new builder for CEL-based sampler.
84+
*
85+
* @param fallback The fallback sampler to use when no expressions match
86+
* @return A new builder instance
87+
*/
7888
public static CelBasedSamplerBuilder builder(Sampler fallback) {
7989
return new CelBasedSamplerBuilder(
8090
requireNonNull(fallback, "fallback sampler must not be null"), celCompiler);
@@ -98,34 +108,40 @@ public SamplingResult shouldSample(
98108

99109
for (CelBasedSamplingExpression expression : expressions) {
100110
try {
101-
CelRuntime.Program program = celRuntime.createProgram(expression.abstractSyntaxTree);
111+
CelRuntime.Program program = celRuntime.createProgram(expression.getAbstractSyntaxTree());
102112
Object result = program.eval(evaluationContext);
103113
// Happy path: Perform sampling based on the boolean result
104114
if (result instanceof Boolean && ((Boolean) result)) {
105-
return expression.delegate.shouldSample(
106-
parentContext, traceId, name, spanKind, attributes, parentLinks);
115+
return expression
116+
.getDelegate()
117+
.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
107118
}
108119
// If result is not boolean, treat as false
109120
logger.log(
110121
Level.FINE,
111-
"Expression '" + expression.expression + "' returned non-boolean result: " + result);
122+
"Expression '"
123+
+ expression.getExpression()
124+
+ "' returned non-boolean result: "
125+
+ result);
112126
} catch (CelEvaluationException e) {
113127
logger.log(
114128
Level.FINE,
115-
"Expression '" + expression.expression + "' evaluation error: " + e.getMessage());
129+
"Expression '" + expression.getExpression() + "' evaluation error: " + e.getMessage());
116130
}
117131
}
118132

119133
return fallback.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
120134
}
121135

122-
/** Convert OpenTelemetry Attributes to a Map that CEL can work with */
136+
/**
137+
* Convert OpenTelemetry Attributes to a Map that CEL can work with.
138+
*
139+
* @param attributes The OpenTelemetry attributes
140+
* @return A map representation of the attributes
141+
*/
123142
private static Map<String, Object> convertAttributesToMap(Attributes attributes) {
124143
Map<String, Object> map = new HashMap<>();
125-
attributes.forEach(
126-
(key, value) -> {
127-
map.put(key.getKey(), value);
128-
});
144+
attributes.forEach((key, value) -> map.put(key.getKey(), value));
129145
return map;
130146
}
131147

samplers/src/main/java/io/opentelemetry/contrib/sampler/CelBasedSamplerBuilder.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,40 @@
1111
import dev.cel.common.CelAbstractSyntaxTree;
1212
import dev.cel.common.CelValidationException;
1313
import dev.cel.compiler.CelCompiler;
14-
import io.opentelemetry.api.common.AttributeKey;
1514
import io.opentelemetry.sdk.trace.samplers.Sampler;
1615
import java.util.ArrayList;
1716
import java.util.List;
1817

18+
/**
19+
* Builder for {@link CelBasedSampler}.
20+
*
21+
* <p>This builder allows configuring CEL expressions with their associated sampling actions. Each
22+
* expression is evaluated in order, and the first matching expression determines the sampling
23+
* decision for a span.
24+
*/
1925
public final class CelBasedSamplerBuilder {
2026
private final CelCompiler celCompiler;
2127
private final List<CelBasedSamplingExpression> expressions = new ArrayList<>();
2228
private final Sampler defaultDelegate;
2329

30+
/**
31+
* Creates a new builder with the specified fallback sampler and CEL compiler.
32+
*
33+
* @param defaultDelegate The fallback sampler to use when no expressions match
34+
* @param celCompiler The CEL compiler for compiling expressions
35+
*/
2436
CelBasedSamplerBuilder(Sampler defaultDelegate, CelCompiler celCompiler) {
2537
this.defaultDelegate = defaultDelegate;
2638
this.celCompiler = celCompiler;
2739
}
2840

2941
/**
30-
* Use the provided sampler when the value of the provided {@link AttributeKey} matches the
31-
* provided pattern.
42+
* Use the provided sampler when the CEL expression evaluates to true.
43+
*
44+
* @param expression The CEL expression to evaluate
45+
* @param sampler The sampler to use when the expression matches
46+
* @return This builder instance for method chaining
47+
* @throws CelValidationException if the expression cannot be compiled
3248
*/
3349
@CanIgnoreReturnValue
3450
public CelBasedSamplerBuilder customize(String expression, Sampler sampler)
@@ -44,24 +60,34 @@ public CelBasedSamplerBuilder customize(String expression, Sampler sampler)
4460
}
4561

4662
/**
47-
* Drop all spans when the value of the provided {@link AttributeKey} matches the provided
48-
* pattern.
63+
* Drop all spans when the CEL expression evaluates to true.
64+
*
65+
* @param expression The CEL expression to evaluate
66+
* @return This builder instance for method chaining
67+
* @throws CelValidationException if the expression cannot be compiled
4968
*/
5069
@CanIgnoreReturnValue
5170
public CelBasedSamplerBuilder drop(String expression) throws CelValidationException {
5271
return customize(expression, Sampler.alwaysOff());
5372
}
5473

5574
/**
56-
* Record and sample all spans when the value of the provided {@link AttributeKey} matches the
57-
* provided pattern.
75+
* Record and sample all spans when the CEL expression evaluates to true.
76+
*
77+
* @param expression The CEL expression to evaluate
78+
* @return This builder instance for method chaining
79+
* @throws CelValidationException if the expression cannot be compiled
5880
*/
5981
@CanIgnoreReturnValue
6082
public CelBasedSamplerBuilder recordAndSample(String expression) throws CelValidationException {
6183
return customize(expression, Sampler.alwaysOn());
6284
}
6385

64-
/** Build the sampler based on the rules provided. */
86+
/**
87+
* Build the sampler based on the configured expressions.
88+
*
89+
* @return a new {@link CelBasedSampler} instance
90+
*/
6591
public CelBasedSampler build() {
6692
return new CelBasedSampler(expressions, defaultDelegate);
6793
}

samplers/src/main/java/io/opentelemetry/contrib/sampler/CelBasedSamplingExpression.java

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,56 @@
1212
import java.util.Objects;
1313
import javax.annotation.Nullable;
1414

15-
public class CelBasedSamplingExpression {
16-
final CelAbstractSyntaxTree abstractSyntaxTree;
17-
final String expression;
18-
final Sampler delegate;
15+
/**
16+
* Represents a CEL-based sampling expression that contains a compiled CEL expression and its
17+
* associated sampler delegate.
18+
*
19+
* <p>This class is used internally by {@link CelBasedSampler} to store and evaluate CEL expressions
20+
* for sampling decisions.
21+
*/
22+
public final class CelBasedSamplingExpression {
23+
private final CelAbstractSyntaxTree abstractSyntaxTree;
24+
private final String expression;
25+
private final Sampler delegate;
1926

27+
/**
28+
* Creates a new CEL-based sampling expression.
29+
*
30+
* @param abstractSyntaxTree The compiled CEL abstract syntax tree
31+
* @param delegate The sampler to use when this expression evaluates to true
32+
*/
2033
CelBasedSamplingExpression(CelAbstractSyntaxTree abstractSyntaxTree, Sampler delegate) {
21-
this.abstractSyntaxTree = requireNonNull(abstractSyntaxTree);
34+
this.abstractSyntaxTree =
35+
requireNonNull(abstractSyntaxTree, "abstractSyntaxTree must not be null");
2236
this.expression = abstractSyntaxTree.getSource().getContent().toString();
23-
this.delegate = requireNonNull(delegate);
37+
this.delegate = requireNonNull(delegate, "delegate must not be null");
38+
}
39+
40+
/**
41+
* Returns the compiled CEL abstract syntax tree.
42+
*
43+
* @return The abstract syntax tree
44+
*/
45+
CelAbstractSyntaxTree getAbstractSyntaxTree() {
46+
return abstractSyntaxTree;
47+
}
48+
49+
/**
50+
* Returns the string representation of the CEL expression.
51+
*
52+
* @return The expression string
53+
*/
54+
String getExpression() {
55+
return expression;
56+
}
57+
58+
/**
59+
* Returns the sampler delegate.
60+
*
61+
* @return The sampler delegate
62+
*/
63+
Sampler getDelegate() {
64+
return delegate;
2465
}
2566

2667
@Override

samplers/src/main/java/io/opentelemetry/contrib/sampler/internal/CelBasedSamplerComponentProvider.java

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
2222
* at any time.
2323
*/
24-
public class CelBasedSamplerComponentProvider implements ComponentProvider<Sampler> {
24+
public final class CelBasedSamplerComponentProvider implements ComponentProvider<Sampler> {
2525

2626
private static final String ACTION_RECORD_AND_SAMPLE = "RECORD_AND_SAMPLE";
2727
private static final String ACTION_DROP = "DROP";
@@ -38,25 +38,12 @@ public String getName() {
3838

3939
@Override
4040
public Sampler create(DeclarativeConfigProperties config) {
41-
DeclarativeConfigProperties fallbackModel = config.getStructured("fallback_sampler");
42-
if (fallbackModel == null) {
43-
throw new DeclarativeConfigException(
44-
"cel_based sampler .fallback_sampler is required but is null");
45-
}
46-
Sampler fallbackSampler;
47-
try {
48-
fallbackSampler = DeclarativeConfiguration.createSampler(fallbackModel);
49-
} catch (DeclarativeConfigException e) {
50-
throw new DeclarativeConfigException(
51-
"cel_based sampler failed to create .fallback_sampler sampler", e);
52-
}
53-
5441
List<DeclarativeConfigProperties> expressions = config.getStructuredList("expressions");
5542
if (expressions == null || expressions.isEmpty()) {
5643
throw new DeclarativeConfigException("cel_based sampler .expressions is required");
5744
}
5845

59-
CelBasedSamplerBuilder builder = CelBasedSampler.builder(fallbackSampler);
46+
CelBasedSamplerBuilder builder = CelBasedSampler.builder(getFallbackSampler(config));
6047

6148
for (DeclarativeConfigProperties expressionConfig : expressions) {
6249
String expression = expressionConfig.getString("expression");
@@ -83,9 +70,27 @@ public Sampler create(DeclarativeConfigProperties config) {
8370
+ ACTION_DROP);
8471
}
8572
} catch (CelValidationException e) {
86-
throw new DeclarativeConfigException("Failed to compile CEL expression: " + expression, e);
73+
throw new DeclarativeConfigException(
74+
"Failed to compile CEL expression: '" + expression + "'. CEL error: " + e.getMessage(),
75+
e);
8776
}
8877
}
8978
return builder.build();
9079
}
80+
81+
private static Sampler getFallbackSampler(DeclarativeConfigProperties config) {
82+
DeclarativeConfigProperties fallbackModel = config.getStructured("fallback_sampler");
83+
if (fallbackModel == null) {
84+
throw new DeclarativeConfigException(
85+
"cel_based sampler .fallback_sampler is required but is null");
86+
}
87+
Sampler fallbackSampler;
88+
try {
89+
fallbackSampler = DeclarativeConfiguration.createSampler(fallbackModel);
90+
} catch (DeclarativeConfigException e) {
91+
throw new DeclarativeConfigException(
92+
"cel_based sampler failed to create .fallback_sampler sampler", e);
93+
}
94+
return fallbackSampler;
95+
}
9196
}

0 commit comments

Comments
 (0)