Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
5 :
6 : import '../span.dart';
7 : import '../span_processor.dart';
8 : import 'span_exporter.dart';
9 :
10 : /// A simple SpanProcessor that exports spans synchronously when they end.
11 : ///
12 : /// This processor should only be used for testing or debugging purposes as it
13 : /// blocks until the export is complete.
14 : class SimpleSpanProcessor implements SpanProcessor {
15 : final SpanExporter _spanExporter;
16 : bool _isShutdown = false;
17 : final List<Future<void>> _pendingExports = [];
18 :
19 : /// Creates a new SimpleSpanProcessor that exports spans using the given [SpanExporter].
20 20 : SimpleSpanProcessor(this._spanExporter);
21 :
22 19 : @override
23 : Future<void> onStart(Span span, Context? parentContext) async {
24 19 : if (OTelLog.isDebug()) {
25 19 : OTelLog.debug(
26 95 : 'SimpleSpanProcessor: onStart called for span ${span.spanContext.spanId}, traceId: ${span.spanContext.traceId}',
27 : );
28 : }
29 : }
30 :
31 19 : @override
32 : Future<void> onEnd(Span span) async {
33 19 : if (OTelLog.isDebug()) {
34 19 : OTelLog.debug(
35 76 : 'SimpleSpanProcessor: onEnd called for span ${span.name} with ID ${span.spanContext.spanId}',
36 : );
37 : }
38 19 : if (_isShutdown) {
39 5 : if (OTelLog.isDebug()) {
40 5 : OTelLog.debug(
41 : 'SimpleSpanProcessor: Skipping export - processor is shutdown',
42 : );
43 : }
44 5 : print('SimpleSpanProcessor: Skipping export - processor is shutdown');
45 : return;
46 : }
47 :
48 : // Per the Trace SDK spec (Sampling), span exporters MUST receive
49 : // spans with the Sampled flag set and SHOULD NOT receive the ones
50 : // that do not (e.g. RECORD_ONLY spans record but are not exported).
51 57 : if (!span.spanContext.traceFlags.isSampled) {
52 1 : if (OTelLog.isDebug()) {
53 1 : OTelLog.debug(
54 3 : 'SimpleSpanProcessor: Skipping export - span ${span.spanContext.spanId} is not sampled',
55 : );
56 : }
57 : return;
58 : }
59 :
60 : // Verify the span has a valid end time
61 19 : if (span.endTime == null) {
62 1 : if (OTelLog.isWarn()) {
63 1 : OTelLog.warn(
64 4 : 'SimpleSpanProcessor: Span ${span.name} with ID ${span.spanContext.spanId} has no end time, which suggests it may not be properly ended',
65 : );
66 : }
67 : // Continue with export anyway
68 : }
69 :
70 19 : if (OTelLog.isDebug()) {
71 19 : OTelLog.debug(
72 76 : 'SimpleSpanProcessor: Exporting span ${span.spanContext.spanId} with name ${span.name}',
73 : );
74 : }
75 :
76 : try {
77 : // Create a copy of the span list to avoid concurrent modification issues
78 19 : final spanToExport = [span];
79 19 : if (OTelLog.isDebug()) {
80 19 : OTelLog.debug('SimpleSpanProcessor: Created list of spans to export');
81 : }
82 :
83 38 : final pendingExport = _spanExporter.export(spanToExport);
84 38 : _pendingExports.add(pendingExport);
85 19 : if (OTelLog.isDebug()) {
86 19 : OTelLog.debug(
87 : 'SimpleSpanProcessor: Added export to pending exports list',
88 : );
89 : }
90 :
91 : // Directly await the export for better reliability in tests
92 : try {
93 19 : if (OTelLog.isDebug()) {
94 19 : OTelLog.debug(
95 38 : 'SimpleSpanProcessor: Awaiting export completion for span ${span.name}',
96 : );
97 : }
98 : await pendingExport;
99 19 : if (OTelLog.isDebug()) {
100 19 : OTelLog.debug(
101 76 : 'SimpleSpanProcessor: Successfully exported span ${span.name} with ID ${span.spanContext.spanId}',
102 : );
103 : }
104 : } catch (e, stackTrace) {
105 4 : if (OTelLog.isError()) {
106 4 : OTelLog.error(
107 12 : 'SimpleSpanProcessor: Export error while processing span ${span.spanContext.spanId}: $e',
108 : );
109 8 : OTelLog.error('Stack trace: $stackTrace');
110 : }
111 : } finally {
112 38 : _pendingExports.remove(pendingExport);
113 19 : if (OTelLog.isDebug()) {
114 19 : OTelLog.debug(
115 : 'SimpleSpanProcessor: Removed export from pending list',
116 : );
117 : }
118 : }
119 : } catch (e, stackTrace) {
120 1 : if (OTelLog.isError()) {
121 1 : OTelLog.error(
122 3 : 'SimpleSpanProcessor: Failed to start export for span ${span.spanContext.spanId}: $e',
123 : );
124 2 : OTelLog.error('Stack trace: $stackTrace');
125 : }
126 : }
127 : }
128 :
129 3 : @override
130 : Future<void> onNameUpdate(Span span, String newName) async {
131 : // Simple processor doesn't need to do anything for name updates
132 : // since it only processes spans when they end
133 3 : if (OTelLog.isDebug()) {
134 3 : OTelLog.debug(
135 9 : 'SimpleSpanProcessor: Name updated for span ${span.spanContext.spanId} to $newName',
136 : );
137 : }
138 : }
139 :
140 19 : @override
141 : Future<void> shutdown() async {
142 19 : if (_isShutdown) {
143 11 : if (OTelLog.isDebug()) {
144 11 : OTelLog.debug('SimpleSpanProcessor: Already shut down');
145 : }
146 : return;
147 : }
148 :
149 19 : if (OTelLog.isDebug()) {
150 19 : OTelLog.debug(
151 57 : 'SimpleSpanProcessor: Shutting down - waiting for ${_pendingExports.length} pending exports',
152 : );
153 : }
154 19 : _isShutdown = true;
155 :
156 : try {
157 38 : if (_pendingExports.isNotEmpty) {
158 1 : if (OTelLog.isDebug()) {
159 1 : OTelLog.debug(
160 3 : 'SimpleSpanProcessor: Waiting for ${_pendingExports.length} pending exports to complete',
161 : );
162 : }
163 : try {
164 2 : await Future.wait(_pendingExports);
165 1 : if (OTelLog.isDebug()) {
166 1 : OTelLog.debug('SimpleSpanProcessor: All pending exports completed');
167 : }
168 : } catch (e) {
169 0 : if (OTelLog.isError()) {
170 0 : OTelLog.error(
171 0 : 'SimpleSpanProcessor: Error waiting for pending exports: $e',
172 : );
173 : }
174 : }
175 : }
176 :
177 : try {
178 19 : if (OTelLog.isDebug()) {
179 19 : OTelLog.debug('SimpleSpanProcessor: Shutting down exporter');
180 : }
181 38 : await _spanExporter.shutdown();
182 19 : if (OTelLog.isDebug()) {
183 19 : OTelLog.debug('SimpleSpanProcessor: Exporter shutdown complete');
184 : }
185 : } catch (e) {
186 2 : if (OTelLog.isError()) {
187 2 : OTelLog.error(
188 2 : 'SimpleSpanProcessor: Error shutting down exporter: $e',
189 : );
190 : }
191 : }
192 :
193 19 : if (OTelLog.isDebug()) {
194 19 : OTelLog.debug('SimpleSpanProcessor: Shutdown complete');
195 : }
196 : } catch (e, stackTrace) {
197 0 : if (OTelLog.isError()) {
198 0 : OTelLog.error('SimpleSpanProcessor: Error during shutdown: $e');
199 0 : OTelLog.error('Stack trace: $stackTrace');
200 : }
201 : }
202 : }
203 :
204 18 : @override
205 : Future<void> forceFlush() async {
206 18 : if (_isShutdown) {
207 6 : if (OTelLog.isDebug()) {
208 6 : OTelLog.debug(
209 : 'SimpleSpanProcessor: Cannot force flush - processor is shut down',
210 : );
211 : }
212 : return;
213 : }
214 :
215 17 : if (OTelLog.isDebug()) {
216 17 : OTelLog.debug(
217 51 : 'SimpleSpanProcessor: Force flushing - waiting for ${_pendingExports.length} pending exports',
218 : );
219 : }
220 :
221 : try {
222 34 : if (_pendingExports.isEmpty) {
223 14 : if (OTelLog.isDebug()) {
224 14 : OTelLog.debug('SimpleSpanProcessor: No pending exports to flush');
225 : }
226 : // If there are no pending exports, just force flush the exporter
227 28 : await _spanExporter.forceFlush();
228 : } else {
229 11 : if (OTelLog.isDebug()) {
230 11 : OTelLog.debug(
231 33 : 'SimpleSpanProcessor: Waiting for ${_pendingExports.length} pending exports',
232 : );
233 : }
234 22 : await Future.wait(_pendingExports);
235 11 : if (OTelLog.isDebug()) {
236 11 : OTelLog.debug('SimpleSpanProcessor: All pending exports completed');
237 : }
238 :
239 : // Also force flush the exporter
240 11 : if (OTelLog.isDebug()) {
241 11 : OTelLog.debug('SimpleSpanProcessor: Force flushing exporter');
242 : }
243 22 : await _spanExporter.forceFlush();
244 : }
245 :
246 17 : if (OTelLog.isDebug()) {
247 17 : OTelLog.debug('SimpleSpanProcessor: Force flush complete');
248 : }
249 : } catch (e, stackTrace) {
250 2 : if (OTelLog.isError()) {
251 4 : OTelLog.error('SimpleSpanProcessor: Error during force flush: $e');
252 4 : OTelLog.error('Stack trace: $stackTrace');
253 : }
254 : }
255 : }
256 : }
|