@@ -5,38 +5,28 @@ import io.sentry.Hint
5
5
import io.sentry.IScopes
6
6
import io.sentry.ISpan
7
7
import io.sentry.SentryDate
8
- import io.sentry.SentryLevel
9
8
import io.sentry.SpanDataConvention
10
9
import 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
18
10
import io.sentry.util.Platform
19
11
import io.sentry.util.UrlUtils
20
12
import okhttp3.Request
21
13
import okhttp3.Response
22
14
import java.util.Locale
23
15
import java.util.concurrent.ConcurrentHashMap
24
- import java.util.concurrent.RejectedExecutionException
16
+ import java.util.concurrent.TimeUnit
25
17
import java.util.concurrent.atomic.AtomicBoolean
26
18
27
19
private const val PROTOCOL_KEY = " protocol"
28
20
private const val ERROR_MESSAGE_KEY = " error_message"
29
- private const val RESPONSE_BODY_TIMEOUT_MILLIS = 800L
30
21
internal const val TRACE_ORIGIN = " auto.http.okhttp"
31
22
32
23
@Suppress(" TooManyFunctions" )
33
24
internal 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 ()
35
26
private val breadcrumb: Breadcrumb
36
- internal val callRootSpan : ISpan ?
27
+ internal val callSpan : ISpan ?
37
28
private var response: Response ? = null
38
29
private var clientErrorResponse: Response ? = null
39
- private val isReadingResponseBody = AtomicBoolean (false )
40
30
private val isEventFinished = AtomicBoolean (false )
41
31
private val url: String
42
32
private val method: String
@@ -50,52 +40,52 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
50
40
51
41
// We start the call span that will contain all the others
52
42
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 )
56
46
57
47
// We setup a breadcrumb with all meaningful data
58
48
breadcrumb = Breadcrumb .http(url, method)
59
49
breadcrumb.setData(" host" , host)
60
50
breadcrumb.setData(" path" , encodedPath)
61
51
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 ))
67
57
}
68
58
69
59
/* *
70
60
* 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.
72
62
*/
73
63
fun setResponse (response : Response ) {
74
64
this .response = response
75
65
breadcrumb.setData(PROTOCOL_KEY , response.protocol.name)
76
66
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)
79
69
}
80
70
81
71
fun setProtocol (protocolName : String? ) {
82
72
if (protocolName != null ) {
83
73
breadcrumb.setData(PROTOCOL_KEY , protocolName)
84
- callRootSpan ?.setData(PROTOCOL_KEY , protocolName)
74
+ callSpan ?.setData(PROTOCOL_KEY , protocolName)
85
75
}
86
76
}
87
77
88
78
fun setRequestBodySize (byteCount : Long ) {
89
79
if (byteCount > - 1 ) {
90
80
breadcrumb.setData(" request_content_length" , byteCount)
91
- callRootSpan ?.setData(" http.request_content_length" , byteCount)
81
+ callSpan ?.setData(" http.request_content_length" , byteCount)
92
82
}
93
83
}
94
84
95
85
fun setResponseBodySize (byteCount : Long ) {
96
86
if (byteCount > - 1 ) {
97
87
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)
99
89
}
100
90
}
101
91
@@ -107,44 +97,33 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
107
97
fun setError (errorMessage : String? ) {
108
98
if (errorMessage != null ) {
109
99
breadcrumb.setData(ERROR_MESSAGE_KEY , errorMessage)
110
- callRootSpan ?.setData(ERROR_MESSAGE_KEY , errorMessage)
100
+ callSpan ?.setData(ERROR_MESSAGE_KEY , errorMessage)
111
101
}
112
102
}
113
103
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()
125
108
}
126
109
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))
140
117
}
141
118
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) {
144
121
// If the event already finished, we don't do anything
145
122
if (isEventFinished.getAndSet(true )) {
146
123
return
147
124
}
125
+ // We clear any date left, in case an event started, but never finished. Shouldn't happen.
126
+ eventDates.clear()
148
127
// We put data in the hint and send a breadcrumb
149
128
val hint = Hint ()
150
129
hint.set(TypeCheckHint .OKHTTP_REQUEST , request)
@@ -153,75 +132,12 @@ internal class SentryOkHttpEvent(private val scopes: IScopes, private val reques
153
132
// We send the breadcrumb even without spans.
154
133
scopes.addBreadcrumb(breadcrumb, hint)
155
134
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.
177
137
clientErrorResponse?.let {
178
138
SentryOkHttpUtils .captureClientError(scopes, it.request, it)
179
139
}
180
- if (finishDate != null ) {
181
- callRootSpan.finish(callRootSpan.status, finishDate)
182
- } else {
183
- callRootSpan.finish()
184
- }
140
+ callSpan?.finish()
185
141
return
186
142
}
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
- }
227
143
}
0 commit comments