-
Notifications
You must be signed in to change notification settings - Fork 99
Session Management: Added core infrastructure, etc. #1470
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
base: main
Are you sure you want to change the base?
Changes from all commits
adf93a8
a1b0b51
9113132
64318ce
c4c29c3
4110dd9
89e62e3
308a169
11228b6
714a4ef
58537a0
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 |
|---|---|---|
|
|
@@ -94,6 +94,7 @@ object OpenTelemetryRumInitializer { | |
| else -> "none" | ||
| } | ||
|
|
||
| @OptIn(Incubating::class) | ||
|
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. Is there a need to annotate this private function as Incubating? |
||
| private fun createSessionProvider( | ||
| context: Context, | ||
| cfg: OpenTelemetryConfiguration, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,11 +9,87 @@ import kotlin.time.Duration | |
| import kotlin.time.Duration.Companion.hours | ||
| import kotlin.time.Duration.Companion.minutes | ||
|
|
||
| internal class SessionConfig( | ||
| /** | ||
| * Configures session management behavior in the OpenTelemetry Android SDK. | ||
| * | ||
| * Sessions provide a way to group related telemetry data (spans, logs, metrics) that occur during | ||
| * a logical user interaction or application usage period. This configuration controls when sessions | ||
| * expire and new sessions are created. | ||
| * | ||
| * ## Session Lifecycle | ||
| * | ||
| * A session can end due to two conditions: | ||
| * 1. **Background Inactivity**: When the app goes to background and remains inactive for longer than [backgroundInactivityTimeout] | ||
| * 2. **Maximum Lifetime**: When a session has been active for longer than [maxLifetime], regardless of app state | ||
| * | ||
| * When a session ends, a new session will be created on the next telemetry operation, and the previous | ||
| * session ID will be tracked for correlation purposes. | ||
| * | ||
| * ## Usage Example | ||
| * | ||
| * ```kotlin | ||
| * // Use default configuration (15 minutes background timeout, 4 hours max lifetime) | ||
| * val config = SessionConfig.withDefaults() | ||
| * | ||
| * // Custom configuration for shorter sessions | ||
| * val shortSessionConfig = SessionConfig( | ||
| * backgroundInactivityTimeout = 5.minutes, | ||
| * maxLifetime = 1.hours | ||
| * ) | ||
| * ``` | ||
| * | ||
| * @param backgroundInactivityTimeout duration of app backgrounding after which the session expires. | ||
| * Default is 15 minutes, meaning if the app stays in background for 15+ minutes, the current session | ||
| * ends and a new one will be created when the app becomes active again. | ||
| * | ||
| * @param maxLifetime maximum duration a session can remain active regardless of app activity. | ||
| * Default is 4 hours, meaning even if the app stays in foreground continuously, sessions will | ||
| * rotate every 4 hours to prevent unbounded session growth and ensure fresh session context. | ||
| * | ||
| * @see io.opentelemetry.android.agent.session.SessionManager | ||
| * @see io.opentelemetry.android.session.SessionProvider | ||
| */ | ||
| class SessionConfig( | ||
|
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. I'm a bit confused about why we need to expose this class? |
||
| val backgroundInactivityTimeout: Duration = 15.minutes, | ||
| val maxLifetime: Duration = 4.hours, | ||
| ) { | ||
| override fun equals(other: Any?): Boolean { | ||
| if (this === other) return true | ||
| if (javaClass != other?.javaClass) return false | ||
|
|
||
| other as SessionConfig | ||
|
|
||
| if (backgroundInactivityTimeout != other.backgroundInactivityTimeout) return false | ||
| if (maxLifetime != other.maxLifetime) return false | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| override fun hashCode(): Int { | ||
| var result = backgroundInactivityTimeout.hashCode() | ||
| result = 31 * result + maxLifetime.hashCode() | ||
| return result | ||
| } | ||
|
|
||
| override fun toString(): String = | ||
| "SessionConfig(" + | ||
| "backgroundInactivityTimeout=$backgroundInactivityTimeout, " + | ||
| "maxLifetime=$maxLifetime" + | ||
| ")" | ||
|
|
||
| companion object { | ||
| /** | ||
| * Creates a SessionConfig with default values. | ||
| * | ||
| * Default configuration: | ||
| * - Background inactivity timeout: 15 minutes | ||
| * - Maximum session lifetime: 4 hours | ||
| * | ||
| * These defaults balance session continuity with memory efficiency and provide | ||
| * reasonable session boundaries for most mobile applications. | ||
| * | ||
| * @return a new SessionConfig instance with default timeout values. | ||
| */ | ||
| @JvmStatic | ||
| fun withDefaults(): SessionConfig = SessionConfig() | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.android.agent.session.factory | ||
|
|
||
| import android.app.Application | ||
| import io.opentelemetry.android.Incubating | ||
| import io.opentelemetry.android.agent.session.SessionConfig | ||
| import io.opentelemetry.android.agent.session.SessionIdTimeoutHandler | ||
| import io.opentelemetry.android.agent.session.SessionManager | ||
| import io.opentelemetry.android.internal.services.Services | ||
| import io.opentelemetry.android.session.SessionProvider | ||
| import io.opentelemetry.sdk.common.Clock | ||
|
|
||
| /** | ||
| * Default implementation of [SessionProviderFactory] that creates [SessionManager] instances. | ||
| * | ||
| * This implementation creates a [SessionManager] and wires it with: | ||
| * - A timeout handler configured from the session config | ||
| * - Application lifecycle integration for timeout management | ||
| * | ||
| * This class is open to allow custom implementations for specialized use cases. | ||
| * | ||
| * @param application the Android application instance used to access platform services. | ||
| * @param clock the clock instance to use for time-based operations. Defaults to [Clock.getDefault]. | ||
| * @see SessionProviderFactory | ||
| * @see SessionManager | ||
| * @see SessionConfig | ||
| */ | ||
| open class SessionManagerFactory( | ||
|
Member
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. I'm not a fan of exposing concrete types as part of the public API, particularly if they're
Contributor
Author
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. I think it relates to how we're thinking about the SDK's architecture. These are good discussions to have. 😄 Libraries vs SDKs vs FrameworksFrom my perspective, this SDK's design fits that of a framework and not a library. That distinction matters because: Libraries tend to give you code to use as-is without customization This SDK already leans into the framework side:
Making On Your DSL SuggestionAdding OpenTelemetryRumInitializer.initialize(ctx) {
sessionProvider = myCustomProvider
} // But there's still a creation question - how do DI users create that `SessionProvider` in the first place if `SessionManagerFactory` is internal?
@Provides
fun provideSessionProvider(app: Application, config: AppConfig): SessionProvider {
return ??? // Need a way to create this with custom config
} // That's why the factory needs to be public.The Codebase Already Works This Way
The What Remains Hidden The complicated stuff still stays internal:
Only the extension points are public (factory and config). The implementation stays locked down. This is how frameworks like Dagger, OkHttp, and others work. They expose factories because customization is part of the public contract. This is the architectural pattern I'm seeing throughout the SDK. If you have a different approach for how teams should create SessionProviders in DI scenarios, I'd like to understand that.
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. Btw, OpenTelemetryRumBuilder already allows to set an instance of
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. What's the intended use case for this factory? |
||
| private val application: Application, | ||
| private val clock: Clock = Clock.getDefault(), | ||
| ) : SessionProviderFactory { | ||
| /** | ||
| * Creates a session manager instance. | ||
| * | ||
| * This implementation creates a [SessionManager] with a timeout handler that is | ||
| * registered with the application lifecycle service. | ||
| * | ||
| * @param sessionConfig the configuration for session management. | ||
| * @return a session manager instance. | ||
| */ | ||
| @OptIn(Incubating::class) | ||
| override fun createSessionProvider(sessionConfig: SessionConfig): SessionProvider { | ||
| val timeoutHandler = SessionIdTimeoutHandler(sessionConfig, clock) | ||
| Services.get(application).appLifecycle.registerListener(timeoutHandler) | ||
| return SessionManager.create(timeoutHandler, sessionConfig, clock) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| package io.opentelemetry.android.agent.session.factory | ||
|
|
||
| import io.opentelemetry.android.agent.session.SessionConfig | ||
| import io.opentelemetry.android.session.SessionProvider | ||
|
|
||
| /** | ||
| * Factory for creating [SessionProvider] instances. | ||
| * | ||
| * This interface follows the Factory design pattern to enable flexible dependency injection | ||
| * and testing of session management components. Implementations can provide custom session | ||
| * providers with different behaviors while maintaining a consistent creation interface. | ||
| * | ||
| * @see SessionManagerFactory | ||
| * @see SessionProvider | ||
| */ | ||
| interface SessionProviderFactory { | ||
|
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. I'm also not sure what could be the use case for this factory. Users should be able to create their own implementation of |
||
| /** | ||
| * Creates a session provider instance. | ||
| * | ||
| * @param sessionConfig the configuration for session management. | ||
| * @return a session provider instance. | ||
| */ | ||
| fun createSessionProvider(sessionConfig: SessionConfig): SessionProvider | ||
| } | ||
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.
IMO it'd be preferable to use the existing
SessionConfigurationin the DSL API rather than using concrete classes such asSessionConfigandSessionManagerFactory. E.g. something like this should be possible: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.
Got it. So you're suggesting adding
sessionProvideras a property toOpenTelemetryConfigurationso users can inject custom providers via DSL. Is that correct? If yes, then something like this:That works for injection, but there's still a creation question of how do DI users create the
SessionProviderin the first place?For instance, when DI is used:
Here are the options I see for creating the SessionProvider:
Users can do
SessionManagerFactory(app).createSessionProvider(config)SessionProvider.create(app, timeout, maxLifetime)They rebuild SessionManager's logic (~200 lines of concurrent code, session storage, timeout handling, lifecycle integration)
Which approach makes the most sense to you? I'm leaning heavily toward option 1 (public factory) since it matches how DiskBufferingConfiguration works and keeps the actual implementation internal.