Version: 1.1.0This documentation is for Spitfire 1.1.0.
Connecting a Flutter app with OTel
Flutter (Android, iOS, web, desktop): HTTP calls as traces, linked to your backend with traceparent. Values: Connecting services with OTel: overview.
Version table
| Flutter | Dart | Package | Version |
|---|---|---|---|
| 3.22 and later | 3.4 and later | dartastic_opentelemetry |
0.12.0 |
| 3.22 and later | 3.4 and later | http |
1.6.0 |
| 3.19 and earlier | 3.3 and earlier | not supported | upgrade to Flutter 3.22 |
Find your version: flutter --version
OpenTelemetry has no official Dart SDK. dartastic_opentelemetry is a community package (Apache-2.0, being donated to the CNCF); pin its version. Traces only.
Verified: 2026-11-04 (code checked with Flutter 3.47.5, Dart 3.13.4). Source: pub.dev dartastic_opentelemetry, source and README, OTel blog: Dart and Flutter, pub.dev http, Flutter --dart-define-from-file.
What you need
- Spitfire address
https://<spitfire>(HTTPS: Android and iOS refuse plain HTTP) - Service name
<service-name>, version<service-version>, environment<environment> - The app folder (here
/srv/app, wherepubspec.yamlis)
Flutter
Create the key: Observability → Browser and app key; Allowed origins: the web build's address, empty for Android and iOS only; copy
sfrum_…(Observability).Add the packages:
cd /srv/app && flutter pub add 'dartastic_opentelemetry:0.12.0' 'http:1.6.0'- Create
/srv/app/lib/otel.dartand paste:
// IS_OTEL_ENABLED=true: OTel on; any other value: off.
import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
import 'package:http/http.dart' as http;
const otelOn = String.fromEnvironment('IS_OTEL_ENABLED') == 'true';
Future<void> initOtel() async {
if (!otelOn) return;
await OTel.initialize(
serviceName: const String.fromEnvironment('OTEL_SERVICE_NAME'),
serviceVersion: const String.fromEnvironment('OTEL_SERVICE_VERSION'),
resourceAttributes: OTel.attributesFromMap({'deployment.environment': const String.fromEnvironment('OTEL_ENVIRONMENT')}),
spanKind: SpanKind.client,
sampler: ParentBasedSampler(TraceIdRatioSampler(double.parse(const String.fromEnvironment('OTEL_TRACES_SAMPLER_ARG', defaultValue: '0.1')))),
spanProcessor: BatchSpanProcessor(OtlpHttpSpanExporter(OtlpHttpExporterConfig(
endpoint: const String.fromEnvironment('OTEL_TRACES_ENDPOINT'),
headers: {'authorization': 'Bearer ${const String.fromEnvironment('OTEL_KEY')}'},
))),
detectPlatformResources: false,
enableMetrics: false,
enableLogs: false,
);
}
/// An http.Client whose requests are client spans carrying traceparent.
class OtelHttpClient extends http.BaseClient {
final http.Client _inner = http.Client();
final _propagator = W3CTraceContextPropagator();
@override
Future<http.StreamedResponse> send(http.BaseRequest request) async {
if (!otelOn) return _inner.send(request);
final span = OTel.tracer().startSpan('HTTP ${request.method}', kind: SpanKind.client,
attributes: OTel.attributesFromMap({'http.request.method': request.method, 'url.full': request.url.toString()}));
try {
return await OTel.withSpanAsync(span, () async {
_propagator.inject(Context.current, request.headers, _HeaderSetter(request.headers));
final res = await _inner.send(request);
span.addAttributes(OTel.attributesFromMap({'http.response.status_code': res.statusCode}));
return res;
});
} catch (e, st) {
span.recordException(e, stackTrace: st);
span.setStatus(SpanStatusCode.Error, e.toString());
rethrow;
} finally {
span.end();
}
}
}
class _HeaderSetter implements TextMapSetter<String> {
_HeaderSetter(this._headers);
final Map<String, String> _headers;
@override
void set(String key, String value) => _headers[key] = value;
}- In
/srv/app/lib/main.dart, makemainasync and start it first:
import 'otel.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await initOtel();
runApp(const MyApp());
}- Send your API calls with the traced client:
final api = OtelHttpClient();
final res = await api.get(Uri.parse('https://api.example.com/orders'));- Create
/srv/app/otel.jsonand paste:
{
"IS_OTEL_ENABLED": "true",
"OTEL_SERVICE_NAME": "<service-name>",
"OTEL_SERVICE_VERSION": "<service-version>",
"OTEL_ENVIRONMENT": "<environment>",
"OTEL_TRACES_ENDPOINT": "https://<spitfire>/otlp/v1/traces",
"OTEL_KEY": "sfrum_…",
"OTEL_TRACES_SAMPLER_ARG": "0.1"
}- Build with it (iOS:
ipa; web:web):
cd /srv/app && flutter build apk --release --dart-define-from-file=otel.jsonVerify
- Spitfire takes the key from an app (no
Origin):
curl -s -o /dev/null -w '%{http_code}' -X POST https://<spitfire>/otlp/v1/traces -H 'Authorization: Bearer sfrum_…' -H 'Content-Type: application/json' -d '{}' | grep -q '^200