Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
5 : import 'sampler.dart';
6 :
7 : /// A sampler that samples every Nth request.
8 : /// Optionally can be combined with conditions to override the count-based decision.
9 : class CountingSampler implements Sampler {
10 : final int _countInterval;
11 : final List<SamplingCondition> _overrideConditions;
12 : int _currentCount = 0;
13 :
14 2 : @override
15 4 : String get description => 'CountingSampler{interval=$_countInterval}';
16 :
17 : /// Creates a sampler that samples every Nth request.
18 : /// [countInterval] must be positive.
19 : /// [overrideConditions] are optional conditions that can force sampling regardless of count.
20 3 : CountingSampler(
21 : int countInterval, {
22 : List<SamplingCondition>? overrideConditions,
23 : }) : _countInterval = countInterval,
24 3 : _overrideConditions = overrideConditions ?? [] {
25 3 : if (countInterval <= 0) {
26 1 : throw ArgumentError('countInterval must be positive');
27 : }
28 : }
29 :
30 3 : @override
31 : SamplingResult shouldSample({
32 : required Context parentContext,
33 : required String traceId,
34 : required String name,
35 : required SpanKind spanKind,
36 : required Attributes? attributes,
37 : required List<SpanLink>? links,
38 : }) {
39 : // Check override conditions first
40 5 : for (final condition in _overrideConditions) {
41 2 : if (condition.shouldSampleCondition(
42 : name: name,
43 : spanKind: spanKind,
44 : attributes: attributes,
45 : )) {
46 2 : return SamplingResult(
47 : decision: SamplingDecision.recordAndSample,
48 : source: SamplingDecisionSource.tracerConfig,
49 2 : traceState: parentContext.spanContext?.traceState,
50 : );
51 : }
52 : }
53 :
54 : // Increment counter and check if we should sample
55 15 : _currentCount = (_currentCount + 1) % _countInterval;
56 6 : final shouldSample = _currentCount == 0;
57 :
58 3 : return SamplingResult(
59 : decision: shouldSample
60 : ? SamplingDecision.recordAndSample
61 : : SamplingDecision.drop,
62 : source: SamplingDecisionSource.tracerConfig,
63 4 : traceState: parentContext.spanContext?.traceState,
64 : );
65 : }
66 : }
67 :
68 : /// Base class for sampling conditions that can be used with the CountingSampler
69 : /// to override its default behavior based on span properties.
70 : abstract class SamplingCondition implements Sampler {
71 : /// Determines whether a span should be sampled based on its properties.
72 : ///
73 : /// @param name The name of the span
74 : /// @param spanKind The kind of span
75 : /// @param attributes The attributes of the span
76 : /// @return true if the span should be sampled, false otherwise
77 : bool shouldSampleCondition({
78 : required String name,
79 : required SpanKind spanKind,
80 : required Attributes? attributes,
81 : });
82 :
83 2 : @override
84 : SamplingResult shouldSample({
85 : required Context parentContext,
86 : required String traceId,
87 : required String name,
88 : required SpanKind spanKind,
89 : required Attributes? attributes,
90 : required List<SpanLink>? links,
91 : }) {
92 2 : final shouldRecord = shouldSampleCondition(
93 : name: name,
94 : spanKind: spanKind,
95 : attributes: attributes,
96 : );
97 :
98 2 : return SamplingResult(
99 : decision: shouldRecord
100 : ? SamplingDecision.recordAndSample
101 : : SamplingDecision.drop,
102 : source: SamplingDecisionSource.tracerConfig,
103 2 : traceState: parentContext.spanContext?.traceState,
104 : );
105 : }
106 : }
107 :
108 : /// A sampling condition that forces sampling when a span has an error status.
109 : ///
110 : /// This condition can be used to ensure that all spans with errors are sampled,
111 : /// regardless of other sampling decisions.
112 : class ErrorSamplingCondition extends SamplingCondition {
113 : /// Creates a new ErrorSamplingCondition.
114 : ///
115 : /// This condition samples spans that have an error status, ensuring that all
116 : /// spans with errors are recorded even when other sampling strategies might skip them.
117 2 : ErrorSamplingCondition();
118 :
119 : /// Returns the string description of this sampling condition.
120 : ///
121 : /// This is used for logging and debugging purposes.
122 1 : @override
123 : String get description => 'ErrorSamplingCondition';
124 :
125 2 : @override
126 :
127 : /// Determines whether a span should be sampled based on its properties.
128 : ///
129 : /// @param name The name of the span
130 : /// @param spanKind The kind of span
131 : /// @param attributes The attributes of the span
132 : /// @return true if the span should be sampled, false otherwise
133 : bool shouldSampleCondition({
134 : required String name,
135 : required SpanKind spanKind,
136 : required Attributes? attributes,
137 : }) {
138 : if (attributes == null) return false;
139 :
140 : // Check for error status
141 4 : final statusCode = attributes.getString(Otel.otelStatusCode.key);
142 4 : final statusMessage = attributes.getString(Otel.otelStatusDescription.key);
143 :
144 2 : return statusCode == 'ERROR' ||
145 1 : (statusMessage != null && statusMessage.isNotEmpty);
146 : }
147 : }
148 :
149 : /// A sampling condition that forces sampling when a span's name matches a pattern.
150 : ///
151 : /// This condition can be used to ensure that spans with names matching a specific
152 : /// pattern are always sampled, regardless of other sampling decisions.
153 : class NamePatternSamplingCondition extends SamplingCondition {
154 : /// the pattern to match
155 : final Pattern pattern;
156 :
157 : /// Creates a new NamePatternSamplingCondition with the specified pattern.
158 : ///
159 : /// This condition samples spans whose names match the given pattern, allowing
160 : /// targeted sampling of specific operations.
161 : ///
162 : /// @param pattern The pattern to match against span names
163 2 : NamePatternSamplingCondition(this.pattern);
164 :
165 : /// Returns a string description of this sampling condition.
166 1 : @override
167 2 : String get description => 'NamePatternSamplingCondition{$pattern}';
168 :
169 2 : @override
170 :
171 : /// Determines whether a span should be sampled based on its properties.
172 : ///
173 : /// This method checks if the span name matches the pattern specified in the constructor.
174 : ///
175 : /// @param name The name of the span to check against the pattern
176 : /// @param spanKind The kind of span (not used in this implementation)
177 : /// @param attributes The attributes of the span (not used in this implementation)
178 : /// @return true if the span's name matches the pattern, false otherwise
179 : bool shouldSampleCondition({
180 : required String name,
181 : required SpanKind spanKind,
182 : required Attributes? attributes,
183 : }) {
184 4 : return name.contains(pattern);
185 : }
186 : }
187 :
188 : /// A sampling condition that forces sampling when a span has a specific attribute value.
189 : ///
190 : /// This condition can be used to ensure that spans with particular attribute values
191 : /// are always sampled, regardless of other sampling decisions.
192 : class AttributeSamplingCondition extends SamplingCondition {
193 : /// The attribute key to check when determining whether to sample.
194 : final String key;
195 :
196 : /// The string value to match against the attribute, if this is a string attribute.
197 : final String? stringValue;
198 :
199 : /// The boolean value to match against the attribute, if this is a boolean attribute.
200 : final bool? boolValue;
201 :
202 : /// The integer value to match against the attribute, if this is an integer attribute.
203 : final int? intValue;
204 :
205 : /// The double value to match against the attribute, if this is a double attribute.
206 : final double? doubleValue;
207 :
208 : /// Returns a string description of this sampling condition.
209 : ///
210 : /// Used for logging and debugging purposes.
211 1 : @override
212 2 : String get description => 'AttributeSamplingCondition{$key}';
213 :
214 : /// Creates a new AttributeSamplingCondition that matches spans with a specific attribute value.
215 : ///
216 : /// This condition samples spans that have an attribute with the specified key and value.
217 : /// Only one of the type-specific values (stringValue, boolValue, intValue, doubleValue)
218 : /// should be provided.
219 : ///
220 : /// @param key The attribute key to match
221 : /// @param stringValue Optional string value to match
222 : /// @param boolValue Optional boolean value to match
223 : /// @param intValue Optional integer value to match
224 : /// @param doubleValue Optional double value to match
225 2 : AttributeSamplingCondition(
226 : this.key, {
227 : this.stringValue,
228 : this.boolValue,
229 : this.intValue,
230 : this.doubleValue,
231 : }) {
232 : var nonNullCount = 0;
233 2 : if (stringValue != null) {
234 2 : nonNullCount++;
235 : }
236 2 : if (boolValue != null) {
237 1 : nonNullCount++;
238 : }
239 2 : if (intValue != null) {
240 1 : nonNullCount++;
241 : }
242 2 : if (doubleValue != null) {
243 1 : nonNullCount++;
244 : }
245 2 : if (nonNullCount != 1) {
246 1 : throw ArgumentError(
247 5 : 'One of the type values must be non-null. string: $stringValue, bool: $boolValue, int: $intValue, double: $doubleValue',
248 : );
249 : }
250 : }
251 :
252 2 : @override
253 :
254 : /// Determines whether a span should be sampled based on its properties.
255 : ///
256 : /// This method checks if the span has attributes matching the specific key and value
257 : /// configured in this condition.
258 : ///
259 : /// @param name The name of the span
260 : /// @param spanKind The kind of span
261 : /// @param attributes The attributes of the span
262 : /// @return true if the span's attributes match the configured values, false otherwise
263 : bool shouldSampleCondition({
264 : required String name,
265 : required SpanKind spanKind,
266 : required Attributes? attributes,
267 : }) {
268 : if (attributes == null) {
269 : return false;
270 : }
271 2 : if (stringValue != null) {
272 8 : return attributes.getString(key) == stringValue;
273 : }
274 1 : if (boolValue != null) {
275 4 : return attributes.getBool(key) == boolValue;
276 : }
277 1 : if (intValue != null) {
278 4 : return attributes.getInt(key) == intValue;
279 : }
280 1 : if (doubleValue != null) {
281 4 : return attributes.getDouble(key) == doubleValue;
282 : }
283 : return false;
284 : }
285 : }
|