Skip to content

Commit

Permalink
Add custom tracking module (#1168)
Browse files Browse the repository at this point in the history
* Add custom tracking module

* Minor corrections

* Improve MenuFragment code
  • Loading branch information
surbhiia authored Feb 10, 2025
1 parent 1c3a7de commit a320a82
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 4 deletions.
1 change: 1 addition & 0 deletions agent/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ dependencies {
api(project(":integration:networkrequest"))
api(project(":integration:startup"))
api(project(":integration:interactions"))
api(project(":instrumentation:runtime:customtracking"))
}

1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ dependencies {

implementation(project(":agent"))
implementation(project(":integration:sessionreplay"))
implementation(project(":instrumentation:runtime:customtracking"))

implementation(Dependencies.SessionReplay.commonLogger)
implementation(Dependencies.SessionReplay.commonUtils)
Expand Down
5 changes: 4 additions & 1 deletion app/src/main/java/com/splunk/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import com.splunk.rum.integration.sessionreplay.api.sessionReplay
import java.net.URL

class App : Application() {
lateinit var agent: SplunkRUMAgent
private set

override fun onCreate() {
super.onCreate()

Expand All @@ -37,7 +40,7 @@ class App : Application() {
isDebugLogsEnabled = true,
)

val agent = SplunkRUMAgent.install(
agent = SplunkRUMAgent.install(
application = this,
agentConfiguration = agentConfig,
moduleConfigurations = arrayOf(
Expand Down
52 changes: 51 additions & 1 deletion app/src/main/java/com/splunk/app/ui/menu/MenuFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,18 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import com.cisco.android.common.utils.runOnUiThread
import com.splunk.app.App
import com.splunk.app.R
import com.splunk.app.databinding.FragmentMenuBinding
import com.splunk.app.ui.BaseFragment
import com.splunk.app.ui.httpurlconnection.HttpURLConnectionFragment
import com.splunk.app.ui.okhttp.OkHttpFragment
import com.splunk.app.util.FragmentAnimation
import com.splunk.rum.customtracking.extension.customTracking
import com.splunk.rum.integration.agent.api.SplunkRUMAgent
import io.opentelemetry.api.common.Attributes
import com.splunk.rum.integration.agent.api.extension.splunkRumId

class MenuFragment : BaseFragment<FragmentMenuBinding>() {
Expand All @@ -35,6 +41,10 @@ class MenuFragment : BaseFragment<FragmentMenuBinding>() {

override val titleRes: Int = R.string.menu_title

private val agent: SplunkRUMAgent? by lazy {
(requireActivity().application as? App)?.agent
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand All @@ -49,18 +59,22 @@ class MenuFragment : BaseFragment<FragmentMenuBinding>() {
viewBinding.anrEvent.setOnClickListener(onClickListener)
viewBinding.okhttpSampleCalls.setOnClickListener(onClickListener)
viewBinding.httpurlconnection.setOnClickListener(onClickListener)

viewBinding.trackCustomEvent.setOnClickListener(onClickListener)
viewBinding.trackWorkflow.setOnClickListener(onClickListener)
viewBinding.crashReportsIllegal.splunkRumId = "illegalButton"
}

private val onClickListener = View.OnClickListener {
when (it.id) {
viewBinding.crashReportsIllegal.id ->
throw IllegalArgumentException("Illegal Argument Exception Thrown!")

viewBinding.crashReportsMainThread.id ->
throw RuntimeException("Crashing on main thread")

viewBinding.crashReportsInBackground.id ->
Thread { throw RuntimeException("Attempt to crash background thread") }.start()

viewBinding.crashReportsNoAppCode.id -> {
val e = java.lang.RuntimeException("No Application Code")
e.stackTrace = arrayOf(
Expand All @@ -70,36 +84,72 @@ class MenuFragment : BaseFragment<FragmentMenuBinding>() {
)
throw e
}

viewBinding.crashReportsNoStacktrace.id -> {
val e = java.lang.RuntimeException("No Stack Trace")
e.stackTrace = arrayOfNulls(0)
throw e
}

viewBinding.crashReportsOutOfMemoryError.id -> {
val e = OutOfMemoryError("out of memory")
e.stackTrace = arrayOfNulls(0)
throw e
}

viewBinding.crashReportsWithChainedExceptions.id -> {
try {
throw NullPointerException("Simulated error in exception 1")
} catch (e: NullPointerException) {
throw IllegalArgumentException("Simulated error in exception 2", e)
}
}

viewBinding.crashReportsNull.id ->
throw NullPointerException("I am null!")

viewBinding.anrEvent.id -> {
try {
Thread.sleep(6000)
} catch (e: InterruptedException) {
throw RuntimeException(e)
}
}

viewBinding.okhttpSampleCalls.id ->
navigateTo(OkHttpFragment(), FragmentAnimation.FADE)

viewBinding.httpurlconnection.id ->
navigateTo(HttpURLConnectionFragment(), FragmentAnimation.FADE)

viewBinding.trackCustomEvent.id -> {
agent?.let {
val testAttributes = Attributes.builder()
.put("attribute.one", "value1")
.put("attribute.two", "12345")
.build()
it.customTracking.trackCustomEvent("TestEvent", testAttributes)
showDoneToast("Track Custom Event, Done!")
} ?: showDoneToast("Agent is null, cannot track")
}

viewBinding.trackWorkflow.id -> {
agent?.let {
val workflowSpan = it.customTracking.trackWorkflow("Test Workflow")
workflowSpan?.setAttribute("workflow.start.time", System.currentTimeMillis())
// Simulate some processing time
Thread.sleep(125)
workflowSpan?.setAttribute("workflow.end.time", System.currentTimeMillis())
workflowSpan?.end()
showDoneToast("Track Workflow, Done!")
} ?: showDoneToast("Agent is null, cannot track")
}
}
}

private fun showDoneToast(message: String) {
runOnUiThread {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
}
}
35 changes: 34 additions & 1 deletion app/src/main/res/layout/fragment_menu.xml
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,41 @@
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
android:text="@string/menu_httpurlconnection_title"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/okhttp_sample_calls" />

<TextView
android:id="@+id/custom_tracking_title"
style="@style/Header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="@string/menu_custom_tracking_title"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@+id/httpurlconnection" />

<TextView
android:id="@+id/track_custom_event"
style="@style/Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:layout_marginRight="20dp"
android:text="@string/menu_custom_event"
app:layout_constraintTop_toBottomOf="@id/custom_tracking_title" />

<TextView
android:id="@+id/track_workflow"
style="@style/Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginTop="5dp"
android:layout_marginRight="20dp"
android:text="@string/menu_workflow"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/track_custom_event" />

</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<string name="menu_okhttp_sample_calls">Okhttp sample calls</string>
<string name="menu_network_request_title">Network Requests</string>
<string name="menu_httpurlconnection_title">HttpURLConnection</string>
<string name="menu_custom_tracking_title">Custom Tracking</string>
<string name="menu_custom_event">Track Custom Event</string>
<string name="menu_workflow">Track Workflow</string>

<string name="okhttp_title">OkHttp</string>
<string name="httpurlconnection_title">HttpURLConnection</string>
Expand Down
2 changes: 1 addition & 1 deletion common/otel/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ apply<ConfigAndroidLibrary>()
apply<ConfigPublish>()

ext {
set(artifactIdProperty, "$artifactPrefix${commonPrefix}otel-${project.name}")
set(artifactIdProperty, "$artifactPrefix${commonPrefix}${project.name}")
set(versionProperty, Configurations.sdkVersionName)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2025 Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.sdk.common.otel.internal

import io.opentelemetry.api.common.AttributeKey

object RumConstants {
const val RUM_TRACER_NAME: String = "SplunkRum"
val WORKFLOW_NAME_KEY: AttributeKey<String> = AttributeKey.stringKey("workflow.name")
}
31 changes: 31 additions & 0 deletions instrumentation/runtime/customtracking/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import plugins.ConfigAndroidLibrary
import plugins.ConfigPublish
import utils.artifactIdProperty
import utils.artifactPrefix
import utils.instrumentationPrefix
import utils.versionProperty

plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-parcelize")
}

apply<ConfigAndroidLibrary>()
apply<ConfigPublish>()

ext {
set(artifactIdProperty, "$artifactPrefix$instrumentationPrefix${project.name}")
set(versionProperty, Configurations.sdkVersionName)
}

android {
namespace = "com.splunk.rum.customtracking"
}

dependencies {
api(Dependencies.Otel.api)
implementation(project(":integration:agent:api"))
implementation(project(":common:otel"))
implementation(Dependencies.SessionReplay.commonLogger)
}
4 changes: 4 additions & 0 deletions instrumentation/runtime/customtracking/lint-baseline.xml
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.4.0" type="baseline" client="gradle" dependencies="false" name="AGP (7.4.0)" variant="all" version="7.4.0">

</issues>
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2025 Splunk Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.splunk.rum.customtracking

import com.cisco.android.common.logger.Logger
import com.splunk.sdk.common.otel.OpenTelemetry
import com.splunk.sdk.common.otel.internal.RumConstants
import io.opentelemetry.api.common.Attributes
import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.Tracer


class CustomTracking internal constructor() {

/**
* Add a custom event. This can be useful to capture business events.
*
* <p>This event will be turned into a Span and sent to the RUM ingest along with other,
* auto-generated spans.
*
* @param name The name of the event.
* @param attributes Any {@link Attributes} to associate with the event.
*/
fun trackCustomEvent(name: String, attributes: Attributes) {
val tracer = getTracer() ?: return
tracer.spanBuilder(name).setAllAttributes(attributes).startSpan().end()
}

/**
* Start a Span to track a named workflow.
*
* @param workflowName The name of the workflow to start.
* @return A {@link Span} that has been started.
*/
fun trackWorkflow(workflowName: String): Span? {
val tracer = getTracer() ?: return null
return tracer.spanBuilder(workflowName)
.setAttribute(RumConstants.WORKFLOW_NAME_KEY, workflowName)
.startSpan()
}


/**
* Retrieves the Tracer instance for the application.
*
* @return A Tracer instance if available, or null if the OpenTelemetry instance is null.
*/
private fun getTracer(): Tracer? {
return OpenTelemetry.instance?.sdkTracerProvider?.get(RumConstants.RUM_TRACER_NAME).also {
if (it == null) {
Logger.e(
TAG,
"Opentelemetry instance is null. Cannot track custom events/workflow."
)
}
}
}

companion object {

private const val TAG = "CustomTracking"

/**
* The instance of [CustomTracking].
*/
@JvmStatic
val instance by lazy { CustomTracking() }
}
}
Loading

0 comments on commit a320a82

Please sign in to comment.