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