Skip to content

Commit

Permalink
Merge branch 'main' into feat/stack-frame-excludes
Browse files Browse the repository at this point in the history
  • Loading branch information
denrase committed Nov 5, 2024
2 parents eef0828 + 7c7c64f commit e9b16e8
Show file tree
Hide file tree
Showing 14 changed files with 289 additions and 134 deletions.
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,36 @@

## Unreleased

### Features

- Add screenshot to `SentryFeedbackWidget` ([#2369](https://github.com/getsentry/sentry-dart/pull/2369))
- Use `SentryFlutter.captureScreenshot` to create a screenshot attachment
- Call `SentryFeedbackWidget` with this attachment to add it to the user feedback

```dart
final id = await Sentry.captureMessage('UserFeedback');
final screenshot = await SentryFlutter.captureScreenshot();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SentryFeedbackWidget(
associatedEventId: id,
screenshot: screenshot,
),
fullscreenDialog: true,
),
);
```

### Enhancements

- Remove `sentry` frames if SDK falls back to current stack trace ([#2351](https://github.com/getsentry/sentry-dart/pull/2351))
- Cache parsed DSN ([#2365](https://github.com/getsentry/sentry-dart/pull/2365))

- Handle backpressure earlier in pipeline ([#2371](https://github.com/getsentry/sentry-dart/pull/2371))
- Drops max un-awaited parallel tasks earlier, so event processors & callbacks are not executed for them.
- Change by setting `SentryOptions.maxQueueSize`. Default is 30.

## 8.10.0-beta.2

### Fixes
Expand Down
69 changes: 49 additions & 20 deletions dart/lib/src/sentry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import 'sentry_options.dart';
import 'sentry_user_feedback.dart';
import 'tracing.dart';
import 'sentry_attachment/sentry_attachment.dart';
import 'transport/data_category.dart';
import 'transport/task_queue.dart';

/// Configuration options callback
typedef OptionsConfiguration = FutureOr<void> Function(SentryOptions);
Expand All @@ -34,6 +36,7 @@ typedef AppRunner = FutureOr<void> Function();
/// Sentry SDK main entry point
class Sentry {
static Hub _hub = NoOpHub();
static TaskQueue<SentryId> _taskQueue = NoOpTaskQueue();

Sentry._();

Expand All @@ -56,6 +59,11 @@ class Sentry {
if (config is Future) {
await config;
}
_taskQueue = DefaultTaskQueue<SentryId>(
sentryOptions.maxQueueSize,
sentryOptions.logger,
sentryOptions.recorder,
);
} catch (exception, stackTrace) {
sentryOptions.logger(
SentryLevel.error,
Expand Down Expand Up @@ -181,12 +189,17 @@ class Sentry {
Hint? hint,
ScopeCallback? withScope,
}) =>
_hub.captureEvent(
event,
stackTrace: stackTrace,
hint: hint,
withScope: withScope,
);
_taskQueue.enqueue(
() => _hub.captureEvent(
event,
stackTrace: stackTrace,
hint: hint,
withScope: withScope,
),
SentryId.empty(),
event.type != null
? DataCategory.fromItemType(event.type!)
: DataCategory.unknown);

/// Reports the [throwable] and optionally its [stackTrace] to Sentry.io.
static Future<SentryId> captureException(
Expand All @@ -195,11 +208,15 @@ class Sentry {
Hint? hint,
ScopeCallback? withScope,
}) =>
_hub.captureException(
throwable,
stackTrace: stackTrace,
hint: hint,
withScope: withScope,
_taskQueue.enqueue(
() => _hub.captureException(
throwable,
stackTrace: stackTrace,
hint: hint,
withScope: withScope,
),
SentryId.empty(),
DataCategory.error,
);

/// Reports a [message] to Sentry.io.
Expand All @@ -211,13 +228,17 @@ class Sentry {
Hint? hint,
ScopeCallback? withScope,
}) =>
_hub.captureMessage(
message,
level: level,
template: template,
params: params,
hint: hint,
withScope: withScope,
_taskQueue.enqueue(
() => _hub.captureMessage(
message,
level: level,
template: template,
params: params,
hint: hint,
withScope: withScope,
),
SentryId.empty(),
DataCategory.unknown,
);

/// Reports a [userFeedback] to Sentry.io.
Expand All @@ -236,7 +257,15 @@ class Sentry {
Hint? hint,
ScopeCallback? withScope,
}) =>
_hub.captureFeedback(feedback, hint: hint, withScope: withScope);
_taskQueue.enqueue(
() => _hub.captureFeedback(
feedback,
hint: hint,
withScope: withScope,
),
SentryId.empty(),
DataCategory.unknown,
);

/// Close the client SDK
static Future<void> close() async {
Expand All @@ -251,7 +280,7 @@ class Sentry {
/// Last event id recorded by the current Hub
static SentryId get lastEventId => _hub.lastEventId;

/// Adds a breacrumb to the current Scope
/// Adds a breadcrumb to the current Scope
static Future<void> addBreadcrumb(Breadcrumb crumb, {Hint? hint}) =>
_hub.addBreadcrumb(crumb, hint: hint);

Expand Down
10 changes: 1 addition & 9 deletions dart/lib/src/sentry_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import 'transport/http_transport.dart';
import 'transport/noop_transport.dart';
import 'transport/rate_limiter.dart';
import 'transport/spotlight_http_transport.dart';
import 'transport/task_queue.dart';
import 'utils/isolate_utils.dart';
import 'utils/regex_utils.dart';
import 'utils/stacktrace_utils.dart';
Expand All @@ -40,10 +39,6 @@ const _defaultIpAddress = '{{auto}}';
/// Logs crash reports and events to the Sentry.io service.
class SentryClient {
final SentryOptions _options;
late final _taskQueue = TaskQueue<SentryId?>(
_options.maxQueueSize,
_options.logger,
);

final Random? _random;

Expand Down Expand Up @@ -640,9 +635,6 @@ class SentryClient {
Future<SentryId?> _attachClientReportsAndSend(SentryEnvelope envelope) {
final clientReport = _options.recorder.flush();
envelope.addClientReport(clientReport);
return _taskQueue.enqueue(
() => _options.transport.send(envelope),
SentryId.empty(),
);
return _options.transport.send(envelope);
}
}
42 changes: 37 additions & 5 deletions dart/lib/src/transport/task_queue.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
import 'dart:async';

import 'package:meta/meta.dart';

import '../../sentry.dart';
import '../client_reports/client_report_recorder.dart';
import '../client_reports/discard_reason.dart';
import 'data_category.dart';

typedef Task<T> = Future<T> Function();

class TaskQueue<T> {
TaskQueue(this._maxQueueSize, this._logger);
@internal
abstract class TaskQueue<T> {
Future<T> enqueue(Task<T> task, T fallbackResult, DataCategory category);
}

@internal
class DefaultTaskQueue<T> implements TaskQueue<T> {
DefaultTaskQueue(this._maxQueueSize, this._logger, this._recorder);

final int _maxQueueSize;
final SentryLogger _logger;
final ClientReportRecorder _recorder;

int _queueCount = 0;

Future<T> enqueue(Task<T> task, T fallbackResult) async {
@override
Future<T> enqueue(
Task<T> task,
T fallbackResult,
DataCategory category,
) async {
if (_queueCount >= _maxQueueSize) {
_logger(SentryLevel.warning,
'Task dropped due to backpressure. Avoid capturing in a tight loop.');
_recorder.recordLostEvent(DiscardReason.queueOverflow, category);
_logger(
SentryLevel.warning,
'Task dropped due to reaching max ($_maxQueueSize} parallel tasks.).',
);
return fallbackResult;
} else {
_queueCount++;
Expand All @@ -27,3 +47,15 @@ class TaskQueue<T> {
}
}
}

@internal
class NoOpTaskQueue<T> implements TaskQueue<T> {
@override
Future<T> enqueue(
Task<T> task,
T fallbackResult,
DataCategory category,
) {
return task();
}
}
91 changes: 12 additions & 79 deletions dart/test/sentry_client_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1690,85 +1690,6 @@ void main() {
expect(envelope.clientReport, clientReport);
});

test('captureEvent adds trace context', () async {
final client = fixture.getSut();

final scope = Scope(fixture.options);
scope.replayId = SentryId.newId();
scope.span =
SentrySpan(fixture.tracer, fixture.tracer.context, MockHub());

await client.captureEvent(fakeEvent, scope: scope);

final envelope = fixture.transport.envelopes.first;
expect(envelope.header.traceContext, isNotNull);
expect(envelope.header.traceContext?.replayId, scope.replayId);
});

test('captureEvent adds attachments from hint', () async {
final attachment = SentryAttachment.fromIntList([], "fixture-fileName");
final hint = Hint.withAttachment(attachment);

final sut = fixture.getSut();
await sut.captureEvent(fakeEvent, hint: hint);

final capturedEnvelope = (fixture.transport).envelopes.first;
final attachmentItem = IterableUtils.firstWhereOrNull(
capturedEnvelope.items,
(SentryEnvelopeItem e) => e.header.type == SentryItemType.attachment,
);
expect(attachmentItem?.header.attachmentType,
SentryAttachment.typeAttachmentDefault);
});

test('captureEvent adds screenshot from hint', () async {
final client = fixture.getSut();
final screenshot =
SentryAttachment.fromScreenshotData(Uint8List.fromList([0, 0, 0, 0]));
final hint = Hint.withScreenshot(screenshot);

await client.captureEvent(fakeEvent, hint: hint);

final capturedEnvelope = (fixture.transport).envelopes.first;
final attachmentItem = capturedEnvelope.items.firstWhereOrNull(
(element) => element.header.type == SentryItemType.attachment);
expect(attachmentItem?.header.fileName, 'screenshot.png');
});

test('captureEvent adds viewHierarchy from hint', () async {
final client = fixture.getSut();
final view = SentryViewHierarchy('flutter');
final attachment = SentryAttachment.fromViewHierarchy(view);
final hint = Hint.withViewHierarchy(attachment);

await client.captureEvent(fakeEvent, hint: hint);

final capturedEnvelope = (fixture.transport).envelopes.first;
final attachmentItem = capturedEnvelope.items.firstWhereOrNull(
(element) => element.header.type == SentryItemType.attachment);

expect(attachmentItem?.header.attachmentType,
SentryAttachment.typeViewHierarchy);
});

test('captureTransaction adds trace context', () async {
final client = fixture.getSut();

final tr = SentryTransaction(fixture.tracer);

final context = SentryTraceContextHeader.fromJson(<String, dynamic>{
'trace_id': '${tr.eventId}',
'public_key': '123',
'replay_id': '456',
});

await client.captureTransaction(tr, traceContext: context);

final envelope = fixture.transport.envelopes.first;
expect(envelope.header.traceContext, isNotNull);
expect(envelope.header.traceContext?.replayId, SentryId.fromId('456'));
});

test('captureUserFeedback calls flush', () async {
final client = fixture.getSut(eventProcessor: DropAllEventProcessor());

Expand Down Expand Up @@ -1985,6 +1906,14 @@ void main() {

expect(capturedEnvelope.header.dsn, fixture.options.dsn);
});
});

group('Spotlight', () {
late Fixture fixture;

setUp(() {
fixture = Fixture();
});

test(
'Spotlight enabled should not set transport to SpotlightHttpTransport on iOS',
Expand Down Expand Up @@ -2066,13 +1995,15 @@ void main() {
final client = fixture.getSut();

final scope = Scope(fixture.options);
scope.replayId = SentryId.newId();
scope.span =
SentrySpan(fixture.tracer, fixture.tracer.context, MockHub());

await client.captureEvent(fakeEvent, scope: scope);

final envelope = fixture.transport.envelopes.first;
expect(envelope.header.traceContext, isNotNull);
expect(envelope.header.traceContext?.replayId, scope.replayId);
});

test('captureTransaction adds trace context', () async {
Expand All @@ -2083,12 +2014,14 @@ void main() {
final context = SentryTraceContextHeader.fromJson(<String, dynamic>{
'trace_id': '${tr.eventId}',
'public_key': '123',
'replay_id': '456',
});

await client.captureTransaction(tr, traceContext: context);

final envelope = fixture.transport.envelopes.first;
expect(envelope.header.traceContext, isNotNull);
expect(envelope.header.traceContext?.replayId, SentryId.fromId('456'));
});

test('captureFeedback adds trace context', () async {
Expand Down
Loading

0 comments on commit e9b16e8

Please sign in to comment.