Skip to content

Commit

Permalink
Merge pull request #43 from THEOplayer/feature/conviva/error_report
Browse files Browse the repository at this point in the history
Send extended error report
  • Loading branch information
tvanlaerhoven authored Dec 3, 2024
2 parents cb56f57 + 511b16d commit bbe8d96
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ import com.theoplayer.android.api.event.player.*
import com.theoplayer.android.api.player.Player
import com.theoplayer.android.api.source.SourceDescription
import com.theoplayer.android.connector.analytics.conviva.ads.AdReporter
import com.theoplayer.android.connector.analytics.conviva.utils.ErrorReportBuilder
import com.theoplayer.android.connector.analytics.conviva.utils.calculateBufferLength
import com.theoplayer.android.connector.analytics.conviva.utils.calculateConvivaOptions
import com.theoplayer.android.connector.analytics.conviva.utils.collectContentMetadata
import com.theoplayer.android.connector.analytics.conviva.utils.collectPlayerInfo
import com.theoplayer.android.connector.analytics.conviva.utils.flattenErrorObject
import java.lang.Double.isFinite

private const val TAG = "ConvivaHandler"
Expand Down Expand Up @@ -71,6 +71,7 @@ class ConvivaHandler(
private val onSourceChange: EventListener<SourceChangeEvent>
private val onEnded: EventListener<EndedEvent>
private val onDurationChange: EventListener<DurationChangeEvent>
private val errorReportBuilder = ErrorReportBuilder()

init {
ConvivaAnalytics.init(
Expand Down Expand Up @@ -142,10 +143,10 @@ class ConvivaHandler(

val error = event.errorObject
// Report error details in a separate event, which should be passed a flat <String, String> map.
val errorDetails = flattenErrorObject(error)
if (errorDetails.isNotEmpty()) {
convivaVideoAnalytics.reportPlaybackEvent("ErrorDetailsEvent", errorDetails)
}
convivaVideoAnalytics.reportPlaybackEvent("ErrorDetailsEvent", errorReportBuilder.apply {
withPlayerBuffer(player)
withErrorDetails(event.errorObject)
}.build())

// Report error and cleanup immediately.
// The contentInfo provides metadata for the failed video.
Expand Down Expand Up @@ -317,6 +318,8 @@ class ConvivaHandler(
mainHandler.post {
ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleObserver)
}

player.network.addHTTPInterceptor(errorReportBuilder)
}

private fun removeEventListeners() {
Expand All @@ -338,6 +341,8 @@ class ConvivaHandler(
mainHandler.post {
ProcessLifecycleOwner.get().lifecycle.removeObserver(lifecycleObserver)
}

player.network.removeHTTPInterceptor(errorReportBuilder)
}

// Update API will be called by Conviva SDK at regular intervals to compute playback
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.theoplayer.android.connector.analytics.conviva.utils

import com.theoplayer.android.api.error.THEOplayerException
import com.theoplayer.android.api.network.http.HTTPInterceptor
import com.theoplayer.android.api.network.http.InterceptableHTTPResponse
import com.theoplayer.android.api.player.Player

/**
* ErrorReportBuilder provides extra error details that can be send when reporting an error.
*/
class ErrorReportBuilder(private val maxHTTPResponses: Int = 10) : HTTPInterceptor {
private val _responses = LinkedHashMap<String, String>(maxHTTPResponses, 0.75f, true)
private val _report = mutableMapOf<String, String>()

override suspend fun onResponse(response: InterceptableHTTPResponse) {
// Keep info on the last X HTTP responses
if (_responses.size == maxHTTPResponses) {
_responses.remove(_responses.keys.first())
}
// Create an entry with the path & http response status
var respDesc =
"${response.url.path},status: ${response.status} ${response.statusText},"
// Add interesting header values
respDesc += listOf("content-length").joinToString(",") { key ->
"$key: ${getHeaderValue(response.headers, key)}"
}
_responses[System.currentTimeMillis().toString()] = respDesc
super.onResponse(response)
}

fun withPlayerBuffer(player: Player) {
_report["buffered"] = bufferedToString(player.buffered)
}

fun withErrorDetails(exception: THEOplayerException) {
val errorDetails = flattenErrorObject(exception)
if (errorDetails.isNotEmpty()) {
_report += errorDetails
}
}

fun build(): Map<String, String> {
// Merge report and a list of the latest http responses in separate entries as event
// payloads are truncated in the Pulse dashboard.
return _report + _responses
}
}

fun getHeaderValue(headers: Map<String, String>, key: String): String {
return headers.keys.firstOrNull { it.equals(key, ignoreCase = true) }
?.let { headers[it] } ?: "N/A"
}

fun flattenErrorObject(error: THEOplayerException): Map<String, String> {
return mapOf(
"code" to error.code.name,
"category" to error.category.name,
"stack" to (error.stackTraceToString()),
"cause.stack" to (error.cause?.stackTraceToString() ?: ""),
"cause.message" to (error.cause?.message ?: "")
).filterValues { it != "" } // Remove entries with empty values
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import com.conviva.sdk.ConvivaSdkConstants
import com.theoplayer.android.api.THEOplayerGlobal
import com.theoplayer.android.api.ads.AdBreak
import com.theoplayer.android.api.ads.GoogleImaAd
import com.theoplayer.android.api.error.THEOplayerException
import com.theoplayer.android.api.player.Player
import com.theoplayer.android.api.timerange.TimeRanges
import com.theoplayer.android.connector.analytics.conviva.ConvivaConfiguration
import com.theoplayer.android.connector.analytics.conviva.ConvivaMetadata

Expand Down Expand Up @@ -161,12 +161,6 @@ fun calculateBufferLength(player: Player): Long {
return (1e3 * bufferLength).toLong()
}

fun flattenErrorObject(error: THEOplayerException): Map<String, String> {
return mapOf(
"code" to error.code.name,
"category" to error.category.name,
"stack" to (error.stackTraceToString()),
"cause.stack" to (error.cause?.stackTraceToString() ?: ""),
"cause.message" to (error.cause?.message ?: "")
).filterValues { it != "" } // Remove entries with empty values
fun bufferedToString(buffered: TimeRanges): String {
return "[${buffered.joinToString(",") { timeRange -> "${timeRange.start}-${timeRange.end}" }}]"
}

0 comments on commit bbe8d96

Please sign in to comment.