Skip to content

Commit

Permalink
Merge pull request #2415 from DataDog/yl/compose/reflection-telemetry
Browse files Browse the repository at this point in the history
RUM-7081: Decouple Compose reflection functions and report to Telemetry
  • Loading branch information
ambushwork authored Nov 26, 2024
2 parents a67406e + 485ff8c commit 4590058
Show file tree
Hide file tree
Showing 4 changed files with 566 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -86,31 +86,16 @@ internal fun Field.getSafe(target: Any?): Any? {
return try {
get(target)
} catch (e: IllegalAccessException) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ "Unable to get field $name on $target through reflection, field is not accessible" },
e
)
logReflectionException(name, LOG_TYPE_FIELD, LOG_REASON_FIELD_NO_ACCESSIBLE, e)
null
} catch (e: IllegalArgumentException) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ "Unable to get field $name on $target through reflection, target has incompatible type" },
e
)
logReflectionException(name, LOG_TYPE_FIELD, LOG_REASON_INCOMPATIBLE_TYPE, e)
null
} catch (e: NullPointerException) {
logNullPointerException(name, LOG_TYPE_FIELD, e)
null
} catch (e: ExceptionInInitializerError) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ "Unable to get field $name on $target through reflection, initialization error" },
e
)
logReflectionException(name, LOG_TYPE_FIELD, LOG_REASON_INITIALIZATION_ERROR, e)
null
}
}
Expand All @@ -119,20 +104,10 @@ internal fun getClassSafe(className: String): Class<*>? {
return try {
Class.forName(className)
} catch (e: LinkageError) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ "Unable to get class $className through reflection" },
e
)
logReflectionException(className, LOG_TYPE_CLASS, LOG_REASON_LINKAGE_ERROR, e)
null
} catch (e: ExceptionInInitializerError) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{ "Unable to get class $className through reflection, error in Class initialization" },
e
)
logReflectionException(className, LOG_TYPE_CLASS, LOG_REASON_INITIALIZATION_ERROR, e)
null
} catch (e: ClassNotFoundException) {
logNoSuchException(className, LOG_TYPE_CLASS, e)
Expand Down Expand Up @@ -173,39 +148,38 @@ internal fun Class<*>.getDeclaredMethodSafe(methodName: String): Method? {
}

private fun logSecurityException(name: String, type: String, e: SecurityException) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{
"Unable to get $type $name through reflection"
},
e
)
logReflectionException(name = name, type = type, reason = LOG_REASON_SECURITY, e = e)
}

private fun logNullPointerException(name: String, type: String, e: NullPointerException) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{
"Unable to get $type $name through reflection, name is null"
},
e
)
logReflectionException(name = name, type = type, reason = "$name is null", e = e)
}

private fun logNoSuchException(name: String, type: String, e: ReflectiveOperationException) {
logReflectionException(name = name, type = type, reason = LOG_REASON_DEFAULT, e = e)
}

private fun logReflectionException(name: String, type: String, reason: String, e: Throwable) {
(Datadog.getInstance() as? FeatureSdkCore)?.internalLogger?.log(
InternalLogger.Level.ERROR,
InternalLogger.Target.MAINTAINER,
{
"Unable to get $type $name through reflection, " +
"either because of obfuscation or dependency version mismatch"
},
e
level = InternalLogger.Level.ERROR,
targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY),
messageBuilder = { "Unable to get $type [$name] through reflection: $reason" },
throwable = e,
onlyOnce = true,
additionalProperties = mapOf(
"reflection.type" to type,
"reflection.name" to name
)
)
}

private const val LOG_TYPE_METHOD = "method"
private const val LOG_TYPE_FIELD = "field"
private const val LOG_TYPE_CLASS = "field"
private const val LOG_TYPE_METHOD = "Method"
private const val LOG_TYPE_FIELD = "Field"
private const val LOG_TYPE_CLASS = "Class"
private const val LOG_REASON_FIELD_NO_ACCESSIBLE = "Field is not accessible"
private const val LOG_REASON_INCOMPATIBLE_TYPE = "Target has incompatible type"
private const val LOG_REASON_DEFAULT =
"Either because of obfuscation or dependency version mismatch"
private const val LOG_REASON_SECURITY = "Security exception"
private const val LOG_REASON_LINKAGE_ERROR = "Linkage error"
private const val LOG_REASON_INITIALIZATION_ERROR = "Error in initialization"
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
* This product includes software developed at Datadog (https://www.datadoghq.com/).
* Copyright 2016-Present Datadog, Inc.
*/

package com.datadog.android.sessionreplay.compose.internal.utils

import android.view.View
import androidx.compose.runtime.Composition
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorProducer
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.Placeable
import androidx.compose.ui.semantics.SemanticsNode
import androidx.compose.ui.semantics.SemanticsOwner
import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection
import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection.CompositionField
import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection.GetInnerLayerCoordinatorMethod
import com.datadog.android.sessionreplay.compose.internal.reflection.ComposeReflection.LayoutNodeField
import com.datadog.android.sessionreplay.compose.internal.reflection.getSafe

@Suppress("TooManyFunctions")
internal class ReflectionUtils {

fun getComposition(view: View): Composition? {
return CompositionField?.getSafe(view) as? Composition
}

fun isBackgroundElement(modifier: Modifier): Boolean {
return ComposeReflection.BackgroundElementClass?.isInstance(modifier) == true
}

fun isPaddingElement(modifier: Modifier): Boolean {
return ComposeReflection.PaddingElementClass?.isInstance(modifier) == true
}

fun isTextStringSimpleElement(modifier: Modifier): Boolean {
return ComposeReflection.TextStringSimpleElement?.isInstance(modifier) == true
}

fun isWrappedCompositionClass(composition: Composition): Boolean {
return ComposeReflection.WrappedCompositionClass?.isInstance(composition) == true
}

fun isAndroidComposeView(any: Any): Boolean {
return ComposeReflection.AndroidComposeViewClass?.isInstance(any) == true
}

fun getOwner(composition: Composition): Any? {
return ComposeReflection.OwnerField?.getSafe(composition)
}

fun getSemanticsOwner(any: Any): SemanticsOwner? {
return ComposeReflection.SemanticsOwner?.getSafe(any) as? SemanticsOwner
}

fun isGraphicsLayerElement(modifier: Modifier): Boolean {
return ComposeReflection.GraphicsLayerElementClass?.isInstance(modifier) == true
}

fun getColorProducerColor(modifier: Modifier): Color? {
return (ComposeReflection.ColorProducerField?.getSafe(modifier) as? ColorProducer)?.invoke()
}

fun getPlaceable(semanticsNode: SemanticsNode): Placeable? {
val layoutNode = LayoutNodeField?.getSafe(semanticsNode)
val innerLayerCoordinator = layoutNode?.let { GetInnerLayerCoordinatorMethod?.invoke(it) }
return innerLayerCoordinator as? Placeable
}

fun getTopPadding(modifier: Modifier): Float {
return ComposeReflection.TopField?.getSafe(modifier) as? Float ?: 0.0f
}

fun getStartPadding(modifier: Modifier): Float {
return ComposeReflection.StartField?.getSafe(modifier) as? Float ?: 0.0f
}

fun getBottomPadding(modifier: Modifier): Float {
return ComposeReflection.BottomField?.getSafe(modifier) as? Float ?: 0.0f
}

fun getEndPadding(modifier: Modifier): Float {
return ComposeReflection.EndField?.getSafe(modifier) as? Float ?: 0.0f
}

fun getColor(modifier: Modifier): Long? {
return ComposeReflection.ColorField?.getSafe(modifier) as? Long
}

fun getShape(modifier: Modifier): Shape? {
return ComposeReflection.ShapeField?.getSafe(modifier) as? Shape
}

fun getClipShape(modifier: Modifier): Shape? {
return ComposeReflection.ClipShapeField?.getSafe(modifier) as? Shape
}
}
Loading

0 comments on commit 4590058

Please sign in to comment.