Skip to content

Commit 471badc

Browse files
authored
[Span First #2]: Add envelope type to Dart layer (#3366)
* Add setAttributeS * Update * Update * Add removeAttribute * Add scope test * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Update * Merge scope * Update * Fix analyzer * Fix analyzer * Add Spanv2 protocol * Add Spanv2 protocol * Update * Rename * Update * Update * Update * Add support for spans protocol * Add envelope structure * Update * Update * Remove buffer * Update * Update * Update * Dedupe item payload creation * Analze * Analyze * Update * Update
1 parent de96551 commit 471badc

File tree

5 files changed

+151
-24
lines changed

5 files changed

+151
-24
lines changed

packages/dart/lib/src/sentry_envelope.dart

Lines changed: 40 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -104,30 +104,32 @@ class SentryEnvelope {
104104
factory SentryEnvelope.fromLogsData(
105105
List<List<int>> encodedLogs,
106106
SdkVersion sdkVersion,
107-
) {
108-
// Create the payload in the format expected by Sentry
109-
// Format: {"items": [log1, log2, ...]}
110-
final builder = BytesBuilder(copy: false);
111-
builder.add(utf8.encode('{"items":['));
112-
for (int i = 0; i < encodedLogs.length; i++) {
113-
if (i > 0) {
114-
builder.add(utf8.encode(','));
115-
}
116-
builder.add(encodedLogs[i]);
117-
}
118-
builder.add(utf8.encode(']}'));
119-
120-
return SentryEnvelope(
121-
SentryEnvelopeHeader(
122-
null,
123-
sdkVersion,
124-
),
125-
[
126-
SentryEnvelopeItem.fromLogsData(
127-
builder.takeBytes(), encodedLogs.length),
128-
],
129-
);
130-
}
107+
) =>
108+
SentryEnvelope(
109+
SentryEnvelopeHeader(null, sdkVersion),
110+
[
111+
SentryEnvelopeItem.fromLogsData(
112+
_buildItemsPayload(encodedLogs), encodedLogs.length)
113+
],
114+
);
115+
116+
/// Create a [SentryEnvelope] containing raw span data payload.
117+
/// This is used by the span buffer to send pre-encoded spans.
118+
@internal
119+
factory SentryEnvelope.fromSpansData(
120+
List<List<int>> encodedSpans,
121+
SdkVersion sdkVersion, {
122+
String? dsn,
123+
SentryTraceContextHeader? traceContext,
124+
}) =>
125+
SentryEnvelope(
126+
SentryEnvelopeHeader(null, sdkVersion,
127+
dsn: dsn, traceContext: traceContext),
128+
[
129+
SentryEnvelopeItem.fromSpansData(
130+
_buildItemsPayload(encodedSpans), encodedSpans.length)
131+
],
132+
);
131133

132134
/// Stream binary data representation of `Envelope` file encoded.
133135
Stream<List<int>> envelopeStream(SentryOptions options) async* {
@@ -160,6 +162,20 @@ class SentryEnvelope {
160162
}
161163
}
162164

165+
/// Builds a payload in the format {"items": [item1, item2, ...]}
166+
static Uint8List _buildItemsPayload(List<List<int>> encodedItems) {
167+
final builder = BytesBuilder(copy: false);
168+
builder.add(utf8.encode('{"items":['));
169+
for (int i = 0; i < encodedItems.length; i++) {
170+
if (i > 0) {
171+
builder.add(utf8.encode(','));
172+
}
173+
builder.add(encodedItems[i]);
174+
}
175+
builder.add(utf8.encode(']}'));
176+
return builder.takeBytes();
177+
}
178+
163179
/// Add an envelope item containing client report data.
164180
void addClientReport(ClientReport? clientReport) {
165181
if (clientReport != null) {

packages/dart/lib/src/sentry_envelope_item.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,21 @@ class SentryEnvelopeItem {
9494
);
9595
}
9696

97+
/// Create a [SentryEnvelopeItem] which holds pre-encoded span data.
98+
/// This is used by the spans buffer to send pre-encoded spans.
99+
@internal
100+
factory SentryEnvelopeItem.fromSpansData(List<int> payload, int spansCount) {
101+
return SentryEnvelopeItem(
102+
SentryEnvelopeItemHeader(
103+
SentryItemType.span,
104+
itemCount: spansCount,
105+
contentType: 'application/vnd.sentry.items.span.v2+json',
106+
),
107+
() => payload,
108+
originalObject: null,
109+
);
110+
}
111+
97112
/// Header with info about type and length of data in bytes.
98113
final SentryEnvelopeItemHeader header;
99114

packages/dart/lib/src/sentry_item_type.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ class SentryItemType {
66
static const String profile = 'profile';
77
static const String statsd = 'statsd';
88
static const String log = 'log';
9+
static const String span = 'span';
910
static const String unknown = '__unknown__';
1011
}

packages/dart/test/sentry_envelope_item_test.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,41 @@ void main() {
134134
expect(actualData, expectedData);
135135
});
136136

137+
test('fromSpansData', () async {
138+
final span1 = jsonEncode({
139+
'trace_id':
140+
Sentry.currentHub.scope.propagationContext.traceId.toString(),
141+
'span_id': SpanId.newId().toString(),
142+
'name': 'GET /users',
143+
'status': 'ok',
144+
'is_segment': true,
145+
'start_timestamp': 1742921669.158209,
146+
'end_timestamp': 1742921669.180536,
147+
});
148+
final span2 = jsonEncode({
149+
'trace_id':
150+
Sentry.currentHub.scope.propagationContext.traceId.toString(),
151+
'span_id': SpanId.newId().toString(),
152+
'name': 'GET /posts',
153+
'status': 'ok',
154+
'is_segment': true,
155+
'start_timestamp': 1742921669.158209,
156+
'end_timestamp': 1742921669.180536,
157+
});
158+
final payload = utf8.encode('{"items":[$span1,$span2]');
159+
final spansCount = 2;
160+
161+
final sut = SentryEnvelopeItem.fromSpansData(payload, spansCount);
162+
163+
expect(
164+
sut.header.contentType, 'application/vnd.sentry.items.span.v2+json');
165+
expect(sut.header.type, SentryItemType.span);
166+
expect(sut.header.itemCount, spansCount);
167+
168+
final actualData = await sut.dataFactory();
169+
expect(actualData, payload);
170+
});
171+
137172
test('fromLogsData', () async {
138173
final payload =
139174
utf8.encode('{"items":[{"test":"data1"},{"test":"data2"}]');

packages/dart/test/sentry_envelope_test.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,66 @@ void main() {
173173
expect(actualItemData, expectedItemData);
174174
});
175175

176+
test('fromSpansData', () async {
177+
final span1 = utf8JsonEncoder.convert({
178+
'trace_id':
179+
Sentry.currentHub.scope.propagationContext.traceId.toString(),
180+
'span_id': SpanId.newId().toString(),
181+
'name': 'GET /users',
182+
'status': 'ok',
183+
'is_segment': true,
184+
'start_timestamp': 1742921669.158209,
185+
'end_timestamp': 1742921669.180536,
186+
});
187+
final span2 = utf8JsonEncoder.convert({
188+
'trace_id':
189+
Sentry.currentHub.scope.propagationContext.traceId.toString(),
190+
'span_id': SpanId.newId().toString(),
191+
'name': 'GET /posts',
192+
'status': 'ok',
193+
'is_segment': true,
194+
'start_timestamp': 1742921669.158209,
195+
'end_timestamp': 1742921669.180536,
196+
});
197+
final spansCount = 2;
198+
final sdkVersion = SdkVersion(
199+
name: 'fixture-name',
200+
version: 'fixture-version',
201+
);
202+
final traceContext = SentryTraceContextHeader.fromJson(<String, dynamic>{
203+
'trace_id': '${SentryId.newId()}',
204+
'public_key': '123',
205+
});
206+
207+
final sut = SentryEnvelope.fromSpansData([span1, span2], sdkVersion,
208+
dsn: fakeDsn, traceContext: traceContext);
209+
210+
expect(sut.header.eventId, null);
211+
expect(sut.header.sdkVersion, sdkVersion);
212+
expect(sut.header.traceContext, traceContext);
213+
expect(sut.header.dsn, fakeDsn);
214+
expect(sut.items.length, 1);
215+
216+
final expectedEnvelopeItem = SentryEnvelopeItem.fromSpansData(
217+
// The envelope should create the final payload with {"items": [...]} wrapper
218+
utf8.encode('{"items":[') +
219+
span1 +
220+
utf8.encode(',') +
221+
span2 +
222+
utf8.encode(']}'),
223+
spansCount,
224+
);
225+
226+
expect(sut.items[0].header.contentType,
227+
expectedEnvelopeItem.header.contentType);
228+
expect(sut.items[0].header.type, expectedEnvelopeItem.header.type);
229+
expect(sut.items[0].header.itemCount, spansCount);
230+
231+
final actualItem = await sut.items[0].dataFactory();
232+
final expectedItem = await expectedEnvelopeItem.dataFactory();
233+
expect(actualItem, expectedItem);
234+
});
235+
176236
test('fromLogsData', () async {
177237
final encodedLogs = [
178238
utf8.encode(

0 commit comments

Comments
 (0)