diff --git a/.craft.yml b/.craft.yml
index 70e6fb387c..0967399ec2 100644
--- a/.craft.yml
+++ b/.craft.yml
@@ -9,6 +9,7 @@ targets:
flutter:
logging:
dio:
+ file:
- name: github
- name: registry
sdks:
@@ -16,3 +17,4 @@ targets:
pub:sentry_flutter:
pub:sentry_logging:
pub:sentry_dio:
+ #pub:sentry_file:
diff --git a/.github/workflows/file.yml b/.github/workflows/file.yml
new file mode 100644
index 0000000000..3889deac24
--- /dev/null
+++ b/.github/workflows/file.yml
@@ -0,0 +1,64 @@
+name: sentry-file
+on:
+ push:
+ branches:
+ - main
+ - release/**
+ pull_request:
+ paths-ignore:
+ - 'logging/**'
+ - 'flutter/**'
+ - 'dio/**'
+
+jobs:
+ cancel-previous-workflow:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Cancel Previous Runs
+ uses: styfle/cancel-workflow-action@b173b6ec0100793626c2d9e6b90435061f4fc3e5 # pin@0.11.0
+ with:
+ access_token: ${{ github.token }}
+
+ build:
+ name: Build ${{matrix.sdk}} on ${{matrix.os}}
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 30
+ defaults:
+ run:
+ shell: bash
+ working-directory: ./file
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ # removing beta because of Dart 2.19.0
+ sdk: [stable]
+ steps:
+ - uses: dart-lang/setup-dart@6a218f2413a3e78e9087f638a238f6b40893203d # pin@v1
+ with:
+ sdk: ${{ matrix.sdk }}
+ - uses: actions/checkout@v3
+
+ - name: Test VM
+ run: |
+ dart pub get
+ dart test -p vm --coverage=coverage --test-randomize-ordering-seed=random --chain-stack-traces
+ dart pub run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on=lib
+
+ - uses: codecov/codecov-action@d9f34f8cd5cb3b3eb79b3e4b5dae3a16df499a70 # pin@v3
+ if: runner.os == 'Linux' && matrix.sdk == 'stable'
+ with:
+ name: sentry_file
+ files: ./file/coverage/lcov.info
+
+ - uses: VeryGoodOpenSource/very_good_coverage@84e5b54ab888644554e5573dca87d7f76dec9fb3 # pin@v2.0.0
+ if: runner.os == 'Linux' && matrix.sdk == 'stable'
+ with:
+ path: './file/coverage/lcov.info'
+ min_coverage: 55
+
+ analyze:
+ uses: ./.github/workflows/analyze.yml
+ with:
+ package: file
+ panaThreshold: 90
diff --git a/.github/workflows/min_version_test.yml b/.github/workflows/min_version_test.yml
index dcd8ad3f25..a39e13a5ae 100644
--- a/.github/workflows/min_version_test.yml
+++ b/.github/workflows/min_version_test.yml
@@ -31,7 +31,7 @@ jobs:
- uses: subosito/flutter-action@dbf1fa04f4d2e52c33185153d06cdb5443aa189d # pin@v2
with:
flutter-version: '2.0.0'
-
+ # Add flutter build web (missing index)
- name: Build
run: |
cd min_version_test
diff --git a/.gitignore b/.gitignore
index b2807fa8ee..742ddcdedc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ build/
dart/coverage/*
logging/coverage/*
dio/coverage/*
+file/coverage/*
pubspec.lock
Podfile.lock
flutter/coverage/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b996ec1a79..c704a91808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Features
+
+- Tracing for File IO integration ([#1160](https://github.com/getsentry/sentry-dart/pull/1160))
+
### Dependencies
- Bump Cocoa SDK from v7.31.2 to v7.31.3 ([#1157](https://github.com/getsentry/sentry-dart/pull/1157))
diff --git a/README.md b/README.md
index cb5d8f9914..86a9868195 100644
--- a/README.md
+++ b/README.md
@@ -20,10 +20,11 @@ Sentry SDK for Dart and Flutter
| sentry_flutter | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-flutter/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-flutter) | [![pub package](https://img.shields.io/pub/v/sentry_flutter.svg)](https://pub.dev/packages/sentry_flutter) | [![likes](https://img.shields.io/pub/likes/sentry_flutter?logo=dart)](https://pub.dev/packages/sentry_flutter/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_flutter?logo=dart)](https://pub.dev/packages/sentry_flutter/score) | [![pub points](https://img.shields.io/pub/points/sentry_flutter?logo=dart)](https://pub.dev/packages/sentry_flutter/score)
| sentry_logging | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-logging/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Alogging) | [![pub package](https://img.shields.io/pub/v/sentry_logging.svg)](https://pub.dev/packages/sentry_logging) | [![likes](https://img.shields.io/pub/likes/sentry_logging?logo=dart)](https://pub.dev/packages/sentry_logging/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_logging?logo=dart)](https://pub.dev/packages/sentry_logging/score) | [![pub points](https://img.shields.io/pub/points/sentry_logging?logo=dart)](https://pub.dev/packages/sentry_logging/score)
| sentry_dio | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-dio/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-dio) | [![pub package](https://img.shields.io/pub/v/sentry_dio.svg)](https://pub.dev/packages/sentry_dio) | [![likes](https://img.shields.io/pub/likes/sentry_dio?logo=dart)](https://pub.dev/packages/sentry_dio/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_dio?logo=dart)](https://pub.dev/packages/sentry_dio/score) | [![pub points](https://img.shields.io/pub/points/sentry_dio?logo=dart)](https://pub.dev/packages/sentry_dio/score)
+| sentry_file | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry_file/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry_file) | [![pub package](https://img.shields.io/pub/v/sentry_file.svg)](https://pub.dev/packages/sentry_file) | [![likes](https://img.shields.io/pub/likes/sentry_file?logo=dart)](https://pub.dev/packages/sentry_file/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_file?logo=dart)](https://pub.dev/packages/sentry_file/score) | [![pub points](https://img.shields.io/pub/points/sentry_file?logo=dart)](https://pub.dev/packages/sentry_file/score)
##### Usage
-For detailed usage, check out the inner [dart](https://github.com/getsentry/sentry-dart/tree/main/dart), [flutter](https://github.com/getsentry/sentry-dart/tree/main/flutter), [logging](https://github.com/getsentry/sentry-dart/tree/main/logging) and [dio](https://github.com/getsentry/sentry-dart/tree/main/dio) `README's` or our `Resources` section below.
+For detailed usage, check out the inner [dart](https://github.com/getsentry/sentry-dart/tree/main/dart), [flutter](https://github.com/getsentry/sentry-dart/tree/main/flutter), [logging](https://github.com/getsentry/sentry-dart/tree/main/logging), [dio](https://github.com/getsentry/sentry-dart/tree/main/dio) and [file](https://github.com/getsentry/sentry-dart/tree/main/file) `README's` or our `Resources` section below.
#### Blog posts
diff --git a/file/CHANGELOG.md b/file/CHANGELOG.md
new file mode 120000
index 0000000000..04c99a55ca
--- /dev/null
+++ b/file/CHANGELOG.md
@@ -0,0 +1 @@
+../CHANGELOG.md
\ No newline at end of file
diff --git a/file/LICENSE b/file/LICENSE
new file mode 100644
index 0000000000..2a6964d84d
--- /dev/null
+++ b/file/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Sentry
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/file/README.md b/file/README.md
new file mode 100644
index 0000000000..700706d417
--- /dev/null
+++ b/file/README.md
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+Sentry integration for `dart.io.File`
+===========
+
+| package | build | pub | likes | popularity | pub points |
+| ------- | ------- | ------- | ------- | ------- | ------- |
+| sentry_file | [![build](https://github.com/getsentry/sentry-dart/workflows/sentry-file/badge.svg?branch=main)](https://github.com/getsentry/sentry-dart/actions?query=workflow%3Asentry-file) | [![pub package](https://img.shields.io/pub/v/sentry_file.svg)](https://pub.dev/packages/sentry_file) | [![likes](https://img.shields.io/pub/likes/sentry_file)](https://pub.dev/packages/sentry_file/score) | [![popularity](https://img.shields.io/pub/popularity/sentry_file)](https://pub.dev/packages/sentry_file/score) | [![pub points](https://img.shields.io/pub/points/sentry_file)](https://pub.dev/packages/sentry_file/score)
+
+#### Usage
+
+- Sign up for a Sentry.io account and get a DSN at https://sentry.io.
+
+- Follow the installing instructions on [pub.dev](https://pub.dev/packages/sentry/install).
+
+- Initialize the Sentry SDK using the DSN issued by Sentry.io.
+
+- [Set Up](https://docs.sentry.io/platforms/dart/performance/) Performance.
+
+```dart
+import 'package:sentry/sentry.dart';
+import 'package:sentry_file/sentry_file.dart';
+import 'dart:io';
+
+Future main() async {
+ // or SentryFlutter.init
+ await Sentry.init(
+ (options) {
+ options.dsn = 'https://example@sentry.io/example';
+ // To set a uniform sample rate
+ options.tracesSampleRate = 1.0;
+ },
+ appRunner: runApp, // Init your App.
+ );
+}
+
+Future runApp() async {
+ final file = File('my_file.txt');
+ // Call the Sentry extension method to wrap up the File
+ final sentryFile = file.sentryTrace();
+
+ // Start a transaction if there's no active transaction
+ final transaction = Sentry.startTransaction(
+ 'MyFileExample',
+ 'file',
+ bindToScope: true,
+ );
+
+ // create the File
+ await sentryFile.create();
+ // Write some content
+ await sentryFile.writeAsString('Hello World');
+ // Read the content
+ final text = await sentryFile.readAsString();
+
+ print(text);
+
+ // Delete the file
+ await sentryFile.delete();
+
+ // Finish the transaction
+ await transaction.finish(status: SpanStatus.ok());
+
+ await Sentry.close();
+}
+```
+
+#### Resources
+
+* [![Documentation](https://img.shields.io/badge/documentation-sentry.io-green.svg)](https://docs.sentry.io/platforms/dart/)
+* [![Forum](https://img.shields.io/badge/forum-sentry-green.svg)](https://forum.sentry.io/c/sdks)
+* [![Discord](https://img.shields.io/discord/621778831602221064)](https://discord.gg/Ww9hbqr)
+* [![Stack Overflow](https://img.shields.io/badge/stack%20overflow-sentry-green.svg)](https://stackoverflow.com/questions/tagged/sentry)
+* [![Twitter Follow](https://img.shields.io/twitter/follow/getsentry?label=getsentry&style=social)](https://twitter.com/intent/follow?screen_name=getsentry)
diff --git a/file/analysis_options.yaml b/file/analysis_options.yaml
new file mode 100644
index 0000000000..3dd01b96b7
--- /dev/null
+++ b/file/analysis_options.yaml
@@ -0,0 +1,23 @@
+include: package:lints/recommended.yaml
+
+analyzer:
+ exclude:
+ - example/** # the example has its own 'analysis_options.yaml'
+ errors:
+ # treat missing required parameters as a warning (not a hint)
+ missing_required_param: error
+ # treat missing returns as a warning (not a hint)
+ missing_return: error
+ language:
+ strict-casts: true
+ strict-inference: true
+ strict-raw-types: true
+
+linter:
+ rules:
+ prefer_relative_imports: true
+ unnecessary_brace_in_string_interps: true
+ prefer_function_declarations_over_variables: false
+ no_leading_underscores_for_local_identifiers: false
+ avoid_renaming_method_parameters: false
+ unawaited_futures: true
diff --git a/file/example/example.dart b/file/example/example.dart
new file mode 100644
index 0000000000..1423efe4a5
--- /dev/null
+++ b/file/example/example.dart
@@ -0,0 +1,51 @@
+import 'package:sentry/sentry.dart';
+import 'package:sentry_file/sentry_file.dart';
+import 'dart:io';
+
+Future main() async {
+ // ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
+ const dsn =
+ 'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562';
+
+ // or SentryFlutter.init
+ await Sentry.init(
+ (options) {
+ options.dsn = dsn;
+ // To capture the absolute path of the file
+ options.sendDefaultPii = true;
+ // To set a uniform sample rate
+ options.tracesSampleRate = 1.0;
+ },
+ appRunner: runApp, // Init your App.
+ );
+}
+
+Future runApp() async {
+ final file = File('my_file.txt');
+ // Call the Sentry extension method to wrap up the File
+ final sentryFile = file.sentryTrace();
+
+ // Start a transaction if there's no active transaction
+ final transaction = Sentry.startTransaction(
+ 'MyFileExample',
+ 'file',
+ bindToScope: true,
+ );
+
+ // Create the File
+ await sentryFile.create();
+ // Write some content
+ await sentryFile.writeAsString('Hello World');
+ // Read the content
+ final text = await sentryFile.readAsString();
+
+ print(text);
+
+ // Delete the file
+ await sentryFile.delete();
+
+ // Finish the transaction
+ await transaction.finish(status: SpanStatus.ok());
+
+ await Sentry.close();
+}
diff --git a/file/lib/sentry_file.dart b/file/lib/sentry_file.dart
new file mode 100644
index 0000000000..bd59fdea55
--- /dev/null
+++ b/file/lib/sentry_file.dart
@@ -0,0 +1,2 @@
+export 'src/sentry_file.dart';
+export 'src/sentry_file_extension.dart';
diff --git a/file/lib/src/sentry_file.dart b/file/lib/src/sentry_file.dart
new file mode 100644
index 0000000000..36cd88034a
--- /dev/null
+++ b/file/lib/src/sentry_file.dart
@@ -0,0 +1,387 @@
+// Adapted from https://github.com/ueman/sentry-dart-tools/blob/8e41418c0f2c62dc88292cf32a4f22e79112b744/sentry_plus/lib/src/file/sentry_file.dart
+
+// ignore_for_file: invalid_use_of_internal_member
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+import 'package:meta/meta.dart';
+import 'package:sentry/sentry.dart';
+
+typedef Callback = FutureOr Function();
+
+/// The Sentry wrapper for the File IO implementation that creates a span
+/// out of the active transaction in the scope.
+/// The span is started before the operation is executed and finished after.
+/// The File tracing isn't available for Web.
+///
+/// Example:
+///
+/// ```dart
+/// import 'dart:io';
+///
+/// final file = File('test.txt');
+/// final sentryFile = SentryFile(file);
+/// // span starts
+/// await sentryFile.writeAsString('Hello World');
+/// // span finishes
+/// ```
+///
+/// All the copy, create, delete, open, rename, read, and write operations are
+/// supported.
+@experimental
+class SentryFile implements File {
+ SentryFile(
+ this._file, {
+ @internal Hub? hub,
+ }) : _hub = hub ?? HubAdapter() {
+ _hub.options.sdk.addIntegration('SentryFileTracing');
+ }
+
+ final File _file;
+ final Hub _hub;
+
+ @override
+ Future copy(String newPath) {
+ return _wrap(() async => _file.copy(newPath), 'file.copy');
+ }
+
+ @override
+ File copySync(String newPath) {
+ return _wrapSync(() => _file.copySync(newPath), 'file.copy');
+ }
+
+ @override
+ Future create({bool recursive = false}) {
+ return _wrap(
+ () async => _file.create(recursive: recursive),
+ 'file.write',
+ );
+ }
+
+ @override
+ void createSync({bool recursive = false, bool exclusive = false}) {
+ return _wrapSync(
+ () => _file.createSync(recursive: recursive),
+ 'file.write',
+ );
+ }
+
+ @override
+ Future delete({bool recursive = false}) {
+ return _wrap(() async => _file.delete(recursive: recursive), 'file.delete');
+ }
+
+ @override
+ void deleteSync({bool recursive = false}) {
+ _wrapSync(() => _file.deleteSync(recursive: recursive), 'file.delete');
+ }
+
+ @override
+ Future open({FileMode mode = FileMode.read}) {
+ return _wrap(() async => _file.open(mode: mode), 'file.open');
+ }
+
+ // coverage:ignore-start
+
+ @override
+ Stream> openRead([int? start, int? end]) {
+ return _file.openRead(start, end);
+ }
+
+ @override
+ RandomAccessFile openSync({FileMode mode = FileMode.read}) {
+ return _file.openSync(mode: mode);
+ }
+
+ @override
+ IOSink openWrite({FileMode mode = FileMode.write, Encoding encoding = utf8}) {
+ return _file.openWrite(mode: mode, encoding: encoding);
+ }
+
+ // coverage:ignore-end
+
+ @override
+ Future readAsBytes() {
+ return _wrap(() async => _file.readAsBytes(), 'file.read');
+ }
+
+ @override
+ Uint8List readAsBytesSync() {
+ return _wrapSync(() => _file.readAsBytesSync(), 'file.read');
+ }
+
+ @override
+ Future> readAsLines({Encoding encoding = utf8}) {
+ return _wrap(
+ () async => _file.readAsLines(encoding: encoding), 'file.read');
+ }
+
+ @override
+ List readAsLinesSync({Encoding encoding = utf8}) {
+ return _wrapSync(
+ () => _file.readAsLinesSync(encoding: encoding),
+ 'file.read',
+ );
+ }
+
+ @override
+ Future readAsString({Encoding encoding = utf8}) {
+ return _wrap(
+ () async => _file.readAsString(encoding: encoding), 'file.read');
+ }
+
+ @override
+ String readAsStringSync({Encoding encoding = utf8}) {
+ return _wrapSync(
+ () => _file.readAsStringSync(encoding: encoding),
+ 'file.read',
+ );
+ }
+
+ @override
+ Future rename(String newPath) {
+ return _wrap(() async => _file.rename(newPath), 'file.rename');
+ }
+
+ @override
+ File renameSync(String newPath) {
+ return _wrapSync(() => _file.renameSync(newPath), 'file.rename');
+ }
+
+ @override
+ Future writeAsBytes(
+ List bytes, {
+ FileMode mode = FileMode.write,
+ bool flush = false,
+ }) {
+ return _wrap(
+ () async => _file.writeAsBytes(bytes, mode: mode, flush: flush),
+ 'file.write',
+ );
+ }
+
+ @override
+ void writeAsBytesSync(
+ List bytes, {
+ FileMode mode = FileMode.write,
+ bool flush = false,
+ }) {
+ _wrapSync(
+ () => _file.writeAsBytesSync(bytes, mode: mode, flush: flush),
+ 'file.write',
+ );
+ }
+
+ @override
+ Future writeAsString(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) {
+ return _wrap(
+ () async => _file.writeAsString(
+ contents,
+ mode: mode,
+ encoding: encoding,
+ flush: flush,
+ ),
+ 'file.write',
+ );
+ }
+
+ @override
+ void writeAsStringSync(
+ String contents, {
+ FileMode mode = FileMode.write,
+ Encoding encoding = utf8,
+ bool flush = false,
+ }) {
+ _wrapSync(
+ () => _file.writeAsStringSync(
+ contents,
+ mode: mode,
+ encoding: encoding,
+ flush: flush,
+ ),
+ 'file.write',
+ );
+ }
+
+ String _getDesc() {
+ return uri.pathSegments.isNotEmpty ? uri.pathSegments.last : path;
+ }
+
+ Future _wrap(Callback callback, String operation) async {
+ final desc = _getDesc();
+
+ final currentSpan = _hub.getSpan();
+ final span = currentSpan?.startChild(operation, description: desc);
+
+ span?.setData('file.async', true);
+ if (_hub.options.sendDefaultPii) {
+ span?.setData('file.path', absolute.path);
+ }
+ T data;
+ try {
+ // workaround for having the length when the file does not exist
+ // or its being deleted.
+ int? length;
+ var hasLength = false;
+ try {
+ length = await _file.length();
+ hasLength = true;
+ } catch (_) {
+ // ignore in case something goes wrong
+ }
+
+ data = await callback();
+
+ if (!hasLength) {
+ try {
+ length = await _file.length();
+ } catch (_) {
+ // ignore in case something goes wrong
+ }
+ }
+
+ if (length != null) {
+ span?.setData('file.size', length);
+ }
+
+ span?.status = SpanStatus.ok();
+ } catch (exception) {
+ span?.throwable = exception;
+ span?.status = SpanStatus.internalError();
+ rethrow;
+ } finally {
+ await span?.finish();
+ }
+ return data;
+ }
+
+ T _wrapSync(Callback callback, String operation) {
+ final desc = _getDesc();
+
+ final currentSpan = _hub.getSpan();
+ final span = currentSpan?.startChild(operation, description: desc);
+ span?.setData('file.async', false);
+
+ if (_hub.options.sendDefaultPii) {
+ span?.setData('file.path', absolute.path);
+ }
+
+ T data;
+ try {
+ // workaround for having the length when the file does not exist
+ // or its being deleted.
+ int? length;
+ var hasLength = false;
+ try {
+ length = _file.lengthSync();
+ hasLength = true;
+ } catch (_) {
+ // ignore in case something goes wrong
+ }
+
+ data = callback() as T;
+
+ if (!hasLength) {
+ try {
+ length = _file.lengthSync();
+ } catch (_) {
+ // ignore in case something goes wrong
+ }
+ }
+
+ if (length != null) {
+ span?.setData('file.size', length);
+ }
+
+ span?.status = SpanStatus.ok();
+ } catch (exception) {
+ span?.throwable = exception;
+ span?.status = SpanStatus.internalError();
+ rethrow;
+ } finally {
+ span?.finish();
+ }
+ return data;
+ }
+
+ // coverage:ignore-start
+
+ @override
+ Stream watch({
+ int events = FileSystemEvent.all,
+ bool recursive = false,
+ }) =>
+ _file.watch(events: events, recursive: recursive);
+
+ @override
+ Future resolveSymbolicLinks() => _file.resolveSymbolicLinks();
+
+ @override
+ String resolveSymbolicLinksSync() => _file.resolveSymbolicLinksSync();
+
+ @override
+ Future setLastAccessed(DateTime time) => _file.setLastAccessed(time);
+
+ @override
+ void setLastAccessedSync(DateTime time) => _file.setLastAccessedSync(time);
+
+ @override
+ Future setLastModified(DateTime time) => _file.setLastModified(time);
+
+ @override
+ void setLastModifiedSync(DateTime time) => _file.setLastAccessedSync(time);
+
+ @override
+ Directory get parent => _file.parent;
+
+ @override
+ String get path => _file.path;
+
+ @override
+ File get absolute => _file.absolute;
+
+ @override
+ Future exists() => _file.exists();
+
+ @override
+ bool existsSync() => _file.existsSync();
+
+ @override
+ bool get isAbsolute => _file.isAbsolute;
+
+ @override
+ Future lastAccessed() => _file.lastAccessed();
+
+ @override
+ DateTime lastAccessedSync() => _file.lastAccessedSync();
+
+ @override
+ Future lastModified() => _file.lastModified();
+
+ @override
+ DateTime lastModifiedSync() => _file.lastModifiedSync();
+
+ @override
+ Future length() => _file.length();
+
+ @override
+ int lengthSync() => _file.lengthSync();
+
+ @override
+ Future stat() => _file.stat();
+
+ @override
+ FileStat statSync() => _file.statSync();
+
+ @override
+ Uri get uri => _file.uri;
+
+ // coverage:ignore-end
+}
diff --git a/file/lib/src/sentry_file_extension.dart b/file/lib/src/sentry_file_extension.dart
new file mode 100644
index 0000000000..2c2f2657f3
--- /dev/null
+++ b/file/lib/src/sentry_file_extension.dart
@@ -0,0 +1,43 @@
+// ignore_for_file: invalid_use_of_internal_member
+
+import 'dart:io' if (dart.library.html) 'dart:html';
+
+import 'package:meta/meta.dart';
+import 'package:sentry/sentry.dart';
+
+import '../sentry_file.dart';
+
+extension SentryFileExtension on File {
+ /// The Sentry wrapper for the File IO implementation that creates a span
+ /// out of the active transaction in the scope.
+ /// The span is started before the operation is executed and finished after.
+ /// The File tracing isn't available for Web.
+ ///
+ /// Example:
+ ///
+ /// ```dart
+ /// import 'dart:io';
+ ///
+ /// final file = File('test.txt');
+ /// final sentryFile = SentryFile(file);
+ /// // span starts
+ /// await sentryFile.writeAsString('Hello World');
+ /// // span finishes
+ /// ```
+ ///
+ /// All the copy, create, delete, open, rename, read, and write operations are
+ /// supported.
+ @experimental
+ File sentryTrace({
+ @internal Hub? hub,
+ }) {
+ final _hub = hub ?? HubAdapter();
+
+ if (_hub.options.platformChecker.isWeb ||
+ !_hub.options.isTracingEnabled()) {
+ return this;
+ }
+
+ return SentryFile(this, hub: _hub);
+ }
+}
diff --git a/file/lib/src/version.dart b/file/lib/src/version.dart
new file mode 100644
index 0000000000..e8297f135c
--- /dev/null
+++ b/file/lib/src/version.dart
@@ -0,0 +1,2 @@
+/// The SDK version reported to Sentry.io in the submitted events.
+const String sdkVersion = '6.17.0';
diff --git a/file/pubspec.yaml b/file/pubspec.yaml
new file mode 100644
index 0000000000..fe353892c8
--- /dev/null
+++ b/file/pubspec.yaml
@@ -0,0 +1,20 @@
+name: sentry_file
+description: An integration which adds support for performance tracing for dart.io.File.
+version: 6.17.0
+homepage: https://docs.sentry.io/platforms/dart/
+repository: https://github.com/getsentry/sentry-dart
+issue_tracker: https://github.com/getsentry/sentry-dart/issues
+
+environment:
+ # <2.19 because of https://github.com/dart-lang/sdk/issues/49647 breaking change
+ sdk: '>=2.12.0 <2.19.0'
+
+dependencies:
+ sentry: 6.17.0
+ meta: ^1.3.0
+
+dev_dependencies:
+ lints: ^2.0.0
+ test: ^1.21.1
+ coverage: ^1.3.0
+ mockito: ^5.1.0
diff --git a/file/pubspec_overrides.yaml b/file/pubspec_overrides.yaml
new file mode 100644
index 0000000000..16e71d16f0
--- /dev/null
+++ b/file/pubspec_overrides.yaml
@@ -0,0 +1,3 @@
+dependency_overrides:
+ sentry:
+ path: ../dart
diff --git a/file/test/mock_platform_checker.dart b/file/test/mock_platform_checker.dart
new file mode 100644
index 0000000000..6dd95a5c06
--- /dev/null
+++ b/file/test/mock_platform_checker.dart
@@ -0,0 +1,11 @@
+import 'no_such_method_provider.dart';
+import 'package:sentry/src/platform_checker.dart';
+
+class MockPlatformChecker extends PlatformChecker with NoSuchMethodProvider {
+ MockPlatformChecker(this._isWeb);
+
+ final bool _isWeb;
+
+ @override
+ bool get isWeb => _isWeb;
+}
diff --git a/file/test/mock_sentry_client.dart b/file/test/mock_sentry_client.dart
new file mode 100644
index 0000000000..c68fde9b47
--- /dev/null
+++ b/file/test/mock_sentry_client.dart
@@ -0,0 +1,27 @@
+import 'package:sentry/sentry.dart';
+
+import 'no_such_method_provider.dart';
+
+final fakeDsn = 'https://abc@def.ingest.sentry.io/1234567';
+
+class MockSentryClient with NoSuchMethodProvider implements SentryClient {
+ List captureTransactionCalls = [];
+
+ @override
+ Future captureTransaction(
+ SentryTransaction transaction, {
+ Scope? scope,
+ SentryTraceContextHeader? traceContext,
+ }) async {
+ captureTransactionCalls
+ .add(CaptureTransactionCall(transaction, traceContext));
+ return transaction.eventId;
+ }
+}
+
+class CaptureTransactionCall {
+ final SentryTransaction transaction;
+ final SentryTraceContextHeader? traceContext;
+
+ CaptureTransactionCall(this.transaction, this.traceContext);
+}
diff --git a/file/test/no_such_method_provider.dart b/file/test/no_such_method_provider.dart
new file mode 100644
index 0000000000..64253e9651
--- /dev/null
+++ b/file/test/no_such_method_provider.dart
@@ -0,0 +1,7 @@
+mixin NoSuchMethodProvider {
+ @override
+ void noSuchMethod(Invocation invocation) {
+ 'Method ${invocation.memberName} was called '
+ 'with arguments ${invocation.positionalArguments}';
+ }
+}
diff --git a/file/test/sentry_file_extension_test.dart b/file/test/sentry_file_extension_test.dart
new file mode 100644
index 0000000000..73eb94beff
--- /dev/null
+++ b/file/test/sentry_file_extension_test.dart
@@ -0,0 +1,64 @@
+@TestOn('vm')
+
+import 'dart:io';
+
+import 'package:sentry/sentry.dart';
+import 'package:sentry_file/sentry_file.dart';
+import 'package:test/test.dart';
+
+import 'mock_platform_checker.dart';
+import 'mock_sentry_client.dart';
+
+void main() {
+ group('$File extension', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ test('io performance enabled wraps file', () async {
+ final sut = fixture.getSut(
+ tracesSampleRate: 1.0,
+ );
+
+ expect(sut is SentryFile, true);
+ });
+
+ test('io performance disabled does not wrap file', () async {
+ final sut = fixture.getSut(
+ tracesSampleRate: null,
+ );
+
+ expect(sut is SentryFile, false);
+ });
+
+ test('web does not wrap file', () async {
+ final sut = fixture.getSut(
+ tracesSampleRate: 1.0,
+ isWeb: true,
+ );
+
+ expect(sut is SentryFile, false);
+ });
+ });
+}
+
+class Fixture {
+ final options = SentryOptions(dsn: fakeDsn);
+ late Hub hub;
+
+ File getSut({
+ double? tracesSampleRate,
+ bool isWeb = false,
+ }) {
+ options.tracesSampleRate = tracesSampleRate;
+ options.platformChecker = MockPlatformChecker(isWeb);
+
+ hub = Hub(options);
+
+ final file = File('test_resources/testfile.txt');
+
+ return file.sentryTrace(hub: hub);
+ }
+}
diff --git a/file/test/sentry_file_test.dart b/file/test/sentry_file_test.dart
new file mode 100644
index 0000000000..74b10ac8cc
--- /dev/null
+++ b/file/test/sentry_file_test.dart
@@ -0,0 +1,546 @@
+// ignore_for_file: invalid_use_of_internal_member
+
+@TestOn('vm')
+
+import 'dart:io';
+
+import 'package:sentry/sentry.dart';
+import 'package:sentry_file/sentry_file.dart';
+import 'package:test/test.dart';
+
+import 'mock_sentry_client.dart';
+
+typedef Callback = T Function();
+
+void main() {
+ group('$SentryFile copy', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan(bool async) {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.context.operation, 'file.copy');
+ expect(span.data['file.size'], 7);
+ expect(span.data['file.async'], async);
+ expect(span.context.description, 'testfile.txt');
+ expect(
+ (span.data['file.path'] as String)
+ .endsWith('test_resources/testfile.txt'),
+ true);
+ }
+
+ test('async', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = await sut.copy('test_resources/testfile_copy.txt');
+
+ await tr.finish();
+
+ expect(await newFile.exists(), true);
+
+ expect(sut.uri.toFilePath(), isNot(newFile.uri.toFilePath()));
+
+ _assertSpan(true);
+
+ await newFile.delete();
+ });
+
+ test('sync', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = sut.copySync('test_resources/testfile_copy.txt');
+
+ await tr.finish();
+
+ expect(newFile.existsSync(), true);
+
+ expect(sut.uri.toFilePath(), isNot(newFile.uri.toFilePath()));
+
+ _assertSpan(false);
+
+ newFile.deleteSync();
+ });
+ });
+
+ group('$SentryFile create', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan(bool async, {int? size = 0}) {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.context.operation, 'file.write');
+ expect(span.data['file.size'], size);
+ expect(span.data['file.async'], async);
+ expect(span.context.description, 'testfile_create.txt');
+ expect(
+ (span.data['file.path'] as String)
+ .endsWith('test_resources/testfile_create.txt'),
+ true);
+ }
+
+ test('async', () async {
+ final file = File('test_resources/testfile_create.txt');
+ expect(await file.exists(), false);
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = await sut.create();
+
+ await tr.finish();
+
+ expect(await newFile.exists(), true);
+
+ _assertSpan(true);
+
+ await newFile.delete();
+ });
+
+ test('sync', () async {
+ final file = File('test_resources/testfile_create.txt');
+ expect(await file.exists(), false);
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ sut.createSync();
+
+ await tr.finish();
+
+ expect(sut.existsSync(), true);
+
+ _assertSpan(false);
+
+ sut.deleteSync();
+ });
+ });
+
+ group('$SentryFile delete', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan(bool async, {int? size = 0}) {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.context.operation, 'file.delete');
+ expect(span.data['file.size'], size);
+ expect(span.data['file.async'], async);
+ expect(span.context.description, 'testfile_delete.txt');
+ expect(
+ (span.data['file.path'] as String)
+ .endsWith('test_resources/testfile_delete.txt'),
+ true);
+ }
+
+ test('async', () async {
+ final file = File('test_resources/testfile_delete.txt');
+ await file.create();
+ expect(await file.exists(), true);
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = await sut.delete();
+
+ await tr.finish();
+
+ expect(await newFile.exists(), false);
+
+ _assertSpan(true);
+ });
+
+ test('sync', () async {
+ final file = File('test_resources/testfile_delete.txt');
+ file.createSync();
+ expect(file.existsSync(), true);
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ sut.deleteSync();
+
+ await tr.finish();
+
+ expect(sut.existsSync(), false);
+
+ _assertSpan(false);
+ });
+ });
+
+ group('$SentryFile open', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan() {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.context.operation, 'file.open');
+ expect(span.data['file.size'], 3535);
+ expect(span.data['file.async'], true);
+ expect(span.context.description, 'sentry.png');
+ expect(
+ (span.data['file.path'] as String)
+ .endsWith('test_resources/sentry.png'),
+ true);
+ }
+
+ test('async', () async {
+ final file = File('test_resources/sentry.png');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = await sut.open();
+
+ await tr.finish();
+
+ await newFile.close();
+
+ _assertSpan();
+ });
+ });
+
+ group('$SentryFile read', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan(String fileName, bool async, {int? size = 0}) {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.context.operation, 'file.read');
+ expect(span.data['file.size'], size);
+ expect(span.data['file.async'], async);
+ expect(span.context.description, fileName);
+ expect(
+ (span.data['file.path'] as String)
+ .endsWith('test_resources/$fileName'),
+ true);
+ }
+
+ test('as bytes async', () async {
+ final file = File('test_resources/sentry.png');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ await sut.readAsBytes();
+
+ await tr.finish();
+
+ _assertSpan('sentry.png', true, size: 3535);
+ });
+
+ test('as bytes sync', () async {
+ final file = File('test_resources/sentry.png');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ sut.readAsBytesSync();
+
+ await tr.finish();
+
+ _assertSpan('sentry.png', false, size: 3535);
+ });
+
+ test('lines async', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ await sut.readAsLines();
+
+ await tr.finish();
+
+ _assertSpan('testfile.txt', true, size: 7);
+ });
+
+ test('lines sync', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ sut.readAsLinesSync();
+
+ await tr.finish();
+
+ _assertSpan('testfile.txt', false, size: 7);
+ });
+
+ test('string async', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ await sut.readAsString();
+
+ await tr.finish();
+
+ _assertSpan('testfile.txt', true, size: 7);
+ });
+
+ test('string sync', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ sut.readAsStringSync();
+
+ await tr.finish();
+
+ _assertSpan('testfile.txt', false, size: 7);
+ });
+ });
+
+ group('$SentryFile rename', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan(bool async, String name) {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.context.operation, 'file.rename');
+ expect(span.data['file.size'], 0);
+ expect(span.data['file.async'], async);
+ expect(span.context.description, name);
+ expect(
+ (span.data['file.path'] as String).endsWith('test_resources/$name'),
+ true);
+ }
+
+ test('async', () async {
+ final file = File('test_resources/old_name.txt');
+ await file.create();
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = await sut.rename('test_resources/new_name.txt');
+
+ await tr.finish();
+
+ expect(await file.exists(), false);
+ expect(await newFile.exists(), true);
+
+ expect(sut.uri.toFilePath(), isNot(newFile.uri.toFilePath()));
+
+ _assertSpan(true, 'old_name.txt');
+
+ await newFile.delete();
+ });
+
+ test('sync', () async {
+ final file = File('test_resources/old_name.txt');
+ file.createSync();
+
+ final sut = fixture.getSut(
+ file,
+ sendDefaultPii: true,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ final newFile = sut.renameSync('test_resources/testfile_copy.txt');
+
+ await tr.finish();
+
+ expect(file.existsSync(), false);
+ expect(newFile.existsSync(), true);
+
+ expect(sut.uri.toFilePath(), isNot(newFile.uri.toFilePath()));
+
+ _assertSpan(false, 'old_name.txt');
+
+ newFile.deleteSync();
+ });
+ });
+
+ group('$SentryOptions config', () {
+ late Fixture fixture;
+
+ setUp(() {
+ fixture = Fixture();
+ });
+
+ void _assertSpan(bool async) {
+ final call = fixture.client.captureTransactionCalls.first;
+ final span = call.transaction.spans.first;
+
+ expect(span.data['file.async'], async);
+ expect(span.data['file.path'], null);
+ }
+
+ test('does not add file path if sendDefaultPii is disabled async',
+ () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ await sut.readAsBytes();
+
+ await tr.finish();
+
+ _assertSpan(true);
+ });
+
+ test('does not add file path if sendDefaultPii is disabled sync', () async {
+ final file = File('test_resources/testfile.txt');
+
+ final sut = fixture.getSut(
+ file,
+ tracesSampleRate: 1.0,
+ );
+
+ final tr = fixture.hub.startTransaction('name', 'op', bindToScope: true);
+
+ sut.readAsBytesSync();
+
+ await tr.finish();
+
+ _assertSpan(false);
+ });
+
+ test('add SentryFileTracing integration', () async {
+ final file = File('test_resources/testfile.txt');
+
+ fixture.getSut(
+ file,
+ tracesSampleRate: 1.0,
+ );
+
+ expect(fixture.hub.options.sdk.integrations.contains('SentryFileTracing'),
+ true);
+ });
+ });
+}
+
+class Fixture {
+ final client = MockSentryClient();
+ final options = SentryOptions(dsn: fakeDsn);
+ late Hub hub;
+
+ SentryFile getSut(
+ File file, {
+ bool sendDefaultPii = false,
+ double? tracesSampleRate,
+ }) {
+ options.sendDefaultPii = sendDefaultPii;
+ options.tracesSampleRate = tracesSampleRate;
+
+ hub = Hub(options);
+ hub.bindClient(client);
+ return SentryFile(file, hub: hub);
+ }
+}
diff --git a/file/test/version_test.dart b/file/test/version_test.dart
new file mode 100644
index 0000000000..57d12e357a
--- /dev/null
+++ b/file/test/version_test.dart
@@ -0,0 +1,19 @@
+// ignore_for_file: depend_on_referenced_packages
+
+@TestOn('vm')
+
+import 'dart:io';
+
+import 'package:sentry/src/version.dart';
+import 'package:test/test.dart';
+import 'package:yaml/yaml.dart' as yaml;
+
+void main() {
+ group('sdkVersion', () {
+ test('matches that of pubspec.yaml', () {
+ final dynamic pubspec =
+ yaml.loadYaml(File('pubspec.yaml').readAsStringSync());
+ expect(sdkVersion, pubspec['version']);
+ });
+ });
+}
diff --git a/file/test_resources/sentry.png b/file/test_resources/sentry.png
new file mode 100644
index 0000000000..2225be472d
Binary files /dev/null and b/file/test_resources/sentry.png differ
diff --git a/file/test_resources/testfile.txt b/file/test_resources/testfile.txt
new file mode 100644
index 0000000000..96c906756d
--- /dev/null
+++ b/file/test_resources/testfile.txt
@@ -0,0 +1 @@
+foo bar
\ No newline at end of file
diff --git a/flutter/example/pubspec.yaml b/flutter/example/pubspec.yaml
index 6d9d3a2fa0..2f899397fb 100644
--- a/flutter/example/pubspec.yaml
+++ b/flutter/example/pubspec.yaml
@@ -21,6 +21,7 @@ dependencies:
dio: ^4.0.0
logging: ^1.0.0
package_info_plus: ^3.0.0
+ path_provider: ^2.0.0
dev_dependencies:
flutter_lints: ^2.0.0
diff --git a/min_version_test/lib/main.dart b/min_version_test/lib/main.dart
index f4b277b185..e70e318bf2 100644
--- a/min_version_test/lib/main.dart
+++ b/min_version_test/lib/main.dart
@@ -1,25 +1,52 @@
import 'package:flutter/material.dart';
+import 'dart:io' if (dart.library.html) 'dart:html';
+
import 'package:logging/logging.dart';
import 'package:dio/dio.dart';
+import 'package:sentry/sentry.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_logging/sentry_logging.dart';
+import 'package:sentry_file/sentry_file.dart';
// ATTENTION: Change the DSN below with your own to see the events in Sentry. Get one at sentry.io
const String _exampleDsn =
'https://e85b375ffb9f43cf8bdf9787768149e0@o447951.ingest.sentry.io/5428562';
Future main() async {
- await SentryFlutter.init(
- (options) {
- options.dsn = _exampleDsn;
- options.addIntegration(LoggingIntegration());
- },
- // Init your App.
- appRunner: () => runApp(const MyApp()),
- );
+ await setupSentry(() => runApp(
+ SentryScreenshotWidget(
+ child: SentryUserInteractionWidget(
+ child: DefaultAssetBundle(
+ bundle: SentryAssetBundle(enableStructuredDataTracing: true),
+ child: const MyApp(),
+ ),
+ ),
+ ),
+ ));
+}
+
+Future setupSentry(AppRunner appRunner) async {
+ await SentryFlutter.init((options) {
+ options.dsn = _exampleDsn;
+ options.tracesSampleRate = 1.0;
+ options.attachThreads = true;
+ options.enableWindowMetricBreadcrumbs = true;
+ options.addIntegration(LoggingIntegration());
+ options.sendDefaultPii = true;
+ options.reportSilentFlutterErrors = true;
+ options.enableNdkScopeSync = true;
+ options.enableUserInteractionTracing = true;
+ options.attachScreenshot = true;
+ // We can enable Sentry debug logging during development. This is likely
+ // going to log too much for your app, but can be useful when figuring out
+ // configuration issues, e.g. finding out why your events are not uploaded.
+ options.debug = true;
+ },
+ // Init your App.
+ appRunner: appRunner);
}
class MyApp extends StatelessWidget {
@@ -70,6 +97,12 @@ class _MyHomePageState extends State {
Future _incrementCounter() async {
setState(() async {
+ final transaction = Sentry.startTransaction(
+ 'incrementCounter',
+ 'task',
+ bindToScope: true,
+ );
+
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
@@ -78,12 +111,19 @@ class _MyHomePageState extends State {
_counter++;
final dio = Dio();
- dio.addSentry();
+ dio.addSentry(captureFailedRequests: true);
final log = Logger('_MyHomePageState');
+
try {
- await dio.get('https://flutter.dev/');
+ final file = File('response.txt');
+ final sentryFile = file.sentryTrace();
+ final response = await dio.get('https://flutter.dev/');
+ await sentryFile.writeAsString(response.data ?? 'no response');
+
+ await transaction.finish(status: SpanStatus.ok());
} catch (exception, stackTrace) {
log.info(exception.toString(), exception, stackTrace);
+ await transaction.finish(status: SpanStatus.internalError());
}
});
}
diff --git a/min_version_test/pubspec.yaml b/min_version_test/pubspec.yaml
index 368deb46d7..4ecca9a599 100644
--- a/min_version_test/pubspec.yaml
+++ b/min_version_test/pubspec.yaml
@@ -34,6 +34,7 @@ dependencies:
sentry_flutter:
sentry_dio:
sentry_logging:
+ sentry_file:
dio: ^4.0.0
logging: ^1.0.0
@@ -52,6 +53,8 @@ dependency_overrides:
path: ../dio
sentry_logging:
path: ../logging
+ sentry_file:
+ path: ../file
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh
index 6907015617..e8c23baa7b 100755
--- a/scripts/bump-version.sh
+++ b/scripts/bump-version.sh
@@ -10,7 +10,7 @@ NEW_VERSION="${2}"
echo "Current version: ${OLD_VERSION}"
echo "Bumping version: ${NEW_VERSION}"
-for pkg in {dart,flutter,logging,dio}; do
+for pkg in {dart,flutter,logging,dio,file}; do
# Bump version in pubspec.yaml
perl -pi -e "s/^version: .*/version: $NEW_VERSION/" $pkg/pubspec.yaml
# Bump sentry dependency version in pubspec.yaml