All Products
Search
Document Center

Simple Log Service:Import trace data from Flutter and Dart applications to Simple Log Service by using OpenTelemetry SDK for Flutter

Last Updated:Aug 29, 2023

This topic describes how to import trace data from Flutter and Dart applications to Simple Log Service by using OpenTelemetry SDK for Flutter.

Prerequisites

A trace instance is created. For more information, see Create a trace instance.

Step 1: Integrate the SDK

  1. Install Simple Log Service SDK for Flutter. For more information, see Install Simple Log Service SDK for Flutter.

  2. Run the following command in the root directory of the project to import the opentelemetry-dart SDK module:

    flutter pub add opentelemetry_sls

    After the SDK module is imported, the following information is added to the pubspec.yaml file of the project and the flutter pub get command is implicitly run.

    dependencies:
     opentelemetry_sls: ^0.15.1
  3. Import the opentelemetry-dart modules to the specified Dart file.

    import 'package:opentelemetry_sls/api.dart';
    import 'package:opentelemetry_sls/sdk.dart' as otel_sdk;

Step 2: Initialize the SDK

Before you use the SDK, you must initialize the SDK.

TracerProvider? _provider;
Tracer? _tracer;

  void _initOTelSDK() {
    const project = "qs-demos";
    const endpoint = "cn-beijing.log.aliyuncs.com";
    const instanceId = "sls-mall";
    const accessKeyId = "your access key id";
    const accessKeySecret = "your access key secret";
    final exporter =
        otel_sdk.CollectorExporter(Uri.parse("https://${project}.${endpoint}/opentelemetry/v1/traces"), headers: {
      "x-sls-otel-project": "${project}",
      "x-sls-otel-instance-id": "${instanceId}",
      "x-sls-otel-ak-id": "${accessKeyId}",
      "x-sls-otel-ak-secret": "${accessKeySecret}"
    });
    final processor = otel_sdk.BatchSpanProcessor(exporter);
    final simpleProcessor = otel_sdk.SimpleSpanProcessor(otel_sdk.ConsoleExporter());
    _provider = otel_sdk.TracerProviderBase(
        processors: [processor, simpleProcessor],
        resource: otel_sdk.Resource([
          Attribute.fromString("service.name", "main"),
          Attribute.fromString("service.namespace", "flutter"),
          Attribute.fromString("service.version", "1.0.0"),
          Attribute.fromString("deployment.environment", "dev"),
        ]));
  }

Variable

Description

Example

${endpoint}

The Simple Log Service endpoint. For more information, see Endpoints.

cn-hangzhou.log.aliyuncs.com

${project}

The name of the Simple Log Service project.

test-project

${instance}

The ID of the trace instance. For more information, see Create a trace instance.

test-traces

${access-key-id}

The AccessKey ID of your Alibaba Cloud account.

Important

We recommend that you use the AccessKey pair of a RAM user that has only the write permissions on the Simple Log Service project. An AccessKey pair consists of an AccessKey ID and an AccessKey secret. For more information about how to grant the write permissions on a specified project to a RAM user, see Use custom policies to grant permissions to a RAM user. For more information about how to obtain an AccessKey pair, see AccessKey pair.

None

${access-key-secret}

The AccessKey secret of your Alibaba Cloud account.

Important

We recommend that you use the AccessKey pair of a RAM user that has only the write permissions on the Simple Log Service project.

None

${service.namespace}

The namespace to which the service belongs.

order

${service}

The name of the service. Specify the value based on your business scenario.

payment

${version}

The version of the service. We recommend that you specify a version in the va.b.c format.

v0.1.2

${environment}

The deployment environment. Example: test environment or production environment. Specify the value based on your business scenario.

pre

Step 3: Use the SDK

Create a tracer

We recommend that you create tracers based on your different business scenarios. When you create a tracer, you must specify an instrumentation scope name. Then, you can distinguish trace data by scope.

Tracer? _tracer = _provider!.getTracer('hello-otel-dart');

Create a root span

A span indicates an operation in a transaction. Each span encapsulates the operation name, start timestamp, end timestamp, attributes, events, and context.

final span = _tracer!.startSpan("operation");
// do stuff
// ...
span.end();

Create nested spans

If you want to associate nested operations with spans, use the following method:

final parent = _tracer!.startSpan("parent operation");
Context.current.withSpan(parent).execute(() {
  final child = _tracer!.startSpan("child operation");
  // do stuff
  // ...
  child.end();
});
parent.end();

Create a span with attributes

You can provide context for a specific operation in a span by specifying attributes, such as the execution result and other associated business information.

final span = _tracer!.startSpan("GET /resource/catalog", kind: SpanKind.client);
span.setAttribute(Attribute.fromString("http.method", "GET"));
span.setAttribute(Attribute.fromString("http.url", "your http url"));
// do stuff
// ...
span.end();

Specify a status value for a span

You can attach one of the following status values to a span: StatusCode.unset, StatusCode.ok, and StatusCode.error. unset indicates the default status. ok indicates that the operation is completed. error indicates that the operation contains an error.

final span = _tracer!.startSpan("operation");

span.setStatus(StatusCode.error, description: "something error");
// A span can also record exceptions.
try {
  _throwException();
} on Exception catch (e) {
  span.recordException(e);
}

span.end();

Propagate context

OpenTelemetry provides a text-based method to propagate context. The following example shows how to initiate an HTTP GET request by using the Dart http library.

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

final traceContextPropagator = otel_sdk.W3CTraceContextPropagator();
final textMapSetter = HttpClientTextMapSetter();
final headers = <String, String>{};

final httpSpan = _tracer!.startSpan("start http request");
Context.current.withSpan(httpSpan).execute(() {
  traceContextPropagator.inject(Context.current, headers, textMapSetter);
  final client = http.Client();
  client.get(Uri.parse("http://sls-mall.caa227ac081f24f1a8556f33d69b96c99.cn-beijing.alicontainer.com/catalogue"),
             headers: headers);
});
httpSpan.end();

The OpenTelemetry SDK supports context propagation by using the W3C Trace Context HTTP headers. For more information, see W3CTraceContextPropagator class.

For more information about the OpenTelemetry SDK, see OpenTelemetry SDK.

Sample code

The following sample code shows how to collect trace data from a Flutter application by using the OpenTelemetry SDK:

<bx id="1" type="code" text="code id=&quot;000wya&quot; uuid=&quot;lhsl97gcu97amhpewc&quot; code=&quot;// ignore: depend_on_referenced_packages
import 'package:http/http.dart' as http;

import 'package:flutter/material.dart';
import 'package:opentelemetry_sls/api.dart';
import 'package:opentelemetry_sls/sdk.dart' as otel_sdk;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'OTel Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class HttpClientTextMapSetter implements TextMapSetter<Map> {
  @override
  void set(Map carrier, String key, String value) {
    carrier[key] = value;
  }
}

class _MyHomePageState extends State<MyHomePage> {
  TracerProvider? _provider;
  Tracer? _tracer;
  final traceContextPropagator = otel_sdk.W3CTraceContextPropagator();

  void _initOTelSDK() {
    const project = &quot;qs-demos&quot;;
    const endpoint = &quot;cn-beijing.log.aliyuncs.com&quot;;
    final exporter =
        otel_sdk.CollectorExporter(Uri.parse(&quot;https://$project.$endpoint/opentelemetry/v1/traces&quot;), headers: {
      &quot;x-sls-otel-project&quot;: &quot;$project&quot;,
      &quot;x-sls-otel-instance-id&quot;: &quot;sls-mall&quot;,
      &quot;x-sls-otel-ak-id&quot;: &quot;&quot;,
      &quot;x-sls-otel-ak-secret&quot;: &quot;&quot;
    });
    final processor = otel_sdk.BatchSpanProcessor(exporter);
    final simpleProcessor = otel_sdk.SimpleSpanProcessor(otel_sdk.ConsoleExporter());
    _provider = otel_sdk.TracerProviderBase(
        processors: [processor, simpleProcessor],
        resource: otel_sdk.Resource([
          Attribute.fromString(&quot;service.name&quot;, &quot;main&quot;),
          Attribute.fromString(&quot;service.namespace&quot;, &quot;flutter&quot;),
          Attribute.fromString(&quot;service.version&quot;, &quot;1.0.0&quot;),
          Attribute.fromString(&quot;deployment.environment&quot;, &quot;dev&quot;),
        ]));

    _tracer = _provider!.getTracer('hello-otel-dart');
  }

  void _simpleSpan() {
    final span = _tracer!.startSpan(&quot;operation&quot;);
    // do stuff
    // ...
    span.end();
  }

  void _nestedSpan() {
    final parent = _tracer!.startSpan(&quot;parent operation&quot;);
    Context.current.withSpan(parent).execute(() {
      final child = _tracer!.startSpan(&quot;child operation&quot;);
      // do stuff
      // ...
      child.end();
    });
    parent.end();
  }

  void _spanWithAttribute() {
    final span = _tracer!.startSpan(&quot;GET /resource/catalog&quot;, kind: SpanKind.client);
    span.setAttribute(Attribute.fromString(&quot;http.method&quot;, &quot;GET&quot;));
    span.setAttribute(Attribute.fromString(&quot;http.url&quot;, &quot;your http url&quot;));
    // do stuff
    // ...
    span.end();
  }

  void _spanWithEvent() {
    final span = _tracer!.startSpan(&quot;span with event&quot;);
    final dateProvider = otel_sdk.DateTimeTimeProvider();

    span.addEvent(&quot;start&quot;, dateProvider.now);
    // do stuff
    // ...
    span.addEvent(&quot;middle&quot;, dateProvider.now);
    // do stuff
    // ...
    span.addEvent(&quot;end&quot;, dateProvider.now, attributes: [Attribute.fromBoolean(&quot;success&quot;, true)]);

    span.end();
  }

  void _spanWithStatus() {
    final span = _tracer!.startSpan(&quot;operation&quot;);

    span.setStatus(StatusCode.error, description: &quot;something error&quot;);
    
    try {
      _throwException();
    } on Exception catch (e) {
      span.recordException(e);
    }

    span.end();
  }

  void _throwException() {
    throw Exception(&quot;Something bad happened!&quot;);
  }

  void _propagateContext() {
    final textMapSetter = HttpClientTextMapSetter();
    final headers = <String, String>{};

    final httpSpan = _tracer!.startSpan(&quot;start http request&quot;);
    Context.current.withSpan(httpSpan).execute(() {
      traceContextPropagator.inject(Context.current, headers, textMapSetter);
      final client = http.Client();
      client.get(Uri.parse(&quot;http://sls-mall.caa227ac081f24f1a8556f33d69b96c99.cn-beijing.alicontainer.com/catalogue&quot;),
          headers: headers);
    });
    httpSpan.end();
  }

  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
    _initOTelSDK();

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          _buildButton(color, 'init', _initOTelSDK),
          _buildButton(color, 'simple span', _simpleSpan),
          _buildButton(color, 'nested span', _nestedSpan),
          _buildButton(color, 'span with attribute', _spanWithAttribute),
          // _buildButton(color, 'span with event', _spanWithEvent),
          _buildButton(color, 'span with status', _spanWithStatus),
          _buildButton(color, 'propagate context', _propagateContext),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _buildButton(Color color, String label, VoidCallback? onPressed) {
    return Row(
      mainAxisSize: MainAxisSize.max,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Expanded(
            flex: 1,
            child: Container(
              margin: const EdgeInsets.only(left: 16, top: 8, right: 16),
              child: TextButton(
                  onPressed: onPressed,
                  style: ButtonStyle(
                      shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
                      side: MaterialStateProperty.all(BorderSide(color: color, width: 0.67)),
                      backgroundColor: MaterialStateProperty.all(Colors.transparent),
                      padding:
                          MaterialStateProperty.all(const EdgeInsets.only(left: 12, top: 6, right: 12, bottom: 6))),
                  child: Text(
                    label,
                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.w400, color: color),
                  )),
            )),
      ],
    );
  }
}
&quot; data-tag=&quot;codeblock&quot; outputclass=&quot;language-application/dart&quot;"/>// ignore: depend_on_referenced_packages
import 'package:http/http.dart' as http;

import 'package:flutter/material.dart';
import 'package:opentelemetry_sls/api.dart';
import 'package:opentelemetry_sls/sdk.dart' as otel_sdk;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'OTel Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class HttpClientTextMapSetter implements TextMapSetter<Map> {
  @override
  void set(Map carrier, String key, String value) {
    carrier[key] = value;
  }
}

class _MyHomePageState extends State<MyHomePage> {
  TracerProvider? _provider;
  Tracer? _tracer;
  final traceContextPropagator = otel_sdk.W3CTraceContextPropagator();

  void _initOTelSDK() {
    const project = "qs-demos";
    const endpoint = "cn-beijing.log.aliyuncs.com";
    final exporter =
        otel_sdk.CollectorExporter(Uri.parse("https://$project.$endpoint/opentelemetry/v1/traces"), headers: {
      "x-sls-otel-project": "$project",
      "x-sls-otel-instance-id": "sls-mall",
      "x-sls-otel-ak-id": "",
      "x-sls-otel-ak-secret": ""
    });
    final processor = otel_sdk.BatchSpanProcessor(exporter);
    final simpleProcessor = otel_sdk.SimpleSpanProcessor(otel_sdk.ConsoleExporter());
    _provider = otel_sdk.TracerProviderBase(
        processors: [processor, simpleProcessor],
        resource: otel_sdk.Resource([
          Attribute.fromString("service.name", "main"),
          Attribute.fromString("service.namespace", "flutter"),
          Attribute.fromString("service.version", "1.0.0"),
          Attribute.fromString("deployment.environment", "dev"),
        ]));

    _tracer = _provider!.getTracer('hello-otel-dart');
  }

  void _simpleSpan() {
    final span = _tracer!.startSpan("operation");
    // do stuff
    // ...
    span.end();
  }

  void _nestedSpan() {
    final parent = _tracer!.startSpan("parent operation");
    Context.current.withSpan(parent).execute(() {
      final child = _tracer!.startSpan("child operation");
      // do stuff
      // ...
      child.end();
    });
    parent.end();
  }

  void _spanWithAttribute() {
    final span = _tracer!.startSpan("GET /resource/catalog", kind: SpanKind.client);
    span.setAttribute(Attribute.fromString("http.method", "GET"));
    span.setAttribute(Attribute.fromString("http.url", "your http url"));
    // do stuff
    // ...
    span.end();
  }

  void _spanWithStatus() {
    final span = _tracer!.startSpan("operation");

    span.setStatus(StatusCode.error, description: "something error");
    // A span can also record exceptions.
    try {
      _throwException();
    } on Exception catch (e) {
      span.recordException(e);
    }

    span.end();
  }

  void _throwException() {
    throw Exception("Something bad happened!");
  }

  void _propagateContext() {
    final textMapSetter = HttpClientTextMapSetter();
    final headers = <String, String>{};

    final httpSpan = _tracer!.startSpan("start http request");
    Context.current.withSpan(httpSpan).execute(() {
      traceContextPropagator.inject(Context.current, headers, textMapSetter);
      final client = http.Client();
      client.get(Uri.parse("http://sls-mall.caa227ac081f24f1a8556f33d69b96c99.cn-beijing.alicontainer.com/catalogue"),
          headers: headers);
    });
    httpSpan.end();
  }

  @override
  Widget build(BuildContext context) {
    Color color = Theme.of(context).primaryColor;
    _initOTelSDK();

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Column(
        children: [
          _buildButton(color, 'init', _initOTelSDK),
          _buildButton(color, 'simple span', _simpleSpan),
          _buildButton(color, 'nested span', _nestedSpan),
          _buildButton(color, 'span with attribute', _spanWithAttribute),
          _buildButton(color, 'span with status', _spanWithStatus),
          _buildButton(color, 'propagate context', _propagateContext),
        ],
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  Widget _buildButton(Color color, String label, VoidCallback? onPressed) {
    return Row(
      mainAxisSize: MainAxisSize.max,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Expanded(
            flex: 1,
            child: Container(
              margin: const EdgeInsets.only(left: 16, top: 8, right: 16),
              child: TextButton(
                  onPressed: onPressed,
                  style: ButtonStyle(
                      shape: MaterialStateProperty.all(RoundedRectangleBorder(borderRadius: BorderRadius.circular(12))),
                      side: MaterialStateProperty.all(BorderSide(color: color, width: 0.67)),
                      backgroundColor: MaterialStateProperty.all(Colors.transparent),
                      padding:
                          MaterialStateProperty.all(const EdgeInsets.only(left: 12, top: 6, right: 12, bottom: 6))),
                  child: Text(
                    label,
                    style: TextStyle(fontSize: 22, fontWeight: FontWeight.w400, color: color),
                  )),
            )),
      ],
    );
  }
}