Skip to content
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

POC Global Attributes implementation #1181

Draft
wants to merge 2 commits into
base: feature/next-gen
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/src/main/java/com/splunk/app/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -33,13 +35,22 @@ 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(
url = URL("https://alameda-eum-qe.saas.appd-test.com"),
appName = "smartlook-android",
appVersion = "0.1",
isDebugLogsEnabled = true,
globalAttributes = globalAttributes
),
moduleConfigurations = arrayOf(
InteractionsModuleConfiguration(
Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/splunk/app/ui/menu/MenuFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -129,6 +131,8 @@ class MenuFragment : BaseFragment<FragmentMenuBinding>() {
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),
Expand All @@ -139,6 +143,9 @@ class MenuFragment : BaseFragment<FragmentMenuBinding>() {
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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.splunk.rum.integration.agent.api

import io.opentelemetry.api.common.Attributes
import java.net.URL

/**
Expand All @@ -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
)
Original file line number Diff line number Diff line change
@@ -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 {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove GlobalAttributeSpanProcessor if possible


override fun onStart(parentContext: Context, span: ReadWriteSpan) {
// Fetch the current attributes from GlobalAttributes and apply them to the span
GlobalAttributes.instance.attributes.forEach { attribute ->

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use forEachFast

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
}
}
Original file line number Diff line number Diff line change
@@ -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<Attribute> = ArrayList()
val attributes: List<Attribute> get() = _attributes // Expose as immutable list

// this method definitely can be streamlined
operator fun set(key: Any, value: Any) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO, it would be better to write a function for each type. These "Any functions" are often leads into issues.

operator fun set(key: String, value: String) {
   remove(key)
   _attributes += Attribute.String(key, value)
}

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 }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be always at most one element with the key.

for (i in _attributes.indices)
   if (_attributes[i].name == key) {
      _attributes.removeAt(i)
     break
}

}

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
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
Expand Down