-
Notifications
You must be signed in to change notification settings - Fork 2
feat: adds otel-android click instrumentation #200
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
Closed
Closed
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
28da5f2
feat: adds otel-android HTTP instrumentation
tanderson-ld 458e5c6
Merge branch 'main' into ta/O11Y-371/reqeust-instrumentation-take2
tanderson-ld 3b87436
removing unnecessary dependencies
tanderson-ld 18bcf3d
removing unnecessary dependencies
tanderson-ld 0a26a1f
feat: adds otel-android click instrumentation
tanderson-ld 8b82295
Merge remote-tracking branch 'origin' into ta/O11Y-417/interaction-in…
tanderson-ld 445f897
small self review tweaks
tanderson-ld 92ca68a
removing extra button in e2e test app
tanderson-ld File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,6 +14,8 @@ import androidx.compose.material3.Scaffold | |
| import androidx.compose.material3.Text | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.semantics.contentDescription | ||
| import androidx.compose.ui.semantics.semantics | ||
| import androidx.compose.ui.tooling.preview.Preview | ||
| import com.example.androidobservability.ui.theme.AndroidObservabilityTheme | ||
|
|
||
|
|
@@ -27,52 +29,73 @@ class MainActivity : ComponentActivity() { | |
| Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> | ||
| Column { | ||
| Text( | ||
| text = "Hello Telemetry", | ||
| text = "Hello Android Observability", | ||
| modifier = Modifier.padding(innerPadding) | ||
| ) | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonGoToSecondActivity" | ||
| }, | ||
| onClick = { | ||
| [email protected](Intent(this@MainActivity, SecondaryActivity::class.java)) | ||
| } | ||
| ) { | ||
| Text("Go to Secondary Activity") | ||
| } | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonTriggerHttp" | ||
| }, | ||
| onClick = { | ||
| viewModel.triggerHttpRequests() | ||
| } | ||
| ) { | ||
| Text("Trigger HTTP Request") | ||
| } | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonTriggerMetric" | ||
| }, | ||
| onClick = { | ||
| viewModel.triggerMetric() | ||
| } | ||
| ) { | ||
| Text("Trigger Metric") | ||
| } | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonTriggerError" | ||
| }, | ||
| onClick = { | ||
| viewModel.triggerError() | ||
| } | ||
| ) { | ||
| Text("Trigger Error") | ||
| } | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonTriggerLog" | ||
| }, | ||
| onClick = { | ||
| viewModel.triggerLog() | ||
| } | ||
| ) { | ||
| Text("Trigger Log") | ||
| } | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonTriggerNestedSpans" | ||
| }, | ||
| onClick = { | ||
| viewModel.triggerNestedSpans() | ||
| } | ||
| ) { | ||
| Text("Trigger Nested Spans") | ||
| } | ||
| Button( | ||
| modifier = Modifier.semantics { | ||
| contentDescription = "buttonTriggerCrash" | ||
| }, | ||
| onClick = { | ||
| viewModel.triggerCrash() | ||
| } | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
...b/src/main/kotlin/com/launchdarkly/observability/vendored/otel/SessionIdTimeoutHandler.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| /** | ||
| * Originally from https://github.com/open-telemetry/opentelemetry-android/blob/main/android-agent/src/main/kotlin/io/opentelemetry/android/agent/session/SessionManager.kt | ||
| * | ||
| * Was publicly available before 0.14.0-alpha and this implementation meets our needs. We will come back | ||
| * to check for any updates before this is released in a 1.X version of our plugin. O11Y-443 tracks this task. | ||
| * | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package com.launchdarkly.observability.vendored.otel | ||
|
|
||
| import io.opentelemetry.android.agent.session.SessionConfig | ||
| import io.opentelemetry.android.internal.services.applifecycle.ApplicationStateListener | ||
| import io.opentelemetry.sdk.common.Clock | ||
| import kotlin.time.Duration | ||
|
|
||
| /** | ||
| * This class encapsulates the following criteria about the sessionId timeout: | ||
| * | ||
| * | ||
| * * If the app is in the foreground sessionId should never time out. | ||
| * * If the app is in the background and no activity (spans) happens for >15 minutes, sessionId | ||
| * should time out. | ||
| * * If the app is in the background and some activity (spans) happens in <15 minute intervals, | ||
| * sessionId should not time out. | ||
| * | ||
| * | ||
| * Consequently, when the app spent >15 minutes without any activity (spans) in the background, | ||
| * after moving to the foreground the first span should trigger the sessionId timeout. | ||
| */ | ||
| internal class SessionIdTimeoutHandler( | ||
| private val clock: Clock, | ||
| private val sessionBackgroundInactivityTimeout: Duration, | ||
| ) : ApplicationStateListener { | ||
| @Volatile | ||
| private var timeoutStartNanos: Long = 0 | ||
|
|
||
| @Volatile | ||
| private var state = State.FOREGROUND | ||
|
Contributor
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. Since line 69 performs a non atomic operation (check and set), I think |
||
|
|
||
| // for testing | ||
| internal constructor(sessionConfig: SessionConfig) : this( | ||
| Clock.getDefault(), | ||
| sessionConfig.backgroundInactivityTimeout, | ||
| ) | ||
|
|
||
| override fun onApplicationForegrounded() { | ||
| state = State.TRANSITIONING_TO_FOREGROUND | ||
| } | ||
|
|
||
| override fun onApplicationBackgrounded() { | ||
| state = State.BACKGROUND | ||
| } | ||
|
|
||
| fun hasTimedOut(): Boolean { | ||
| // don't apply sessionId timeout to apps in the foreground | ||
| if (state == State.FOREGROUND) { | ||
| return false | ||
| } | ||
| val elapsedTime = clock.nanoTime() - timeoutStartNanos | ||
| return elapsedTime >= sessionBackgroundInactivityTimeout.inWholeNanoseconds | ||
| } | ||
|
|
||
| fun bump() { | ||
| timeoutStartNanos = clock.nanoTime() | ||
|
|
||
| // move from the temporary transition state to foreground after the first span | ||
| if (state == State.TRANSITIONING_TO_FOREGROUND) { | ||
| state = State.FOREGROUND | ||
| } | ||
| } | ||
|
|
||
| private enum class State { | ||
| FOREGROUND, | ||
| BACKGROUND, | ||
|
|
||
| /** A temporary state representing the first event after the app has been brought back. */ | ||
| TRANSITIONING_TO_FOREGROUND, | ||
| } | ||
| } | ||
92 changes: 92 additions & 0 deletions
92
...ndroid/lib/src/main/kotlin/com/launchdarkly/observability/vendored/otel/SessionManager.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| /** | ||
| * Originally from https://github.com/open-telemetry/opentelemetry-android/blob/main/android-agent/src/main/kotlin/io/opentelemetry/android/agent/session/SessionManager.kt | ||
| * | ||
| * Was publicly available before 0.14.0-alpha and this implementation meets our needs. There are a couple thread safety concerns in this code, | ||
| * but we expect those to be addressed by the otel-android maintainers. Rather than fix them ourselves and deviate/fork, we will come back | ||
| * to check for any updates before this is released in a 1.X version of our plugin. O11Y-443 tracks this task. | ||
| * | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package com.launchdarkly.observability.vendored.otel | ||
|
|
||
| import io.opentelemetry.android.agent.session.SessionConfig | ||
| import io.opentelemetry.android.agent.session.SessionIdGenerator | ||
| import io.opentelemetry.android.agent.session.SessionStorage | ||
| import io.opentelemetry.android.session.Session | ||
| import io.opentelemetry.android.session.SessionObserver | ||
| import io.opentelemetry.android.session.SessionProvider | ||
| import io.opentelemetry.android.session.SessionPublisher | ||
| import io.opentelemetry.sdk.common.Clock | ||
| import java.util.Collections.synchronizedList | ||
| import kotlin.time.Duration | ||
|
|
||
| internal class SessionManager( | ||
| private val clock: Clock = Clock.getDefault(), | ||
| private val sessionStorage: SessionStorage = SessionStorage.InMemory(), | ||
| private val timeoutHandler: SessionIdTimeoutHandler, | ||
| private val idGenerator: SessionIdGenerator = SessionIdGenerator.DEFAULT, | ||
| private val maxSessionLifetime: Duration, | ||
| ) : SessionProvider, | ||
| SessionPublisher { | ||
| // TODO: Make thread safe / wrap with AtomicReference? | ||
| private var session: Session = Session.NONE | ||
| private val observers = synchronizedList(ArrayList<SessionObserver>()) | ||
|
|
||
| init { | ||
| sessionStorage.save(session) | ||
| } | ||
|
|
||
| override fun addObserver(observer: SessionObserver) { | ||
| observers.add(observer) | ||
| } | ||
|
|
||
| override fun getSessionId(): String { | ||
| // value will never be null | ||
| var newSession = session | ||
|
|
||
| if (sessionHasExpired() || timeoutHandler.hasTimedOut()) { | ||
| val newId = idGenerator.generateSessionId() | ||
|
|
||
| // TODO FIXME: This is not threadsafe -- if two threads call getSessionId() | ||
| // at the same time while timed out, two new sessions are created | ||
| // Could require SessionStorage impls to be atomic/threadsafe or | ||
| // do the locking in this class? | ||
|
|
||
| newSession = Session.DefaultSession(newId, clock.now()) | ||
| sessionStorage.save(newSession) | ||
| } | ||
|
|
||
| timeoutHandler.bump() | ||
|
|
||
| // observers need to be called after bumping the timer because it may | ||
| // create a new span | ||
| if (newSession != session) { | ||
| val previousSession = session | ||
| session = newSession | ||
| observers.forEach { | ||
| it.onSessionEnded(previousSession) | ||
| it.onSessionStarted(session, previousSession) | ||
| } | ||
| } | ||
| return session.getId() | ||
| } | ||
|
|
||
| private fun sessionHasExpired(): Boolean { | ||
| val elapsedTime = clock.now() - session.getStartTimestamp() | ||
| return elapsedTime >= maxSessionLifetime.inWholeNanoseconds | ||
| } | ||
|
|
||
| companion object { | ||
| @JvmStatic | ||
| fun create( | ||
| timeoutHandler: SessionIdTimeoutHandler, | ||
| sessionConfig: SessionConfig, | ||
| ): SessionManager = | ||
| SessionManager( | ||
| timeoutHandler = timeoutHandler, | ||
| maxSessionLifetime = sessionConfig.maxLifetime, | ||
| ) | ||
| } | ||
| } |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Unmodified vendored file