Skip to content

Commit

Permalink
Feature: Add http response to event (#934)
Browse files Browse the repository at this point in the history
  • Loading branch information
ueman authored Jul 25, 2022
1 parent 982893c commit 94daf24
Show file tree
Hide file tree
Showing 11 changed files with 469 additions and 95 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Unreleased

### Features

* Dio Integration adds response data ([#934](https://github.com/getsentry/sentry-dart/pull/934))

## 6.7.0

### Fixes
Expand Down
3 changes: 2 additions & 1 deletion dart/lib/src/protocol.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export 'protocol/mechanism.dart';
export 'protocol/sentry_message.dart';
export 'protocol/sentry_operating_system.dart';
export 'protocol/sentry_request.dart';
export 'protocol/sentry_response.dart';
export 'protocol/sdk_info.dart';
export 'protocol/sdk_version.dart';
export 'protocol/sentry_event.dart';
Expand All @@ -22,7 +23,7 @@ export 'protocol/sentry_runtime.dart';
export 'protocol/sentry_stack_frame.dart';
export 'protocol/sentry_stack_trace.dart';
export 'protocol/sentry_user.dart';
export 'protocol/max_request_body_size.dart';
export 'protocol/max_body_size.dart';
export 'protocol/sentry_culture.dart';
export 'protocol/sentry_thread.dart';
export 'sentry_event_like.dart';
Expand Down
24 changes: 24 additions & 0 deletions dart/lib/src/protocol/contexts.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'dart:collection';

import 'package:meta/meta.dart';

import '../protocol.dart';

/// The context interfaces provide additional context data.
Expand All @@ -18,6 +20,7 @@ class Contexts extends MapView<String, dynamic> {
SentryGpu? gpu,
SentryCulture? culture,
SentryTraceContext? trace,
SentryResponse? response,
}) : super({
SentryDevice.type: device,
SentryOperatingSystem.type: operatingSystem,
Expand All @@ -27,6 +30,7 @@ class Contexts extends MapView<String, dynamic> {
SentryGpu.type: gpu,
SentryCulture.type: culture,
SentryTraceContext.type: trace,
SentryResponse.type: response,
});

/// Deserializes [Contexts] from JSON [Map].
Expand Down Expand Up @@ -57,6 +61,9 @@ class Contexts extends MapView<String, dynamic> {
runtimes: data[SentryRuntime.type] != null
? [SentryRuntime.fromJson(Map.from(data[SentryRuntime.type]))]
: null,
response: data[SentryResponse.type] != null
? SentryResponse.fromJson(Map.from(data[SentryResponse.type]))
: null,
);

data.keys
Expand Down Expand Up @@ -126,6 +133,12 @@ class Contexts extends MapView<String, dynamic> {

set trace(SentryTraceContext? trace) => this[SentryTraceContext.type] = trace;

/// Response context for a HTTP response.
@experimental
SentryResponse? get response => this[SentryResponse.type];

set response(SentryResponse? value) => this[SentryResponse.type] = value;

/// Produces a [Map] that can be serialized to JSON.
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
Expand Down Expand Up @@ -174,6 +187,13 @@ class Contexts extends MapView<String, dynamic> {
}
break;

case SentryResponse.type:
final responseMap = response?.toJson();
if (responseMap?.isNotEmpty ?? false) {
json[SentryResponse.type] = responseMap;
}
break;

case SentryTraceContext.type:
final traceMap = trace?.toJson();
if (traceMap?.isNotEmpty ?? false) {
Expand Down Expand Up @@ -230,6 +250,7 @@ class Contexts extends MapView<String, dynamic> {
culture: culture?.clone(),
gpu: gpu?.clone(),
trace: trace?.clone(),
response: response?.clone(),
runtimes: runtimes.map((runtime) => runtime.clone()).toList(),
)..addEntries(
entries.where((element) => !_defaultFields.contains(element.key)),
Expand All @@ -247,6 +268,7 @@ class Contexts extends MapView<String, dynamic> {
SentryCulture? culture,
SentryGpu? gpu,
SentryTraceContext? trace,
SentryResponse? response,
}) =>
Contexts(
device: device ?? this.device,
Expand All @@ -257,6 +279,7 @@ class Contexts extends MapView<String, dynamic> {
gpu: gpu ?? this.gpu,
culture: culture ?? this.culture,
trace: trace ?? this.trace,
response: response ?? this.response,
)..addEntries(
entries.where((element) => !_defaultFields.contains(element.key)),
);
Expand All @@ -271,5 +294,6 @@ class Contexts extends MapView<String, dynamic> {
SentryBrowser.type,
SentryCulture.type,
SentryTraceContext.type,
SentryResponse.type,
];
}
79 changes: 79 additions & 0 deletions dart/lib/src/protocol/max_body_size.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// See https://docs.sentry.io/platforms/dotnet/guides/aspnetcore/configuration/options/#max-request-body-size
import 'package:meta/meta.dart';

const _mediumSize = 10000;
const _smallSize = 4000;

/// Describes the size of http request bodies which should be added to an event
enum MaxRequestBodySize {
/// Request bodies are never sent
never,

/// Only small request bodies will be captured where the cutoff for small
/// depends on the SDK (typically 4KB)
small,

/// Medium and small requests will be captured (typically 10KB)
medium,

/// The SDK will always capture the request body for as long as Sentry can
/// make sense of it
always,
}

extension MaxRequestBodySizeX on MaxRequestBodySize {
bool shouldAddBody(int contentLength) {
if (this == MaxRequestBodySize.never) {
return false;
}
if (this == MaxRequestBodySize.always) {
return true;
}
if (this == MaxRequestBodySize.medium && contentLength <= _mediumSize) {
return true;
}

if (this == MaxRequestBodySize.small && contentLength <= _smallSize) {
return true;
}
return false;
}
}

/// Describes the size of http response bodies which should be added to an event
/// This enum might be removed at any time.
@experimental
enum MaxResponseBodySize {
/// Response bodies are never sent
never,

/// Only small response bodies will be captured where the cutoff for small
/// depends on the SDK (typically 4KB)
small,

/// Medium and small response will be captured (typically 10KB)
medium,

/// The SDK will always capture the request body for as long as Sentry can
/// make sense of it
always,
}

extension MaxResponseBodySizeX on MaxResponseBodySize {
bool shouldAddBody(int contentLength) {
if (this == MaxResponseBodySize.never) {
return false;
}
if (this == MaxResponseBodySize.always) {
return true;
}
if (this == MaxResponseBodySize.medium && contentLength <= _mediumSize) {
return true;
}

if (this == MaxResponseBodySize.small && contentLength <= _smallSize) {
return true;
}
return false;
}
}
36 changes: 0 additions & 36 deletions dart/lib/src/protocol/max_request_body_size.dart

This file was deleted.

107 changes: 107 additions & 0 deletions dart/lib/src/protocol/sentry_response.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import 'package:meta/meta.dart';
import 'contexts.dart';

/// The response interface contains information on a HTTP request related to the event.
/// This is an experimental feature. It might be removed at any time.
@experimental
@immutable
class SentryResponse {
/// The tpye of this class in the [Contexts] field
static const String type = 'response';

/// The URL of the response if available.
/// This might be the redirected URL
final String? url;

/// Indicates whether or not the response is the result of a redirect
/// (that is, its URL list has more than one entry).
final bool? redirected;

/// The body of the response
final Object? body;

/// The HTTP status code of the response.
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
final int? statusCode;

/// The status message for the corresponding [statusCode]
final String? status;

/// An immutable dictionary of submitted headers.
/// If a header appears multiple times it,
/// needs to be merged according to the HTTP standard for header merging.
/// Header names are treated case-insensitively by Sentry.
Map<String, String> get headers => Map.unmodifiable(_headers ?? const {});

final Map<String, String>? _headers;

Map<String, String> get other => Map.unmodifiable(_other ?? const {});

final Map<String, String>? _other;

SentryResponse({
this.url,
this.body,
this.redirected,
this.statusCode,
this.status,
Map<String, String>? headers,
Map<String, String>? other,
}) : _headers = headers != null ? Map.from(headers) : null,
_other = other != null ? Map.from(other) : null;

/// Deserializes a [SentryResponse] from JSON [Map].
factory SentryResponse.fromJson(Map<String, dynamic> json) {
return SentryResponse(
url: json['url'],
headers: json['headers'],
other: json['other'],
body: json['body'],
statusCode: json['status_code'],
status: json['status'],
redirected: json['redirected'],
);
}

/// Produces a [Map] that can be serialized to JSON.
Map<String, dynamic> toJson() {
return <String, dynamic>{
if (url != null) 'url': url,
if (headers.isNotEmpty) 'headers': headers,
if (other.isNotEmpty) 'other': other,
if (redirected != null) 'redirected': redirected,
if (body != null) 'body': body,
if (status != null) 'status': status,
if (statusCode != null) 'status_code': statusCode,
};
}

SentryResponse copyWith({
String? url,
bool? redirected,
int? statusCode,
String? status,
Object? body,
Map<String, String>? headers,
Map<String, String>? other,
}) =>
SentryResponse(
url: url ?? this.url,
headers: headers ?? _headers,
redirected: redirected ?? this.redirected,
other: other ?? _other,
body: body ?? this.body,
status: status ?? this.status,
statusCode: statusCode ?? this.statusCode,
);

SentryResponse clone() => SentryResponse(
body: body,
headers: headers,
other: other,
redirected: redirected,
status: status,
statusCode: statusCode,
url: url,
);
}
Loading

0 comments on commit 94daf24

Please sign in to comment.