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