Line data Source code
1 : // Licensed under the Apache License, Version 2.0
2 : // Copyright 2025, Michael Bushe, All rights reserved.
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](https://opentelemetry.io/docs/specs/semconv/general/events/),
25 : /// span events are being deprecated and will be replaced by the Logging API in future versions.
26 : ///
27 : /// More information:
28 : /// https://opentelemetry.io/docs/specs/otel/trace/sdk/
29 : class Span implements APISpan {
30 : final APISpan _delegate;
31 : final Tracer _sdkTracer;
32 :
33 : /// Private constructor for creating Span instances.
34 : ///
35 : /// @param delegate The API Span implementation to delegate to
36 : /// @param sdkTracer The SDK Tracer that created this Span
37 28 : Span._(APISpan delegate, Tracer sdkTracer)
38 : : _delegate = delegate,
39 : _sdkTracer = sdkTracer {
40 28 : if (OTelLog.isDebug()) {
41 84 : OTelLog.debug('SDKSpan: Created new span with name ${delegate.name}');
42 : }
43 : }
44 :
45 : /// Gets the resource associated with this span's tracer.
46 : ///
47 : /// @return The resource associated with this span
48 57 : Resource? get resource => _sdkTracer.resource;
49 :
50 22 : @override
51 : void end({DateTime? endTime, SpanStatusCode? spanStatus}) {
52 22 : if (OTelLog.isDebug()) {
53 22 : OTelLog.debug(
54 88 : 'SDKSpan: Starting to end span ${spanContext.spanId} with name $name');
55 : }
56 :
57 : if (spanStatus != null) {
58 0 : setStatus(spanStatus);
59 : }
60 :
61 : try {
62 22 : if (OTelLog.isDebug()) {
63 66 : OTelLog.debug('SDKSpan: Calling delegate.end() for span $name');
64 : }
65 44 : _delegate.end(endTime: endTime, spanStatus: spanStatus);
66 22 : if (OTelLog.isDebug()) {
67 66 : OTelLog.debug('SDKSpan: Delegate.end() completed for span $name');
68 : }
69 :
70 : // Notify span processors that this span has ended
71 44 : final provider = _sdkTracer.provider;
72 22 : if (OTelLog.isDebug()) {
73 22 : OTelLog.debug(
74 66 : 'SDKSpan: Notifying ${provider.spanProcessors.length} span processors');
75 : }
76 42 : for (final processor in provider.spanProcessors) {
77 : try {
78 20 : if (OTelLog.isDebug()) {
79 20 : OTelLog.debug(
80 40 : 'SDKSpan: Calling onEnd for processor ${processor.runtimeType}');
81 : }
82 20 : processor.onEnd(this);
83 20 : if (OTelLog.isDebug()) {
84 20 : OTelLog.debug(
85 40 : 'SDKSpan: Successfully called onEnd for processor ${processor.runtimeType}');
86 : }
87 : } catch (e, stackTrace) {
88 0 : if (OTelLog.isError()) {
89 0 : OTelLog.error(
90 0 : 'SDKSpan: Error calling onEnd for processor ${processor.runtimeType}: $e');
91 0 : OTelLog.error('Stack trace: $stackTrace');
92 : }
93 : }
94 : }
95 : } catch (e, stackTrace) {
96 0 : if (OTelLog.isError()) OTelLog.error('SDKSpan: Error during end(): $e');
97 0 : if (OTelLog.isError()) OTelLog.error('Stack trace: $stackTrace');
98 : rethrow;
99 : }
100 : }
101 :
102 0 : @override
103 : set attributes(Attributes newAttributes) =>
104 0 : _delegate.attributes = newAttributes;
105 :
106 0 : @override
107 : void addAttributes(Attributes attributes) =>
108 0 : _delegate.addAttributes(attributes);
109 :
110 2 : @override
111 4 : void addEvent(SpanEvent spanEvent) => _delegate.addEvent(spanEvent);
112 :
113 3 : @override
114 : void addEventNow(String name, [Attributes? attributes]) =>
115 6 : _delegate.addEventNow(name, attributes);
116 :
117 0 : @override
118 : void addEvents(Map<String, Attributes?> spanEvents) =>
119 0 : _delegate.addEvents(spanEvents);
120 :
121 2 : @override
122 : void addLink(SpanContext spanContext, [Attributes? attributes]) =>
123 4 : _delegate.addLink(spanContext, attributes);
124 :
125 1 : @override
126 2 : void addSpanLink(SpanLink spanLink) => _delegate.addSpanLink(spanLink);
127 :
128 25 : @override
129 50 : DateTime? get endTime => _delegate.endTime;
130 :
131 7 : @override
132 14 : bool get isEnded => _delegate.isEnded;
133 :
134 5 : @override
135 10 : bool get isRecording => _delegate.isRecording;
136 :
137 21 : @override
138 42 : SpanKind get kind => _delegate.kind;
139 :
140 26 : @override
141 52 : String get name => _delegate.name;
142 :
143 20 : @override
144 40 : APISpan? get parentSpan => _delegate.parentSpan;
145 :
146 5 : @override
147 : void recordException(Object exception,
148 : {StackTrace? stackTrace, Attributes? attributes, bool? escaped}) =>
149 10 : _delegate.recordException(exception,
150 : stackTrace: stackTrace, attributes: attributes, escaped: escaped);
151 :
152 2 : @override
153 : void setBoolAttribute(String name, bool value) =>
154 4 : _delegate.setBoolAttribute(name, value);
155 :
156 0 : @override
157 : void setBoolListAttribute(String name, List<bool> value) =>
158 0 : _delegate.setBoolListAttribute(name, value);
159 :
160 2 : @override
161 : void setDoubleAttribute(String name, double value) =>
162 4 : _delegate.setDoubleAttribute(name, value);
163 :
164 0 : @override
165 : void setDoubleListAttribute(String name, List<double> value) =>
166 0 : _delegate.setDoubleListAttribute(name, value);
167 :
168 4 : @override
169 : void setIntAttribute(String name, int value) =>
170 8 : _delegate.setIntAttribute(name, value);
171 :
172 0 : @override
173 : void setIntListAttribute(String name, List<int> value) =>
174 0 : _delegate.setIntListAttribute(name, value);
175 :
176 6 : @override
177 : void setStatus(SpanStatusCode statusCode, [String? description]) {
178 12 : _delegate.setStatus(statusCode, description);
179 6 : if (OTelLog.isDebug()) {
180 6 : OTelLog.debug(
181 18 : 'SDKSpan: Set status to $statusCode for span ${spanContext.spanId}');
182 : }
183 : }
184 :
185 4 : @override
186 : void setStringAttribute<T>(String name, String value) =>
187 8 : _delegate.setStringAttribute<T>(name, value);
188 :
189 0 : @override
190 : void setStringListAttribute<T>(String name, List<String> value) =>
191 0 : _delegate.setStringListAttribute<T>(name, value);
192 :
193 0 : @override
194 : void setDateTimeAsStringAttribute(String name, DateTime value) =>
195 0 : _delegate.setDateTimeAsStringAttribute(name, value);
196 :
197 28 : @override
198 56 : SpanContext get spanContext => _delegate.spanContext;
199 :
200 22 : @override
201 44 : List<SpanEvent>? get spanEvents => _delegate.spanEvents;
202 :
203 2 : @override
204 4 : SpanId get spanId => _delegate.spanId;
205 :
206 22 : @override
207 44 : List<SpanLink>? get spanLinks => _delegate.spanLinks;
208 :
209 22 : @override
210 44 : DateTime get startTime => _delegate.startTime;
211 :
212 23 : @override
213 46 : SpanStatusCode get status => _delegate.status;
214 :
215 22 : @override
216 44 : String? get statusDescription => _delegate.statusDescription;
217 :
218 1 : @override
219 : void updateName(String name) {
220 2 : _delegate.updateName(name);
221 :
222 2 : final provider = _sdkTracer.provider;
223 2 : for (final processor in provider.spanProcessors) {
224 1 : processor.onNameUpdate(this, name);
225 : }
226 : }
227 :
228 20 : @override
229 : InstrumentationScope get instrumentationScope =>
230 40 : _delegate.instrumentationScope;
231 :
232 4 : @override
233 8 : SpanContext? get parentSpanContext => _delegate.parentSpanContext;
234 :
235 16 : @override
236 : String toString() {
237 : final indent = ' ';
238 16 : final buffer = StringBuffer()
239 16 : ..writeln('Span {')
240 48 : ..writeln('$indent name: $name,')
241 48 : ..writeln('$indent spanContext: $spanContext,')
242 48 : ..writeln('$indent kind: $kind,')
243 58 : ..writeln('$indent parentSpan: ${parentSpan?.spanContext ?? "none"},')
244 48 : ..writeln('$indent instrumentationScope: $instrumentationScope,')
245 48 : ..writeln('$indent startTime: $startTime,')
246 48 : ..writeln('$indent endTime: $endTime,')
247 48 : ..writeln('$indent status: $status,')
248 48 : ..writeln('$indent statusDescription: $statusDescription,')
249 48 : ..writeln('$indent attributes: $attributes,');
250 :
251 : // Span Events
252 19 : if (spanEvents?.isNotEmpty ?? false) {
253 6 : buffer.writeln('$indent spanEvents: [');
254 9 : for (final e in spanEvents!) {
255 6 : buffer.writeln('$indent$indent$e,');
256 : }
257 6 : buffer.writeln('$indent ],');
258 : } else {
259 32 : buffer.writeln('$indent spanEvents: [],');
260 : }
261 :
262 : // Span Links
263 17 : if (spanLinks?.isNotEmpty ?? false) {
264 2 : buffer.writeln('$indent spanLinks: [');
265 3 : for (final l in spanLinks!) {
266 2 : buffer.writeln('$indent$indent$l,');
267 : }
268 2 : buffer.writeln('$indent ]');
269 : } else {
270 32 : buffer.writeln('$indent spanLinks: []');
271 : }
272 :
273 16 : buffer.writeln('}');
274 16 : return buffer.toString();
275 : }
276 :
277 : /// Returns whether this span context is valid
278 : /// A span context is valid when it has a non-zero traceId and a non-zero spanId.
279 3 : @override
280 6 : bool get isValid => spanContext.isValid;
281 :
282 24 : @visibleForTesting
283 : @override
284 : // ignore: invalid_use_of_visible_for_testing_member
285 48 : Attributes get attributes => _delegate.attributes;
286 :
287 : // This check is always true because the method is part of the interface implementation
288 : // and the delegate is already an APISpan.
289 : /// Checks if this object is an instance of the specified type.
290 : ///
291 : /// This method is used for type checking and compatibility with the API Span implementation.
292 : /// It returns true if the specified type is APISpan or the exact runtime type of this object.
293 : ///
294 : /// @param type The type to check against
295 : /// @return true if this object is an instance of the specified type, false otherwise
296 0 : bool isInstanceOf(Type type) => type == APISpan || runtimeType == type;
297 : }
|