このトピックでは、OpenTelemetry SDK for Flutterを使用して、FlutterおよびDartアプリケーションからSimple Log Serviceにトレースデータをインポートする方法について説明します。
前提条件
トレースインスタンスが作成されます。 詳細については、「トレースインスタンスの作成」をご参照ください。
ステップ1: SDKの統合
Flutter用Simple Log Service SDKをインストールします。 詳細については、「Flutter用Simple Log Service SDKのインストール」をご参照ください。
プロジェクトのルートディレクトリで次のコマンドを実行して、opentelemetry-dart SDKモジュールをインポートします。
flutter pub add opentelemetry_slsSDKモジュールがインポートされると、次の情報がプロジェクトの
pubspec.yamlファイルに追加され、flutter pub getコマンドが暗黙的に実行されます。dependencies: opentelemetry_sls: ^0.15.1opentelemetry-dartモジュールを指定したDartファイルにインポートします。
import 'package:opentelemetry_sls/api.dart'; import 'package:opentelemetry_sls/sdk.dart' as otel_sdk;
ステップ2: SDKの初期化
SDKを使用する前に、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"),
]));
}変数 | 説明 | 例 |
| Simple Log Serviceエンドポイント。 詳細については、「エンドポイント」をご参照ください。 | cn-hangzhou.log.aliyuncs.com |
| Simple Log Serviceプロジェクトの名前。 | test-project |
| トレースインスタンスのID。 詳細については、「トレースインスタンスの作成」をご参照ください。 | テストトレース |
| Alibaba CloudアカウントのAccessKey ID。 重要 Simple Log Serviceプロジェクトに対する書き込み権限のみを持つRAMユーザーのAccessKeyペアを使用することを推奨します。 AccessKey ペアは、AccessKey ID と AccessKey Secret で構成されます。 指定したプロジェクトの書き込み権限をRAMユーザーに付与する方法の詳細については、「カスタムポリシーを使用してRAMユーザーに権限を付与する」をご参照ください。 AccessKeyペアを取得する方法の詳細については、「AccessKeyペア」をご参照ください。 | なし |
| Alibaba CloudアカウントのAccessKeyシークレット。 重要 Simple Log Serviceプロジェクトに対する書き込み権限のみを持つRAMユーザーのAccessKeyペアを使用することを推奨します。 | なし |
| サービスが属する名前空間。 | order |
| サービスの名前です。 ビジネスシナリオに基づいて値を指定します。 | payment |
| サービスのバージョンです。 | v0.1.2 |
| デプロイ環境。 例: テスト環境または本番環境。 ビジネスシナリオに基づいて値を指定します。 | 前 |
ステップ3: SDKの使用
トレーサーの作成
さまざまなビジネスシナリオに基づいてトレーサーを作成することを推奨します。 トレーサーを作成するときは、インストルメンテーションスコープ名を指定する必要があります。 これにより、トレースデータをスコープで区別できます。
Tracer? _tracer = _provider!.getTracer('hello-otel-dart');ルートスパンの作成
スパンは、トランザクションにおける動作を示す。 各スパンは、操作名、開始タイムスタンプ、終了タイムスタンプ、属性、イベント、およびコンテキストをカプセル化します。
final span = _tracer!.startSpan("operation");
// do stuff
// ...
span.end();ネストされたスパンの作成
ネストされた操作をスパンに関連付ける場合は、次の方法を使用します。
final parent = _tracer!.startSpan("parent operation");
Context.current.withSpan(parent).execute(() {
final child = _tracer!.startSpan("child operation");
// do stuff
// ...
child.end();
});
parent.end();属性を持つスパンの作成
実行結果やその他の関連するビジネス情報などの属性を指定することで、スパン内の特定の操作のコンテキストを指定できます。
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();スパンのステータス値の指定
StatusCode.unset、StatusCode.ok、およびStatusCode.errorのいずれかのステータス値をスパンにアタッチできます。 unsetはデフォルトのステータスを示します。 okは、操作が完了したことを示します。 エラーは、操作にエラーが含まれていることを示します。
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();コンテキストの伝播
OpenTelemetryは、コンテキストを伝播するテキストベースのメソッドを提供します。 次の例は、Dart HTTPライブラリを使用してhttp GETリクエストを開始する方法を示しています。
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();OpenTelemetry SDKは、W3CトレースコンテキストHTTPヘッダーを使用したコンテキスト伝播をサポートしています。 詳細については、「W3CTraceContextPropagatorクラス」をご参照ください。
OpenTelemetry SDKの詳細については、「OpenTelemetry SDK」をご参照ください。
サンプルコード
次のサンプルコードは、OpenTelemetry SDKを使用してFlutterアプリケーションからトレースデータを収集する方法を示しています。
<bx id="1" type="code" text="code id="000wya" uuid="lhsl97gcu97amhpewc" code="// 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 _spanWithEvent() {
final span = _tracer!.startSpan("span with event");
final dateProvider = otel_sdk.DateTimeTimeProvider();
span.addEvent("start", dateProvider.now);
// do stuff
// ...
span.addEvent("middle", dateProvider.now);
// do stuff
// ...
span.addEvent("end", dateProvider.now, attributes: [Attribute.fromBoolean("success", true)]);
span.end();
}
void _spanWithStatus() {
final span = _tracer!.startSpan("operation");
span.setStatus(StatusCode.error, description: "something error");
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 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),
)),
)),
],
);
}
}
" data-tag="codeblock" outputclass="language-application/dart""/>// 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),
)),
)),
],
);
}
}