Skip to content
6 changes: 5 additions & 1 deletion dart/lib/src/client_reports/client_report.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import 'package:meta/meta.dart';

import '../../sentry.dart';
import 'discarded_event.dart';
import '../utils.dart';

@internal
class ClientReport {
class ClientReport implements SentryEnvelopeItemPayload {
ClientReport(this.timestamp, this.discardedEvents);

final DateTime? timestamp;
Expand All @@ -27,4 +28,7 @@ class ClientReport {

return json;
}

@override
Future<dynamic> getPayload() => Future.value(toJson());
}
19 changes: 19 additions & 0 deletions dart/lib/src/metrics/metric.dart
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,13 @@ class GaugeMetric extends Metric {

@visibleForTesting
num get last => _last;

num get minimum => _minimum;

num get maximum => _maximum;

num get sum => _sum;

int get count => _count;
}

Expand Down Expand Up @@ -289,3 +293,18 @@ enum MetricType {

const MetricType(this.statsdType);
}

@internal
class MetricsData implements SentryEnvelopeItemPayload {
final Map<int, Iterable<Metric>> buckets;

MetricsData(this.buckets);

@override
Future<dynamic> getPayload() {
return Future.value(buckets.map((key, value) {
final metrics = value.map((metric) => metric.encodeToStatsd(key));
return MapEntry(key.toString(), metrics.toList());
}));
}
}
10 changes: 9 additions & 1 deletion dart/lib/src/protocol/sentry_event.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import 'package:meta/meta.dart';

import '../../sentry.dart';
import '../protocol.dart';
import '../throwable_mechanism.dart';
import '../utils.dart';
import 'access_aware_map.dart';

/// An event to be reported to Sentry.io.
@immutable
class SentryEvent with SentryEventLike<SentryEvent> {
class SentryEvent
with SentryEventLike<SentryEvent>
implements SentryEnvelopeItemPayload {
/// Creates an event.
SentryEvent({
SentryId? eventId,
Expand Down Expand Up @@ -411,4 +414,9 @@ class SentryEvent with SentryEventLike<SentryEvent> {
if (threadJson?.isNotEmpty ?? false) 'threads': {'values': threadJson},
};
}

@override
Future<dynamic> getPayload() {
return Future.value(toJson());
}
}
8 changes: 7 additions & 1 deletion dart/lib/src/sentry_attachment/sentry_attachment.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:typed_data';

import '../../sentry.dart';
import '../protocol/sentry_view_hierarchy.dart';
import '../utils.dart';

Expand All @@ -10,7 +11,7 @@ import '../utils.dart';
typedef ContentLoader = FutureOr<Uint8List> Function();

/// Arbitrary content which gets attached to an event.
class SentryAttachment {
class SentryAttachment implements SentryEnvelopeItemPayload {
/// Standard attachment without special meaning.
static const String typeAttachmentDefault = 'event.attachment';

Expand Down Expand Up @@ -122,4 +123,9 @@ class SentryAttachment {
/// If true, attachment should be added to every transaction.
/// Defaults to false.
final bool addToTransactions;

@override
Future<dynamic> getPayload() async {
return await bytes;
}
}
2 changes: 1 addition & 1 deletion dart/lib/src/sentry_envelope.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ class SentryEnvelope {
sdkVersion,
dsn: dsn,
),
[SentryEnvelopeItem.fromMetrics(metricsBuckets)],
[SentryEnvelopeItem.fromMetrics(MetricsData(metricsBuckets))],
);
}

Expand Down
16 changes: 10 additions & 6 deletions dart/lib/src/sentry_envelope_item.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import 'sentry_item_type.dart';
import 'sentry_envelope_item_header.dart';
import 'sentry_user_feedback.dart';

abstract class SentryEnvelopeItemPayload {
Future<dynamic> getPayload();
}

/// Item holding header information and JSON encoded data.
class SentryEnvelopeItem {
/// The original, non-encoded object, used when direct access to the source data is needed.
Object? originalObject;

SentryEnvelopeItem(this.header, this.dataFactory, {this.originalObject});

/// Creates a [SentryEnvelopeItem] which sends [SentryTransaction].
Expand Down Expand Up @@ -91,12 +92,12 @@ class SentryEnvelopeItem {
}

/// Creates a [SentryEnvelopeItem] which holds several [Metric] data.
factory SentryEnvelopeItem.fromMetrics(Map<int, Iterable<Metric>> buckets) {
factory SentryEnvelopeItem.fromMetrics(MetricsData metricsData) {
final cachedItem = _CachedItem(() async {
final statsd = StringBuffer();
// Encode all metrics of a bucket in statsd format, using the bucket key,
// which is the timestamp of the bucket.
for (final bucket in buckets.entries) {
for (final bucket in metricsData.buckets.entries) {
final encodedMetrics =
bucket.value.map((metric) => metric.encodeToStatsd(bucket.key));
statsd.write(encodedMetrics.join('\n'));
Expand All @@ -110,9 +111,12 @@ class SentryEnvelopeItem {
contentType: 'application/octet-stream',
);
return SentryEnvelopeItem(header, cachedItem.getData,
originalObject: buckets);
originalObject: metricsData);
}

/// The original, non-encoded object, used when direct access to the source data is needed.
SentryEnvelopeItemPayload? originalObject;

/// Header with info about type and length of data in bytes.
final SentryEnvelopeItemHeader header;

Expand Down
8 changes: 7 additions & 1 deletion dart/lib/src/sentry_user_feedback.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import 'package:meta/meta.dart';

import '../sentry.dart';
import 'protocol.dart';
import 'protocol/access_aware_map.dart';

class SentryUserFeedback {
class SentryUserFeedback implements SentryEnvelopeItemPayload {
SentryUserFeedback({
required this.eventId,
this.name,
Expand Down Expand Up @@ -65,4 +66,9 @@ class SentryUserFeedback {
unknown: unknown,
);
}

@override
Future<dynamic> getPayload() {
return Future.value(toJson());
}
}
4 changes: 4 additions & 0 deletions dart/lib/src/transport/transport.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ import '../protocol.dart';
abstract class Transport {
Future<SentryId?> send(SentryEnvelope envelope);
}

abstract class EventTransport {
Future<SentryId?> sendEvent(SentryEvent event);
}
2 changes: 1 addition & 1 deletion flutter/example/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import UIKit
import Flutter
import Sentry

@UIApplicationMain
@main
@objc class AppDelegate: FlutterAppDelegate {
private let _channel = "example.flutter.sentry.io"

Expand Down
9 changes: 7 additions & 2 deletions flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import 'package:sentry_isar/sentry_isar.dart';
import 'package:sentry_logging/sentry_logging.dart';
import 'package:sentry_sqflite/sentry_sqflite.dart';
import 'package:sqflite/sqflite.dart';

// import 'package:sqflite_common_ffi/sqflite_ffi.dart';
// import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
import 'package:universal_platform/universal_platform.dart';
Expand Down Expand Up @@ -82,14 +83,18 @@ Future<void> setupSentry(
// 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;
options.spotlight = Spotlight(enabled: true);
options.enableTimeToFullDisplayTracing = true;
// options.spotlight = Spotlight(enabled: true);
// options.enableTimeToFullDisplayTracing = true;
options.enableMetrics = true;
options.release = '0.0.2-dart';

options.maxRequestBodySize = MaxRequestBodySize.always;
options.maxResponseBodySize = MaxResponseBodySize.always;
options.navigatorKey = navigatorKey;

options.experimental.replay.sessionSampleRate = 1;
options.experimental.replay.errorSampleRate = 1;

_isIntegrationTest = isIntegrationTest;
if (_isIntegrationTest) {
options.dist = '1';
Expand Down
44 changes: 44 additions & 0 deletions flutter/lib/src/integrations/web_sdk_integration.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'dart:async';

import '../../sentry_flutter.dart';
import '../web/sentry_web_binding.dart';

/// Initializes the Javascript SDK with the given options.
class WebSdkIntegration implements Integration<SentryFlutterOptions> {
WebSdkIntegration(this._webBinding);

final SentryWebBinding _webBinding;
SentryFlutterOptions? _options;

@override
FutureOr<void> call(Hub hub, SentryFlutterOptions options) {
_options = options;

try {
_webBinding.init(options);

options.sdk.addIntegration('WebSdkIntegration');
} catch (exception, stackTrace) {
options.logger(
SentryLevel.fatal,
'WebSdkIntegration failed to be installed',
exception: exception,
stackTrace: stackTrace,
);
}
}

@override
FutureOr<void> close() {
try {
_webBinding.close();
} catch (exception, stackTrace) {
_options?.logger(
SentryLevel.fatal,
'WebSdkIntegration failed to be closed',
exception: exception,
stackTrace: stackTrace,
);
}
}
}
28 changes: 25 additions & 3 deletions flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@ import 'dart:ui';

import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'transport/javascript_transport.dart';

import '../sentry_flutter.dart';
import 'event_processor/android_platform_exception_event_processor.dart';
import 'event_processor/flutter_enricher_event_processor.dart';
import 'event_processor/flutter_exception_event_processor.dart';
import 'event_processor/platform_exception_event_processor.dart';
import 'event_processor/widget_event_processor.dart';
import 'file_system_transport.dart';
import 'transport/file_system_transport.dart';
import 'flutter_exception_type_identifier.dart';
import 'frame_callback_handler.dart';
import 'integrations/connectivity/connectivity_integration.dart';
import 'integrations/integrations.dart';
import 'integrations/screenshot_integration.dart';
import 'native/factory.dart';
import 'integrations/web_sdk_integration.dart';
import 'web/factory.dart' as webFactory;
import 'native/factory.dart' as nativeFactory;
import 'native/native_scope_observer.dart';
import 'native/sentry_native_binding.dart';
import 'profiling.dart';
Expand All @@ -25,6 +28,9 @@ import 'span_frame_metrics_collector.dart';
import 'version.dart';
import 'view_hierarchy/view_hierarchy_integration.dart';

import 'web/sentry_web_binding.dart';
import 'web_replay_event_processor.dart';

/// Configuration options callback
typedef FlutterOptionsConfiguration = FutureOr<void> Function(
SentryFlutterOptions);
Expand Down Expand Up @@ -66,7 +72,12 @@ mixin SentryFlutter {
}

if (flutterOptions.platformChecker.hasNativeIntegration) {
_native = createBinding(flutterOptions);
_native = nativeFactory.createBinding(flutterOptions);
}

// todo: maybe makes sense to combine the bindings into a single interface
if (flutterOptions.platformChecker.isWeb) {
_webBinding = webFactory.createBinding(flutterOptions);
}

final platformDispatcher = PlatformDispatcher.instance;
Expand Down Expand Up @@ -129,6 +140,11 @@ mixin SentryFlutter {
options.addScopeObserver(NativeScopeObserver(_native!));
}

if (options.platformChecker.isWeb) {
options.transport = JavascriptEnvelopeTransport(_webBinding!, options);
options.addEventProcessor(WebReplayEventProcessor(_webBinding!, options));
}

options.addEventProcessor(FlutterEnricherEventProcessor(options));
options.addEventProcessor(WidgetEventProcessor());

Expand Down Expand Up @@ -190,6 +206,7 @@ mixin SentryFlutter {

if (platformChecker.isWeb) {
integrations.add(ConnectivityIntegration());
integrations.add(WebSdkIntegration(_webBinding!));
}

// works with Skia, CanvasKit and HTML renderer
Expand Down Expand Up @@ -280,4 +297,9 @@ mixin SentryFlutter {
static set native(SentryNativeBinding? value) => _native = value;

static SentryNativeBinding? _native;

@internal
static SentryWebBinding? get webBinding => _webBinding;

static SentryWebBinding? _webBinding;
}
Loading