diff --git a/app/src/main/java/com/splunk/app/App.kt b/app/src/main/java/com/splunk/app/App.kt index 7d87c4fce..2bcd22f38 100644 --- a/app/src/main/java/com/splunk/app/App.kt +++ b/app/src/main/java/com/splunk/app/App.kt @@ -23,6 +23,8 @@ import com.splunk.rum.integration.agent.api.SplunkRUMAgent import com.splunk.rum.integration.interactions.InteractionsModuleConfiguration import com.splunk.rum.integration.navigation.NavigationModuleConfiguration import com.splunk.rum.integration.sessionreplay.api.sessionReplay +import io.opentelemetry.api.common.AttributeKey +import io.opentelemetry.api.common.Attributes import java.net.URL class App : Application() { @@ -33,6 +35,14 @@ class App : Application() { // TODO: Reenable with the bridge support // BridgeManager.bridgeInterfaces += TomasBridgeInterface() + val globalAttributes = Attributes.of( + AttributeKey.stringKey("globKeyConfig1"), "12345", + AttributeKey.booleanKey("globKeyConfig2"), true, + AttributeKey.doubleKey("globKeyConfig3"), 1200.50, + AttributeKey.longKey("globKeyConfig4"), 30L, + AttributeKey.stringKey("globKeyConfig5"), "US" + ) + val agent = SplunkRUMAgent.install( application = this, agentConfiguration = AgentConfiguration( @@ -40,6 +50,7 @@ class App : Application() { appName = "smartlook-android", appVersion = "0.1", isDebugLogsEnabled = true, + globalAttributes = globalAttributes ), moduleConfigurations = arrayOf( InteractionsModuleConfiguration( diff --git a/app/src/main/java/com/splunk/app/ui/menu/MenuFragment.kt b/app/src/main/java/com/splunk/app/ui/menu/MenuFragment.kt index 21fa5e60b..805f745b1 100644 --- a/app/src/main/java/com/splunk/app/ui/menu/MenuFragment.kt +++ b/app/src/main/java/com/splunk/app/ui/menu/MenuFragment.kt @@ -30,6 +30,8 @@ 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 com.splunk.rum.integration.agent.api.attributes.GlobalAttributes +import com.splunk.rum.integration.agent.api.attributes.extension.globalAttributes import com.splunk.rum.integration.agent.api.extension.splunkRumId import com.splunk.rum.integration.navigation.extension.navigation import io.opentelemetry.api.common.Attributes @@ -129,6 +131,8 @@ class MenuFragment : BaseFragment() { showDoneToast("Track Workflow, Done!") } viewBinding.trackException.id -> { + SplunkRUMAgent.instance.globalAttributes.set("globkey12", "val12") // temporarily hijacking use of this button to also add global attribute on the fly + val e = Exception("Custom Exception To Be Tracked"); e.stackTrace = arrayOf( StackTraceElement("android.fake.Crash", "crashMe", "NotARealFile.kt", 12), @@ -139,6 +143,9 @@ class MenuFragment : BaseFragment() { showDoneToast("Track Exception, Done!") } viewBinding.trackExceptionWithAttributes.id -> { + SplunkRUMAgent.instance.globalAttributes.remove("globkey12") // temporarily hijacking use of this button to also remove global attribute on the fly + SplunkRUMAgent.instance.globalAttributes.remove("globkey") + val e = Exception("Custom Exception (with attributes) To Be Tracked"); e.stackTrace = arrayOf( StackTraceElement("android.fake.Crash", "crashMe", "NotARealFile.kt", 12), diff --git a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/AgentConfiguration.kt b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/AgentConfiguration.kt index b000edbd8..9b787f75c 100644 --- a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/AgentConfiguration.kt +++ b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/AgentConfiguration.kt @@ -16,6 +16,7 @@ package com.splunk.rum.integration.agent.api +import io.opentelemetry.api.common.Attributes import java.net.URL /** @@ -34,4 +35,5 @@ data class AgentConfiguration( var appName: String? = null, var appVersion: String? = null, var isDebugLogsEnabled: Boolean = false, // temporary name till product decides on more suitable one + var globalAttributes: Attributes = Attributes.empty() // Default to empty attributes if not provided ) diff --git a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/GlobalAttributeSpanProcessor2.kt b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/GlobalAttributeSpanProcessor2.kt new file mode 100644 index 000000000..c7a50ac4c --- /dev/null +++ b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/GlobalAttributeSpanProcessor2.kt @@ -0,0 +1,34 @@ +package com.splunk.rum.integration.agent.api.attributes + +import com.cisco.android.common.utils.extensions.forEachFast +import com.splunk.rum.integration.agent.api.attributes.GlobalAttributes +import io.opentelemetry.context.Context +import io.opentelemetry.sdk.trace.ReadWriteSpan +import io.opentelemetry.sdk.trace.ReadableSpan +import io.opentelemetry.sdk.trace.SpanProcessor + +class GlobalAttributeSpanProcessor2 : SpanProcessor { + + override fun onStart(parentContext: Context, span: ReadWriteSpan) { + // Fetch the current attributes from GlobalAttributes and apply them to the span + GlobalAttributes.instance.attributes.forEach { attribute -> + when (attribute) { + is GlobalAttributes.Attribute.Boolean -> span.setAttribute(attribute.name, attribute.value) + is GlobalAttributes.Attribute.Double -> span.setAttribute(attribute.name, attribute.value) + is GlobalAttributes.Attribute.Long -> span.setAttribute(attribute.name, attribute.value) + is GlobalAttributes.Attribute.String -> span.setAttribute(attribute.name, attribute.value) + } + } + } + + override fun isStartRequired(): Boolean { + return true + } + + override fun onEnd(span: ReadableSpan) { + } + + override fun isEndRequired(): Boolean { + return true + } +} diff --git a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/GlobalAttributes.kt b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/GlobalAttributes.kt new file mode 100644 index 000000000..6c749f3af --- /dev/null +++ b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/GlobalAttributes.kt @@ -0,0 +1,60 @@ +package com.splunk.rum.integration.agent.api.attributes + +import io.opentelemetry.api.common.AttributeKey + +class GlobalAttributes private constructor() { + + companion object { + val instance: GlobalAttributes by lazy { GlobalAttributes() } + } + + private val _attributes: MutableList = ArrayList() + val attributes: List get() = _attributes // Expose as immutable list + + // this method definitely can be streamlined + operator fun set(key: Any, value: Any) { + if (key is String) { + val existingAttribute = _attributes.find { it.name == key } + if (existingAttribute != null) { + _attributes.remove(existingAttribute) + } + + _attributes += when (value) { + is String -> Attribute.String(key, value) + is Boolean -> Attribute.Boolean(key, value) + is Long -> Attribute.Long(key, value) + is Double -> Attribute.Double(key, value) + else -> throw IllegalArgumentException("Unsupported attribute type") + } + } else if (key is AttributeKey<*>) { + val existingAttribute = _attributes.find { it.name == key.key } + if (existingAttribute != null) { + _attributes.remove(existingAttribute) + } + + _attributes += when (value) { + is String -> Attribute.String(key.key, value) + is Boolean -> Attribute.Boolean(key.key, value) + is Long -> Attribute.Long(key.key, value) + is Double -> Attribute.Double(key.key, value) + else -> throw IllegalArgumentException("Unsupported attribute type") + } + } else { + throw IllegalArgumentException("Key must be either String or AttributeKey") + } + } + + fun remove(key: String) { + _attributes.removeAll { it.name == key } + } + + sealed interface Attribute { + + val name: kotlin.String + + data class Boolean(override val name: kotlin.String, val value: kotlin.Boolean) : Attribute + data class Double(override val name: kotlin.String, val value: kotlin.Double) : Attribute + data class String(override val name: kotlin.String, val value: kotlin.String) : Attribute + data class Long(override val name: kotlin.String, val value: kotlin.Long) : Attribute + } +} diff --git a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/extension/SplunkRumAgentGlobalAttributesExt.kt b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/extension/SplunkRumAgentGlobalAttributesExt.kt new file mode 100644 index 000000000..5553122c5 --- /dev/null +++ b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/attributes/extension/SplunkRumAgentGlobalAttributesExt.kt @@ -0,0 +1,11 @@ +package com.splunk.rum.integration.agent.api.attributes.extension + +import com.splunk.rum.integration.agent.api.SplunkRUMAgent +import com.splunk.rum.integration.agent.api.attributes.GlobalAttributes + +/** + * Extension property to access the [GlobalAttributes] instance via [SplunkRUMAgent]. + */ +@Suppress("UnusedReceiverParameter") +val SplunkRUMAgent.globalAttributes: GlobalAttributes + get() = GlobalAttributes.instance diff --git a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/internal/MRUMAgentCore.kt b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/internal/MRUMAgentCore.kt index e2bdd95e1..927ae64b6 100644 --- a/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/internal/MRUMAgentCore.kt +++ b/integration/agent/api/src/main/java/com/splunk/rum/integration/agent/api/internal/MRUMAgentCore.kt @@ -23,6 +23,8 @@ import com.splunk.sdk.common.otel.OpenTelemetryInitializer import com.splunk.rum.integration.agent.api.AgentConfiguration import com.splunk.rum.integration.agent.api.attributes.ErrorIdentifierAttributesSpanProcessor import com.splunk.rum.integration.agent.api.attributes.GenericAttributesLogProcessor +import com.splunk.rum.integration.agent.api.attributes.GlobalAttributeSpanProcessor2 +import com.splunk.rum.integration.agent.api.attributes.GlobalAttributes import com.splunk.rum.integration.agent.api.configuration.ConfigurationManager import com.splunk.rum.integration.agent.api.extension.toResource import com.splunk.rum.integration.agent.api.sessionId.SessionIdLogProcessor @@ -66,9 +68,15 @@ internal object MRUMAgentCore { SessionStartEventManager.obtainInstance(agentIntegration.sessionManager) SessionPulseEventManager.obtainInstance(agentIntegration.sessionManager) + // adding agent config global attributes to global attributes + agentConfiguration.globalAttributes.forEach { attributeKey, value -> + GlobalAttributes.instance[attributeKey] = value + } + OpenTelemetryInitializer(application) .joinResources(finalConfiguration.toResource()) .addSpanProcessor(ErrorIdentifierAttributesSpanProcessor(application)) + .addSpanProcessor(GlobalAttributeSpanProcessor2()) .addSpanProcessor(SessionIdSpanProcessor(agentIntegration.sessionManager)) .addSpanProcessor(GlobalAttributeSpanProcessor()) .addLogRecordProcessor(GenericAttributesLogProcessor())