Line data Source code
1 : // Licensed under the Apache License, Version 2.0
2 : // Copyright 2025, Michael Bushe, All rights reserved.
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 0 : @override
15 0 : 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 1 : CountingSampler(
21 : int countInterval, {
22 : List<SamplingCondition>? overrideConditions,
23 : }) : _countInterval = countInterval,
24 1 : _overrideConditions = overrideConditions ?? [] {
25 1 : if (countInterval <= 0) {
26 0 : throw ArgumentError('countInterval must be positive');
27 : }
28 : }
29 :
30 1 : @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 2 : for (final condition in _overrideConditions) {
41 1 : 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 5 : _currentCount = (_currentCount + 1) % _countInterval;
55 2 : final shouldSample = _currentCount == 0;
56 :
57 1 : 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 1 : @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 1 : final shouldRecord = shouldSampleCondition(
91 : name: name,
92 : spanKind: spanKind,
93 : attributes: attributes,
94 : );
95 :
96 1 : 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 1 : ErrorSamplingCondition();
115 :
116 : /// Returns the string description of this sampling condition.
117 : ///
118 : /// This is used for logging and debugging purposes.
119 0 : @override
120 : String get description => 'ErrorSamplingCondition';
121 :
122 1 : @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 1 : final statusCode = attributes.getString('otel.status_code');
139 1 : final statusMessage = attributes.getString('otel.status_description');
140 :
141 1 : return (statusCode == 'ERROR' ||
142 0 : (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 1 : NamePatternSamplingCondition(this.pattern);
161 :
162 : /// Returns a string description of this sampling condition.
163 0 : @override
164 0 : String get description => 'NamePatternSamplingCondition{$pattern}';
165 :
166 1 : @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 2 : 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 0 : @override
209 0 : 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 1 : AttributeSamplingCondition(this.key,
223 : {this.stringValue, this.boolValue, this.intValue, this.doubleValue}) {
224 : int nonNullCount = 0;
225 1 : if (stringValue != null) {
226 1 : nonNullCount++;
227 : }
228 1 : if (boolValue != null) {
229 0 : nonNullCount++;
230 : }
231 1 : if (intValue != null) {
232 0 : nonNullCount++;
233 : }
234 1 : if (doubleValue != null) {
235 0 : nonNullCount++;
236 : }
237 1 : if (nonNullCount != 1) {
238 0 : throw ArgumentError(
239 0 : 'One of the type values must be non-null. string: $stringValue, bool: $boolValue, int: $intValue, double: $doubleValue');
240 : }
241 : }
242 :
243 1 : @override
244 :
245 : /// Determines whether a span should be sampled based on its properties.
246 : ///
247 : /// This method checks if the span has attributes matching the specific key and value
248 : /// configured in this condition.
249 : ///
250 : /// @param name The name of the span
251 : /// @param spanKind The kind of span
252 : /// @param attributes The attributes of the span
253 : /// @return true if the span's attributes match the configured values, false otherwise
254 : bool shouldSampleCondition({
255 : required String name,
256 : required SpanKind spanKind,
257 : required Attributes? attributes,
258 : }) {
259 : if (attributes == null) {
260 : return false;
261 : }
262 1 : if (stringValue != null) {
263 4 : return attributes.getString(key) == stringValue;
264 : }
265 0 : if (boolValue != null) {
266 0 : return attributes.getBool(key) == boolValue;
267 : }
268 0 : if (intValue != null) {
269 0 : return attributes.getInt(key) == intValue;
270 : }
271 0 : if (doubleValue != null) {
272 0 : return attributes.getDouble(key) == doubleValue;
273 : }
274 : return false;
275 : }
276 : }
|