Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions posthog-android/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- add evaluation tags to android SDK ([#301](https://github.com/PostHog/posthog-android/pull/301))
- feat: add manual captureException ([#300](https://github.com/PostHog/posthog-android/issues/300))
- feat: add exception autocapture ([#305](https://github.com/PostHog/posthog-android/issues/305))

## 3.23.0 - 2025-10-06

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public class PostHogAndroid private constructor() {

// only frames coming from the package name will be considered inApp by default
if (packageName.isNotEmpty() && !packageName.startsWith("android.")) {
config.inAppIncludes.add(packageName)
config.errorTrackingConfig.inAppIncludes.add(packageName)
}

config.context = config.context ?: PostHogAndroidContext(context, config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class DoSomething {
try {
throw MyCustomException("Something went wrong")
} catch (e: Throwable) {
PostHog.captureException(e, mapOf("am-i-stupid" to true))
PostHog.captureException(e, mapOf("my-custom-error" to true))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ class MyApp : Application() {
captureScreenViews = false
sessionReplay = true
preloadFeatureFlags = true
sendFeatureFlagEvent = true
onFeatureFlags = PostHogOnFeatureFlags { print("feature flags loaded") }
addBeforeSend { event ->
if (event.event == "test_name") {
if (event.event == "test_event") {
null
} else {
event
Expand All @@ -41,6 +42,7 @@ class MyApp : Application() {
sessionReplayConfig.captureLogcat = true
sessionReplayConfig.screenshot = true
surveys = true
errorTrackingConfig.autoCapture = true
}
PostHogAndroid.setup(this, config)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ class NormalActivity : ComponentActivity() {
// finish()
// Check if the "enable_network_request" feature flag is enabled

try {
throw RuntimeException("Test error")
} catch (e: Throwable) {
PostHog.captureException(e, mapOf("am-i-stupid" to true))
}
// var str: String? = null
// if (str!!.startsWith("")) {
// str = "123"
// Toast.makeText(this, str, Toast.LENGTH_SHORT).show()
// }
val doSomething = DoSomething()
doSomething.doSomethingNow()

val isNetworkRequestEnabled = PostHog.isFeatureEnabled("enable_network_request", false)

Expand Down
1 change: 1 addition & 0 deletions posthog/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Next

- feat: add manual captureException ([#300](https://github.com/PostHog/posthog-android/issues/300))
- feat: add exception autocapture ([#305](https://github.com/PostHog/posthog-android/issues/305))

## 4.0.0 - 2025-10-03

Expand Down
26 changes: 23 additions & 3 deletions posthog/api/posthog.api
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ public class com/posthog/PostHogConfig {
public static final field DEFAULT_HOST Ljava/lang/String;
public static final field DEFAULT_US_ASSETS_HOST Ljava/lang/String;
public static final field DEFAULT_US_HOST Ljava/lang/String;
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Ljava/util/List;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZZZIZLjava/util/List;ZIIIILcom/posthog/PostHogEncryption;Lcom/posthog/PostHogOnFeatureFlags;ZLcom/posthog/PostHogPropertiesSanitizer;Lkotlin/jvm/functions/Function1;ZLcom/posthog/PersonProfiles;ZLjava/net/Proxy;Lcom/posthog/surveys/PostHogSurveysConfig;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function5;Lcom/posthog/errortracking/PostHogErrorTrackingConfig;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addBeforeSend (Lcom/posthog/PostHogBeforeSend;)V
public final fun addIntegration (Lcom/posthog/PostHogIntegration;)V
public final fun getApiKey ()Ljava/lang/String;
Expand All @@ -97,13 +97,13 @@ public class com/posthog/PostHogConfig {
public final fun getDateProvider ()Lcom/posthog/internal/PostHogDateProvider;
public final fun getDebug ()Z
public final fun getEncryption ()Lcom/posthog/PostHogEncryption;
public final fun getErrorTrackingConfig ()Lcom/posthog/errortracking/PostHogErrorTrackingConfig;
public final fun getEvaluationEnvironments ()Ljava/util/List;
public final fun getFeatureFlagCalledCacheSize ()I
public final fun getFlushAt ()I
public final fun getFlushIntervalSeconds ()I
public final fun getGetAnonymousId ()Lkotlin/jvm/functions/Function1;
public final fun getHost ()Ljava/lang/String;
public final fun getInAppIncludes ()Ljava/util/List;
public final fun getIntegrations ()Ljava/util/List;
public final fun getLegacyStoragePrefix ()Ljava/lang/String;
public final fun getLogger ()Lcom/posthog/internal/PostHogLogger;
Expand Down Expand Up @@ -215,6 +215,8 @@ public final class com/posthog/PostHogEvent {
public final fun getType ()Ljava/lang/String;
public final fun getUuid ()Ljava/util/UUID;
public fun hashCode ()I
public final fun isExceptionEvent ()Z
public final fun isFatalExceptionEvent ()Z
public final fun setApiKey (Ljava/lang/String;)V
public fun toString ()Ljava/lang/String;
}
Expand Down Expand Up @@ -385,6 +387,23 @@ public final class com/posthog/PostHogStatelessInterface$DefaultImpls {
public abstract interface annotation class com/posthog/PostHogVisibleForTesting : java/lang/annotation/Annotation {
}

public final class com/posthog/errortracking/PostHogErrorTrackingAutoCaptureIntegration : com/posthog/PostHogIntegration, java/lang/Thread$UncaughtExceptionHandler {
public fun <init> (Lcom/posthog/PostHogConfig;)V
public fun install (Lcom/posthog/PostHogInterface;)V
public fun uncaughtException (Ljava/lang/Thread;Ljava/lang/Throwable;)V
public fun uninstall ()V
}

public final class com/posthog/errortracking/PostHogErrorTrackingConfig {
public fun <init> ()V
public fun <init> (Z)V
public fun <init> (ZLjava/util/List;)V
public synthetic fun <init> (ZLjava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getAutoCapture ()Z
public final fun getInAppIncludes ()Ljava/util/List;
public final fun setAutoCapture (Z)V
}

public final class com/posthog/internal/EvaluationReason {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V
public final fun component1 ()Ljava/lang/String;
Expand Down Expand Up @@ -636,6 +655,7 @@ public final class com/posthog/internal/PostHogUtilsKt {
public static final fun executeSafely (Ljava/util/concurrent/Executor;Ljava/lang/Runnable;)V
public static final fun interruptSafely (Ljava/lang/Thread;)V
public static final fun isNetworkingError (Ljava/lang/Throwable;)Z
public static final fun submitSyncSafely (Ljava/util/concurrent/ExecutorService;Ljava/lang/Runnable;)V
}

public abstract interface class com/posthog/internal/replay/PostHogSessionReplayHandler {
Expand Down
6 changes: 4 additions & 2 deletions posthog/src/main/java/com/posthog/PostHog.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.posthog

import com.posthog.errortracking.PostHogErrorTrackingAutoCaptureIntegration
import com.posthog.internal.PostHogApi
import com.posthog.internal.PostHogApiEndpoint
import com.posthog.internal.PostHogNoOpLogger
Expand All @@ -19,7 +20,7 @@ import com.posthog.internal.PostHogSendCachedEventsIntegration
import com.posthog.internal.PostHogSerializer
import com.posthog.internal.PostHogSessionManager
import com.posthog.internal.PostHogThreadFactory
import com.posthog.internal.exceptions.ThrowableCoercer
import com.posthog.internal.errortracking.ThrowableCoercer
import com.posthog.internal.replay.PostHogSessionReplayHandler
import com.posthog.internal.surveys.PostHogSurveysHandler
import com.posthog.vendor.uuid.TimeBasedEpochGenerator
Expand Down Expand Up @@ -134,6 +135,7 @@ public class PostHog private constructor(
}

config.addIntegration(sendCachedEventsIntegration)
config.addIntegration(PostHogErrorTrackingAutoCaptureIntegration(config))

legacyPreferences(config, config.serializer)

Expand Down Expand Up @@ -495,7 +497,7 @@ public class PostHog private constructor(
val exceptionProperties =
throwableCoercer.fromThrowableToPostHogProperties(
throwable,
inAppIncludes = config?.inAppIncludes ?: listOf(),
inAppIncludes = config?.errorTrackingConfig?.inAppIncludes ?: listOf(),
)

properties?.let {
Expand Down
14 changes: 3 additions & 11 deletions posthog/src/main/java/com/posthog/PostHogConfig.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.posthog

import com.posthog.errortracking.PostHogErrorTrackingConfig
import com.posthog.internal.PostHogApi
import com.posthog.internal.PostHogApiEndpoint
import com.posthog.internal.PostHogContext
Expand Down Expand Up @@ -199,18 +200,9 @@ public open class PostHogConfig(
public val queueProvider: (PostHogConfig, PostHogApi, PostHogApiEndpoint, String?, ExecutorService) -> PostHogQueueInterface =
{ config, api, endpoint, storagePrefix, executor -> PostHogQueue(config, api, endpoint, storagePrefix, executor) },
/**
* List of package names to be considered inApp frames for error tracking
*
* inApp Example:
* inAppIncludes=["com.yourapp"]
* All Exception stacktrace frames that start with com.yourapp will be considered inApp*
*
* On Android only frames coming from the app's package name will be considered inApp by default
* On Android, We add your app's package name to this list automatically (read from applicationId at runtime)
*
* If this list of package names is empty, all frames will be considered inApp
* Configuration for PostHog Error Tracking feature.
*/
public val inAppIncludes: MutableList<String> = mutableListOf(),
Copy link
Member Author

Choose a reason for hiding this comment

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

ok breaking change since the manual capture isnt released yet

public val errorTrackingConfig: PostHogErrorTrackingConfig = PostHogErrorTrackingConfig(),
) {
@PostHogInternal
public var logger: PostHogLogger = PostHogNoOpLogger()
Expand Down
18 changes: 17 additions & 1 deletion posthog/src/main/java/com/posthog/PostHogEvent.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.posthog

import com.google.gson.annotations.SerializedName
import com.posthog.internal.errortracking.ThrowableCoercer.Companion.EXCEPTION_LEVEL_ATTRIBUTE
import com.posthog.internal.errortracking.ThrowableCoercer.Companion.EXCEPTION_LEVEL_FATAL
import com.posthog.vendor.uuid.TimeBasedEpochGenerator
import java.util.Date
import java.util.UUID
Expand Down Expand Up @@ -35,4 +37,18 @@ public data class PostHogEvent(
// Only used for Replay
@SerializedName("api_key")
var apiKey: String? = null,
)
) {
/**
* Checks if the event is an exception event ($exception)
*/
public fun isExceptionEvent(): Boolean {
return event == PostHogEventName.EXCEPTION.event
}

/**
* Checks if the event is a fatal exception event ($exception) and properties ($exception_level=fatal)
*/
public fun isFatalExceptionEvent(): Boolean {
return isExceptionEvent() && properties?.get(EXCEPTION_LEVEL_ATTRIBUTE) == EXCEPTION_LEVEL_FATAL
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.posthog.errortracking

import com.posthog.PostHogConfig
import com.posthog.PostHogIntegration
import com.posthog.PostHogInterface
import com.posthog.internal.errortracking.PostHogThrowable
import com.posthog.internal.errortracking.UncaughtExceptionHandlerAdapter

public class PostHogErrorTrackingAutoCaptureIntegration : PostHogIntegration, Thread.UncaughtExceptionHandler {
private val config: PostHogConfig
private val adapterExceptionHandler: UncaughtExceptionHandlerAdapter
private var defaultExceptionHandler: Thread.UncaughtExceptionHandler? = null
private var postHog: PostHogInterface? = null

public constructor(config: PostHogConfig) {
this.config = config
this.adapterExceptionHandler = UncaughtExceptionHandlerAdapter.Adapter.getInstance()
}

internal constructor(config: PostHogConfig, adapterExceptionHandler: UncaughtExceptionHandlerAdapter) {
this.config = config
this.adapterExceptionHandler = adapterExceptionHandler
}

private companion object {
@Volatile
private var integrationInstalled = false
}

override fun install(postHog: PostHogInterface) {
if (integrationInstalled) {
return
}

if (!config.errorTrackingConfig.autoCapture) {
return
}

this.postHog = postHog

val currentExceptionHandler = adapterExceptionHandler.getDefaultUncaughtExceptionHandler()

if (currentExceptionHandler != null) {
if (currentExceptionHandler !is PostHogErrorTrackingAutoCaptureIntegration) {
defaultExceptionHandler = currentExceptionHandler
installHandler()
}
// we don't install if the handler is us already
} else {
defaultExceptionHandler = null
installHandler()
}
}

private fun installHandler() {
adapterExceptionHandler.setDefaultUncaughtExceptionHandler(this)
integrationInstalled = true
config.logger.log("Exception autocapture is enabled.")
}

override fun uninstall() {
if (!integrationInstalled) {
return
}
adapterExceptionHandler.setDefaultUncaughtExceptionHandler(defaultExceptionHandler)
integrationInstalled = false
postHog = null
config.logger.log("Exception autocapture is disabled.")
}

override fun uncaughtException(
thread: Thread,
throwable: Throwable,
) {
postHog?.let { postHog ->
postHog.captureException(PostHogThrowable(throwable, thread))
postHog.flush()
}

defaultExceptionHandler?.uncaughtException(thread, throwable)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.posthog.errortracking

import com.posthog.PostHog

public class PostHogErrorTrackingConfig
@JvmOverloads
public constructor(
/**
* Enable autocapture of exceptions
* This feature installs an uncaught exception handler (Thread.UncaughtExceptionHandler) that will capture exceptions
*
* Disabled by default
*
* You can manually capture exceptions by calling [PostHog.captureException]
*/
public var autoCapture: Boolean = false,
/**
* List of package names to be considered inApp frames for error tracking
*
* inApp Example:
* inAppIncludes=["com.yourapp"]
* All Exception stacktrace frames that start with com.yourapp will be considered inApp*
*
* On Android only frames coming from the app's package name will be considered inApp by default
* On Android, We add your app's package name to this list automatically (read from applicationId at runtime)
*
* If this list of package names is empty, all frames will be considered inApp
*/
public val inAppIncludes: MutableList<String> = mutableListOf(),
)
Loading
Loading