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