dartastic_opentelemetry 0.8.1 copy "dartastic_opentelemetry: ^0.8.1" to clipboard
dartastic_opentelemetry: ^0.8.1 copied to clipboard

Dartastic.io's OpenTelemetry SDK for Dart

OpenTelemetry SDK for Dart #

Pub Version License OpenTelemetry Specification

A Dart implementation of the OpenTelemetry SDK that strictly adheres to the OpenTelemetry (OTel) specification. This package provides a production-ready implementation of OpenTelemetry telemetry collection and export for Dart applications.

Overview #

The OpenTelemetry SDK for Dart implements the OpenTelemetry specification, allowing you to collect telemetry data (traces, metrics, logs coming) from your Dart applications and export it to your backend of choice.

This SDK implements the OpenTelemetry API for Dart and provides additional components needed for a full telemetry solution:

  • Span processors for processing and enriching spans
  • Exporters for sending data to backends (OTLP, Console, Custom)
  • Resource providers for adding service information
  • Samplers for controlling data volume
  • Propagators for cross-service context propagation

Dartastic.io provides an OpenTelemetry Observability backend specifically built for Dart and Flutter applications, offering rich features like source code integration, function call visualization, and more.

Features #

  • Complete OpenTelemetry SDK implementation for Dart
  • Strict adherence to the OpenTelemetry specification
  • Support for all telemetry signals:
    • Tracing with span processors and samplers
    • Metrics collection and aggregation
    • Logging integration
    • Context propagation
    • Baggage management
  • Multiple export protocols:
    • OTLP over gRPC
    • OTLP over HTTP/JSON
    • Zipkin
    • Jaeger
    • Console (for debugging)
  • Configurable resource providers
  • Cross-platform compatibility - works across all Dart environments
  • Low overhead with efficient processing and batching
  • Pluggable architecture for custom extensions

Getting Started #

Installation #

Add the package to your pubspec.yaml: The API is a separate package but it's not necessary since the SDK re-exports the relevant members.

dependencies:
  opentelemetry_sdk: ^0.8.0

Then run:

dart pub get

Basic Configuration #

To start using the SDK with default settings:

import 'package:opentelemetry_sdk/opentelemetry_sdk.dart';

void main() {
  // Initialize the SDK with defaults (console exporter)
  OTel.initialize(
    endpoint: 'http://localhost:4317',
    serviceName: 'my-service', 
    serviceVersion: '1.0.0'
  );
  
  // Your application code here
  
  // Shutdown the SDK before application exit
  OTel.shutdown();
}

Advanced Configuration #

For more control over the SDK configuration:

import 'package:opentelemetry_sdk/opentelemetry_sdk.dart';

void main() async {
  // Create a resource describing your service
  final resource = OTel.resource([
    OTel.resourceAttribute('service.name', 'payment-service'),
    OTel.resourceAttribute('service.version', '1.2.3'),
    OTel.resourceAttribute('deployment.environment', 'production'),
  ]);
  
  // Configure span processors
  final batchProcessor = OTel.batchSpanProcessor(
    OTel.otlpHttpExporter(
      endpoint: 'https://api.example.com/v1/traces',
      headers: {'Authorization': 'Bearer token123'},
    ),
    maxQueueSize: 2048,
    scheduledDelayMillis: 5000,
  );
  
  // Configure samplers
  final sampler = OTel.parentBasedSampler(
    OTel.traceIdRatioSampler(0.1), // Sample 10% of traces
  );
  
  // Initialize the SDK with custom configuration
  OTel.initializeAdvanced(
    resource: resource,
    spanProcessors: [batchProcessor],
    sampler: sampler,
    propagators: [
      OTel.w3cTraceContextPropagator(),
      OTel.w3cBaggagePropagator(),
    ],
  );
  
  // Application shutdown
  await OTel.shutdownAsync(); // Flush pending telemetry
}

Usage Examples #

Tracing Example #

import 'package:opentelemetry_sdk/opentelemetry_sdk.dart';

void main() {
  // Initialize the SDK
  OTel.initialize(
    endpoint: 'http://localhost:4317',
    serviceName: 'example-service',
  );
  
  // Get a tracer
  final tracer = OTel.tracer('example-component');
  
  // Create and use a span
  tracer.startActiveSpan(
    name: 'main-operation', 
    kind: SpanKind.server,
    fn: (span) {
      // Your business logic here
      span.setAttribute('operation.type', 'example');
      
      try {
        // Do work - call a nested operation
        performSubOperation(tracer);
        span.setStatus(SpanStatusCode.Ok);
        return 'Operation completed successfully';
      } catch (e, stackTrace) {
        // Record the error
        span
          ..setStatus(SpanStatusCode.Error, e.toString())
          ..recordException(e, stackTrace: stackTrace);
        rethrow;
      }
    },
  );
  
  // Always shutdown the SDK before the application exits
  OTel.shutdown();
}

void performSubOperation(APITracer tracer) {
  tracer.startActiveSpan(
    name: 'sub-operation', 
    fn: (span) {
      // Sub-operation logic
      span.setAttribute('operation.value', 42);
      span.setStatus(SpanStatusCode.Ok);
      return 'Sub-operation complete';
    },
  );
}

HTTP Client Instrumentation Example #

import 'package:http/http.dart' as http;
import 'package:opentelemetry_sdk/opentelemetry_sdk.dart';

Future<void> main() async {
  // Initialize the SDK
  OTel.initialize(
    endpoint: 'http://localhost:4317',
    serviceName: 'http-client-example',
  );
  
  final tracer = OTel.tracer('http-client');
  
  // Make an HTTP request with tracing
  final result = await tracer.startActiveSpanAsync(
    name: 'GET /api/users',
    kind: SpanKind.client,
    fn: (span) async {
      try {
        // Add HTTP attributes
        span.setAttribute('http.method', 'GET');
        span.setAttribute('http.url', 'https://api.example.com/users');
        
        // Extract the current context's headers for propagation
        final headers = <String, String>{};
        OTel.propagator().inject(
          Context.current,
          headers,
          defaultSetter,
        );
        
        // Make the HTTP request with propagation headers
        final response = await http.get(
          Uri.parse('https://api.example.com/users'),
          headers: headers,
        );
        
        // Record response details
        span.setAttribute('http.status_code', response.statusCode);
        
        if (response.statusCode >= 400) {
          span.setStatus(SpanStatusCode.Error, 'HTTP error ${response.statusCode}');
        } else {
          span.setStatus(SpanStatusCode.Ok);
        }
        
        return response;
      } catch (e, stackTrace) {
        span.recordException(e, stackTrace: stackTrace);
        span.setStatus(SpanStatusCode.Error, e.toString());
        rethrow;
      }
    },
  );
  
  print('Response status: ${result.statusCode}');
  
  // Shut down the SDK
  await OTel.shutdownAsync();
}

void defaultSetter(Map<String, String> carrier, String key, String value) {
  carrier[key] = value;
}

More examples can be found in the /example directory.

Configuration Options #

Exporters #

The SDK provides multiple exporter options:

// OTLP over gRPC
final otlpGrpcExporter = OTel.otlpGrpcExporter(
  endpoint: 'http://collector:4317',
  headers: {'x-api-key': 'your-api-key'},
);

// OTLP over HTTP/JSON
final otlpHttpExporter = OTel.otlpHttpExporter(
  endpoint: 'http://collector:4318/v1/traces',
  headers: {'x-api-key': 'your-api-key'},
);

// Zipkin exporter
final zipkinExporter = OTel.zipkinExporter(
  endpoint: 'http://zipkin:9411/api/v2/spans',
);

// Console exporter (for debugging)
final consoleExporter = OTel.consoleExporter();

Span Processors #

Configure how spans are processed before export:

// Simple span processor - exports immediately
final simpleProcessor = OTel.simpleSpanProcessor(exporter);

// Batch span processor - batches spans for efficiency
final batchProcessor = OTel.batchSpanProcessor(
  exporter,
  maxQueueSize: 2048,
  scheduledDelayMillis: 5000,
  maxExportBatchSize: 512,
);

Samplers #

Control which spans are sampled:

// Always sample
final alwaysOnSampler = OTel.alwaysOnSampler();

// Never sample
final alwaysOffSampler = OTel.alwaysOffSampler();

// Sample based on trace ID
final traceIdRatioSampler = OTel.traceIdRatioSampler(0.1); // 10% sampling

// Parent-based sampling
final parentSampler = OTel.parentBasedSampler(
  traceIdRatioSampler, // root sampler
);

Propagators #

Configure context propagation:

// W3C Trace Context propagator
final traceContextPropagator = OTel.w3cTraceContextPropagator();

// W3C Baggage propagator
final baggagePropagator = OTel.w3cBaggagePropagator();

// Composite propagator (combines multiple propagators)
final compositePropagator = OTel.compositePropagator([
  traceContextPropagator,
  baggagePropagator,
]);

Advanced Topics #

Custom Span Processors #

You can create custom span processors by implementing the SpanProcessor interface:

class CustomSpanProcessor implements SpanProcessor {
  @override
  void onStart(ReadWriteSpan span, Context parentContext) {
    // Add custom logic when a span starts
  }

  @override
  void onEnd(ReadOnlySpan span) {
    // Add custom logic when a span ends
  }

  @override
  Future<void> shutdown() async {
    // Cleanup resources
  }

  @override
  Future<void> forceFlush() async {
    // Force export of any pending spans
  }
}

Custom Resource Providers #

Implement custom resource providers to add additional service information:

class EnvironmentResourceProvider implements ResourceProvider {
  @override
  Resource get() {
    final envVars = Platform.environment;
    return Resource.create([
      OTel.resourceAttribute('host.name', envVars['HOSTNAME'] ?? 'unknown'),
      OTel.resourceAttribute('deployment.environment', 
                            envVars['ENV'] ?? 'development'),
    ]);
  }
}

Custom Exporters #

Implement your own exporters by extending the SpanExporter interface:

class CustomExporter implements SpanExporter {
  @override
  Future<ExportResult> export(List<ReadOnlySpan> spans) async {
    // Custom export logic
    for (final span in spans) {
      // Process and send the span data
    }
    return ExportResult.success;
  }

  @override
  Future<void> shutdown() async {
    // Clean up resources
  }

  @override
  Future<void> force() async {
    // Forcefully flush any pending spans
  }
}

Integration with OpenTelemetry Collector #

For production deployments, it's recommended to use the OpenTelemetry Collector:

// Configure to send to an OpenTelemetry Collector
OTel.initialize(
  endpoint: 'http://otel-collector:4317', // gRPC endpoint
  serviceName: 'my-service',
  // Alternative HTTP endpoint: 'http://otel-collector:4318/v1/traces'
);

Testing #

Running Tests #

The project includes a comprehensive test suite:

# Run all unit tests
make test

# Run tests safely in sequence (for problematic tests)
make test-safe

# Run web-specific tests (requires Chrome)
make test-web

# Run all checks including tests and coverage
make check

Web Testing #

Some components use platform-specific implementations, especially for web environments. To ensure these components work correctly in browsers:

# Run only web-specific tests in Chrome
make test-web

Web-specific tests verify JS interop functionality and browser API usage like Compression Streams. For more details on running and writing web tests, see test/web/README.md.

Platform Support #

  • Dart VM - For server applications
  • Flutter - For mobile applications (see also flutterrific_opentelemetry)
  • Web - For browser applications (with some limitations)

Commercial Support #

Dartastic.io provides an OpenTelemetry Observability backend specifically built for Dart and Flutter applications. Features include:

  • Enhanced tracing with source code integration
  • Real-time user monitoring for Flutter apps
  • Advanced dashboard and visualization
  • Integration with native platforms
  • Generous free tier and enterprise support options

Roadmap #

  • ❌ Enhanced metrics support
  • ❌ Additional exporters
  • ❌ Automatic instrumentation for common Dart libraries
  • ❌ Configuration through environment variables
  • ❌ Enhanced context propagation options

CNCF Contribution and Alignment #

This project aims to align with Cloud Native Computing Foundation (CNCF) best practices:

  • Interoperability - Works with the broader OpenTelemetry ecosystem
  • Specification compliance - Strictly follows the OpenTelemetry specification
  • Vendor neutrality - Provides a vendor-neutral implementation

License #

Apache 2.0 - See the LICENSE file for details.

Commercial Support #

Dartastic.io provides an OpenTelemetry support, training, consulting, enhanced private packages and an Observability backend customized for Flutter apps, Dart backends, and any other service or process that produces OpenTelemetry data. Dartastic.io is built on open standards, specifically catering to Flutter and Dart applications with the ability to show Dart source code lines and function calls from production errors and logs.

Dartastic.io offers:

  • Various levels of free, paid, and enterprise support
  • Packages with advanced features not available in the open source offering
  • Native code integration and Real-Time User Monitoring for Flutter apps
  • Multiple backends (Elastic, Grafana) customized for Flutter apps.

Additional Information #

Acknowledgements #

This OpenTelemetry SDK for Dart is made with 💙 by Michael Bushe at Mindful Software.