-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Track Exception APIs, and Error Identifier Attributes Span Processor #1172
base: feature/next-gen
Are you sure you want to change the base?
Changes from all commits
7783c48
7f525ec
f716fde
bb6d321
b4ec428
b19495c
bf30fae
0299619
aab47e3
f3fce1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import plugins.ConfigAndroidLibrary | ||
import plugins.ConfigPublish | ||
import utils.artifactIdProperty | ||
import utils.artifactPrefix | ||
import utils.commonPrefix | ||
import utils.versionProperty | ||
|
||
plugins { | ||
id("com.android.library") | ||
id("kotlin-android") | ||
id("kotlin-parcelize") | ||
} | ||
|
||
apply<ConfigAndroidLibrary>() | ||
apply<ConfigPublish>() | ||
|
||
ext { | ||
set(artifactIdProperty, "$artifactPrefix$commonPrefix${project.name}") | ||
set(versionProperty, Configurations.sdkVersionName) | ||
} | ||
|
||
android { | ||
namespace = "com.splunk.sdk.common.utils" | ||
} | ||
|
||
dependencies { | ||
implementation(Dependencies.SessionReplay.commonLogger) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<issues format="6" by="lint 7.3.1" type="baseline" client="gradle" dependencies="false" name="AGP (7.3.1)" variant="all" version="7.3.1"> | ||
|
||
</issues> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-repackageclasses 'com.splunk.sdk.common.utils' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package com.splunk.sdk.utils | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Should we add "common" word in the package name like the other common packages - com.splunk.sdk.common.utils ? If not, we might need to change the package name in pro guard-rules.pro |
||
|
||
import android.app.Application | ||
import android.content.pm.ApplicationInfo | ||
import android.content.pm.PackageManager | ||
import android.os.Build | ||
import com.cisco.android.common.logger.Logger | ||
|
||
class ErrorIdentifierExtractor(private val application: Application) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Class named There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please see above comment: These fields: -application ID only need to be added to crash and error spans that have stacktraces. This is because these attributes are used to correlate an android stacktrace with the correct mapping file for deobfuscation in the backend. They do not need to be added to any other spans, as they are only relevant to identifying errors. That is why this class is called the error identifier extractor. Because it extracts the attributes used to identify an error stacktrace |
||
private val packageManager: PackageManager = application.packageManager | ||
private val applicationInfo: ApplicationInfo? | ||
|
||
init { | ||
applicationInfo = try { | ||
packageManager.getApplicationInfo(application.packageName, PackageManager.GET_META_DATA) | ||
} catch (e: Exception) { | ||
Logger.e(TAG, "Failed to initialize ErrorIdentifierExtractor: ${e.message}") | ||
null | ||
} | ||
} | ||
|
||
fun retrieveApplicationId(): String? { | ||
if (applicationInfo != null) { | ||
return applicationInfo.packageName | ||
} else { | ||
Logger.e(TAG, "ApplicationInfo is null, cannot extract applicationId") | ||
} | ||
return null | ||
} | ||
|
||
fun retrieveVersionCode(): String? { | ||
try { | ||
val packageInfo = | ||
packageManager.getPackageInfo(application.packageName, 0) | ||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | ||
packageInfo.longVersionCode.toString() | ||
} else { | ||
packageInfo.versionCode.toString() | ||
} | ||
} catch (e: Exception) { | ||
Logger.e(TAG, "Failed to get application version code", e) | ||
return null | ||
} | ||
} | ||
|
||
fun retrieveCustomUUID(): String? { | ||
if (applicationInfo == null) { | ||
Logger.e(TAG, "ApplicationInfo is null; cannot retrieve Custom UUID.") | ||
return null | ||
} | ||
val bundle = applicationInfo.metaData | ||
if (bundle != null) { | ||
return bundle.getString(SPLUNK_UUID_MANIFEST_KEY) | ||
} else { | ||
Logger.e(TAG, "Application MetaData bundle is null") | ||
return null | ||
} | ||
} | ||
|
||
private companion object { | ||
private const val TAG = "ErrorIdentifier" | ||
|
||
private const val SPLUNK_UUID_MANIFEST_KEY = "SPLUNK_O11Y_CUSTOM_UUID" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,7 +23,6 @@ import io.opentelemetry.api.common.Attributes | |
import io.opentelemetry.api.trace.Span | ||
import io.opentelemetry.api.trace.Tracer | ||
|
||
|
||
class CustomTracking internal constructor() { | ||
|
||
/** | ||
|
@@ -53,6 +52,42 @@ class CustomTracking internal constructor() { | |
.startSpan() | ||
} | ||
|
||
/** | ||
* Add a custom exception to RUM monitoring. This can be useful for tracking custom error | ||
* handling in your application. | ||
* | ||
* | ||
* This event will be turned into a Span and sent to the RUM ingest along with other, | ||
* auto-generated spans. | ||
* | ||
* @param throwable A [Throwable] associated with this event. | ||
*/ | ||
fun trackException(throwable: Throwable) { | ||
trackException(throwable, Attributes.empty()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn't it be better to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't make a difference There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. According to the source code, using |
||
} | ||
|
||
/** | ||
* Add a custom exception to RUM monitoring. This can be useful for tracking custom error | ||
* handling in your application. | ||
* | ||
* | ||
* This event will be turned into a Span and sent to the RUM ingest along with other, | ||
* auto-generated spans. | ||
* | ||
* @param throwable A [Throwable] associated with this event. | ||
* @param attributes Any [Attributes] to associate with the event. | ||
*/ | ||
fun trackException(throwable: Throwable, attributes: Attributes?) { | ||
val tracer = getTracer() ?: return | ||
val spanBuilder = tracer.spanBuilder(throwable.javaClass.simpleName) | ||
attributes?.let { | ||
spanBuilder.setAllAttributes(it) | ||
} | ||
spanBuilder.setAttribute(RumConstants.COMPONENT_KEY, RumConstants.COMPONENT_ERROR) | ||
.startSpan() | ||
.recordException(throwable) | ||
.end() | ||
} | ||
|
||
/** | ||
* Retrieves the Tracer instance for the application. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.splunk.rum.integration.agent.api.attributes | ||
|
||
import android.app.Application | ||
import com.splunk.sdk.common.otel.internal.RumConstants | ||
import com.splunk.sdk.utils.ErrorIdentifierExtractor | ||
import io.opentelemetry.context.Context | ||
import io.opentelemetry.sdk.trace.ReadWriteSpan | ||
import io.opentelemetry.sdk.trace.ReadableSpan | ||
import io.opentelemetry.sdk.trace.SpanProcessor | ||
|
||
internal class ErrorIdentifierAttributesSpanProcessor(application: Application) : SpanProcessor { | ||
|
||
private var applicationId: String? = null | ||
private var versionCode: String? = null | ||
private var customUUID: String? = null | ||
|
||
init { | ||
val extractor = ErrorIdentifierExtractor(application) | ||
|
||
applicationId = extractor.retrieveApplicationId() | ||
versionCode = extractor.retrieveVersionCode() | ||
customUUID = extractor.retrieveCustomUUID() | ||
} | ||
|
||
override fun onStart(parentContext: Context, span: ReadWriteSpan) { | ||
if (span.getAttribute(RumConstants.COMPONENT_KEY) == RumConstants.COMPONENT_ERROR || | ||
span.getAttribute(RumConstants.COMPONENT_KEY) == RumConstants.COMPONENT_CRASH) { | ||
applicationId?.let { | ||
span.setAttribute(RumConstants.APPLICATION_ID_KEY, it) | ||
} | ||
versionCode?.let { | ||
span.setAttribute(RumConstants.APP_VERSION_CODE_KEY, it) | ||
} | ||
customUUID?.let { | ||
span.setAttribute(RumConstants.SPLUNK_OLLY_UUID_KEY, it) | ||
} | ||
} | ||
} | ||
|
||
override fun isStartRequired(): Boolean = true | ||
|
||
override fun onEnd(span: ReadableSpan) = Unit | ||
|
||
override fun isEndRequired(): Boolean = false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's a good idea to have attributes as global properties. It goes against the principle of an independent modular architecture.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these are going to be used by multiple modules, so that's why it's helpful to keep them in a common module. I also came across another class we already have where we can park commonly used attributes (I Missed it in my PR as well where I added workflow.name) - AttributeConstants