Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : library;
5 :
6 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
7 : import 'package:meta/meta.dart';
8 :
9 : import '../resource/resource.dart';
10 : import 'tracer.dart';
11 :
12 : part 'span_create.dart';
13 :
14 : /// SDK implementation of the APISpan interface.
15 : ///
16 : /// A Span represents a single operation within a trace. Spans can be nested
17 : /// to form a trace tree. Each trace contains a root span, which typically
18 : /// describes the entire operation and, optionally, one or more sub-spans
19 : /// for its sub-operations.
20 : ///
21 : /// This implementation delegates most functionality to the API Span implementation
22 : /// while adding SDK-specific behaviors like span processor notification.
23 : ///
24 : /// Note: Per [OTEP 0265: Event Vision](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/0265-event-vision.md)
25 : /// and [OTEP 4430: Span Event API deprecation plan](https://github.com/open-telemetry/opentelemetry-specification/blob/main/oteps/4430-span-event-api-deprecation-plan.md),
26 : /// span events are planned for deprecation in favor of log-based events
27 : /// emitted via the Logs API; SDKs will provide options to render log-based
28 : /// events as span events for compatibility.
29 : ///
30 : /// More information:
31 : /// https://opentelemetry.io/docs/specs/otel/trace/sdk/
32 : class Span implements APISpan {
33 : final APISpan _delegate;
34 : final Tracer _sdkTracer;
35 :
36 : /// The recording state decided by the sampler at creation time.
37 : ///
38 : /// Per the Trace SDK spec (ShouldSample), a DROP decision creates a span
39 : /// with `IsRecording == false`; the delegate APISpan does not carry this
40 : /// state, so the SDK span holds it.
41 : final bool _isRecording;
42 :
43 : /// Private constructor for creating Span instances.
44 : ///
45 : /// @param delegate The API Span implementation to delegate to
46 : /// @param sdkTracer The SDK Tracer that created this Span
47 : /// @param isRecording Whether this span records data (sampling decision)
48 59 : Span._(APISpan delegate, Tracer sdkTracer, {bool isRecording = true})
49 : : _delegate = delegate,
50 : _sdkTracer = sdkTracer,
51 : _isRecording = isRecording {
52 59 : if (OTelLog.isDebug()) {
53 162 : OTelLog.debug('SDKSpan: Created new span with name ${delegate.name}');
54 : }
55 : }
56 :
57 : /// Gets the resource associated with this span's tracer.
58 : ///
59 : /// @return The resource associated with this span
60 126 : Resource? get resource => _sdkTracer.resource;
61 :
62 53 : @override
63 : void end({DateTime? endTime, SpanStatusCode? spanStatus}) {
64 53 : if (OTelLog.isDebug()) {
65 48 : OTelLog.debug(
66 192 : 'SDKSpan: Starting to end span ${spanContext.spanId} with name $name',
67 : );
68 : }
69 :
70 : if (spanStatus != null) {
71 1 : setStatus(spanStatus);
72 : }
73 :
74 : try {
75 53 : if (OTelLog.isDebug()) {
76 144 : OTelLog.debug('SDKSpan: Calling delegate.end() for span $name');
77 : }
78 : // `spanStatus` is deliberately NOT forwarded: setStatus() above already
79 : // applied it to the same delegate, and the API's end(spanStatus:) is
80 : // deprecated because the spec's End operation takes only a timestamp.
81 106 : _delegate.end(endTime: endTime);
82 53 : if (OTelLog.isDebug()) {
83 144 : OTelLog.debug('SDKSpan: Delegate.end() completed for span $name');
84 : }
85 :
86 : // Notify span processors that this span has ended. Per the Trace
87 : // SDK spec (Sampling), span processors MUST receive only spans
88 : // with IsRecording == true — a dropped span ends silently.
89 53 : if (!_isRecording) {
90 5 : if (OTelLog.isDebug()) {
91 5 : OTelLog.debug(
92 10 : 'SDKSpan: Span $name is not recording; skipping processor onEnd',
93 : );
94 : }
95 : return;
96 : }
97 106 : final provider = _sdkTracer.provider;
98 53 : if (OTelLog.isDebug()) {
99 48 : OTelLog.debug(
100 144 : 'SDKSpan: Notifying ${provider.spanProcessors.length} span processors',
101 : );
102 : }
103 99 : for (final processor in provider.spanProcessors) {
104 : try {
105 46 : if (OTelLog.isDebug()) {
106 45 : OTelLog.debug(
107 90 : 'SDKSpan: Calling onEnd for processor ${processor.runtimeType}',
108 : );
109 : }
110 46 : processor.onEnd(this);
111 46 : if (OTelLog.isDebug()) {
112 45 : OTelLog.debug(
113 90 : 'SDKSpan: Successfully called onEnd for processor ${processor.runtimeType}',
114 : );
115 : }
116 : } catch (e, stackTrace) {
117 1 : if (OTelLog.isError()) {
118 1 : OTelLog.error(
119 2 : 'SDKSpan: Error calling onEnd for processor ${processor.runtimeType}: $e',
120 : );
121 2 : OTelLog.error('Stack trace: $stackTrace');
122 : }
123 : }
124 : }
125 : } catch (e, stackTrace) {
126 0 : if (OTelLog.isError()) OTelLog.error('SDKSpan: Error during end(): $e');
127 0 : if (OTelLog.isError()) OTelLog.error('Stack trace: $stackTrace');
128 : rethrow;
129 : }
130 : }
131 :
132 : // All mutators below are gated on the sampling decision: when a span is
133 : // not recording, "all this data is discarded right away" and mutation
134 : // attempts are no-ops (Trace API spec, IsRecording). The delegate only
135 : // guards against mutation after end, so the gate lives here.
136 :
137 2 : @override
138 : set attributes(Attributes newAttributes) {
139 2 : if (!_isRecording) return;
140 2 : _delegate.attributes = newAttributes;
141 : }
142 :
143 5 : @override
144 : void addAttributes(Attributes attributes) {
145 5 : if (!_isRecording) return;
146 8 : _delegate.addAttributes(attributes);
147 : }
148 :
149 3 : @override
150 : void addEvent(SpanEvent spanEvent) {
151 3 : if (!_isRecording) return;
152 6 : _delegate.addEvent(spanEvent);
153 : }
154 :
155 7 : @override
156 : void addEventNow(String name, [Attributes? attributes]) {
157 7 : if (!_isRecording) return;
158 12 : _delegate.addEventNow(name, attributes);
159 : }
160 :
161 2 : @override
162 : void addEvents(Map<String, Attributes?> spanEvents) {
163 2 : if (!_isRecording) return;
164 2 : _delegate.addEvents(spanEvents);
165 : }
166 :
167 4 : @override
168 : void addLink(SpanContext spanContext, [Attributes? attributes]) {
169 4 : if (!_isRecording) return;
170 8 : _delegate.addLink(spanContext, attributes);
171 : }
172 :
173 3 : @override
174 : void addSpanLink(SpanLink spanLink) {
175 3 : if (!_isRecording) return;
176 4 : _delegate.addSpanLink(spanLink);
177 : }
178 :
179 50 : @override
180 100 : DateTime? get endTime => _delegate.endTime;
181 :
182 9 : @override
183 18 : bool get isEnded => _delegate.isEnded;
184 :
185 8 : @override
186 24 : bool get isRecording => _isRecording && _delegate.isRecording;
187 :
188 44 : @override
189 88 : SpanKind get kind => _delegate.kind;
190 :
191 54 : @override
192 108 : String get name => _delegate.name;
193 :
194 43 : @override
195 86 : APISpan? get parentSpan => _delegate.parentSpan;
196 :
197 9 : @override
198 : void recordException(
199 : Object exception, {
200 : StackTrace? stackTrace,
201 : Attributes? attributes,
202 : bool? escaped,
203 : }) {
204 9 : if (!_isRecording) return;
205 16 : _delegate.recordException(
206 : exception,
207 : stackTrace: stackTrace,
208 : attributes: attributes,
209 : escaped: escaped,
210 : );
211 : }
212 :
213 4 : @override
214 : void setBoolAttribute(String name, bool value) {
215 4 : if (!_isRecording) return;
216 6 : _delegate.setBoolAttribute(name, value);
217 : }
218 :
219 2 : @override
220 : void setBoolListAttribute(String name, List<bool> value) {
221 2 : if (!_isRecording) return;
222 4 : _delegate.setBoolListAttribute(name, value);
223 : }
224 :
225 4 : @override
226 : void setDoubleAttribute(String name, double value) {
227 4 : if (!_isRecording) return;
228 6 : _delegate.setDoubleAttribute(name, value);
229 : }
230 :
231 2 : @override
232 : void setDoubleListAttribute(String name, List<double> value) {
233 2 : if (!_isRecording) return;
234 4 : _delegate.setDoubleListAttribute(name, value);
235 : }
236 :
237 6 : @override
238 : void setIntAttribute(String name, int value) {
239 6 : if (!_isRecording) return;
240 10 : _delegate.setIntAttribute(name, value);
241 : }
242 :
243 2 : @override
244 : void setIntListAttribute(String name, List<int> value) {
245 2 : if (!_isRecording) return;
246 4 : _delegate.setIntListAttribute(name, value);
247 : }
248 :
249 11 : @override
250 : void setStatus(SpanStatusCode statusCode, [String? description]) {
251 11 : if (!_isRecording) return;
252 20 : _delegate.setStatus(statusCode, description);
253 10 : if (OTelLog.isDebug()) {
254 10 : OTelLog.debug(
255 30 : 'SDKSpan: Set status to $statusCode for span ${spanContext.spanId}',
256 : );
257 : }
258 : }
259 :
260 6 : @override
261 : void setStringAttribute<T>(String name, String value) {
262 6 : if (!_isRecording) return;
263 10 : _delegate.setStringAttribute<T>(name, value);
264 : }
265 :
266 2 : @override
267 : void setStringListAttribute<T>(String name, List<String> value) {
268 2 : if (!_isRecording) return;
269 4 : _delegate.setStringListAttribute<T>(name, value);
270 : }
271 :
272 2 : @override
273 : void setDateTimeAsStringAttribute(String name, DateTime value) {
274 2 : if (!_isRecording) return;
275 4 : _delegate.setDateTimeAsStringAttribute(name, value);
276 : }
277 :
278 57 : @override
279 114 : SpanContext get spanContext => _delegate.spanContext;
280 :
281 47 : @override
282 94 : List<SpanEvent>? get spanEvents => _delegate.spanEvents;
283 :
284 3 : @override
285 6 : SpanId get spanId => _delegate.spanId;
286 :
287 45 : @override
288 90 : List<SpanLink>? get spanLinks => _delegate.spanLinks;
289 :
290 45 : @override
291 90 : DateTime get startTime => _delegate.startTime;
292 :
293 47 : @override
294 94 : SpanStatusCode get status => _delegate.status;
295 :
296 46 : @override
297 92 : String? get statusDescription => _delegate.statusDescription;
298 :
299 3 : @override
300 : void updateName(String name) {
301 3 : if (!_isRecording) return;
302 4 : _delegate.updateName(name);
303 :
304 4 : final provider = _sdkTracer.provider;
305 4 : for (final processor in provider.spanProcessors) {
306 2 : processor.onNameUpdate(this, name);
307 : }
308 : }
309 :
310 43 : @override
311 : InstrumentationScope get instrumentationScope =>
312 86 : _delegate.instrumentationScope;
313 :
314 6 : @override
315 12 : SpanContext? get parentSpanContext => _delegate.parentSpanContext;
316 :
317 36 : @override
318 : String toString() {
319 : final indent = ' ';
320 36 : final buffer = StringBuffer()
321 36 : ..writeln('Span {')
322 108 : ..writeln('$indent name: $name,')
323 108 : ..writeln('$indent spanContext: $spanContext,')
324 108 : ..writeln('$indent kind: $kind,')
325 118 : ..writeln('$indent parentSpan: ${parentSpan?.spanContext ?? "none"},')
326 108 : ..writeln('$indent instrumentationScope: $instrumentationScope,')
327 108 : ..writeln('$indent startTime: $startTime,')
328 108 : ..writeln('$indent endTime: $endTime,')
329 108 : ..writeln('$indent status: $status,')
330 108 : ..writeln('$indent statusDescription: $statusDescription,')
331 108 : ..writeln('$indent attributes: $attributes,');
332 :
333 : // Span Events
334 43 : if (spanEvents?.isNotEmpty ?? false) {
335 14 : buffer.writeln('$indent spanEvents: [');
336 21 : for (final e in spanEvents!) {
337 14 : buffer.writeln('$indent$indent$e,');
338 : }
339 14 : buffer.writeln('$indent ],');
340 : } else {
341 70 : buffer.writeln('$indent spanEvents: [],');
342 : }
343 :
344 : // Span Links
345 41 : if (spanLinks?.isNotEmpty ?? false) {
346 10 : buffer.writeln('$indent spanLinks: [');
347 15 : for (final l in spanLinks!) {
348 10 : buffer.writeln('$indent$indent$l,');
349 : }
350 10 : buffer.writeln('$indent ]');
351 : } else {
352 70 : buffer.writeln('$indent spanLinks: []');
353 : }
354 :
355 36 : buffer.writeln('}');
356 36 : return buffer.toString();
357 : }
358 :
359 : /// Returns whether this span context is valid
360 : /// A span context is valid when it has a non-zero traceId and a non-zero spanId.
361 9 : @override
362 18 : bool get isValid => spanContext.isValid;
363 :
364 46 : @visibleForTesting
365 : @override
366 : // ignore: invalid_use_of_visible_for_testing_member
367 92 : Attributes get attributes => _delegate.attributes;
368 :
369 : // This check is always true because the method is part of the interface implementation
370 : // and the delegate is already an APISpan.
371 : /// Checks if this object is an instance of the specified type.
372 : ///
373 : /// This method is used for type checking and compatibility with the API Span implementation.
374 : /// It returns true if the specified type is APISpan or the exact runtime type of this object.
375 : ///
376 : /// @param type The type to check against
377 : /// @return true if this object is an instance of the specified type, false otherwise
378 4 : bool isInstanceOf(Type type) => type == APISpan || runtimeType == type;
379 : }
|