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 :
6 : import 'always_off_sampler.dart';
7 : import 'always_on_sampler.dart';
8 : import 'sampler.dart';
9 :
10 : /// A sampler that respects the parent span's sampling decision.
11 : ///
12 : /// This sampler implements a composite sampling strategy that bases its
13 : /// decision on the parent span's sampling decision. If there is no parent
14 : /// span, or if the parent is not valid, it uses a root sampler to make
15 : /// the decision.
16 : ///
17 : /// This is particularly important for maintaining complete traces across
18 : /// service boundaries, ensuring that a trace is either fully sampled or
19 : /// not sampled at all across its entire path.
20 : ///
21 : /// More information:
22 : /// https://opentelemetry.io/docs/specs/otel/trace/sdk/#parentbased
23 : class ParentBasedSampler implements Sampler {
24 : final Sampler _root;
25 : final Sampler _remoteParentSampled;
26 : final Sampler _remoteParentNotSampled;
27 : final Sampler _localParentSampled;
28 : final Sampler _localParentNotSampled;
29 :
30 : /// Gets a description of this sampler.
31 : ///
32 : /// @return A description including the root sampler's description
33 1 : @override
34 3 : String get description => 'ParentBased{root=${_root.description}}';
35 :
36 : /// Creates a parent-based sampler with the specified configuration.
37 : ///
38 : /// @param root The sampler to use when there is no parent span
39 : /// @param remoteParentSampled The sampler to use when the remote parent is sampled (defaults to AlwaysOnSampler)
40 : /// @param remoteParentNotSampled The sampler to use when the remote parent is not sampled (defaults to AlwaysOffSampler)
41 : /// @param localParentSampled The sampler to use when the local parent is sampled (defaults to AlwaysOnSampler)
42 : /// @param localParentNotSampled The sampler to use when the local parent is not sampled (defaults to AlwaysOffSampler)
43 4 : ParentBasedSampler(
44 : this._root, {
45 : Sampler? remoteParentSampled,
46 : Sampler? remoteParentNotSampled,
47 : Sampler? localParentSampled,
48 : Sampler? localParentNotSampled,
49 : }) : _remoteParentSampled = remoteParentSampled ?? const AlwaysOnSampler(),
50 : _remoteParentNotSampled =
51 : remoteParentNotSampled ?? const AlwaysOffSampler(),
52 : _localParentSampled = localParentSampled ?? const AlwaysOnSampler(),
53 : _localParentNotSampled =
54 : localParentNotSampled ?? const AlwaysOffSampler();
55 :
56 : /// Makes a sampling decision based on the parent context.
57 : ///
58 : /// This method checks if there's a valid parent span context in the parent context.
59 : /// If there is, it uses the appropriate sampler based on whether the parent is
60 : /// remote or local, and whether it is sampled or not. If there's no valid parent,
61 : /// it uses the root sampler.
62 : ///
63 : /// @param parentContext The parent context containing the parent span
64 : /// @param traceId The trace ID of the span
65 : /// @param name The name of the span
66 : /// @param spanKind The kind of the span
67 : /// @param attributes The attributes of the span
68 : /// @param links The links to other spans
69 : /// @return A sampling result based on the appropriate sampler's decision
70 4 : @override
71 : SamplingResult shouldSample({
72 : required Context parentContext,
73 : required String traceId,
74 : required String name,
75 : required SpanKind spanKind,
76 : required Attributes? attributes,
77 : required List<SpanLink>? links,
78 : }) {
79 : // Extract SpanContext from the parent context
80 4 : final parentSpanContext = parentContext.spanContext;
81 :
82 : // If no parent, use root sampler
83 4 : if (parentSpanContext == null || !parentSpanContext.isValid) {
84 8 : return _root.shouldSample(
85 : parentContext: parentContext,
86 : traceId: traceId,
87 : name: name,
88 : spanKind: spanKind,
89 : attributes: attributes,
90 : links: links,
91 : );
92 : }
93 :
94 : // Parent exists, use appropriate sampler based on parent's state
95 4 : final isRemote = parentSpanContext.isRemote;
96 8 : final isSampled = parentSpanContext.traceFlags.isSampled;
97 :
98 : if (isRemote) {
99 : return isSampled
100 4 : ? _remoteParentSampled.shouldSample(
101 : parentContext: parentContext,
102 : traceId: traceId,
103 : name: name,
104 : spanKind: spanKind,
105 : attributes: attributes,
106 : links: links,
107 : )
108 4 : : _remoteParentNotSampled.shouldSample(
109 : parentContext: parentContext,
110 : traceId: traceId,
111 : name: name,
112 : spanKind: spanKind,
113 : attributes: attributes,
114 : links: links,
115 : );
116 : } else {
117 : return isSampled
118 8 : ? _localParentSampled.shouldSample(
119 : parentContext: parentContext,
120 : traceId: traceId,
121 : name: name,
122 : spanKind: spanKind,
123 : attributes: attributes,
124 : links: links,
125 : )
126 4 : : _localParentNotSampled.shouldSample(
127 : parentContext: parentContext,
128 : traceId: traceId,
129 : name: name,
130 : spanKind: spanKind,
131 : attributes: attributes,
132 : links: links,
133 : );
134 : }
135 : }
136 : }
|