Skip to content

Commit 42cf10a

Browse files
authored
Merge 6268a19 into 4ca1d7b
2 parents 4ca1d7b + 6268a19 commit 42cf10a

28 files changed

Lines changed: 574 additions & 98 deletions

File tree

sentry-android-core/src/main/java/io/sentry/android/core/ManifestMetadataReader.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ final class ManifestMetadataReader {
7878
static final String CLIENT_REPORTS_ENABLE = "io.sentry.send-client-reports";
7979
static final String COLLECT_ADDITIONAL_CONTEXT = "io.sentry.additional-context";
8080

81+
static final String SEND_DEFAULT_PII = "io.sentry.send-default-pii";
82+
8183
/** ManifestMetadataReader ctor */
8284
private ManifestMetadataReader() {}
8385

@@ -297,6 +299,9 @@ static void applyMetadata(
297299
sdkInfo.setName(readStringNotNull(metadata, logger, SDK_NAME, sdkInfo.getName()));
298300
sdkInfo.setVersion(readStringNotNull(metadata, logger, SDK_VERSION, sdkInfo.getVersion()));
299301
options.setSdkVersion(sdkInfo);
302+
303+
options.setSendDefaultPii(
304+
readBool(metadata, logger, SEND_DEFAULT_PII, options.isSendDefaultPii()));
300305
}
301306

302307
options

sentry-android-okhttp/api/sentry-android-okhttp.api

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public final class io/sentry/android/okhttp/BuildConfig {
99
public final class io/sentry/android/okhttp/SentryOkHttpInterceptor : okhttp3/Interceptor {
1010
public fun <init> ()V
1111
public fun <init> (Lio/sentry/IHub;)V
12-
public fun <init> (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;)V
13-
public synthetic fun <init> (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
12+
public fun <init> (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;)V
13+
public synthetic fun <init> (Lio/sentry/IHub;Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;ZLjava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
1414
public fun <init> (Lio/sentry/android/okhttp/SentryOkHttpInterceptor$BeforeSpanCallback;)V
1515
public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response;
1616
}

sentry-android-okhttp/src/main/java/io/sentry/android/okhttp/SentryOkHttpInterceptor.kt

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,36 @@ package io.sentry.android.okhttp
33
import io.sentry.BaggageHeader
44
import io.sentry.Breadcrumb
55
import io.sentry.Hint
6+
import io.sentry.HttpStatusCodeRange
67
import io.sentry.HubAdapter
78
import io.sentry.IHub
89
import io.sentry.ISpan
10+
import io.sentry.SentryEvent
911
import io.sentry.SpanStatus
10-
import io.sentry.TracePropagationTargets
1112
import io.sentry.TypeCheckHint.OKHTTP_REQUEST
1213
import io.sentry.TypeCheckHint.OKHTTP_RESPONSE
14+
import io.sentry.exception.ExceptionMechanismException
15+
import io.sentry.exception.SentryHttpClientException
16+
import io.sentry.protocol.Mechanism
17+
import io.sentry.util.HttpUtils
18+
import io.sentry.util.PropagationTargetsUtils
19+
import okhttp3.Headers
1320
import okhttp3.Interceptor
1421
import okhttp3.Request
1522
import okhttp3.Response
1623
import java.io.IOException
1724

1825
class SentryOkHttpInterceptor(
1926
private val hub: IHub = HubAdapter.getInstance(),
20-
private val beforeSpan: BeforeSpanCallback? = null
27+
private val beforeSpan: BeforeSpanCallback? = null,
28+
// should this be under the options or here? also define the names
29+
private val captureFailedRequests: Boolean = false,
30+
private val failedRequestStatusCode: List<HttpStatusCodeRange> = listOf(
31+
HttpStatusCodeRange(HttpStatusCodeRange.DEFAULT_MIN, HttpStatusCodeRange.DEFAULT_MAX)),
32+
private val failedRequestsTargets: List<String> = listOf(".*")
2133
) : Interceptor {
2234

35+
constructor() : this(HubAdapter.getInstance())
2336
constructor(hub: IHub) : this(hub, null)
2437
constructor(beforeSpan: BeforeSpanCallback) : this(HubAdapter.getInstance(), beforeSpan)
2538

@@ -38,7 +51,7 @@ class SentryOkHttpInterceptor(
3851
try {
3952
val requestBuilder = request.newBuilder()
4053
if (span != null &&
41-
TracePropagationTargets.contain(hub.options.tracePropagationTargets, request.url.toString())
54+
PropagationTargetsUtils.contain(hub.options.tracePropagationTargets, request.url.toString())
4255
) {
4356
span.toSentryTrace().let {
4457
requestBuilder.addHeader(it.name, it.value)
@@ -53,6 +66,12 @@ class SentryOkHttpInterceptor(
5366
response = chain.proceed(request)
5467
code = response.code
5568
span?.status = SpanStatus.fromHttpStatusCode(code)
69+
70+
// OkHttp errors (4xx, 5xx) don't throw, so it's safe to call within this block.
71+
// breadcrumbs are added on the finally block because we'd like to know if the device
72+
// had an unstable connection or something similar
73+
captureEvent(request, response)
74+
5675
return response
5776
} catch (e: IOException) {
5877
span?.apply {
@@ -104,6 +123,117 @@ class SentryOkHttpInterceptor(
104123
}
105124
}
106125

126+
private fun captureEvent(request: Request, response: Response) {
127+
// not possible to get a parameterized url, but we remove at least the
128+
// query string and the fragment.
129+
// url example: https://api.github.com/users/getsentry/repos/#fragment?query=query
130+
// url will be: https://api.github.com/users/getsentry/repos/
131+
// ideally we'd like a parameterized url: https://api.github.com/users/{user}/repos/
132+
// but that's not possible
133+
var requestUrl = request.url.toString()
134+
135+
val query = request.url.query
136+
if (!query.isNullOrEmpty()) {
137+
requestUrl = requestUrl.replace("?$query", "")
138+
}
139+
140+
val urlFragment = request.url.fragment
141+
if (!urlFragment.isNullOrEmpty()) {
142+
requestUrl = requestUrl.replace("#$urlFragment", "")
143+
}
144+
145+
if (!captureFailedRequests ||
146+
!PropagationTargetsUtils.contain(failedRequestsTargets, requestUrl) ||
147+
!containsStatusCode(response.code)) {
148+
return
149+
}
150+
151+
val mechanism = Mechanism().apply {
152+
type = "SentryOkHttpInterceptor"
153+
}
154+
val exception = SentryHttpClientException(
155+
"Event was captured because the request status code was ${response.code}")
156+
val mechanismException = ExceptionMechanismException(mechanism, exception, Thread.currentThread(), true)
157+
val event = SentryEvent(mechanismException)
158+
159+
val hint = Hint()
160+
hint.set("request", request)
161+
hint.set("response", response)
162+
163+
// remove after fields indexed
164+
// val tags = mutableMapOf<String, String>()
165+
// tags["status_code"] = response.code.toString()
166+
// tags["url"] = requestUrl
167+
168+
val unknownRequestFields = mutableMapOf<String, Any>()
169+
170+
val sentryRequest = io.sentry.protocol.Request().apply {
171+
url = requestUrl
172+
// Cookie is only sent if isSendDefaultPii is enabled
173+
cookies = if (hub.options.isSendDefaultPii) request.headers["Cookie"] else null
174+
method = request.method
175+
queryString = query
176+
headers = getHeaders(request.headers)
177+
fragment = urlFragment
178+
179+
request.body?.contentLength().ifHasValidLength {
180+
// should be mapped in relay and added to the protocol, right now
181+
// relay isn't retaining unmapped fields
182+
unknownRequestFields["body_size"] = it
183+
}
184+
185+
unknown = unknownRequestFields.ifEmpty { null }
186+
}
187+
188+
val sentryResponse = io.sentry.protocol.Response().apply {
189+
// Cookie is only sent if isSendDefaultPii is enabled due to PII
190+
cookies = if (hub.options.isSendDefaultPii) response.headers["Cookie"] else null
191+
headers = getHeaders(response.headers)
192+
statusCode = response.code
193+
194+
response.body?.contentLength().ifHasValidLength {
195+
bodySize = it
196+
}
197+
}
198+
199+
// event.tags = tags
200+
event.request = sentryRequest
201+
event.contexts.setResponse(sentryResponse)
202+
203+
hub.captureEvent(event, hint)
204+
}
205+
206+
private fun containsStatusCode(statusCode: Int): Boolean {
207+
for (item in failedRequestStatusCode) {
208+
if (item.isInRange(statusCode)) {
209+
return true
210+
}
211+
}
212+
return false
213+
}
214+
215+
private fun getHeaders(requestHeaders: Headers): MutableMap<String, String>? {
216+
// Headers are only sent if isSendDefaultPii is enabled due to PII
217+
if (!hub.options.isSendDefaultPii) {
218+
return null
219+
}
220+
221+
val headers = mutableMapOf<String, String>()
222+
223+
for (i in 0 until requestHeaders.size) {
224+
val name = requestHeaders.name(i)
225+
226+
// header is only sent if isn't sensitive
227+
if (HttpUtils.containsSensitiveHeader(name)) {
228+
continue
229+
}
230+
231+
val value = requestHeaders.value(i)
232+
headers[name] = value
233+
}
234+
return headers
235+
}
236+
107237
/**
108238
* The BeforeSpan callback
109239
*/

sentry-apollo-3/src/main/java/io/sentry/apollo3/SentryApollo3HttpInterceptor.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import io.sentry.IHub
1515
import io.sentry.ISpan
1616
import io.sentry.SentryLevel
1717
import io.sentry.SpanStatus
18-
import io.sentry.TracePropagationTargets
1918
import io.sentry.TypeCheckHint
19+
import io.sentry.util.PropagationTargetsUtils
2020

2121
class SentryApollo3HttpInterceptor @JvmOverloads constructor(private val hub: IHub = HubAdapter.getInstance(), private val beforeSpan: BeforeSpanCallback? = null) :
2222
HttpInterceptor {
@@ -33,7 +33,7 @@ class SentryApollo3HttpInterceptor @JvmOverloads constructor(private val hub: IH
3333

3434
var cleanedHeaders = removeSentryInternalHeaders(request.headers).toMutableList()
3535

36-
if (TracePropagationTargets.contain(hub.options.tracePropagationTargets, request.url)) {
36+
if (PropagationTargetsUtils.contain(hub.options.tracePropagationTargets, request.url)) {
3737
val sentryTraceHeader = span.toSentryTrace()
3838
val baggageHeader = span.toBaggageHeader(request.headers.filter { it.name == BaggageHeader.BAGGAGE_HEADER }.map { it.value })
3939
cleanedHeaders.add(HttpHeader(sentryTraceHeader.name, sentryTraceHeader.value))

sentry-openfeign/src/main/java/io/sentry/openfeign/SentryFeignClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
import io.sentry.ISpan;
1414
import io.sentry.SentryTraceHeader;
1515
import io.sentry.SpanStatus;
16-
import io.sentry.TracePropagationTargets;
1716
import io.sentry.util.Objects;
17+
import io.sentry.util.PropagationTargetsUtils;
1818
import java.io.IOException;
1919
import java.util.ArrayList;
2020
import java.util.Collection;
@@ -55,7 +55,7 @@ public Response execute(final @NotNull Request request, final @NotNull Request.O
5555

5656
final RequestWrapper requestWrapper = new RequestWrapper(request);
5757

58-
if (TracePropagationTargets.contain(hub.getOptions().getTracePropagationTargets(), url)) {
58+
if (PropagationTargetsUtils.contain(hub.getOptions().getTracePropagationTargets(), url)) {
5959
final SentryTraceHeader sentryTraceHeader = span.toSentryTrace();
6060
final @Nullable Collection<String> requestBaggageHeader =
6161
request.headers().get(BaggageHeader.BAGGAGE_HEADER);

sentry-samples/sentry-samples-android/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,8 @@
135135
<!-- how to enable the attach screenshot feature-->
136136
<meta-data android:name="io.sentry.attach-screenshot" android:value="true" />
137137

138+
<!-- how to enable the send default pii-->
139+
<meta-data android:name="io.sentry.send-default-pii" android:value="true" />
140+
138141
</application>
139142
</manifest>

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/GitHubService.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ interface GitHubService {
1010
@GET("users/{user}/repos")
1111
fun listRepos(@Path("user") user: String): Call<List<Repo>>
1212

13-
@GET("users/{user}/repos")
13+
// TODO: @GET("users/{user}/repos/#test") throws 404
14+
@GET("users/{user}/repos/")
1415
suspend fun listReposAsync(@Path("user") user: String, @Query("per_page") perPage: Int): List<Repo>
1516
}
1617

1718
class Repo {
1819
val full_name: String = ""
20+
// TODO: throws json serialization error, because it should be a boolean
21+
// val private: Int = 1
1922
}

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/GithubAPI.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
package io.sentry.samples.android
22

3+
import io.sentry.HttpStatusCodeRange
34
import io.sentry.android.okhttp.SentryOkHttpInterceptor
45
import okhttp3.OkHttpClient
56
import retrofit2.Retrofit
67
import retrofit2.converter.gson.GsonConverterFactory
78

89
object GithubAPI {
910

10-
private val client = OkHttpClient.Builder().addInterceptor(SentryOkHttpInterceptor()).build()
11+
private val client = OkHttpClient.Builder().addInterceptor(
12+
SentryOkHttpInterceptor(
13+
captureFailedRequests = true,
14+
// TODO: 200 just for testing
15+
failedRequestStatusCode = listOf(
16+
HttpStatusCodeRange(200, 599)
17+
)
18+
)
19+
).build()
1120

1221
private val retrofit = Retrofit.Builder()
1322
.baseUrl("https://api.github.com/")

sentry-samples/sentry-samples-android/src/main/java/io/sentry/samples/android/compose/ComposeActivity.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,12 @@ fun Github(
9090
val scope = rememberCoroutineScope()
9191

9292
LaunchedEffect(perPage) {
93-
result = GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
93+
try {
94+
result = GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
95+
} catch (e: Throwable) {
96+
// TODO: event processor that converts retrofit HttpException to a proper sentry event
97+
Sentry.captureException(e)
98+
}
9499
}
95100

96101
Column(
@@ -108,7 +113,12 @@ fun Github(
108113
Button(
109114
onClick = {
110115
scope.launch {
111-
result = GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
116+
try {
117+
result =
118+
GithubAPI.service.listReposAsync(user.text, perPage).random().full_name
119+
} catch (e: Throwable) {
120+
Sentry.captureException(e)
121+
}
112122
}
113123
},
114124
modifier = Modifier.padding(top = 32.dp)

sentry-spring/src/main/java/io/sentry/spring/SentryRequestResolver.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,18 @@
33
import com.jakewharton.nopen.annotation.Open;
44
import io.sentry.IHub;
55
import io.sentry.protocol.Request;
6+
import io.sentry.util.HttpUtils;
67
import io.sentry.util.Objects;
7-
import java.util.Arrays;
88
import java.util.Collections;
99
import java.util.Enumeration;
1010
import java.util.HashMap;
11-
import java.util.List;
12-
import java.util.Locale;
1311
import java.util.Map;
1412
import javax.servlet.http.HttpServletRequest;
1513
import org.jetbrains.annotations.NotNull;
1614
import org.jetbrains.annotations.Nullable;
1715

1816
@Open
1917
public class SentryRequestResolver {
20-
private static final List<String> SENSITIVE_HEADERS =
21-
Arrays.asList("X-FORWARDED-FOR", "AUTHORIZATION", "COOKIE");
22-
2318
private final @NotNull IHub hub;
2419

2520
public SentryRequestResolver(final @NotNull IHub hub) {
@@ -46,8 +41,7 @@ Map<String, String> resolveHeadersMap(final @NotNull HttpServletRequest request)
4641
final Map<String, String> headersMap = new HashMap<>();
4742
for (String headerName : Collections.list(request.getHeaderNames())) {
4843
// do not copy personal information identifiable headers
49-
if (hub.getOptions().isSendDefaultPii()
50-
|| !SENSITIVE_HEADERS.contains(headerName.toUpperCase(Locale.ROOT))) {
44+
if (hub.getOptions().isSendDefaultPii() || !HttpUtils.containsSensitiveHeader(headerName)) {
5145
headersMap.put(headerName, toString(request.getHeaders(headerName)));
5246
}
5347
}

0 commit comments

Comments
 (0)