purple_otel_dio 0.1.0 copy "purple_otel_dio: ^0.1.0" to clipboard
purple_otel_dio: ^0.1.0 copied to clipboard

OpenTelemetry auto-instrumentation for Dio HTTP client — automatic CLIENT spans with W3C trace context.

purple_otel_dio #

Pub Version License: AGPL-3.0 Dart Tests

Zero-code distributed tracing for every Dio HTTP request.

Add one line to your Dio instance and every outbound request becomes a fully traced OpenTelemetry span — complete with W3C trace context propagation, semantic HTTP attributes, error recording, and automatic span lifecycle management. No manual span creation. No boilerplate. Just plug it in and your HTTP traces appear in your backend.


Features #

  • Automatic CLIENT spans — every request creates an OTel span named GET api.example.com/users, tagged with SpanKind.client, via Dio's interceptor chain
  • W3C traceparent injection — trace context propagates automatically in outgoing request headers so downstream services link correctly in distributed traces
  • HTTP semantic attributeshttp.method, http.url, http.host, and http.status_code are set on every span, conforming to the OpenTelemetry HTTP semantic conventions
  • Error recordingDioException failures capture the exception message, type, stack trace (span.recordException), and mark the span as errored
  • Status mapping — 4xx and 5xx responses automatically set the span to error status; 2xx responses mark it ok
  • Extension method.addOtelInterceptor(tracer) on any Dio instance — no subclassing, no mixins, no DI tricks
  • Powered by purple_otel_sdk — leverages the full OpenTelemetry SDK (sampling, batch processing, OTLP export, context propagation) under the hood

Installation #

Add to your pubspec.yaml:

dependencies:
  purple_otel_dio: ^0.1.0
  purple_otel_sdk: ^0.1.0   # required peer — provides Tracer, Span, exporters
  dio: ^5.7.0                 # the HTTP client being instrumented

Then run:

dart pub get

Quick Start #

Below is a complete, runnable example that wires up a Dio client with tracing and exports spans to an OTLP collector (e.g. Jaeger, Grafana Tempo, Azure Monitor).

import 'package:dio/dio.dart';
import 'package:purple_otel_sdk/purple_otel_sdk.dart';
import 'package:purple_otel_dio/purple_otel_dio.dart';

Future<void> main() async {
  // 1. Create the OTel tracer provider with an OTLP exporter
  final tracerProvider = SDKTracerProvider(
    resource: Resource(
      attributes: [
        Attribute('service.name', AttributeValue.string('my-flutter-app')),
        Attribute('service.version', AttributeValue.string('1.0.0')),
      ],
    ),
    processors: [
      BatchSpanProcessor(
        OtlpHttpSpanExporter(
          endpoint: Uri.parse('http://localhost:4318/v1/traces'),
        ),
      ),
    ],
  );

  // 2. Get a named tracer
  final tracer = tracerProvider.get('http-client');

  // 3. Create Dio and add the interceptor — one line
  final dio = Dio(BaseOptions(baseUrl: 'https://api.example.com'))
    ..addOtelInterceptor(tracer);

  // 4. Every request automatically produces a traced span
  final users = await dio.get('/users');
  final posts = await dio.get('/posts');
  final result = await dio.post('/search', data: {'query': 'Flutter'});

  print('Status: ${users.statusCode}');

  // 5. Shut down to flush pending spans
  await tracerProvider.shutdown();
}

What happens at runtime:

  1. dio.get('/users') fires → interceptor creates a span named GET api.example.com/users
  2. A traceparent header is injected into the request (e.g. 00-<trace-id>-<span-id>-01)
  3. The response arrives → http.status_code=200 is recorded, span marked OK, span ended
  4. All three spans are exported as a batch to localhost:4318/v1/traces

Before / After #

Without purple_otel_dio (~25 lines per endpoint) #

Future<Response> getUsers(Dio dio, Tracer tracer) async {
  final span = tracer.startSpan('GET api.example.com/users',
      kind: SpanKind.client);
  span.setAttribute('http.method', AttributeValue.string('GET'));
  span.setAttribute('http.url',
      AttributeValue.string('https://api.example.com/users'));

  final carrier = <String, String>{};
  W3CTraceContextPropagator.inject(
      Context.root.withValue(spanContextKey, span), carrier);

  try {
    final response = await dio.get('/users',
        options: Options(headers: carrier));
    span.setAttribute('http.status_code',
        AttributeValue.int(response.statusCode ?? 0));
    span.setStatus(response.statusCode == 200
        ? SpanStatus.ok
        : SpanStatus.error('HTTP ${response.statusCode}'));
    return response;
  } on DioException catch (e) {
    span.recordException(e, stackTrace: e.stackTrace);
    span.setStatus(SpanStatus.error('${e.type.name}: ${e.message}'));
    rethrow;
  } finally {
    span.end();
  }
}

With purple_otel_dio (1 line, once) #

final dio = Dio()..addOtelInterceptor(tracer);

// That's it. All get/post/put/delete requests are traced.
final users = await dio.get('/users');
final posts = await dio.get('/posts');
final result = await dio.post('/search', data: {'query': 'Flutter'});

Savings: ~25 lines of manual instrumentation per endpoint → 1 line of setup total. For an app with 10 API calls, that's ~250 lines eliminated.


Interceptor Lifecycle #

Dio Hook OTel Action Details
onRequest Create span Starts a new CLIENT span named {METHOD} {host}{path}, sets http.method, http.url, http.host attributes
onRequest Inject traceparent Uses W3CTraceContextPropagator.inject() to add traceparent (and optionally tracestate) to the request headers
onRequest Store span Saves the span reference in options.extra['otel_span'] for retrieval in onResponse / onError
onResponse Record status Sets http.status_code attribute from the response
onResponse Map status statusCode >= 400 → span error; otherwise → span OK
onResponse End span Calls span.end(), closing the span timing
onError Record exception Calls span.recordException(err, stackTrace: err.stackTrace)
onError Set error Marks the span status as error with {type}: {message}
onError End span Calls span.end() so the error span is exported

How It Works #

                     ┌─────────────────────────────────┐
                     │          Your Flutter App        │
                     │                                 │
                     │   dio.get('/users')              │
                     │         │                       │
                     │         ▼                       │
                     │  ┌──────────────────────────┐   │
                     │  │   OtelDioInterceptor      │   │
                     │  │                           │   │
                     │  │  onRequest:               │   │
                     │  │   1. tracer.startSpan()   │   │
                     │  │   2. set http.* attributes│   │
                     │  │   3. inject traceparent   │──────── traceparent: 00-abc123-def456-01
                     │  │   4. store span in extra  │   │
                     │  │                           │   │
                     │  │  onResponse:              │   │
                     │  │   5. set http.status_code │   │
                     │  │   6. set span status      │   │
                     │  │   7. span.end()           │   │
                     │  │                           │   │
                     │  │  onError:                 │   │
                     │  │   8. recordException()    │   │
                     │  │   9. set span status      │   │
                     │  │  10. span.end()           │   │
                     │  └──────────┬───────────────┘   │
                     │             │                   │
                     └─────────────┼───────────────────┘
                                   │
                     ┌─────────────▼───────────────────┐
                     │     SpanProcessor Pipeline      │
                     │                                 │
                     │  SimpleSpanProcessor /          │
                     │  BatchSpanProcessor             │
                     │         │                       │
                     │         ▼                       │
                     │  SpanExporter (OTLP / Console)  │
                     └─────────────┬───────────────────┘
                                   │
                                   ▼
                     ┌─────────────────────────────────┐
                     │  OTLP Collector / Backend       │
                     │  (Jaeger, Grafana Tempo, etc.)  │
                     └─────────────────────────────────┘
  1. Your code calls dio.get(...) as usual
  2. Dio invokes onRequest — the interceptor creates an OTel span, attaches HTTP attributes, and injects the traceparent header so the receiving service can continue the trace
  3. Dio sends the HTTP request (now with distributed tracing headers)
  4. When the response arrives, onResponse records the status code, maps the span status, and ends the span
  5. If the request fails, onError records the exception with stack trace and ends the span as errored
  6. The finished span flows through the configured SpanProcessor pipeline and is exported to your observability backend

Real-World Example #

A typical Flutter app with multiple API endpoints — all traced with zero ceremony:

import 'package:dio/dio.dart';
import 'package:purple_otel_sdk/purple_otel_sdk.dart';
import 'package:purple_otel_dio/purple_otel_dio.dart';

class ApiClient {
  late final Dio _dio;

  ApiClient({required Tracer tracer}) {
    _dio = Dio(BaseOptions(baseUrl: 'https://jsonplaceholder.typicode.com'))
      ..addOtelInterceptor(tracer);
  }

  Future<Response> getUsers() => _dio.get('/users');
  Future<Response> getUser(int id) => _dio.get('/users/$id');
  Future<Response> createPost(Map<String, dynamic> data) =>
      _dio.post('/posts', data: data);
  Future<Response> updatePost(int id, Map<String, dynamic> data) =>
      _dio.put('/posts/$id', data: data);
  Future<Response> deletePost(int id) => _dio.delete('/posts/$id');
}

Future<void> main() async {
  final tracerProvider = SDKTracerProvider(
    resource: Resource.empty,
    processors: [SimpleSpanProcessor(ConsoleSpanExporter())],
  );
  final tracer = tracerProvider.get('api-client');

  final api = ApiClient(tracer: tracer);

  // Each of these creates a distinct span with full HTTP attributes:
  //
  //   GET  jsonplaceholder.typicode.com/users        (200 OK)
  //   GET  jsonplaceholder.typicode.com/users/1       (200 OK)
  //   POST jsonplaceholder.typicode.com/posts         (201 Created)
  //   PUT  jsonplaceholder.typicode.com/posts/1       (200 OK)
  //   DELETE jsonplaceholder.typicode.com/posts/1     (200 OK)

  await api.getUsers();
  await api.getUser(1);
  await api.createPost({'title': 'Hello', 'body': 'World', 'userId': 1});
  await api.updatePost(1, {'title': 'Updated'});
  await api.deletePost(1);

  await tracerProvider.shutdown();
}

Every endpoint call above generates a properly-named span with the correct HTTP method, full URL, status code, and W3C trace context — all without a single line of manual instrumentation in ApiClient.


Companion Packages #

purple_otel_dio is part of the Purple OTel ecosystem:

Package Description
purple_otel_api OpenTelemetry API contracts — Tracer, Span, Context, Attribute, etc.
purple_otel_sdk OpenTelemetry SDK implementation — SDKTracerProvider, exporters, processors, samplers
purple_otel_dio This package — auto-instrumentation for Dio HTTP client
purple_otel_flutter Flutter-specific instrumentation — navigation observers, widget lifecycle spans
purple_logger Structured logging framework
purple_logger_otel Bridge: send purple_logger records as OTel log records
purple_logger_otel_sdk OTel SDK log exporter integration for purple_logger







Built by PurpleSoft #

PurpleSoft S.r.l. — software house with offices in Monza, Milano, and Lugano (Switzerland). Since 2017.

We build what doesn't exist yet.

The sectors we dominate #

Database & Storage Engines. We design, implement, and ship production-grade databases. Our LSM-tree storage engine — built in Rust for performance, exposed via FFI to Dart/Flutter, compiled to WASM for web — uses WiscKey-style key-value separation with concurrent compaction and crash recovery. It runs on all 6 platforms. This is not a wrapper around SQLite. It is a ground-up embedded NoSQL database written from scratch.

Conversational AI & Voice Assistants. We ship end-to-end AI voice platforms — from the physical device (ESP32 with custom Opus codec firmware) to the cloud backend (.NET 10, ASP.NET Core, Blazor Server) to the mobile companion app (Flutter with BLE). Our multi-agent LLM architecture orchestrates specialized agents (conversation, memory, content enrichment, research) with a multi-layered memory system spanning graph, episodic, and working memory — including adaptive forgetting, poison detection via statistical outlier analysis, and automatic episodic-to-semantic compression. Our content intelligence pipelines ingest, enrich via LLM, embed (1024-dim vectors), and serve via hybrid semantic search with HNSW indexing and training-free chunk pre-filtering — 28,000+ items enriched, 30,000+ embeddings generated.

Fintech & Payments. We build payment orchestration layers that abstract multiple gateways and cryptocurrencies behind a single API. Our engineers have shipped POS terminal firmware, fiscal receipt systems compliant with Italian regulatory standards, and cash register management platforms processing millions of transactions.

Cybersecurity & Identity. We ship post-quantum cryptography implementations using NIST-standardized algorithms on .NET 10 — the cryptography standard that will replace RSA and ECC. Our authentication infrastructure integrates SPID (Italian public digital identity) and OAuth 2.0/OpenID Connect across every major identity provider. We build digital signature platforms with PKCS#11 HSM support handling the full envelope lifecycle.

Artificial Intelligence & On-Device ML. We deploy ONNX models to phones via custom Dart runtime bindings with GPU acceleration. We integrate on-device LLM inference engines. We build neural text-to-speech engines that run across all 6 Flutter platforms and speech recognition systems with Italian dialect support. Our scientific research pipeline uses a multi-scorer verification chain with consensus voting to ensure factual accuracy in LLM outputs.

Enterprise SaaS & Cloud-Native Architecture. We architect and operate platforms at enterprise scale. Our .NET 10 monorepo spans 239 C# projects with consistent CI/CD, 297 test suites, zero build errors. We design multi-engine database abstraction layers (PostgreSQL, MySQL, Microsoft SQL Server) with automated schema-to-code generation. Our notification engine handles 6 channels with DNS-based email validation.

IoT & Embedded Systems. We write ESP32 firmware targeting ESP-IDF v5.4 with 21 FreeRTOS tasks, I²S audio pipelines, Opus codec integration, and a validated WiFi state machine. We build certificate authority infrastructure for device TLS. We write native Flutter plugins for hardware that doesn't have one yet.

Observability & DevOps. Full-stack observability is the foundation of our client solutions. We ship a complete OpenTelemetry SDK for Dart/Flutter (traces, logs, metrics, W3C propagation, OTLP export), enterprise structured logging with file rotation, and auto-instrumentation — all red-team audited with 256 automated tests.

The technologies we master #

Our engineering team works across the full stack — from ESP32 firmware to cloud-native backends to cross-platform mobile apps. We write production code in Rust (database engines, FFI), C# (.NET 10), Dart/Flutter, C (ESP-IDF, audio codecs), TypeScript, Python, and C++. Our frameworks of choice are ASP.NET Core, Blazor Server, Flutter, Angular, and React. We operate Microsoft Azure (Key Vault, Blob Storage, IoT Hub), deploy on NGINX, and manage PostgreSQL + pgvector, MySQL, Microsoft SQL Server, and ESP-IDF at scale. Our database layer uses EF Core with Npgsql, our embedded engine uses Rust + dart:ffi + WASM. Our AI stack spans LLMs, embeddings, vector search, semantic kernel, and multi-agent orchestration. We use FFmpeg for audio, LVGL for embedded displays, and BLE for device provisioning. Our CI/CD runs on Azure Pipelines.

Microsoft Partner since 2018 · SumUp Partner · Dell Partner #


Trusted by #

ABB Intesa Sanpaolo Tenaris Reply Aubay Comune di Milano BCC FIMAP Alten Altran Prometeia illimity Be Shaping the Future DS Group NVALUE Inoptim Docflow P&C

and 40+ other enterprises across banking, manufacturing, energy, and public sector.


Your project can't wait. We've solved these exact problems for companies you know. Let's solve them for you.

🌐 purplesoft.io  ·  📧 developers@purplesoft.io  ·  📞 +39 0362 148 3978  ·  💼 LinkedIn  ·  🐙 GitHub

License #

GNU Affero General Public License v3.0 — see LICENSE for the full text.

0
likes
145
points
96
downloads

Documentation

API reference

Publisher

verified publisherpurplesoft.io

Weekly Downloads

OpenTelemetry auto-instrumentation for Dio HTTP client — automatic CLIENT spans with W3C trace context.

Homepage
Repository

Topics

#opentelemetry #dio #http #tracing

License

AGPL-3.0 (license)

Dependencies

dio, purple_otel_sdk

More

Packages that depend on purple_otel_dio