@@ -5,38 +5,28 @@ import io.sentry.Hint
55import io.sentry.IScopes
66import io.sentry.ISpan
77import io.sentry.SentryDate
8- import io.sentry.SentryLevel
98import io.sentry.SpanDataConvention
109import io.sentry.TypeCheckHint
11- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECTION_EVENT
12- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.CONNECT_EVENT
13- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_BODY_EVENT
14- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.REQUEST_HEADERS_EVENT
15- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_BODY_EVENT
16- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.RESPONSE_HEADERS_EVENT
17- import io.sentry.okhttp.SentryOkHttpEventListener.Companion.SECURE_CONNECT_EVENT
1810import io.sentry.util.Platform
1911import io.sentry.util.UrlUtils
2012import okhttp3.Request
2113import okhttp3.Response
2214import java.util.Locale
2315import java.util.concurrent.ConcurrentHashMap
24- import java.util.concurrent.RejectedExecutionException
16+ import java.util.concurrent.TimeUnit
2517import java.util.concurrent.atomic.AtomicBoolean
2618
2719private const val PROTOCOL_KEY = " protocol"
2820private const val ERROR_MESSAGE_KEY = " error_message"
29- private const val RESPONSE_BODY_TIMEOUT_MILLIS = 800L
3021internal const val TRACE_ORIGIN = " auto.http.okhttp"
3122
3223@Suppress(" TooManyFunctions" )
3324internal class SentryOkHttpEvent (private val scopes : IScopes , private val request : Request ) {
34- private val eventSpans : MutableMap <String , ISpan > = ConcurrentHashMap ()
25+ private val eventDates : MutableMap <String , SentryDate > = ConcurrentHashMap ()
3526 private val breadcrumb: Breadcrumb
36- internal val callRootSpan : ISpan ?
27+ internal val callSpan : ISpan ?
3728 private var response: Response ? = null
3829 private var clientErrorResponse: Response ? = null
39- private val isReadingResponseBody = AtomicBoolean (false )
4030 private val isEventFinished = AtomicBoolean (false )
4131 private val url: String
4232 private val method: String
@@ -50,52 +40,52 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
5040
5141 // We start the call span that will contain all the others
5242 val parentSpan = if (Platform .isAndroid()) scopes.transaction else scopes.span
53- callRootSpan = parentSpan?.startChild(" http.client" , " $method $url " )
54- callRootSpan ?.spanContext?.origin = TRACE_ORIGIN
55- urlDetails.applyToSpan(callRootSpan )
43+ callSpan = parentSpan?.startChild(" http.client" , " $method $url " )
44+ callSpan ?.spanContext?.origin = TRACE_ORIGIN
45+ urlDetails.applyToSpan(callSpan )
5646
5747 // We setup a breadcrumb with all meaningful data
5848 breadcrumb = Breadcrumb .http(url, method)
5949 breadcrumb.setData(" host" , host)
6050 breadcrumb.setData(" path" , encodedPath)
6151
62- // We add the same data to the root call span
63- callRootSpan ?.setData(" url" , url)
64- callRootSpan ?.setData(" host" , host)
65- callRootSpan ?.setData(" path" , encodedPath)
66- callRootSpan ?.setData(SpanDataConvention .HTTP_METHOD_KEY , method.uppercase(Locale .ROOT ))
52+ // We add the same data to the call span
53+ callSpan ?.setData(" url" , url)
54+ callSpan ?.setData(" host" , host)
55+ callSpan ?.setData(" path" , encodedPath)
56+ callSpan ?.setData(SpanDataConvention .HTTP_METHOD_KEY , method.uppercase(Locale .ROOT ))
6757 }
6858
6959 /* *
7060 * Sets the [Response] that will be sent in the breadcrumb [Hint].
71- * Also, it sets the protocol and status code in the breadcrumb and the call root span.
61+ * Also, it sets the protocol and status code in the breadcrumb and the call span.
7262 */
7363 fun setResponse (response : Response ) {
7464 this .response = response
7565 breadcrumb.setData(PROTOCOL_KEY , response.protocol.name)
7666 breadcrumb.setData(" status_code" , response.code)
77- callRootSpan ?.setData(PROTOCOL_KEY , response.protocol.name)
78- callRootSpan ?.setData(SpanDataConvention .HTTP_STATUS_CODE_KEY , response.code)
67+ callSpan ?.setData(PROTOCOL_KEY , response.protocol.name)
68+ callSpan ?.setData(SpanDataConvention .HTTP_STATUS_CODE_KEY , response.code)
7969 }
8070
8171 fun setProtocol (protocolName : String? ) {
8272 if (protocolName != null ) {
8373 breadcrumb.setData(PROTOCOL_KEY , protocolName)
84- callRootSpan ?.setData(PROTOCOL_KEY , protocolName)
74+ callSpan ?.setData(PROTOCOL_KEY , protocolName)
8575 }
8676 }
8777
8878 fun setRequestBodySize (byteCount : Long ) {
8979 if (byteCount > - 1 ) {
9080 breadcrumb.setData(" request_content_length" , byteCount)
91- callRootSpan ?.setData(" http.request_content_length" , byteCount)
81+ callSpan ?.setData(" http.request_content_length" , byteCount)
9282 }
9383 }
9484
9585 fun setResponseBodySize (byteCount : Long ) {
9686 if (byteCount > - 1 ) {
9787 breadcrumb.setData(" response_content_length" , byteCount)
98- callRootSpan ?.setData(SpanDataConvention .HTTP_RESPONSE_CONTENT_LENGTH_KEY , byteCount)
88+ callSpan ?.setData(SpanDataConvention .HTTP_RESPONSE_CONTENT_LENGTH_KEY , byteCount)
9989 }
10090 }
10191
@@ -107,44 +97,33 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
10797 fun setError (errorMessage : String? ) {
10898 if (errorMessage != null ) {
10999 breadcrumb.setData(ERROR_MESSAGE_KEY , errorMessage)
110- callRootSpan ?.setData(ERROR_MESSAGE_KEY , errorMessage)
100+ callSpan ?.setData(ERROR_MESSAGE_KEY , errorMessage)
111101 }
112102 }
113103
114- /* * Starts a span, if the callRootSpan is not null. */
115- fun startSpan (event : String ) {
116- // Find the parent of the span being created. E.g. secureConnect is child of connect
117- val parentSpan = findParentSpan(event)
118- val span = parentSpan?.startChild(" http.client.$event " , " $method $url " ) ? : return
119- if (event == RESPONSE_BODY_EVENT ) {
120- // We save this event is reading the response body, so that it will not be auto-finished
121- isReadingResponseBody.set(true )
122- }
123- span.spanContext.origin = TRACE_ORIGIN
124- eventSpans[event] = span
104+ /* * Record event start if the callRootSpan is not null. */
105+ fun onEventStart (event : String ) {
106+ callSpan ? : return
107+ eventDates[event] = scopes.options.dateProvider.now()
125108 }
126109
127- /* * Finishes a previously started span, and runs [beforeFinish] on it, on its parent and on the call root span. */
128- fun finishSpan (event : String , beforeFinish : ((span: ISpan ) -> Unit )? = null): ISpan ? {
129- val span = eventSpans[event] ? : return null
130- val parentSpan = findParentSpan(event)
131- beforeFinish?.invoke(span)
132- moveThrowableToRootSpan(span)
133- if (parentSpan != null && parentSpan != callRootSpan) {
134- beforeFinish?.invoke(parentSpan)
135- moveThrowableToRootSpan(parentSpan)
136- }
137- callRootSpan?.let { beforeFinish?.invoke(it) }
138- span.finish()
139- return span
110+ /* * Record event finish and runs [beforeFinish] on the call span. */
111+ fun onEventFinish (event : String , beforeFinish : ((span: ISpan ) -> Unit )? = null) {
112+ val eventDate = eventDates.remove(event) ? : return
113+ callSpan ? : return
114+ beforeFinish?.invoke(callSpan)
115+ val eventDurationNanos = scopes.options.dateProvider.now().diff(eventDate)
116+ callSpan.setData(event, TimeUnit .NANOSECONDS .toMillis(eventDurationNanos))
140117 }
141118
142- /* * Finishes the call root span, and runs [beforeFinish] on it. Then a breadcrumb is sent. */
143- fun finishEvent ( finishDate : SentryDate ? = null, beforeFinish : ((span: ISpan ) -> Unit )? = null) {
119+ /* * Finishes the call span, and runs [beforeFinish] on it. Then a breadcrumb is sent. */
120+ fun finish ( beforeFinish : ((span: ISpan ) -> Unit )? = null) {
144121 // If the event already finished, we don't do anything
145122 if (isEventFinished.getAndSet(true )) {
146123 return
147124 }
125+ // We clear any date left, in case an event started, but never finished. Shouldn't happen.
126+ eventDates.clear()
148127 // We put data in the hint and send a breadcrumb
149128 val hint = Hint ()
150129 hint.set(TypeCheckHint .OKHTTP_REQUEST , request)
@@ -153,75 +132,12 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
153132 // We send the breadcrumb even without spans.
154133 scopes.addBreadcrumb(breadcrumb, hint)
155134
156- // No span is created (e.g. no transaction is running)
157- if (callRootSpan == null ) {
158- // We report the client error even without spans.
159- clientErrorResponse?.let {
160- SentryOkHttpUtils .captureClientError(scopes, it.request, it)
161- }
162- return
163- }
164-
165- // We forcefully finish all spans, even if they should already have been finished through finishSpan()
166- eventSpans.values.filter { ! it.isFinished }.forEach {
167- moveThrowableToRootSpan(it)
168- if (finishDate != null ) {
169- it.finish(it.status, finishDate)
170- } else {
171- it.finish()
172- }
173- }
174- beforeFinish?.invoke(callRootSpan)
175- // We report the client error here, after all sub-spans finished, so that it will be bound
176- // to the root call span.
135+ callSpan?.let { beforeFinish?.invoke(it) }
136+ // We report the client error here so that it will be bound to the call span. We send it even if there is no running span.
177137 clientErrorResponse?.let {
178138 SentryOkHttpUtils .captureClientError(scopes, it.request, it)
179139 }
180- if (finishDate != null ) {
181- callRootSpan.finish(callRootSpan.status, finishDate)
182- } else {
183- callRootSpan.finish()
184- }
140+ callSpan?.finish()
185141 return
186142 }
187-
188- /* * Move any throwable from an inner span to the call root span. */
189- private fun moveThrowableToRootSpan (span : ISpan ) {
190- if (span != callRootSpan && span.throwable != null && span.status != null ) {
191- callRootSpan?.throwable = span.throwable
192- callRootSpan?.status = span.status
193- span.throwable = null
194- }
195- }
196-
197- private fun findParentSpan (event : String ): ISpan ? = when (event) {
198- // PROXY_SELECT, DNS, CONNECT and CONNECTION are not children of one another
199- SECURE_CONNECT_EVENT -> eventSpans[CONNECT_EVENT ]
200- REQUEST_HEADERS_EVENT -> eventSpans[CONNECTION_EVENT ]
201- REQUEST_BODY_EVENT -> eventSpans[CONNECTION_EVENT ]
202- RESPONSE_HEADERS_EVENT -> eventSpans[CONNECTION_EVENT ]
203- RESPONSE_BODY_EVENT -> eventSpans[CONNECTION_EVENT ]
204- else -> callRootSpan
205- } ? : callRootSpan
206-
207- fun scheduleFinish (timestamp : SentryDate ) {
208- try {
209- scopes.options.executorService.schedule({
210- if (! isReadingResponseBody.get() &&
211- (eventSpans.values.all { it.isFinished } || callRootSpan?.isFinished != true )
212- ) {
213- finishEvent(timestamp)
214- }
215- }, RESPONSE_BODY_TIMEOUT_MILLIS )
216- } catch (e: RejectedExecutionException ) {
217- scopes.options
218- .logger
219- .log(
220- SentryLevel .ERROR ,
221- " Failed to call the executor. OkHttp span will not be finished " +
222- " automatically. Did you call Sentry.close()?" ,
223- e
224- )
225- }
226- }
227143}
0 commit comments