diff --git a/embrace-android-sdk/api/embrace-android-sdk.api b/embrace-android-sdk/api/embrace-android-sdk.api index e700e6c7e9..31ddfcd2bc 100644 --- a/embrace-android-sdk/api/embrace-android-sdk.api +++ b/embrace-android-sdk/api/embrace-android-sdk.api @@ -375,12 +375,10 @@ public final class io/embrace/android/embracesdk/spans/EmbraceSpanEventJsonAdapt public fun toString ()Ljava/lang/String; } -public final class io/embrace/android/embracesdk/spans/ErrorCode : java/lang/Enum, io/embrace/android/embracesdk/internal/spans/EmbraceAttributes$Attribute { +public final class io/embrace/android/embracesdk/spans/ErrorCode : java/lang/Enum { public static final field FAILURE Lio/embrace/android/embracesdk/spans/ErrorCode; public static final field UNKNOWN Lio/embrace/android/embracesdk/spans/ErrorCode; public static final field USER_ABANDON Lio/embrace/android/embracesdk/spans/ErrorCode; - public fun getCanonicalName ()Ljava/lang/String; - public fun keyName ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lio/embrace/android/embracesdk/spans/ErrorCode; public static fun values ()[Lio/embrace/android/embracesdk/spans/ErrorCode; } diff --git a/embrace-android-sdk/build.gradle b/embrace-android-sdk/build.gradle index 5045dcc6c9..6b50441cd0 100644 --- a/embrace-android-sdk/build.gradle +++ b/embrace-android-sdk/build.gradle @@ -103,6 +103,7 @@ dependencies { testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3" testImplementation("com.google.protobuf:protobuf-java:3.24.0") testImplementation("com.google.protobuf:protobuf-java-util:3.24.0") + testImplementation "org.jetbrains.kotlin:kotlin-reflect:1.4.32" dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:${Versions.dokka}") dokkaHtmlPlugin("org.jetbrains.dokka:android-documentation-plugin:${Versions.dokka}") diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt index 310cd34cbf..a1d734bd32 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/assertions/SpanAssertions.kt @@ -1,9 +1,13 @@ package io.embrace.android.embracesdk.assertions +import io.embrace.android.embracesdk.arch.assertError +import io.embrace.android.embracesdk.arch.assertIsKeySpan +import io.embrace.android.embracesdk.arch.assertIsPrivateSpan +import io.embrace.android.embracesdk.arch.assertNotKeySpan +import io.embrace.android.embracesdk.arch.assertNotPrivateSpan +import io.embrace.android.embracesdk.arch.assertSuccessful import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData -import io.embrace.android.embracesdk.internal.spans.isKey -import io.embrace.android.embracesdk.internal.spans.isPrivate import io.embrace.android.embracesdk.spans.EmbraceSpanEvent import io.embrace.android.embracesdk.spans.ErrorCode import io.opentelemetry.api.trace.StatusCode @@ -36,12 +40,24 @@ internal fun assertEmbraceSpanData( assertEquals(32, traceId.length) } assertEquals(expectedStatus, status) - assertEquals(errorCode?.name, attributes[errorCode?.keyName()]) + if (errorCode == null) { + assertSuccessful() + } else { + assertError(errorCode) + } expectedCustomAttributes.forEach { entry -> assertEquals(entry.value, attributes[entry.key]) } assertEquals(expectedEvents, events) - assertEquals(private, isPrivate()) - assertEquals(key, isKey()) + if (private) { + assertIsPrivateSpan() + } else { + assertNotPrivateSpan() + } + if (key) { + assertIsKeySpan() + } else { + assertNotKeySpan() + } } } \ No newline at end of file diff --git a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt index 31fcc516a2..35e71b7845 100644 --- a/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt +++ b/embrace-android-sdk/src/integrationTest/kotlin/io/embrace/android/embracesdk/testcases/TracingApiTest.kt @@ -204,6 +204,7 @@ internal class TracingApiTest { expectedParentId = traceRootSpan.spanId, expectedTraceId = traceRootSpan.traceId, expectedStatus = StatusCode.ERROR, + errorCode = ErrorCode.FAILURE, expectedCustomAttributes = mapOf(Pair("test-attr", "false")), expectedEvents = listOf( checkNotNull( diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/EmbraceNativeThreadSamplerService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/EmbraceNativeThreadSamplerService.kt index 0134e0c012..8f287d15a5 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/EmbraceNativeThreadSamplerService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/EmbraceNativeThreadSamplerService.kt @@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.anr.ndk import io.embrace.android.embracesdk.config.ConfigService import io.embrace.android.embracesdk.config.behavior.AnrBehavior import io.embrace.android.embracesdk.internal.DeviceArchitecture +import io.embrace.android.embracesdk.internal.SharedObjectLoader import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger import io.embrace.android.embracesdk.payload.NativeThreadAnrInterval @@ -25,7 +26,8 @@ internal class EmbraceNativeThreadSamplerService @JvmOverloads constructor( private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger, private val delegate: NdkDelegate = NativeThreadSamplerNdkDelegate(), private val scheduledWorker: ScheduledWorker, - private val deviceArchitecture: DeviceArchitecture + private val deviceArchitecture: DeviceArchitecture, + private val sharedObjectLoader: SharedObjectLoader ) : NativeThreadSamplerService { companion object { @@ -55,11 +57,16 @@ internal class EmbraceNativeThreadSamplerService @JvmOverloads constructor( private var targetThread: Thread = Thread.currentThread() override fun setupNativeSampler(): Boolean { - logger.logDeveloper( - "EmbraceNativeThreadSamplerService", - "Target thread found, attempting to install NativeThreadSampler" - ) - return delegate.setupNativeThreadSampler(deviceArchitecture.is32BitDevice) + return if (sharedObjectLoader.loadEmbraceNative()) { + logger.logDeveloper( + "EmbraceNativeThreadSamplerService", + "Target thread found, attempting to install NativeThreadSampler" + ) + delegate.setupNativeThreadSampler(deviceArchitecture.is32BitDevice) + } else { + logger.logWarning("Embrace native binary load failed. Native thread sampler setup aborted.") + false + } } override fun monitorCurrentThread(): Boolean { diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/NativeThreadSamplerInstaller.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/NativeThreadSamplerInstaller.kt index f279acbcd0..1280eafeec 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/NativeThreadSamplerInstaller.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/anr/ndk/NativeThreadSamplerInstaller.kt @@ -4,6 +4,7 @@ import android.os.Handler import android.os.Looper import io.embrace.android.embracesdk.anr.AnrService import io.embrace.android.embracesdk.config.ConfigService +import io.embrace.android.embracesdk.internal.SharedObjectLoader import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger import io.embrace.android.embracesdk.payload.NativeThreadAnrSample @@ -17,9 +18,9 @@ internal class NativeThreadSamplerNdkDelegate : EmbraceNativeThreadSamplerServic } internal class NativeThreadSamplerInstaller( - private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger + private val sharedObjectLoader: SharedObjectLoader, + private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger, ) { - private val isMonitoring = AtomicBoolean(false) private var targetHandler: Handler? = null @@ -64,6 +65,9 @@ internal class NativeThreadSamplerInstaller( } currentThread = Thread.currentThread() + if (!sharedObjectLoader.loadEmbraceNative()) { + return + } prepareTargetHandler() if (configService.anrBehavior.isNativeThreadAnrSamplingEnabled()) { diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/LogEventData.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/LogEventData.kt index 249ba4cde0..96ac269e75 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/LogEventData.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/LogEventData.kt @@ -17,5 +17,5 @@ internal class LogEventData( val severity: Severity, val message: String ) { - val attributes = schemaType.attrs.plus(Pair("emb.type", schemaType.telemetryType.description)) + val attributes = schemaType.attrs.plus(schemaType.telemetryType.toOTelKeyValuePair()) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/SpanEventData.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/SpanEventData.kt index 4623d03643..7834089bda 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/SpanEventData.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/SpanEventData.kt @@ -13,5 +13,5 @@ internal class SpanEventData( val schemaType: SchemaType, val spanStartTimeMs: Long ) { - val attributes = schemaType.attrs.plus(Pair("emb.type", schemaType.telemetryType.description)) + val attributes = schemaType.attrs.plus(schemaType.telemetryType.toOTelKeyValuePair()) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/StartSpanData.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/StartSpanData.kt index 2570b4730b..9d53677fbd 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/StartSpanData.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/destination/StartSpanData.kt @@ -13,5 +13,5 @@ internal class StartSpanData( val schemaType: SchemaType, val spanStartTimeMs: Long, ) { - val attributes = schemaType.attrs.plus(Pair("emb.type", schemaType.telemetryType.description)) + val attributes = schemaType.attrs.plus(schemaType.telemetryType.toOTelKeyValuePair()) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/AppTerminationCause.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/AppTerminationCause.kt new file mode 100644 index 0000000000..beb405fa15 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/AppTerminationCause.kt @@ -0,0 +1,16 @@ +package io.embrace.android.embracesdk.arch.schema + +/** + * Attribute that stores the reason an app instance terminated + */ +internal sealed class AppTerminationCause( + override val attributeValue: String +) : EmbraceAttribute { + override val attributeName: String = "termination_cause" + + internal object Crash : AppTerminationCause("crash") + + internal object UserTermination : AppTerminationCause("user_termination") + + internal object Unknown : AppTerminationCause("unknown") +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbType.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbType.kt index 39c272abb0..82385440b7 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbType.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbType.kt @@ -1,30 +1,39 @@ package io.embrace.android.embracesdk.arch.schema -internal sealed class EmbType { +internal sealed class EmbType(type: String, subtype: String?) : TelemetryType { + override val attributeName: String = "type" + override val attributeValue = type + (subtype?.run { ".$this" } ?: "") /** * Keys that track how fast a time interval is. Only applies to spans. */ - internal sealed class Performance : TelemetryType + internal sealed class Performance(subtype: String?) : EmbType("performance", subtype) { + + internal object Default : Performance(null) + + internal object Network : Performance("network") + } /** * Keys that track a point in time & is visual in nature. Applies to spans, logs, and span events. */ - internal sealed class Ux(subtype: String) : TelemetryType { - internal object View : Ux("view") + internal sealed class Ux(subtype: String) : EmbType("ux", subtype) { + + internal object Session : Ux("session") - override val description = "ux.$subtype" + internal object View : Ux("view") } /** * Keys that track a point in time that is not visual in nature. Applies to spans, logs, and span events. */ - internal sealed class System(subtype: String) : TelemetryType { + internal sealed class System(subtype: String) : EmbType("system", subtype) { + internal object Breadcrumb : System("breadcrumb") + internal object Log : System("log") - internal object Exit : System("exit") - override val description = "system.$subtype" + internal object Exit : System("exit") } } @@ -33,6 +42,4 @@ internal sealed class EmbType { * a visual event around a UI element. ux is the type, and view is the subtype. This tells the * backend that it can assume the data in the event follows a particular schema. */ -internal interface TelemetryType { - val description: String -} +internal interface TelemetryType : EmbraceAttribute diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbraceAttribute.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbraceAttribute.kt new file mode 100644 index 0000000000..569e6a1fcf --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/EmbraceAttribute.kt @@ -0,0 +1,28 @@ +package io.embrace.android.embracesdk.arch.schema + +import io.embrace.android.embracesdk.internal.spans.toEmbraceAttributeName + +/** + * Instance of a valid value for an attribute that has special meaning in the Embrace platform. + */ +internal interface EmbraceAttribute { + /** + * The unique name given to the attribute + */ + val attributeName: String + + /** + * The value of the particular instance of the attribute + */ + val attributeValue: String + + /** + * Return the appropriate key name for this attribute to use in an OpenTelemetry attribute + */ + fun otelAttributeName(): String = attributeName.toEmbraceAttributeName() + + /** + * Return attribute as a key-value pair appropriate to use as an OpenTelemetry attribute + */ + fun toOTelKeyValuePair() = Pair(otelAttributeName(), attributeValue) +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/ErrorCodeAttribute.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/ErrorCodeAttribute.kt new file mode 100644 index 0000000000..990da3d1b0 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/ErrorCodeAttribute.kt @@ -0,0 +1,20 @@ +package io.embrace.android.embracesdk.arch.schema + +import io.embrace.android.embracesdk.spans.ErrorCode +import java.util.Locale + +/** + * Attribute that stores the [ErrorCode] in an OpenTelemetry span + */ +internal sealed class ErrorCodeAttribute( + errorCode: ErrorCode +) : EmbraceAttribute { + override val attributeName: String = "error_code" + override val attributeValue: String = errorCode.name.toLowerCase(Locale.ENGLISH) + + internal object Failure : ErrorCodeAttribute(ErrorCode.FAILURE) + + internal object UserAbandon : ErrorCodeAttribute(ErrorCode.USER_ABANDON) + + internal object Unknown : ErrorCodeAttribute(ErrorCode.UNKNOWN) +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/KeySpan.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/KeySpan.kt new file mode 100644 index 0000000000..5b603ae6f1 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/KeySpan.kt @@ -0,0 +1,9 @@ +package io.embrace.android.embracesdk.arch.schema + +/** + * Denotes an important span to be aggregated and displayed as such in the platform. + */ +internal object KeySpan : EmbraceAttribute { + override val attributeName: String = "key" + override val attributeValue: String = "true" +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/PrivateSpan.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/PrivateSpan.kt new file mode 100644 index 0000000000..c6158ce982 --- /dev/null +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/arch/schema/PrivateSpan.kt @@ -0,0 +1,10 @@ +package io.embrace.android.embracesdk.arch.schema + +/** + * Denotes a private span recorded by Embrace for diagnostic or internal usage purposes that is not meant to be consumed directly by + * users of the SDK, nor is considered part of the public API. + */ +internal object PrivateSpan : EmbraceAttribute { + override val attributeName: String = "private" + override val attributeValue: String = "true" +} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt index 10be94e20b..a775a83aa6 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImpl.kt @@ -1,7 +1,13 @@ package io.embrace.android.embracesdk.capture.envelope.session import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause import io.embrace.android.embracesdk.internal.payload.SessionPayload +import io.embrace.android.embracesdk.internal.payload.Span +import io.embrace.android.embracesdk.internal.payload.toNewPayload +import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.internal.spans.SpanSink import io.embrace.android.embracesdk.logging.InternalErrorService import io.embrace.android.embracesdk.session.captureDataSafely import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType @@ -9,12 +15,24 @@ import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType internal class SessionPayloadSourceImpl( private val internalErrorService: InternalErrorService, private val nativeThreadSamplerService: NativeThreadSamplerService?, + private val spanSink: SpanSink, + private val currentSessionSpan: CurrentSessionSpan ) : SessionPayloadSource { - override fun getSessionPayload(endType: SessionSnapshotType) = SessionPayload( - spans = null, - spanSnapshots = null, - internalError = captureDataSafely { internalErrorService.currentExceptionError?.toNewPayload() }, - sharedLibSymbolMapping = captureDataSafely { nativeThreadSamplerService?.getNativeSymbols() } - ) + override fun getSessionPayload(endType: SessionSnapshotType): SessionPayload { + return SessionPayload( + spans = retrieveSpanData(endType), + spanSnapshots = null, + internalError = captureDataSafely { internalErrorService.currentExceptionError?.toNewPayload() }, + sharedLibSymbolMapping = captureDataSafely { nativeThreadSamplerService?.getNativeSymbols() } + ) + } + + private fun retrieveSpanData(endType: SessionSnapshotType): List? = captureDataSafely { + when (endType) { + SessionSnapshotType.NORMAL_END -> currentSessionSpan.endSession(null) + SessionSnapshotType.PERIODIC_CACHE -> spanSink.completedSpans() + SessionSnapshotType.JVM_CRASH -> currentSessionSpan.endSession(AppTerminationCause.Crash) + }.map(EmbraceSpanData::toNewPayload) + } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/user/EmbraceUserService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/user/EmbraceUserService.kt index c65eb59d27..eaadb618d1 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/user/EmbraceUserService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/capture/user/EmbraceUserService.kt @@ -1,18 +1,22 @@ package io.embrace.android.embracesdk.capture.user +import io.embrace.android.embracesdk.internal.utils.Provider import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.payload.UserInfo import io.embrace.android.embracesdk.payload.UserInfo.Companion.ofStored import io.embrace.android.embracesdk.prefs.PreferencesService +import java.util.concurrent.atomic.AtomicReference import java.util.regex.Pattern internal class EmbraceUserService( private val preferencesService: PreferencesService, private val logger: InternalEmbraceLogger ) : UserService { - - @Volatile - internal var info: UserInfo = ofStored(preferencesService) + /** + * Do not access this directly - use [userInfo] and [modifyUserInfo] to get and set this + */ + private val userInfoReference = AtomicReference(DEFAULT_USER) + private val userInfoProvider: Provider = { ofStored(preferencesService) } override fun loadUserInfoFromDisk(): UserInfo? { return try { @@ -23,14 +27,14 @@ internal class EmbraceUserService( } } - override fun getUserInfo(): UserInfo = info.copy() + override fun getUserInfo(): UserInfo = userInfo().copy() override fun setUserIdentifier(userId: String?) { - val currentUserId = info.userId + val currentUserId = userInfo().userId if (currentUserId != null && currentUserId == userId) { return } - info = info.copy(userId = userId) + modifyUserInfo(userInfo().copy(userId = userId)) preferencesService.userIdentifier = userId } @@ -39,11 +43,11 @@ internal class EmbraceUserService( } override fun setUsername(username: String?) { - val currentUserName = info.username + val currentUserName = userInfo().username if (currentUserName != null && currentUserName == username) { return } - info = info.copy(username = username) + modifyUserInfo(userInfo().copy(username = username)) preferencesService.username = username } @@ -52,11 +56,11 @@ internal class EmbraceUserService( } override fun setUserEmail(email: String?) { - val currentEmail = info.email + val currentEmail = userInfo().email if (currentEmail != null && currentEmail == email) { return } - info = info.copy(email = email) + modifyUserInfo(userInfo().copy(email = email)) preferencesService.userEmailAddress = email } @@ -80,7 +84,7 @@ internal class EmbraceUserService( logger.logWarning("Ignoring persona " + persona + " as it does not match " + VALID_PERSONA.pattern()) return } - val currentPersonas = info.personas + val currentPersonas = userInfo().personas if (currentPersonas != null) { if (currentPersonas.size >= PERSONA_LIMIT) { logger.logWarning("Cannot set persona as the limit of " + PERSONA_LIMIT + " has been reached") @@ -91,8 +95,8 @@ internal class EmbraceUserService( } } - val newPersonas: Set = info.personas?.plus(persona) ?: mutableSetOf(persona) - info = info.copy(personas = newPersonas) + val newPersonas: Set = userInfo().personas?.plus(persona) ?: mutableSetOf(persona) + modifyUserInfo(userInfo().copy(personas = newPersonas)) preferencesService.userPersonas = newPersonas } @@ -100,19 +104,19 @@ internal class EmbraceUserService( if (persona == null) { return } - val currentPersonas = info.personas + val currentPersonas = userInfo().personas if (currentPersonas != null && !currentPersonas.contains(persona)) { logger.logWarning("Persona '$persona' is not set") return } - val newPersonas: Set = info.personas?.minus(persona) ?: mutableSetOf() - info = info.copy(personas = newPersonas) + val newPersonas: Set = userInfo().personas?.minus(persona) ?: mutableSetOf() + modifyUserInfo(userInfo().copy(personas = newPersonas)) preferencesService.userPersonas = newPersonas } override fun clearAllUserPersonas() { - val currentPersonas = info.personas + val currentPersonas = userInfo().personas if (currentPersonas != null && currentPersonas.isEmpty()) { return } @@ -123,7 +127,7 @@ internal class EmbraceUserService( if (preferencesService.isUsersFirstDay()) { personas.add(UserInfo.PERSONA_FIRST_DAY_USER) } - info = info.copy(personas = personas) + modifyUserInfo(userInfo().copy(personas = personas)) preferencesService.userPersonas = personas } @@ -134,11 +138,36 @@ internal class EmbraceUserService( clearAllUserPersonas() } + private fun userInfo(): UserInfo { + if (userInfoReference.get() === DEFAULT_USER) { + synchronized(userInfoReference) { + if (userInfoReference.get() === DEFAULT_USER) { + userInfoReference.set(userInfoProvider()) + } + } + } + + return userInfoReference.get() + } + + private fun modifyUserInfo(newUserInfo: UserInfo) { + synchronized(userInfoReference) { + userInfoReference.set(newUserInfo) + } + } + companion object { // Valid persona regex representation. val VALID_PERSONA: Pattern = Pattern.compile("^[a-zA-Z0-9_]{1,32}$") // Maximum number of allowed personas. const val PERSONA_LIMIT = 10 + + private val DEFAULT_USER = UserInfo( + userId = "", + email = "", + username = "", + personas = emptySet() + ) } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt index 6804878e32..9a1072c7e0 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/comms/api/EmbraceApiService.kt @@ -8,6 +8,7 @@ import io.embrace.android.embracesdk.comms.delivery.DeliveryCacheManager import io.embrace.android.embracesdk.comms.delivery.NetworkStatus import io.embrace.android.embracesdk.comms.delivery.PendingApiCallsSender import io.embrace.android.embracesdk.config.remote.RemoteConfig +import io.embrace.android.embracesdk.internal.Systrace import io.embrace.android.embracesdk.internal.compression.ConditionalGzipOutputStream import io.embrace.android.embracesdk.internal.logs.LogPayload import io.embrace.android.embracesdk.internal.payload.Envelope @@ -38,14 +39,24 @@ internal class EmbraceApiService( networkConnectivityService: NetworkConnectivityService, ) : ApiService, NetworkConnectivityListener { - private val mapper = ApiRequestMapper(urlBuilder, lazyDeviceId, appId) - private val configUrl = urlBuilder.getConfigUrl() + private val mapper by lazy { + Systrace.traceSynchronous("api-request-mapper-init") { + ApiRequestMapper(urlBuilder, lazyDeviceId, appId) + } + } + private val configUrl by lazy { + Systrace.traceSynchronous("config-url-init") { + urlBuilder.getConfigUrl() + } + } private var lastNetworkStatus: NetworkStatus = NetworkStatus.UNKNOWN init { - networkConnectivityService.addNetworkConnectivityListener(this) - lastNetworkStatus = networkConnectivityService.getCurrentNetworkStatus() - pendingApiCallsSender.setSendMethod(this::executePost) + Systrace.traceSynchronous("api-service-init-block") { + networkConnectivityService.addNetworkConnectivityListener(this) + lastNetworkStatus = networkConnectivityService.getCurrentNetworkStatus() + pendingApiCallsSender.setSendMethod(this::executePost) + } } @Throws(IllegalStateException::class) @@ -64,23 +75,28 @@ internal class EmbraceApiService( serializer.fromJson(it, RemoteConfig::class.java) } } + is ApiResponse.NotModified -> { logger.logInfo("Confirmed config has not been modified.") cachedResponse.remoteConfig } + is ApiResponse.TooManyRequests -> { // TODO: We should retry after the retryAfter time or 3 seconds and apply exponential backoff. logger.logWarning("Too many requests. ") null } + is ApiResponse.Failure -> { logger.logInfo("Failed to fetch config (no response).") null } + is ApiResponse.Incomplete -> { logger.logWarning("Failed to fetch config.", response.exception) throw response.exception } + ApiResponse.PayloadTooLarge -> { // Not expected to receive a 413 response for a GET request. null diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/SharedObjectLoader.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/SharedObjectLoader.kt index aae7a49f87..29f5325bdd 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/SharedObjectLoader.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/SharedObjectLoader.kt @@ -1,17 +1,35 @@ package io.embrace.android.embracesdk.internal import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger +import java.util.concurrent.atomic.AtomicBoolean /** - * Loads shared object files. + * Component to load native binaries */ internal class SharedObjectLoader { + val loaded = AtomicBoolean(false) - fun loadEmbraceNative() = try { - System.loadLibrary("embrace-native") - true - } catch (exc: UnsatisfiedLinkError) { - InternalStaticEmbraceLogger.logError("Failed to load SO file embrace-native", exc) - false + /** + * Load Embrace native binary if necessary + */ + fun loadEmbraceNative(): Boolean { + if (loaded.get()) { + return true + } + synchronized(loaded) { + if (!loaded.get()) { + try { + Systrace.traceSynchronous("load-embrace-native-lib") { + System.loadLibrary("embrace-native") + } + loaded.set(true) + } catch (exc: UnsatisfiedLinkError) { + InternalStaticEmbraceLogger.logError("Failed to load SO file embrace-native", exc) + return false + } + } + + return true + } } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt index 42f05b7203..144e841c27 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/payload/SpanMapper.kt @@ -1,32 +1,31 @@ package io.embrace.android.embracesdk.internal.payload -import io.opentelemetry.api.common.Attributes +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.spans.EmbraceSpanEvent import io.opentelemetry.api.trace.StatusCode -import io.opentelemetry.sdk.trace.data.EventData -import io.opentelemetry.sdk.trace.data.SpanData -internal fun SpanData.toNewPayload() = Span( +internal fun EmbraceSpanData.toNewPayload() = Span( traceId = traceId, spanId = spanId, parentSpanId = parentSpanId, name = name, - startTimeUnixNano = startEpochNanos, - endTimeUnixNano = endEpochNanos, - status = when (status.statusCode) { + startTimeUnixNano = startTimeNanos, + endTimeUnixNano = endTimeNanos, + status = when (status) { StatusCode.UNSET -> Span.Status.UNSET StatusCode.OK -> Span.Status.OK StatusCode.ERROR -> Span.Status.ERROR else -> Span.Status.UNSET }, - events = events.map(EventData::toNewPayload), + events = events.map(EmbraceSpanEvent::toNewPayload), attributes = attributes.toNewPayload() ) -internal fun EventData.toNewPayload() = SpanEvent( +internal fun EmbraceSpanEvent.toNewPayload() = SpanEvent( name = name, - timeUnixNano = epochNanos, + timeUnixNano = timestampNanos, attributes = attributes.toNewPayload() ) -internal fun Attributes.toNewPayload(): List = - asMap().map { (key, value) -> Attribute(key.key, value.toString()) } +internal fun Map.toNewPayload(): List = + map { (key, value) -> Attribute(key, value) } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpan.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpan.kt index 777b9aaf2c..60b9cb014e 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpan.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpan.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.internal.spans import io.embrace.android.embracesdk.arch.destination.SessionSpanWriter +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause import io.embrace.android.embracesdk.internal.Initializable import io.embrace.android.embracesdk.spans.EmbraceSpan @@ -11,7 +12,7 @@ internal interface CurrentSessionSpan : Initializable, SessionSpanWriter { /** * End the current session span and start a new one if the app is not terminating */ - fun endSession(appTerminationCause: EmbraceAttributes.AppTerminationCause? = null): List + fun endSession(appTerminationCause: AppTerminationCause? = null): List /** * Returns true if a span with the given parameters can be started in the current session diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImpl.kt index 887301fe3a..734e41d3be 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImpl.kt @@ -3,7 +3,10 @@ package io.embrace.android.embracesdk.internal.spans import io.embrace.android.embracesdk.arch.destination.SessionSpanWriter import io.embrace.android.embracesdk.arch.destination.SpanAttributeData import io.embrace.android.embracesdk.arch.destination.SpanEventData +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.internal.clock.nanosToMillis +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanImpl.Companion.setEmbraceAttribute import io.embrace.android.embracesdk.internal.utils.Provider import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.telemetry.TelemetryService @@ -64,7 +67,7 @@ internal class CurrentSessionSpanImpl( } } - override fun endSession(appTerminationCause: EmbraceAttributes.AppTerminationCause?): List { + override fun endSession(appTerminationCause: AppTerminationCause?): List { val endingSessionSpan = sessionSpan.get() ?: return emptyList() synchronized(sessionSpan) { // Right now, session spans don't survive native crashes and sudden process terminations, @@ -80,10 +83,7 @@ internal class CurrentSessionSpanImpl( spanRepository.clearCompletedSpans() sessionSpan.set(startSessionSpan(clock.now().nanosToMillis())) } else { - endingSessionSpan.addAttribute( - appTerminationCause.keyName(), - appTerminationCause.name - ) + endingSessionSpan.setEmbraceAttribute(appTerminationCause) endingSessionSpan.stop() } return spanSink.flushSpans() @@ -110,7 +110,7 @@ internal class CurrentSessionSpanImpl( val spanBuilder = createEmbraceSpanBuilder( tracer = tracerSupplier(), name = "session", - type = EmbraceAttributes.Type.SESSION + type = EmbType.Ux.Session ) .setNoParent() .setStartTimestamp(startTimeMs, TimeUnit.MILLISECONDS) diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceExtensions.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceExtensions.kt index f7a1898473..ee5f3d773b 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceExtensions.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceExtensions.kt @@ -1,6 +1,9 @@ package io.embrace.android.embracesdk.internal.spans -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes.Attribute +import io.embrace.android.embracesdk.arch.schema.EmbraceAttribute +import io.embrace.android.embracesdk.arch.schema.KeySpan +import io.embrace.android.embracesdk.arch.schema.PrivateSpan +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.internal.utils.Provider import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent @@ -40,17 +43,6 @@ private const val EMBRACE_USAGE_ATTRIBUTE_NAME_PREFIX = "emb.usage." */ private const val SEQUENCE_ID_ATTRIBUTE_NAME = EMBRACE_ATTRIBUTE_NAME_PREFIX + "sequence_id" -/** - * Denotes an important span to be listed in the Spans listing page in the UI. Currently defined as any spans that are the root of a trace - */ -private const val KEY_SPAN_ATTRIBUTE_NAME = EMBRACE_ATTRIBUTE_NAME_PREFIX + "key" - -/** - * Denotes a private span logged by Embrace for diagnostic purposes and should not be displayed to customers in the dashboard, but - * should be shown to Embrace employees. - */ -private const val PRIVATE_SPAN_ATTRIBUTE_NAME = EMBRACE_ATTRIBUTE_NAME_PREFIX + "private" - /** * Creates a new [SpanBuilder] with the correctly prefixed name, to be used for recording Spans in the SDK internally */ @@ -68,9 +60,9 @@ internal fun Tracer.embraceSpanBuilder(name: String, internal: Boolean): SpanBui internal fun createEmbraceSpanBuilder( tracer: Tracer, name: String, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean = true -): SpanBuilder = tracer.embraceSpanBuilder(name, internal).setType(type) +): SpanBuilder = tracer.embraceSpanBuilder(name, internal).setEmbraceAttribute(type) /** * Create a [SpanBuilder] that will create a root [Span] with the appropriate custom Embrace attributes @@ -78,33 +70,31 @@ internal fun createEmbraceSpanBuilder( internal fun createRootSpanBuilder( tracer: Tracer, name: String, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean ): SpanBuilder = createEmbraceSpanBuilder(tracer = tracer, name = name, type = type, internal = internal).setNoParent() /** - * Sets and returns the [EmbraceAttributes.Type] attribute for the given [SpanBuilder] + * Sets an [EmbraceAttribute] on the given [SpanBuilder] and return it */ -internal fun SpanBuilder.setType(value: EmbraceAttributes.Type): SpanBuilder { - setAttribute(value.keyName(), value.typeName) +internal fun SpanBuilder.setEmbraceAttribute(embraceAttribute: EmbraceAttribute): SpanBuilder { + setAttribute(embraceAttribute.otelAttributeName(), embraceAttribute.attributeValue) return this } /** - * Mark the span generated by this builder as an important one, to be listed in the Spans listing page in the UI. It is currently used - * for any spans that are the root of a trace. + * Mark the span generated by this builder as a [KeySpan] */ internal fun SpanBuilder.makeKey(): SpanBuilder { - setAttribute(KEY_SPAN_ATTRIBUTE_NAME, true) + setEmbraceAttribute(KeySpan) return this } /** - * Mark the span generated by this builder as a private span logged by Embrace for diagnostic purposes and that should not be displayed - * to customers in the dashboard, but should be shown to Embrace employees. + * Mark the span generated by this builder as [PrivateSpan] */ internal fun SpanBuilder.makePrivate(): SpanBuilder { - setAttribute(PRIVATE_SPAN_ATTRIBUTE_NAME, true) + setEmbraceAttribute(PrivateSpan) return this } @@ -172,6 +162,10 @@ internal fun Span.setSequenceId(id: Long): Span { return this } +internal fun Span.setEmbraceAttribute(embraceAttribute: EmbraceAttribute) { + setAttribute(embraceAttribute.otelAttributeName(), embraceAttribute.attributeValue) +} + /** * Ends the given [Span], and setting the correct properties per the optional [ErrorCode] passed in. If [errorCode] * is not specified, it means the [Span] completed successfully, and no [ErrorCode] will be set. @@ -181,7 +175,7 @@ internal fun Span.endSpan(errorCode: ErrorCode? = null, endTimeMs: Long? = null) setStatus(StatusCode.OK) } else { setStatus(StatusCode.ERROR) - setAttribute(errorCode.keyName(), errorCode.toString()) + setEmbraceAttribute(errorCode.fromErrorCode()) } if (endTimeMs != null) { @@ -203,16 +197,6 @@ internal fun AttributesBuilder.fromMap(attributes: Map): Attribu return this } -/** - * Returns true if the Span is a Key Span - */ -internal fun EmbraceSpanData.isKey(): Boolean = attributes[KEY_SPAN_ATTRIBUTE_NAME] == true.toString() - -/** - * Returns true if the Span is private - */ -internal fun EmbraceSpanData.isPrivate(): Boolean = attributes[PRIVATE_SPAN_ATTRIBUTE_NAME] == true.toString() - /** * Return the appropriate internal Embrace Span name given the current value */ @@ -227,55 +211,3 @@ internal fun String.toEmbraceAttributeName(): String = EMBRACE_ATTRIBUTE_NAME_PR * Return the appropriate internal Embrace attribute usage name given the current string */ internal fun String.toEmbraceUsageAttributeName(): String = EMBRACE_USAGE_ATTRIBUTE_NAME_PREFIX + this - -/** - * Contains the set of attributes (i.e. implementers of the [Attribute] interface) set on a [Span] by the SDK that has special meaning - * in the Embrace world. Each enum defines the attribute name used in the [Span] and specifies the set of valid values it can be set to. - */ -internal object EmbraceAttributes { - - /** - * Attribute to categorize a [Span] and give it a distinct semantic meaning. Spans of each [Type] may be treated differently by the - * backend and can be expected to contain a set of attributes to further flesh out the given semantic meanings. - */ - internal enum class Type(val typeName: String) : Attribute { - /** - * Spans that model an Embrace session or background activity. - */ - SESSION("ux.session"), - - /** - * A [Span] created by an SDK user to measure the performance of an operation - */ - PERFORMANCE("performance"); - - override val canonicalName = "type" - } - - /** - * The reason for the termination of a process span - */ - internal enum class AppTerminationCause : Attribute { - CRASH, - USER_TERMINATION, - UNKNOWN; - - override val canonicalName: String = "termination_cause" - } - - /** - * Denotes an attribute added by the SDK with a restricted set of valid values - */ - internal interface Attribute { - - /** - * The name used to identify this [Attribute] - */ - val canonicalName: String - - /** - * The name used as the key for the [Attribute] in the attributes map - */ - fun keyName(): String = EMBRACE_ATTRIBUTE_NAME_PREFIX + canonicalName - } -} diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImpl.kt index e6771e35c0..cd34caf0a0 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanImpl.kt @@ -1,5 +1,6 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.schema.EmbraceAttribute import io.embrace.android.embracesdk.internal.clock.normalizeTimestampAsMillis import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent @@ -144,5 +145,10 @@ internal class EmbraceSpanImpl( internal fun attributeValid(key: String, value: String) = key.length <= MAX_ATTRIBUTE_KEY_LENGTH && value.length <= MAX_ATTRIBUTE_VALUE_LENGTH + + internal fun EmbraceSpan.setEmbraceAttribute(embraceAttribute: EmbraceAttribute): EmbraceSpan { + addAttribute(embraceAttribute.otelAttributeName(), embraceAttribute.attributeValue) + return this + } } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanService.kt index de6e4ff598..4ec0690fa8 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanService.kt @@ -1,5 +1,6 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.internal.utils.Provider import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent @@ -42,13 +43,13 @@ internal class EmbraceSpanService( override fun initialized(): Boolean = currentDelegate is SpanServiceImpl - override fun createSpan(name: String, parent: EmbraceSpan?, type: EmbraceAttributes.Type, internal: Boolean): EmbraceSpan? = + override fun createSpan(name: String, parent: EmbraceSpan?, type: TelemetryType, internal: Boolean): EmbraceSpan? = currentDelegate.createSpan(name = name, parent = parent, type = type, internal = internal) override fun recordSpan( name: String, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, @@ -68,7 +69,7 @@ internal class EmbraceSpanService( startTimeMs: Long, endTimeMs: Long, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanService.kt index c3375c1d1a..ff11096c51 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanService.kt @@ -1,5 +1,7 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.schema.EmbType +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.internal.Initializable import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent @@ -16,7 +18,7 @@ internal interface SpanService : Initializable { fun createSpan( name: String, parent: EmbraceSpan? = null, - type: EmbraceAttributes.Type = EmbraceAttributes.Type.PERFORMANCE, + type: TelemetryType = EmbType.Performance.Default, internal: Boolean = true ): EmbraceSpan? @@ -27,7 +29,7 @@ internal interface SpanService : Initializable { name: String, parent: EmbraceSpan? = null, startTimeMs: Long? = null, - type: EmbraceAttributes.Type = EmbraceAttributes.Type.PERFORMANCE, + type: TelemetryType = EmbType.Performance.Default, internal: Boolean = true ): EmbraceSpan? { createSpan( @@ -51,7 +53,7 @@ internal interface SpanService : Initializable { fun recordSpan( name: String, parent: EmbraceSpan? = null, - type: EmbraceAttributes.Type = EmbraceAttributes.Type.PERFORMANCE, + type: TelemetryType = EmbType.Performance.Default, internal: Boolean = true, attributes: Map = emptyMap(), events: List = emptyList(), @@ -67,7 +69,7 @@ internal interface SpanService : Initializable { startTimeMs: Long, endTimeMs: Long, parent: EmbraceSpan? = null, - type: EmbraceAttributes.Type = EmbraceAttributes.Type.PERFORMANCE, + type: TelemetryType = EmbType.Performance.Default, internal: Boolean = true, attributes: Map = emptyMap(), events: List = emptyList(), diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImpl.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImpl.kt index 13a4af801d..ba9fc8faee 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImpl.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImpl.kt @@ -1,5 +1,6 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent import io.embrace.android.embracesdk.spans.ErrorCode @@ -27,7 +28,7 @@ internal class SpanServiceImpl( override fun initialized(): Boolean = initialized.get() - override fun createSpan(name: String, parent: EmbraceSpan?, type: EmbraceAttributes.Type, internal: Boolean): EmbraceSpan? { + override fun createSpan(name: String, parent: EmbraceSpan?, type: TelemetryType, internal: Boolean): EmbraceSpan? { return if (EmbraceSpanImpl.inputsValid(name) && currentSessionSpan.canStartNewSpan(parent, internal)) { EmbraceSpanImpl( spanBuilder = createRootSpanBuilder(tracer = tracer, name = name, type = type, internal = internal), @@ -42,7 +43,7 @@ internal class SpanServiceImpl( override fun recordSpan( name: String, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, @@ -62,7 +63,7 @@ internal class SpanServiceImpl( startTimeMs: Long, endTimeMs: Long, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/UninitializedSdkSpanService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/UninitializedSdkSpanService.kt index b45fc1d856..20fe1f1734 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/UninitializedSdkSpanService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/internal/spans/UninitializedSdkSpanService.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.internal.spans import io.embrace.android.embracesdk.annotation.InternalApi +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent import io.embrace.android.embracesdk.spans.ErrorCode @@ -21,12 +22,12 @@ internal class UninitializedSdkSpanService : SpanService { override fun initialized(): Boolean = true - override fun createSpan(name: String, parent: EmbraceSpan?, type: EmbraceAttributes.Type, internal: Boolean): EmbraceSpan? = null + override fun createSpan(name: String, parent: EmbraceSpan?, type: TelemetryType, internal: Boolean): EmbraceSpan? = null override fun recordSpan( name: String, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, @@ -38,7 +39,7 @@ internal class UninitializedSdkSpanService : SpanService { startTimeMs: Long, endTimeMs: Long, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, @@ -116,7 +117,7 @@ internal class UninitializedSdkSpanService : SpanService { val startTimeMs: Long, val endTimeMs: Long, val parent: EmbraceSpan?, - val type: EmbraceAttributes.Type, + val type: TelemetryType, val internal: Boolean, val attributes: Map, val events: List, diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/EmbraceNdkService.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/EmbraceNdkService.kt index abf0137408..06db0e084e 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/EmbraceNdkService.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/EmbraceNdkService.kt @@ -88,13 +88,14 @@ internal class EmbraceNdkService( null } if (configService.autoDataCaptureBehavior.isNdkEnabled()) { - processStateService.addListener(this) - if (appFramework == AppFramework.UNITY) { - unityCrashId = getEmbUuid() + Systrace.traceSynchronous("init-ndk-service") { + processStateService.addListener(this) + if (appFramework == AppFramework.UNITY) { + unityCrashId = getEmbUuid() + } + startNdk() + cleanOldCrashFiles() } - - Systrace.traceSynchronous("start-ndk-service") { startNdk() } - Systrace.traceSynchronous("clear-stale-crashes") { cleanOldCrashFiles() } } } @@ -230,26 +231,31 @@ internal class EmbraceNdkService( "EmbraceNDKService", "Installing signal handlers. 32bit=$is32bit, crashId=$nativeCrashId" ) - val initialMetaData = serializer.toJson( - NativeCrashMetadata( - metadataService.getLightweightAppInfo(), - metadataService.getLightweightDeviceInfo(), - userService.getUserInfo(), - sessionProperties.get().toMap() + + val initialMetaData = Systrace.traceSynchronous("init-native-crash-metadata") { + serializer.toJson( + NativeCrashMetadata( + metadataService.getLightweightAppInfo(), + metadataService.getLightweightDeviceInfo(), + userService.getUserInfo(), + sessionProperties.get().toMap() + ) ) - ) - delegate._installSignalHandlers( - reportBasePath, - markerFilePath, - initialMetaData, - "null", - metadataService.getAppState(), - nativeCrashId, - Build.VERSION.SDK_INT, - is32bit, - ApkToolsConfig.IS_DEVELOPER_LOGGING_ENABLED - ) - updateDeviceMetaData() + } + Systrace.traceSynchronous("native-install-handlers") { + delegate._installSignalHandlers( + reportBasePath, + markerFilePath, + initialMetaData, + "null", + metadataService.getAppState(), + nativeCrashId, + Build.VERSION.SDK_INT, + is32bit, + ApkToolsConfig.IS_DEVELOPER_LOGGING_ENABLED + ) + } + Systrace.traceSynchronous("update-metadata") { updateDeviceMetaData() } isInstalled = true } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/NativeModule.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/NativeModule.kt index 41e69f8779..76f8768734 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/NativeModule.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/ndk/NativeModule.kt @@ -10,7 +10,6 @@ import io.embrace.android.embracesdk.injection.DeliveryModule import io.embrace.android.embracesdk.injection.EssentialServiceModule import io.embrace.android.embracesdk.injection.StorageModule import io.embrace.android.embracesdk.injection.singleton -import io.embrace.android.embracesdk.internal.SharedObjectLoader import io.embrace.android.embracesdk.internal.Systrace import io.embrace.android.embracesdk.session.properties.EmbraceSessionProperties import io.embrace.android.embracesdk.worker.WorkerName @@ -59,12 +58,13 @@ internal class NativeModuleImpl( override val nativeThreadSamplerService: NativeThreadSamplerService? by singleton { Systrace.traceSynchronous("native-thread-sampler-init") { - if (nativeThreadSamplingEnabled(essentialServiceModule.configService, essentialServiceModule.sharedObjectLoader)) { + if (nativeThreadSamplingEnabled(essentialServiceModule.configService)) { EmbraceNativeThreadSamplerService( essentialServiceModule.configService, lazy { ndkService.getSymbolsForCurrentArch() }, scheduledWorker = workerThreadModule.scheduledWorker(WorkerName.BACKGROUND_REGISTRATION), - deviceArchitecture = essentialServiceModule.deviceArchitecture + deviceArchitecture = essentialServiceModule.deviceArchitecture, + sharedObjectLoader = essentialServiceModule.sharedObjectLoader ) } else { null @@ -74,16 +74,15 @@ internal class NativeModuleImpl( override val nativeThreadSamplerInstaller: NativeThreadSamplerInstaller? by singleton { Systrace.traceSynchronous("native-thread-sampler-installer-init") { - if (nativeThreadSamplingEnabled(essentialServiceModule.configService, essentialServiceModule.sharedObjectLoader)) { - NativeThreadSamplerInstaller() + if (nativeThreadSamplingEnabled(essentialServiceModule.configService)) { + NativeThreadSamplerInstaller(sharedObjectLoader = essentialServiceModule.sharedObjectLoader) } else { null } } } - private fun nativeThreadSamplingEnabled(configService: ConfigService, sharedObjectLoader: SharedObjectLoader) = - configService.autoDataCaptureBehavior.isNdkEnabled() && sharedObjectLoader.loadEmbraceNative() + private fun nativeThreadSamplingEnabled(configService: ConfigService) = configService.autoDataCaptureBehavior.isNdkEnabled() private val embraceNdkServiceRepository by singleton { EmbraceNdkServiceRepository( diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/PayloadMessageCollator.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/PayloadMessageCollator.kt index 51d63e86f3..df77a08f3e 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/PayloadMessageCollator.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/message/PayloadMessageCollator.kt @@ -1,6 +1,7 @@ package io.embrace.android.embracesdk.session.message import io.embrace.android.embracesdk.anr.ndk.NativeThreadSamplerService +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause import io.embrace.android.embracesdk.capture.PerformanceInfoService import io.embrace.android.embracesdk.capture.crumbs.BreadcrumbService import io.embrace.android.embracesdk.capture.metadata.MetadataService @@ -12,7 +13,6 @@ import io.embrace.android.embracesdk.config.ConfigService import io.embrace.android.embracesdk.event.EventService import io.embrace.android.embracesdk.event.LogMessageService import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData import io.embrace.android.embracesdk.internal.spans.SpanSink import io.embrace.android.embracesdk.internal.utils.Uuid @@ -160,11 +160,12 @@ internal class PayloadMessageCollator( when { !params.isCacheAttempt -> { val appTerminationCause = when { - finalPayload.crashReportId != null -> EmbraceAttributes.AppTerminationCause.CRASH + finalPayload.crashReportId != null -> AppTerminationCause.Crash else -> null } currentSessionSpan.endSession(appTerminationCause) } + else -> spanSink.completedSpans() } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/properties/EmbraceSessionProperties.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/properties/EmbraceSessionProperties.kt index 620a9ce12f..4c133f214b 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/properties/EmbraceSessionProperties.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/session/properties/EmbraceSessionProperties.kt @@ -1,9 +1,11 @@ package io.embrace.android.embracesdk.session.properties import io.embrace.android.embracesdk.config.ConfigService +import io.embrace.android.embracesdk.internal.utils.Provider import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.logging.InternalStaticEmbraceLogger import io.embrace.android.embracesdk.prefs.PreferencesService +import java.util.concurrent.atomic.AtomicReference internal class EmbraceSessionProperties( private val preferencesService: PreferencesService, @@ -11,16 +13,25 @@ internal class EmbraceSessionProperties( private val logger: InternalEmbraceLogger = InternalStaticEmbraceLogger.logger ) { private val temporary: MutableMap = HashMap() - private var permanent: MutableMap + private val permanentPropertiesReference = AtomicReference(NOT_LOADED) + private val permanentPropertiesProvider: Provider> = { + preferencesService.permanentSessionProperties?.let(::HashMap) ?: HashMap() + } + + private fun permanentProperties(): MutableMap { + if (permanentPropertiesReference.get() === NOT_LOADED) { + synchronized(permanentPropertiesReference) { + if (permanentPropertiesReference.get() === NOT_LOADED) { + permanentPropertiesReference.set(permanentPropertiesProvider()) + } + } + } - init { - // TODO: this blocks on the preferences being successfully read from this. Are we cool with this? - val existingPermanent: Map? = preferencesService.permanentSessionProperties - permanent = existingPermanent?.let(::HashMap) ?: HashMap() + return permanentPropertiesReference.get() } private fun haveKey(key: String): Boolean { - return permanent.containsKey(key) || temporary.containsKey(key) + return permanentProperties().containsKey(key) || temporary.containsKey(key) } private fun isValidKey(key: String?): Boolean { @@ -51,58 +62,64 @@ internal class EmbraceSessionProperties( return value.substring(0, maxLength - endChars.length) + endChars } - @Synchronized fun add(key: String, value: String, isPermanent: Boolean): Boolean { - if (!isValidKey(key)) { - return false - } - val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT) - if (!isValidValue(value)) { - return false - } - val sanitizedValue = enforceLength(value, SESSION_PROPERTY_VALUE_LIMIT) - val maxSessionProperties = configService.sessionBehavior.getMaxSessionProperties() - if (size() > maxSessionProperties || size() == maxSessionProperties && !haveKey(sanitizedKey)) { - logger.logError("Session property count is at its limit. Rejecting.") - return false - } + synchronized(permanentPropertiesReference) { + if (!isValidKey(key)) { + return false + } + val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT) + if (!isValidValue(value)) { + return false + } + val sanitizedValue = enforceLength(value, SESSION_PROPERTY_VALUE_LIMIT) + val maxSessionProperties = configService.sessionBehavior.getMaxSessionProperties() + if (size() > maxSessionProperties || size() == maxSessionProperties && !haveKey(sanitizedKey)) { + logger.logError("Session property count is at its limit. Rejecting.") + return false + } - // add to selected destination, deleting the key if it exists in the other destination - if (isPermanent) { - permanent[sanitizedKey] = sanitizedValue - temporary.remove(sanitizedKey) - preferencesService.permanentSessionProperties = permanent - } else { - // only save the permanent values if the key existed in the permanent map - if (permanent.remove(sanitizedKey) != null) { - preferencesService.permanentSessionProperties = permanent + // add to selected destination, deleting the key if it exists in the other destination + if (isPermanent) { + permanentProperties()[sanitizedKey] = sanitizedValue + temporary.remove(sanitizedKey) + preferencesService.permanentSessionProperties = permanentProperties() + } else { + // only save the permanent values if the key existed in the permanent map + val newPermanent = permanentProperties() + if (newPermanent.remove(sanitizedKey) != null) { + permanentPropertiesReference.set(newPermanent) + preferencesService.permanentSessionProperties = permanentProperties() + } + temporary[sanitizedKey] = sanitizedValue } - temporary[sanitizedKey] = sanitizedValue + return true } - return true } - @Synchronized fun remove(key: String): Boolean { - if (!isValidKey(key)) { - return false - } - val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT) - var existed = false - if (temporary.remove(sanitizedKey) != null) { - existed = true - } - if (permanent.remove(sanitizedKey) != null) { - preferencesService.permanentSessionProperties = permanent - existed = true + synchronized(permanentPropertiesReference) { + if (!isValidKey(key)) { + return false + } + val sanitizedKey = enforceLength(key, SESSION_PROPERTY_KEY_LIMIT) + var existed = false + if (temporary.remove(sanitizedKey) != null) { + existed = true + } + + val newPermanent = permanentProperties() + if (newPermanent.remove(sanitizedKey) != null) { + permanentPropertiesReference.set(newPermanent) + preferencesService.permanentSessionProperties = permanentProperties() + existed = true + } + return existed } - return existed } - @Synchronized - fun get(): Map = permanent.plus(temporary) + fun get(): Map = synchronized(permanentPropertiesReference) { permanentProperties().plus(temporary) } - private fun size(): Int = permanent.size + temporary.size + private fun size(): Int = permanentProperties().size + temporary.size fun clearTemporary() = temporary.clear() @@ -113,5 +130,6 @@ internal class EmbraceSessionProperties( */ private const val SESSION_PROPERTY_KEY_LIMIT = 128 private const val SESSION_PROPERTY_VALUE_LIMIT = 1024 + private val NOT_LOADED = mutableMapOf() } } diff --git a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/spans/ErrorCode.kt b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/spans/ErrorCode.kt index 3db786c8c8..85e07ac3b4 100644 --- a/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/spans/ErrorCode.kt +++ b/embrace-android-sdk/src/main/java/io/embrace/android/embracesdk/spans/ErrorCode.kt @@ -1,14 +1,13 @@ package io.embrace.android.embracesdk.spans import io.embrace.android.embracesdk.annotation.BetaApi -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes +import io.embrace.android.embracesdk.arch.schema.ErrorCodeAttribute /** - * Attribute to categorize the broad reason a Span completed unsuccessfully. + * Categorize the broad reason a Span completed unsuccessfully. */ @BetaApi -public enum class ErrorCode : EmbraceAttributes.Attribute { - +public enum class ErrorCode { /** * An application failure caused the Span to terminate */ @@ -24,5 +23,9 @@ public enum class ErrorCode : EmbraceAttributes.Attribute { */ UNKNOWN; - override val canonicalName: String = "error_code" + internal fun fromErrorCode(): ErrorCodeAttribute = when (this) { + FAILURE -> ErrorCodeAttribute.Failure + USER_ABANDON -> ErrorCodeAttribute.UserAbandon + UNKNOWN -> ErrorCodeAttribute.Unknown + } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceNativeThreadSamplerServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceNativeThreadSamplerServiceTest.kt index bdf7deb625..707639a21a 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceNativeThreadSamplerServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceNativeThreadSamplerServiceTest.kt @@ -9,6 +9,7 @@ import io.embrace.android.embracesdk.config.remote.AnrRemoteConfig import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeDeviceArchitecture import io.embrace.android.embracesdk.fakes.fakeAnrBehavior +import io.embrace.android.embracesdk.internal.SharedObjectLoader import io.embrace.android.embracesdk.logging.InternalEmbraceLogger import io.embrace.android.embracesdk.payload.NativeThreadAnrInterval import io.embrace.android.embracesdk.payload.NativeThreadAnrSample @@ -32,6 +33,7 @@ import java.util.concurrent.ThreadFactory internal class EmbraceNativeThreadSamplerServiceTest { private lateinit var sampler: EmbraceNativeThreadSamplerService + private lateinit var sharedObjectLoader: SharedObjectLoader private lateinit var configService: ConfigService private lateinit var delegate: EmbraceNativeThreadSamplerService.NdkDelegate private lateinit var random: Random @@ -46,6 +48,7 @@ internal class EmbraceNativeThreadSamplerServiceTest { cfg = AnrRemoteConfig(pctNativeThreadAnrSamplingEnabled = 100f) anrBehavior = fakeAnrBehavior { cfg } configService = FakeConfigService(anrBehavior = anrBehavior) + sharedObjectLoader = mockk(relaxed = true) delegate = mockk(relaxed = true) val logger = InternalEmbraceLogger() random = mockk(relaxed = true) @@ -58,9 +61,11 @@ internal class EmbraceNativeThreadSamplerServiceTest { logger, delegate, ScheduledWorker(executorService), - FakeDeviceArchitecture() + FakeDeviceArchitecture(), + sharedObjectLoader ) every { random.nextInt(any()) } returns 0 + every { sharedObjectLoader.loadEmbraceNative() } returns true } @Test diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceUserServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceUserServiceTest.kt index 5173e28f2e..3e7f00cb9d 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceUserServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/EmbraceUserServiceTest.kt @@ -49,21 +49,21 @@ internal class EmbraceUserServiceTest { @Test fun testUserInfoNotLoaded() { mockNoUserInfo() - assertNotNull(service.info) - service.info.verifyNoUserInfo() + assertNotNull(service.getUserInfo()) + service.getUserInfo().verifyNoUserInfo() } @Test fun testUserInfoLoaded() { mockUserInfo() - assertNotNull(service.info) - service.info.verifyExpectedUserInfo() + assertNotNull(service.getUserInfo()) + service.getUserInfo().verifyExpectedUserInfo() } @Test fun testUserInfoSessionCopy() { mockUserInfo() - assertNotSame(service.info, service.getUserInfo()) + assertNotSame(service.getUserInfo(), service.getUserInfo()) } @Test @@ -71,11 +71,11 @@ internal class EmbraceUserServiceTest { mockUserInfo() with(service) { - assertEquals("f0a923498c", info.userId) + assertEquals("f0a923498c", getUserInfo().userId) setUserIdentifier("abc") - assertEquals("abc", info.userId) + assertEquals("abc", getUserInfo().userId) service.clearUserIdentifier() - assertNull(info.userId) + assertNull(getUserInfo().userId) } } @@ -84,11 +84,11 @@ internal class EmbraceUserServiceTest { mockUserInfo() with(service) { - assertEquals("Mr Test", info.username) + assertEquals("Mr Test", getUserInfo().username) setUsername("Joe") - assertEquals("Joe", info.username) + assertEquals("Joe", getUserInfo().username) service.clearUsername() - assertNull(info.username) + assertNull(getUserInfo().username) } } @@ -97,11 +97,11 @@ internal class EmbraceUserServiceTest { mockUserInfo() with(service) { - assertEquals("test@example.com", info.email) + assertEquals("test@example.com", getUserInfo().email) setUserEmail("foo@test.com") - assertEquals("foo@test.com", info.email) + assertEquals("foo@test.com", getUserInfo().email) service.clearUserEmail() - assertNull(info.email) + assertNull(getUserInfo().email) } } @@ -110,11 +110,11 @@ internal class EmbraceUserServiceTest { mockUserInfo() with(service) { - assertTrue(checkNotNull(info.personas).contains("payer")) + assertTrue(checkNotNull(getUserInfo().personas).contains("payer")) clearUserAsPayer() - assertFalse(checkNotNull(info.personas).contains("payer")) + assertFalse(checkNotNull(getUserInfo().personas).contains("payer")) setUserAsPayer() - assertTrue(checkNotNull(info.personas).contains("payer")) + assertTrue(checkNotNull(getUserInfo().personas).contains("payer")) } } @@ -123,12 +123,12 @@ internal class EmbraceUserServiceTest { mockUserInfo() with(service) { - info.verifyExpectedUserInfo() + getUserInfo().verifyExpectedUserInfo() service.clearAllUserInfo() - assertNull(info.email) - assertNull(info.userId) - assertNull(info.username) - assertEquals(extraPersonas, info.personas) + assertNull(getUserInfo().email) + assertNull(getUserInfo().userId) + assertNull(getUserInfo().username) + assertEquals(extraPersonas, getUserInfo().personas) } } @@ -137,7 +137,7 @@ internal class EmbraceUserServiceTest { mockUserInfo() val persona = "!@£$$%*(" service.addUserPersona(persona) - val personas = checkNotNull(service.info.personas) + val personas = checkNotNull(service.getUserInfo().personas) assertFalse(personas.contains(persona)) } @@ -148,7 +148,7 @@ internal class EmbraceUserServiceTest { repeat(11) { k -> service.addUserPersona("Persona_$k") } - val personas = checkNotNull(service.info.personas) + val personas = checkNotNull(service.getUserInfo().personas) assertTrue(personas.contains("Persona_1")) assertTrue(personas.contains("Persona_9")) assertFalse(personas.contains("Persona_10")) diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/NativeThreadSamplerInstallerTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/NativeThreadSamplerInstallerTest.kt index 018d873f66..e7993c99f4 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/NativeThreadSamplerInstallerTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/NativeThreadSamplerInstallerTest.kt @@ -7,6 +7,7 @@ import io.embrace.android.embracesdk.config.ConfigService import io.embrace.android.embracesdk.config.remote.AnrRemoteConfig import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.fakeAnrBehavior +import io.embrace.android.embracesdk.internal.SharedObjectLoader import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -19,31 +20,34 @@ import java.util.concurrent.TimeUnit internal class NativeThreadSamplerInstallerTest { private lateinit var sampler: EmbraceNativeThreadSamplerService + private lateinit var sharedObjectLoader: SharedObjectLoader private lateinit var configService: ConfigService private lateinit var anrService: AnrService private lateinit var delegate: EmbraceNativeThreadSamplerService.NdkDelegate private lateinit var cfg: AnrRemoteConfig + private lateinit var installer: NativeThreadSamplerInstaller @Before fun setUp() { + sharedObjectLoader = mockk(relaxed = true) anrService = mockk(relaxed = true) delegate = mockk(relaxed = true) sampler = mockk(relaxed = true) cfg = AnrRemoteConfig(pctNativeThreadAnrSamplingEnabled = 100f) configService = FakeConfigService(anrBehavior = fakeAnrBehavior { cfg }) + installer = NativeThreadSamplerInstaller(sharedObjectLoader = sharedObjectLoader) + every { sharedObjectLoader.loadEmbraceNative() } returns true } @Test fun testInstallDisabled() { - val installer = NativeThreadSamplerInstaller() installer.monitorCurrentThread(sampler, configService, anrService) verify(exactly = 0) { delegate.setupNativeThreadSampler(false) } } @Test fun testInstallEnabledSuccess() { - val installer = NativeThreadSamplerInstaller() every { sampler.setupNativeSampler() } returns true every { sampler.monitorCurrentThread() } returns true @@ -56,7 +60,6 @@ internal class NativeThreadSamplerInstallerTest { @Test fun testInstallEnabledFailure() { - val installer = NativeThreadSamplerInstaller() every { sampler.setupNativeSampler() } returns false every { sampler.monitorCurrentThread() } returns false sampler.setupNativeSampler() @@ -81,7 +84,6 @@ internal class NativeThreadSamplerInstallerTest { @Test fun testConfigListener() { - val installer = NativeThreadSamplerInstaller() every { sampler.setupNativeSampler() } returns true every { sampler.monitorCurrentThread() } returns true sampler.setupNativeSampler() @@ -95,7 +97,6 @@ internal class NativeThreadSamplerInstallerTest { @Test fun testInstallNewThread() { - val installer = NativeThreadSamplerInstaller() every { sampler.setupNativeSampler() } returns true every { sampler.monitorCurrentThread() } returns true diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/EmbraceAttributeExtensions.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/EmbraceAttributeExtensions.kt new file mode 100644 index 0000000000..71309e604a --- /dev/null +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/EmbraceAttributeExtensions.kt @@ -0,0 +1,85 @@ +package io.embrace.android.embracesdk.arch + +import io.embrace.android.embracesdk.arch.destination.LogEventData +import io.embrace.android.embracesdk.arch.destination.StartSpanData +import io.embrace.android.embracesdk.arch.schema.EmbType +import io.embrace.android.embracesdk.arch.schema.EmbraceAttribute +import io.embrace.android.embracesdk.arch.schema.ErrorCodeAttribute +import io.embrace.android.embracesdk.arch.schema.KeySpan +import io.embrace.android.embracesdk.arch.schema.PrivateSpan +import io.embrace.android.embracesdk.arch.schema.TelemetryType +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.spans.ErrorCode +import io.opentelemetry.api.trace.StatusCode +import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNull +import org.junit.Assert.assertFalse + +/** + * Assert [EmbraceSpanData] is of type [EmbType.Performance.Default] + */ +internal fun EmbraceSpanData.assertIsTypePerformance() = assertIsType(EmbType.Performance.Default) + +/** + * Assert [EmbraceSpanData] is of type [telemetryType] + */ +internal fun EmbraceSpanData.assertIsType(telemetryType: TelemetryType) = assertHasEmbraceAttribute(telemetryType) + +internal fun EmbraceSpanData.assertIsKeySpan() = assertHasEmbraceAttribute(KeySpan) + +internal fun EmbraceSpanData.assertNotKeySpan() = assertDoesNotHaveEmbraceAttribute(KeySpan) + +internal fun EmbraceSpanData.assertIsPrivateSpan() = assertHasEmbraceAttribute(PrivateSpan) + +internal fun EmbraceSpanData.assertNotPrivateSpan() = assertDoesNotHaveEmbraceAttribute(PrivateSpan) + +/** + * Assert [EmbraceSpanData] has the [EmbraceAttribute] defined by [embraceAttribute] + */ +internal fun EmbraceSpanData.assertHasEmbraceAttribute(embraceAttribute: EmbraceAttribute) { + assertEquals(embraceAttribute.attributeValue, attributes[embraceAttribute.otelAttributeName()]) +} + +internal fun EmbraceSpanData.assertDoesNotHaveEmbraceAttribute(embraceAttribute: EmbraceAttribute) { + assertFalse(attributes[embraceAttribute.otelAttributeName()]?.equals(embraceAttribute.attributeValue) ?: false) +} + +/** + * Assert [EmbraceSpanData] has ended with the error defined by [errorCode] + */ +internal fun EmbraceSpanData.assertError(errorCode: ErrorCode) { + assertEquals(StatusCode.ERROR, status) + assertHasEmbraceAttribute(errorCode.fromErrorCode()) +} + +/** + * Assert [EmbraceSpanData] has ended successfully + */ +internal fun EmbraceSpanData.assertSuccessful() { + assertEquals(StatusCode.OK, status) + assertNull(attributes[ErrorCodeAttribute.Failure.otelAttributeName()]) +} + +/** + * Assert [StartSpanData] is of type [telemetryType] + */ +internal fun StartSpanData.assertIsType(telemetryType: TelemetryType) = assertHasEmbraceAttribute(telemetryType) + +/** + * Assert [StartSpanData] has the [EmbraceAttribute] defined by [embraceAttribute] + */ +internal fun StartSpanData.assertHasEmbraceAttribute(embraceAttribute: EmbraceAttribute) { + assertEquals(embraceAttribute.attributeValue, attributes[embraceAttribute.otelAttributeName()]) +} + +/** + * Assert [LogEventData] is of type [telemetryType] + */ +internal fun LogEventData.assertIsType(telemetryType: TelemetryType) = assertHasEmbraceAttribute(telemetryType) + +/** + * Assert [LogEventData] has the [EmbraceAttribute] defined by [embraceAttribute] + */ +internal fun LogEventData.assertHasEmbraceAttribute(embraceAttribute: EmbraceAttribute) { + assertEquals(embraceAttribute.attributeValue, attributes[embraceAttribute.otelAttributeName()]) +} diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/SpanDataSourceKtTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/SpanDataSourceKtTest.kt index 99f8ea2436..3ded981e7d 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/SpanDataSourceKtTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/arch/SpanDataSourceKtTest.kt @@ -2,6 +2,7 @@ package io.embrace.android.embracesdk.arch import io.embrace.android.embracesdk.arch.datasource.startSpanCapture import io.embrace.android.embracesdk.arch.destination.StartSpanData +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.arch.schema.SchemaType import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule @@ -25,7 +26,7 @@ internal class SpanDataSourceKtTest { SchemaType.ViewBreadcrumb("my-view"), 1500000000000 ) - assertEquals("ux.view", data.attributes["emb.type"]) + data.assertIsType(EmbType.Ux.View) assertEquals("my-view", data.attributes["view.name"]) val span = service.startSpanCapture("") { data } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/aei/AeiDataSourceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/aei/AeiDataSourceImplTest.kt index 30711d1e04..216f9417b5 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/aei/AeiDataSourceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/aei/AeiDataSourceImplTest.kt @@ -4,6 +4,8 @@ import android.app.ActivityManager import android.app.ApplicationExitInfo import com.google.common.util.concurrent.MoreExecutors import io.embrace.android.embracesdk.Severity +import io.embrace.android.embracesdk.arch.assertIsType +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.config.remote.AppExitInfoConfig import io.embrace.android.embracesdk.config.remote.RemoteConfig import io.embrace.android.embracesdk.fakes.FakeConfigService @@ -360,7 +362,7 @@ internal class AeiDataSourceImplTest { val logEventData = logWriter.logEvents.single() assertEquals("aei-record", logEventData.schemaType.name) assertEquals(Severity.INFO, logEventData.severity) - assertEquals("system.exit", logEventData.attributes["emb.type"]) + logEventData.assertIsType(EmbType.System.Exit) return logEventData.attributes } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/CustomBreadcrumbDataSourceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/CustomBreadcrumbDataSourceTest.kt index 665151b19f..4bc4f78f8a 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/CustomBreadcrumbDataSourceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/CustomBreadcrumbDataSourceTest.kt @@ -1,5 +1,6 @@ package io.embrace.android.embracesdk.capture.crumbs +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.internal.clock.millisToNanos @@ -35,7 +36,7 @@ internal class CustomBreadcrumbDataSourceTest { assertEquals(15000000000.millisToNanos(), spanStartTimeMs) assertEquals( mapOf( - "emb.type" to "system.breadcrumb", + EmbType.System.Breadcrumb.toOTelKeyValuePair(), "message" to "Hello, world!" ), attributes diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/FragmentBreadcrumbDataSourceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/FragmentBreadcrumbDataSourceTest.kt index 2cf5e414be..7e64e788ed 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/FragmentBreadcrumbDataSourceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/crumbs/FragmentBreadcrumbDataSourceTest.kt @@ -1,9 +1,9 @@ package io.embrace.android.embracesdk.capture.crumbs +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.FakeConfigService import io.embrace.android.embracesdk.fakes.FakeSpanService -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -35,12 +35,12 @@ internal class FragmentBreadcrumbDataSourceTest { val span = spanService.createdSpans.single() assertEquals("view-breadcrumb", span.name) - assertEquals(EmbraceAttributes.Type.PERFORMANCE, span.type) + assertEquals(EmbType.Performance.Default, span.type) assertTrue(span.isRecording) assertEquals( mapOf( "view.name" to "my_fragment", - "emb.type" to "ux.view" + EmbType.Ux.View.toOTelKeyValuePair(), ), span.attributes ) @@ -54,12 +54,12 @@ internal class FragmentBreadcrumbDataSourceTest { val span = spanService.createdSpans.single() assertEquals("view-breadcrumb", span.name) - assertEquals(EmbraceAttributes.Type.PERFORMANCE, span.type) + assertEquals(EmbType.Performance.Default, span.type) assertFalse(span.isRecording) assertEquals( mapOf( "view.name" to "my_fragment", - "emb.type" to "ux.view" + EmbType.Ux.View.toOTelKeyValuePair() ), span.attributes ) diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt index 930d6466e3..995788b271 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/SessionEnvelopeSourceTest.kt @@ -1,8 +1,10 @@ package io.embrace.android.embracesdk.capture.envelope import io.embrace.android.embracesdk.capture.envelope.session.SessionPayloadSourceImpl +import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.FakeInternalErrorService import io.embrace.android.embracesdk.fakes.FakeNativeThreadSamplerService +import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType import org.junit.Test @@ -13,7 +15,9 @@ internal class SessionEnvelopeSourceTest { SessionEnvelopeSource( SessionPayloadSourceImpl( FakeInternalErrorService(), - FakeNativeThreadSamplerService() + FakeNativeThreadSamplerService(), + SpanSinkImpl(), + FakeCurrentSessionSpan() ) ).getEnvelope(SessionSnapshotType.NORMAL_END) } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt index c3ad2c53b7..71422798e8 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/envelope/session/SessionPayloadSourceImplTest.kt @@ -1,18 +1,26 @@ package io.embrace.android.embracesdk.capture.envelope.session import io.embrace.android.embracesdk.fakes.FakeClock +import io.embrace.android.embracesdk.fakes.FakeCurrentSessionSpan import io.embrace.android.embracesdk.fakes.FakeInternalErrorService import io.embrace.android.embracesdk.fakes.FakeNativeThreadSamplerService +import io.embrace.android.embracesdk.fakes.FakeSpanData import io.embrace.android.embracesdk.internal.payload.SessionPayload +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData +import io.embrace.android.embracesdk.internal.spans.SpanSinkImpl import io.embrace.android.embracesdk.payload.LegacyExceptionError import io.embrace.android.embracesdk.session.orchestrator.SessionSnapshotType import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Before import org.junit.Test internal class SessionPayloadSourceImplTest { private lateinit var impl: SessionPayloadSourceImpl + private lateinit var sink: SpanSinkImpl + private lateinit var currentSessionSpan: FakeCurrentSessionSpan + private val cacheSpan = FakeSpanData(name = "cache-span") @Before fun setUp() { @@ -21,9 +29,17 @@ internal class SessionPayloadSourceImplTest { addException(RuntimeException(), "test", FakeClock()) } } + sink = SpanSinkImpl().apply { + storeCompletedSpans(listOf(cacheSpan)) + } + currentSessionSpan = FakeCurrentSessionSpan().apply { + spanData = listOf(EmbraceSpanData(FakeSpanData("my-span"))) + } impl = SessionPayloadSourceImpl( errorService, - FakeNativeThreadSamplerService() + FakeNativeThreadSamplerService(), + sink, + currentSessionSpan ) } @@ -31,23 +47,30 @@ internal class SessionPayloadSourceImplTest { fun `session crash`() { val payload = impl.getSessionPayload(SessionSnapshotType.JVM_CRASH) assertPayloadPopulated(payload) + val span = checkNotNull(payload.spans?.single()) + assertEquals("my-span", span.name) } @Test fun `session cache`() { val payload = impl.getSessionPayload(SessionSnapshotType.PERIODIC_CACHE) assertPayloadPopulated(payload) + val span = checkNotNull(payload.spans?.single()) + assertEquals("cache-span", span.name) } @Test fun `session lifecycle change`() { val payload = impl.getSessionPayload(SessionSnapshotType.NORMAL_END) assertPayloadPopulated(payload) + val span = checkNotNull(payload.spans?.single()) + assertEquals("my-span", span.name) } private fun assertPayloadPopulated(payload: SessionPayload) { val err = checkNotNull(payload.internalError) assertEquals(1, err.count) assertEquals(mapOf("armeabi-v7a" to "my-symbols"), payload.sharedLibSymbolMapping) + assertNull(payload.spanSnapshots) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/startup/StartupServiceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/startup/StartupServiceImplTest.kt index 1e157db4d9..be1d8777e7 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/startup/StartupServiceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/capture/startup/StartupServiceImplTest.kt @@ -1,18 +1,18 @@ package io.embrace.android.embracesdk.capture.startup +import io.embrace.android.embracesdk.arch.assertIsPrivateSpan +import io.embrace.android.embracesdk.arch.assertIsTypePerformance import io.embrace.android.embracesdk.concurrency.BlockableExecutorService import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.internal.spans.SpanService import io.embrace.android.embracesdk.internal.spans.SpanSink -import io.embrace.android.embracesdk.internal.spans.isPrivate import io.embrace.android.embracesdk.worker.BackgroundWorker import io.opentelemetry.api.trace.SpanId import io.opentelemetry.api.trace.StatusCode import org.junit.Assert.assertEquals import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -49,11 +49,8 @@ internal class StartupServiceImplTest { assertEquals(SpanId.getInvalid(), parentSpanId) assertEquals(startTimeMillis, startTimeNanos.nanosToMillis()) assertEquals(endTimeMillis, endTimeNanos.nanosToMillis()) - assertEquals( - io.embrace.android.embracesdk.internal.spans.EmbraceAttributes.Type.PERFORMANCE.typeName, - attributes[io.embrace.android.embracesdk.internal.spans.EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertTrue(isPrivate()) + assertIsTypePerformance() + assertIsPrivateSpan() assertEquals(StatusCode.OK, status) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt index e140332d18..a87ef46a5a 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeCurrentSessionSpan.kt @@ -2,8 +2,8 @@ package io.embrace.android.embracesdk.fakes import io.embrace.android.embracesdk.arch.destination.SpanAttributeData import io.embrace.android.embracesdk.arch.destination.SpanEventData +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause import io.embrace.android.embracesdk.internal.spans.CurrentSessionSpan -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData import io.embrace.android.embracesdk.spans.EmbraceSpan @@ -12,6 +12,7 @@ internal class FakeCurrentSessionSpan : CurrentSessionSpan { var initializedCallCount = 0 var addedEvents = mutableListOf() var addedAttributes = mutableListOf() + var spanData = listOf() override fun initializeService(sdkInitStartTimeMs: Long) { } @@ -31,8 +32,8 @@ internal class FakeCurrentSessionSpan : CurrentSessionSpan { return true } - override fun endSession(appTerminationCause: EmbraceAttributes.AppTerminationCause?): List { - return emptyList() + override fun endSession(appTerminationCause: AppTerminationCause?): List { + return spanData } override fun canStartNewSpan(parent: EmbraceSpan?, internal: Boolean): Boolean { diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeEmbraceSpan.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeEmbraceSpan.kt index 44ddff240b..8ec0e242c1 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeEmbraceSpan.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeEmbraceSpan.kt @@ -1,8 +1,9 @@ package io.embrace.android.embracesdk.fakes import io.embrace.android.embracesdk.arch.destination.SpanEventData +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.arch.schema.SchemaType -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.ErrorCode import io.opentelemetry.sdk.trace.IdGenerator @@ -10,7 +11,7 @@ import io.opentelemetry.sdk.trace.IdGenerator internal class FakeEmbraceSpan( override val parent: EmbraceSpan?, val name: String? = null, - val type: EmbraceAttributes.Type = EmbraceAttributes.Type.PERFORMANCE, + val type: TelemetryType = EmbType.Performance.Default, val internal: Boolean = true ) : EmbraceSpan { diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeSpanService.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeSpanService.kt index 770eac38b4..2f2b03d7a0 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeSpanService.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fakes/FakeSpanService.kt @@ -1,6 +1,6 @@ package io.embrace.android.embracesdk.fakes -import io.embrace.android.embracesdk.internal.spans.EmbraceAttributes +import io.embrace.android.embracesdk.arch.schema.TelemetryType import io.embrace.android.embracesdk.internal.spans.SpanService import io.embrace.android.embracesdk.spans.EmbraceSpan import io.embrace.android.embracesdk.spans.EmbraceSpanEvent @@ -18,7 +18,7 @@ internal class FakeSpanService : SpanService { override fun createSpan( name: String, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean ): EmbraceSpan = FakeEmbraceSpan(null, name, type, internal).apply { createdSpans.add(this) @@ -27,7 +27,7 @@ internal class FakeSpanService : SpanService { override fun recordSpan( name: String, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, @@ -41,7 +41,7 @@ internal class FakeSpanService : SpanService { startTimeMs: Long, endTimeMs: Long, parent: EmbraceSpan?, - type: EmbraceAttributes.Type, + type: TelemetryType, internal: Boolean, attributes: Map, events: List, diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fixtures/SpansTestFixtures.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fixtures/SpansTestFixtures.kt index 3abd470a58..5e8df060ed 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fixtures/SpansTestFixtures.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/fixtures/SpansTestFixtures.kt @@ -1,5 +1,6 @@ package io.embrace.android.embracesdk.fixtures +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData import io.embrace.android.embracesdk.internal.spans.EmbraceSpanImpl import io.embrace.android.embracesdk.spans.EmbraceSpanEvent @@ -30,7 +31,10 @@ internal val testSpan = EmbraceSpanData( ) ) ), - attributes = mapOf(Pair("emb.sequence_id", "3"), Pair("emb.type", "performance")) + attributes = mapOf( + Pair("emb.sequence_id", "3"), + EmbType.Performance.Default.toOTelKeyValuePair(), + ) ) private fun createMapOfSize(size: Int): Map { diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt index b8043585f0..b5411da97b 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/logs/EmbraceLogServiceTest.kt @@ -4,6 +4,8 @@ import com.google.common.util.concurrent.MoreExecutors import io.embrace.android.embracesdk.Embrace.AppFramework import io.embrace.android.embracesdk.LogExceptionType import io.embrace.android.embracesdk.Severity +import io.embrace.android.embracesdk.arch.assertIsType +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.config.ConfigService import io.embrace.android.embracesdk.config.remote.LogRemoteConfig import io.embrace.android.embracesdk.config.remote.RemoteConfig @@ -103,7 +105,7 @@ internal class EmbraceLogServiceTest { assertNotNull(third.attributes["emb.log_id"]) assertEquals("session-123", third.attributes["emb.session_id"]) assertNull(third.attributes["emb.exception_type"]) - assertEquals("system.log", third.attributes["emb.type"]) + third.assertIsType(EmbType.System.Log) } @Test @@ -132,11 +134,11 @@ internal class EmbraceLogServiceTest { assertNotNull(log.attributes["emb.log_id"]) assertEquals("session-123", log.attributes["emb.session_id"]) assertEquals("none", log.attributes["emb.exception_type"]) - assertEquals("system.log", log.attributes["emb.type"]) + log.assertIsType(EmbType.System.Log) } @Test - fun `Embrace properties can not be overriden by custom properties`() { + fun `Embrace properties can not be overridden by custom properties`() { val logService = getLogService() val props = mapOf("emb.session_id" to "session-456") logService.log("Hello world", Severity.INFO, props) diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt index b709b2aa11..ad46f09581 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/payload/SpanMapperTest.kt @@ -1,7 +1,7 @@ package io.embrace.android.embracesdk.internal.payload import io.embrace.android.embracesdk.fakes.FakeSpanData -import io.opentelemetry.sdk.trace.data.SpanData +import io.embrace.android.embracesdk.internal.spans.EmbraceSpanData import org.junit.Assert.assertEquals import org.junit.Test @@ -9,33 +9,33 @@ internal class SpanMapperTest { @Test fun toSpan() { - val input: SpanData = FakeSpanData() + val input = EmbraceSpanData(FakeSpanData()) val output = input.toNewPayload() assertEquals(input.traceId, output.traceId) assertEquals(input.spanId, output.spanId) assertEquals(input.parentSpanId, output.parentSpanId) assertEquals(input.name, output.name) - assertEquals(input.startEpochNanos, output.startTimeUnixNano) - assertEquals(input.endEpochNanos, output.endTimeUnixNano) - assertEquals(input.status.statusCode.name, checkNotNull(output.status).name) + assertEquals(input.startTimeNanos, output.startTimeUnixNano) + assertEquals(input.endTimeNanos, output.endTimeUnixNano) + assertEquals(input.status.name, checkNotNull(output.status).name) // validate event copied val inputEvent = input.events.single() val outputEvent = checkNotNull(output.events).single() assertEquals(inputEvent.name, outputEvent.name) - assertEquals(inputEvent.epochNanos, outputEvent.timeUnixNano) + assertEquals(inputEvent.timestampNanos, outputEvent.timeUnixNano) // test event attributes - val inputEventAttrs = checkNotNull(inputEvent.attributes?.asMap()) + val inputEventAttrs = checkNotNull(inputEvent.attributes) val outputEventAttrs = checkNotNull(outputEvent.attributes?.single()) - assertEquals(inputEventAttrs.keys.single().key, outputEventAttrs.key) + assertEquals(inputEventAttrs.keys.single(), outputEventAttrs.key) assertEquals(inputEventAttrs.values.single(), outputEventAttrs.data) // test attributes - val inputAttrs = checkNotNull(input.attributes?.asMap()) + val inputAttrs = checkNotNull(input.attributes) val outputAttrs = checkNotNull(output.attributes?.single()) - assertEquals(inputAttrs.keys.single().key, outputAttrs.key) + assertEquals(inputAttrs.keys.single(), outputAttrs.key) assertEquals(inputAttrs.values.single(), outputAttrs.data) } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImplTests.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImplTests.kt index 87d6384450..29b81331d3 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImplTests.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/CurrentSessionSpanImplTests.kt @@ -1,13 +1,18 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.assertHasEmbraceAttribute +import io.embrace.android.embracesdk.arch.assertIsType +import io.embrace.android.embracesdk.arch.assertNotKeySpan +import io.embrace.android.embracesdk.arch.assertSuccessful import io.embrace.android.embracesdk.arch.destination.SpanAttributeData import io.embrace.android.embracesdk.arch.destination.SpanEventData +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.arch.schema.SchemaType import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.clock.nanosToMillis import io.embrace.android.embracesdk.spans.EmbraceSpan -import io.opentelemetry.api.trace.StatusCode import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -130,23 +135,21 @@ internal class CurrentSessionSpanImplTests { @Test fun `flushing with app termination and termination reason flushes session span with right termination type`() { - EmbraceAttributes.AppTerminationCause.values().forEach { + AppTerminationCause::class.sealedSubclasses.forEach { + val cause = checkNotNull(it.objectInstance) val module = FakeInitModule(clock = clock) val sessionSpan = module.openTelemetryModule.currentSessionSpan module.openTelemetryModule.spanService.initializeService(clock.now()) - val flushedSpans = sessionSpan.endSession(it) + val flushedSpans = sessionSpan.endSession(cause) assertEquals(1, flushedSpans.size) val lastFlushedSpan = flushedSpans[0] with(lastFlushedSpan) { assertEquals("emb-session", name) - assertEquals( - EmbraceAttributes.Type.SESSION.typeName, - attributes[EmbraceAttributes.Type.SESSION.keyName()] - ) - assertEquals(StatusCode.OK, status) - assertFalse(isKey()) - assertEquals(it.name, attributes[it.keyName()]) + assertIsType(EmbType.Ux.Session) + assertSuccessful() + assertNotKeySpan() + assertHasEmbraceAttribute(cause) } assertEquals(0, module.openTelemetryModule.spanSink.completedSpans().size) @@ -191,7 +194,13 @@ internal class CurrentSessionSpanImplTests { val testEvent = span.events.single() assertEquals("custom-breadcrumb", testEvent.name) assertEquals(1000, testEvent.timestampNanos.nanosToMillis()) - assertEquals(mapOf("emb.type" to "system.breadcrumb", "message" to "test-event"), testEvent.attributes) + assertEquals( + mapOf( + EmbType.System.Breadcrumb.toOTelKeyValuePair(), + "message" to "test-event" + ), + testEvent.attributes + ) } @Test diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanServiceTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanServiceTest.kt index 852fcc083f..7d5b900727 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanServiceTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceSpanServiceTest.kt @@ -1,5 +1,7 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.assertIsTypePerformance +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.clock.nanosToMillis @@ -58,7 +60,7 @@ internal class EmbraceSpanServiceTest { val expectedName = "test-span" val expectedStartTimeMs = clock.now() val expectedEndTimeMs = expectedStartTimeMs + 100L - val expectedType = EmbraceAttributes.Type.PERFORMANCE + val expectedType = EmbType.Performance.Default val expectedAttributes = mapOf( Pair("attribute1", "value1"), Pair("attribute2", "value2") @@ -86,7 +88,7 @@ internal class EmbraceSpanServiceTest { assertEquals(name, name) assertEquals(expectedStartTimeMs, startTimeNanos.nanosToMillis()) assertEquals(expectedEndTimeMs, endTimeNanos.nanosToMillis()) - assertEquals(expectedType.typeName, attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()]) + assertIsTypePerformance() expectedAttributes.forEach { assertEquals(it.value, attributes[it.key]) } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceTracerTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceTracerTest.kt index fb41f2095d..59281a3809 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceTracerTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/EmbraceTracerTest.kt @@ -1,5 +1,11 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.assertError +import io.embrace.android.embracesdk.arch.assertIsKeySpan +import io.embrace.android.embracesdk.arch.assertIsTypePerformance +import io.embrace.android.embracesdk.arch.assertNotKeySpan +import io.embrace.android.embracesdk.arch.assertNotPrivateSpan +import io.embrace.android.embracesdk.arch.assertSuccessful import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fixtures.TOO_LONG_SPAN_NAME @@ -9,7 +15,6 @@ import io.embrace.android.embracesdk.spans.EmbraceSpanEvent import io.embrace.android.embracesdk.spans.ErrorCode import io.opentelemetry.api.trace.StatusCode import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Assert.assertSame @@ -65,7 +70,7 @@ internal class EmbraceTracerTest { assertNotNull(embraceSpan) assertTrue(embraceSpan.start()) assertTrue(embraceSpan.stop(errorCode)) - verifyPublicSpan("test-span") + verifyPublicSpan(name = "test-span", errorCode = errorCode) spanSink.flushSpans() } } @@ -314,13 +319,18 @@ internal class EmbraceTracerTest { assertEquals(1, currentSpans.size) val currentSpan = currentSpans[0] assertEquals(name, currentSpan.name) - assertEquals( - EmbraceAttributes.Type.PERFORMANCE.typeName, - currentSpan.attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertEquals(if (traceRoot) "true" else null, currentSpan.attributes["emb.key"]) - assertEquals(errorCode?.name, currentSpan.attributes[errorCode?.keyName()]) - assertFalse(currentSpan.isPrivate()) + currentSpan.assertIsTypePerformance() + if (traceRoot) { + currentSpan.assertIsKeySpan() + } else { + currentSpan.assertNotKeySpan() + } + if (errorCode == null) { + currentSpan.assertSuccessful() + } else { + currentSpan.assertError(errorCode) + } + currentSpan.assertNotPrivateSpan() return currentSpan } } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/InternalTracerTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/InternalTracerTest.kt index d16098e410..88914de396 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/InternalTracerTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/InternalTracerTest.kt @@ -1,5 +1,11 @@ package io.embrace.android.embracesdk.internal.spans +import io.embrace.android.embracesdk.arch.assertError +import io.embrace.android.embracesdk.arch.assertIsKeySpan +import io.embrace.android.embracesdk.arch.assertIsTypePerformance +import io.embrace.android.embracesdk.arch.assertNotKeySpan +import io.embrace.android.embracesdk.arch.assertNotPrivateSpan +import io.embrace.android.embracesdk.arch.assertSuccessful import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.internal.clock.millisToNanos @@ -209,7 +215,6 @@ internal class InternalTracerTest { val expectedName = "test-span" val expectedStartTimeMs = clock.now() val expectedEndTimeMs = expectedStartTimeMs + 100L - val expectedType = EmbraceAttributes.Type.PERFORMANCE val expectedAttributes = mapOf( Pair("attribute1", "value1"), Pair("attribute2", "value2") @@ -233,11 +238,8 @@ internal class InternalTracerTest { with(verifyPublicSpan(expectedName)) { assertEquals(expectedStartTimeMs, startTimeNanos.nanosToMillis()) assertEquals(expectedEndTimeMs, endTimeNanos.nanosToMillis()) - assertEquals( - expectedType.typeName, - attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertEquals("true", attributes["emb.key"]) + assertIsTypePerformance() + assertIsKeySpan() expectedAttributes.forEach { assertEquals(it.value, attributes[it.key]) } @@ -276,13 +278,18 @@ internal class InternalTracerTest { assertEquals(1, currentSpans.size) val currentSpan = currentSpans[0] assertEquals(name, currentSpan.name) - assertEquals( - EmbraceAttributes.Type.PERFORMANCE.typeName, - currentSpan.attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertEquals(if (traceRoot) "true" else null, currentSpan.attributes["emb.key"]) - assertEquals(errorCode?.name, currentSpan.attributes[errorCode?.keyName()]) - assertFalse(currentSpan.isPrivate()) + currentSpan.assertIsTypePerformance() + if (traceRoot) { + currentSpan.assertIsKeySpan() + } else { + currentSpan.assertNotKeySpan() + } + if (errorCode == null) { + currentSpan.assertSuccessful() + } else { + currentSpan.assertError(errorCode) + } + currentSpan.assertNotPrivateSpan() return currentSpan } diff --git a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImplTest.kt b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImplTest.kt index 617015cf68..accd1fa4fa 100644 --- a/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImplTest.kt +++ b/embrace-android-sdk/src/test/java/io/embrace/android/embracesdk/internal/spans/SpanServiceImplTest.kt @@ -1,6 +1,14 @@ package io.embrace.android.embracesdk.internal.spans import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.embrace.android.embracesdk.arch.assertError +import io.embrace.android.embracesdk.arch.assertIsKeySpan +import io.embrace.android.embracesdk.arch.assertIsPrivateSpan +import io.embrace.android.embracesdk.arch.assertIsType +import io.embrace.android.embracesdk.arch.assertIsTypePerformance +import io.embrace.android.embracesdk.arch.assertNotKeySpan +import io.embrace.android.embracesdk.arch.schema.AppTerminationCause +import io.embrace.android.embracesdk.arch.schema.EmbType import io.embrace.android.embracesdk.fakes.FakeClock import io.embrace.android.embracesdk.fakes.injection.FakeInitModule import io.embrace.android.embracesdk.fixtures.MAX_LENGTH_SPAN_NAME @@ -53,11 +61,8 @@ internal class SpanServiceImplTest { assertTrue(embraceSpan.stop()) with(verifyAndReturnSoleCompletedSpan("emb-test-span")) { assertEquals(SpanId.getInvalid(), parentSpanId) - assertEquals( - EmbraceAttributes.Type.PERFORMANCE.typeName, - attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertTrue(isKey()) + assertIsTypePerformance() + assertIsKeySpan() } } @@ -75,18 +80,15 @@ internal class SpanServiceImplTest { val embraceSpan = checkNotNull( spansService.createSpan( name = "test-span", - type = EmbraceAttributes.Type.PERFORMANCE + type = EmbType.Performance.Default ) ) assertTrue(embraceSpan.start()) assertTrue(embraceSpan.stop()) with(verifyAndReturnSoleCompletedSpan("emb-test-span")) { assertEquals(SpanId.getInvalid(), parentSpanId) - assertEquals( - EmbraceAttributes.Type.PERFORMANCE.typeName, - attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertTrue(isKey()) + assertIsTypePerformance() + assertIsKeySpan() } } @@ -109,8 +111,8 @@ internal class SpanServiceImplTest { assertEquals("emb-child-span", name) assertEquals(childSpan.spanId, spanId) assertEquals(childSpan.traceId, traceId) - assertFalse(isKey()) - assertTrue(isPrivate()) + assertNotKeySpan() + assertIsPrivateSpan() } with(currentSpans[1]) { @@ -118,8 +120,8 @@ internal class SpanServiceImplTest { assertEquals(SpanId.getInvalid(), parentSpanId) assertEquals(parentSpan.spanId, spanId) assertEquals(parentSpan.traceId, traceId) - assertTrue(isKey()) - assertTrue(isPrivate()) + assertIsKeySpan() + assertIsPrivateSpan() } } @@ -167,7 +169,7 @@ internal class SpanServiceImplTest { name = "child-span", parent = parent, startTimeMs = childStartTimeMs, - type = EmbraceAttributes.Type.SESSION, + type = EmbType.Ux.View, internal = true ) ) @@ -177,9 +179,9 @@ internal class SpanServiceImplTest { val completedSpans = spanSink.flushSpans() assertEquals(1, completedSpans.size) with(completedSpans[0]) { - assertTrue(isPrivate()) - assertFalse(isKey()) - assertEquals(EmbraceAttributes.Type.SESSION.typeName, attributes[EmbraceAttributes.Type.SESSION.keyName()]) + assertIsPrivateSpan() + assertNotKeySpan() + assertIsType(EmbType.Ux.View) assertEquals(childStartTimeMs, startTimeNanos.nanosToMillis()) assertEquals(childSpanEndTimeMs, endTimeNanos.nanosToMillis()) } @@ -190,7 +192,7 @@ internal class SpanServiceImplTest { val expectedName = "test-span" val expectedStartTimeMs = clock.now() val expectedEndTimeMs = expectedStartTimeMs + 100L - val expectedType = EmbraceAttributes.Type.PERFORMANCE + val expectedType = EmbType.Performance.Default val expectedAttributes = mapOf( Pair("attribute1", "value1"), Pair("attribute2", "value2") @@ -212,10 +214,10 @@ internal class SpanServiceImplTest { with(verifyAndReturnSoleCompletedSpan("emb-$expectedName")) { assertEquals(expectedStartTimeMs, startTimeNanos.nanosToMillis()) assertEquals(expectedEndTimeMs, endTimeNanos.nanosToMillis()) - assertEquals(expectedType.typeName, attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()]) + assertIsTypePerformance() assertEquals(SpanId.getInvalid(), parentSpanId) - assertTrue(isKey()) - assertTrue(isPrivate()) + assertIsKeySpan() + assertIsPrivateSpan() expectedAttributes.forEach { assertEquals(it.value, attributes[it.key]) } @@ -242,8 +244,8 @@ internal class SpanServiceImplTest { with(verifyAndReturnSoleCompletedSpan("emb-$expectedName")) { assertEquals(expectedStartTimeMs, startTimeNanos.nanosToMillis()) assertEquals(expectedEndTimeMs, endTimeNanos.nanosToMillis()) - assertFalse(isKey()) - assertTrue(isPrivate()) + assertNotKeySpan() + assertIsPrivateSpan() } assertTrue(parentSpan.stop()) @@ -288,17 +290,17 @@ internal class SpanServiceImplTest { @Test fun `record spans with different ending error codes `() { - ErrorCode.values().forEach { + ErrorCode.values().forEach { errorCode -> assertTrue( spansService.recordCompletedSpan( - name = "test${it.name}", + name = "test${errorCode.name}", startTimeMs = 0, endTimeMs = 1, - errorCode = it + errorCode = errorCode ) ) - with(verifyAndReturnSoleCompletedSpan("emb-test${it.name}")) { - assertEquals(it.name, attributes[it.keyName()]) + with(verifyAndReturnSoleCompletedSpan("emb-test${errorCode.name}")) { + assertError(errorCode) } spanSink.flushSpans() } @@ -318,7 +320,7 @@ internal class SpanServiceImplTest { @Test fun `cannot record completed span if there is not current session span`() { currentSessionSpan.endSession( - appTerminationCause = EmbraceAttributes.AppTerminationCause.USER_TERMINATION + appTerminationCause = AppTerminationCause.UserTermination ) assertFalse( spansService.recordCompletedSpan( @@ -339,12 +341,9 @@ internal class SpanServiceImplTest { assertEquals(returnThis, lambdaReturn) with(verifyAndReturnSoleCompletedSpan("emb-test-span")) { assertEquals(SpanId.getInvalid(), parentSpanId) - assertEquals( - EmbraceAttributes.Type.PERFORMANCE.typeName, - attributes[EmbraceAttributes.Type.PERFORMANCE.keyName()] - ) - assertTrue(isKey()) - assertTrue(isPrivate()) + assertIsTypePerformance() + assertIsKeySpan() + assertIsPrivateSpan() } } @@ -365,8 +364,8 @@ internal class SpanServiceImplTest { with(currentSpans[0]) { assertEquals("emb-child-span", name) - assertFalse(isKey()) - assertTrue(isPrivate()) + assertNotKeySpan() + assertIsPrivateSpan() } } @@ -405,17 +404,14 @@ internal class SpanServiceImplTest { } with(verifyAndReturnSoleCompletedSpan("emb-test-span")) { - assertEquals( - ErrorCode.FAILURE.name, - attributes[ErrorCode.FAILURE.keyName()] - ) + assertError(ErrorCode.FAILURE) } } @Test fun `recording span as lambda with no current active session will run code but not log span`() { currentSessionSpan.endSession( - appTerminationCause = EmbraceAttributes.AppTerminationCause.USER_TERMINATION + appTerminationCause = AppTerminationCause.UserTermination ) var executed = false spansService.recordSpan(name = "test-span") { @@ -428,7 +424,7 @@ internal class SpanServiceImplTest { @Test fun `after ending session with app termination, spans cannot be recorded`() { - currentSessionSpan.endSession(EmbraceAttributes.AppTerminationCause.USER_TERMINATION) + currentSessionSpan.endSession(AppTerminationCause.UserTermination) spansService.recordSpan("test-span") { // do thing }