Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/core-captured-throwables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog': patch
---

Add an internal process-wide `PostHogCapturedThrowables` guard (marked `@PostHogInternal`, visible only because of the multi-module architecture) that lets independent error-capture paths avoid double-reporting the same `Throwable` instance. The guard is directional: log-mirror paths (e.g. the `posthog-server-logback` appender) consult it and skip instances already reported, while the uncaught-exception handler only marks β€” a crash is always captured as the authoritative fatal/unhandled record even if the same instance was logged first, and marking it keeps post-crash log mirrors from reporting it again. Membership is keyed on instance identity and held weakly, so the guard never keeps a throwable or its stack alive.
5 changes: 5 additions & 0 deletions .changeset/core-uncaught-gate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog': patch
---

`PostHogErrorTrackingAutoCaptureIntegration` can now be gated on a caller-supplied strategy instead of the built-in gate (local `errorTrackingConfig.autoCapture` with remote config as a kill-switch): a new `PostHogErrorTrackingAutoCaptureIntegration(config, enabledGate)` constructor lets SDK layers that never fetch remote config (e.g. the server SDK) decide autocapture purely from local config. The uncaught handler also delivers captures through an internal `CaptureTarget` seam (`installWith`) so it can drive clients that are not a core `PostHogInterface`, and when no previous default handler exists it now reproduces the JVM's own `Exception in thread ...` stderr output, so installing capture never hides a crash from log collection. Android behavior and the existing `install(PostHogInterface)` path are otherwise unchanged; the additions are internal (`@PostHogInternal`) and visible only because of the multi-module architecture.
9 changes: 9 additions & 0 deletions .changeset/server-error-tracking-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'posthog-server': minor
---

Expose the error-tracking configuration surface on the server SDK:

- `PostHogConfig.inAppIncludes` / `inAppExcludes` control the `in_app` classification of captured stack-trace frames (prefix match on the class name; excludes always win). `inAppExcludes` defaults to the new `PostHogConfig.DEFAULT_IN_APP_EXCLUDES` β€” a list of common JVM/framework prefixes (JDK, Kotlin, Spring, Netty, servlet containers, HTTP clients, the PostHog SDK) β€” so zero-config users get a sensible your-code vs framework split. Assigning your own list replaces the defaults.
- Both are available on the config `Builder` (`inAppIncludes(...)`, `inAppExcludes(...)`).
- New `captureException(exception, distinctId, options)` / `captureException(exception, options)` overloads accept `PostHogCaptureOptions` with the same merging semantics as `capture(..., options)`: custom properties, `$groups`, user properties (`$set`/`$set_once`), timestamp, and feature-flag enrichment via a pre-evaluated `flags` snapshot or `appendFeatureFlags`. Reserved exception properties (e.g. `$exception_level`, `$exception_fingerprint`) can be overridden through options properties. Request-context distinct-id resolution and personless fallback behave exactly like the existing `captureException` overloads. Java callers that passed an explicit untyped `null` as the third argument of `captureException` need to cast it (`(Map<String, Object>) null`), since that call now matches both the properties and the options overload.
10 changes: 10 additions & 0 deletions .changeset/server-logback-appender.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'posthog-server-logback': minor
---

Initial `0.x` release of `com.posthog:posthog-server-logback` β€” a Logback appender that reports logged errors to PostHog Error Tracking through the server SDK. This module is pre-1.0; its public API may change between minor versions.

- `PostHogAppender` (`ch.qos.logback.core.AppenderBase<ILoggingEvent>`, package `com.posthog.server.logback`) captures events at or above `minimumCaptureLevel` (default `ERROR`) that carry a `Throwable`, sending them through the server SDK's `captureException` so request-context distinct-id resolution and in-app frame config apply automatically.
- Register a configured server client once at startup with `PostHogAppender.setPostHog(client)`; events logged before registration are dropped. Alternatively self-configure from `logback.xml` via `<apiKey>`/`<host>` (each self-configuring appender owns its client and closes it on `stop()`, so a Logback config reload keeps capturing). An application-registered client always wins.
- Events from `com.posthog…` loggers are always skipped (recursion guard), events without a throwable are skipped (no message-only synthesis in v1), and captured events carry `$exception_level` (`error`, or `warning` when the threshold is lowered), `logger_name`, and the log message under `log_message` when it differs from the throwable message.
- Depends on `com.posthog:posthog-server` (transitive `api`); Logback is `compileOnly` β€” provide `logback-classic` yourself (1.3.x line for Java 8 compatibility).
5 changes: 5 additions & 0 deletions .changeset/server-uncaught-exceptions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-server': minor
---

Opt in to capturing uncaught JVM exceptions on the server SDK via `PostHogConfig.captureUncaughtExceptions` (also on the config `Builder`). When enabled, `PostHog` installs a global `Thread.defaultUncaughtExceptionHandler` on setup that captures the crashing exception as a fatal, unhandled `$exception` event (mechanism `UncaughtExceptionHandler`), flushes, and then delegates to the previously registered handler; the handler is removed again on `close()`. Unlike the Android SDK this is gated purely on the local flag β€” the server SDK never fetches remote config. A fatal `$exception` event is enqueued and sent synchronously on the crashing thread, bypassing `flushAt`, so capturing the crash does not depend on the periodic flush; delivery is still best-effort under an immediate hard exit.
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ dryReleaseSurveysCompose:
dryReleaseServer:
./gradlew :posthog-server:publishToMavenLocal

dryReleaseServerLogback:
./gradlew :posthog-server-logback:publishToMavenLocal

dryReleaseAndroidPlugin:
./gradlew :posthog-android-gradle-plugin:publishToMavenLocal

Expand All @@ -53,6 +56,9 @@ releaseSurveysCompose:
releaseServer:
./gradlew :posthog-server:publishToSonatype closeAndReleaseSonatypeStagingRepository

releaseServerLogback:
./gradlew :posthog-server-logback:publishToSonatype closeAndReleaseSonatypeStagingRepository

releaseAndroidPlugin:
./gradlew :posthog-android-gradle-plugin:publishToSonatype :posthog-android-gradle-plugin:closeAndReleaseSonatypeStagingRepository

Expand Down
3 changes: 3 additions & 0 deletions buildSrc/src/main/java/PosthogBuildConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ object PosthogBuildConfig {
val GSON = "2.10.1"

val OKHTTP = "4.12.0"

// Logback appender: 1.3.x is the last line targeting Java 8 (1.4.x+ requires Java 11).
val LOGBACK = "1.3.14"
val CURTAINS = "1.2.5"
val ANDROIDX_CORE = "1.5.0"
val ANDROIDX_COMPOSE = "1.0.0"
Expand Down
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ coreVersion=6.32.0
androidVersion=3.58.4
surveysComposeVersion=0.1.0
serverVersion=2.12.1
serverLogbackVersion=0.1.0

# Shared build versions (single source of truth for buildSrc, PosthogBuildConfig, and gradle plugin)
kotlinVersion=2.1.21
Expand Down
2 changes: 2 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ packages:
- "posthog-android"
- "posthog-android-surveys-compose"
- "posthog-server"
- "posthog-server-logback"
- "posthog-android-gradle-plugin"

minimumReleaseAge: 10080
3 changes: 3 additions & 0 deletions posthog-server-logback/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# posthog-server-logback

## Next
21 changes: 21 additions & 0 deletions posthog-server-logback/api/posthog-server-logback.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
public final class com/posthog/server/logback/PostHogAppender : ch/qos/logback/core/AppenderBase {
public static final field Companion Lcom/posthog/server/logback/PostHogAppender$Companion;
public fun <init> ()V
public synthetic fun append (Ljava/lang/Object;)V
public static final fun clearPostHog ()V
public final fun getApiKey ()Ljava/lang/String;
public final fun getHost ()Ljava/lang/String;
public final fun getMinimumCaptureLevel ()Lch/qos/logback/classic/Level;
public final fun setApiKey (Ljava/lang/String;)V
public final fun setHost (Ljava/lang/String;)V
public final fun setMinimumCaptureLevel (Lch/qos/logback/classic/Level;)V
public static final fun setPostHog (Lcom/posthog/server/PostHogInterface;)V
public fun start ()V
public fun stop ()V
}

public final class com/posthog/server/logback/PostHogAppender$Companion {
public final fun clearPostHog ()V
public final fun setPostHog (Lcom/posthog/server/PostHogInterface;)V
}

109 changes: 109 additions & 0 deletions posthog-server-logback/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
@file:Suppress("ktlint:standard:max-line-length")

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

version = properties["serverLogbackVersion"].toString()

plugins {
`java-library`
kotlin("jvm")
id("com.android.lint")

// publish
`maven-publish`
signing
id("org.jetbrains.dokka")

// plugins
id("com.github.gmazzo.buildconfig")

// tests
id("org.jetbrains.kotlinx.kover")

// compatibility
id("ru.vyarus.animalsniffer")
}

buildConfig {
useKotlinOutput()
packageName("com.posthog.server.logback")
buildConfigField("String", "SDK_NAME", "\"posthog-server-logback\"")
buildConfigField("String", "VERSION_NAME", "\"${project.version}\"")
}

java {
withSourcesJar()
sourceCompatibility = PosthogBuildConfig.Build.JAVA_VERSION
targetCompatibility = PosthogBuildConfig.Build.JAVA_VERSION
}

val dokkaJavadocJar by tasks.register<Jar>("dokkaJavadocJar") {
dependsOn(tasks.dokkaJavadoc)
from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
archiveClassifier.set("javadoc")
}

val dokkaHtmlJar by tasks.register<Jar>("dokkaHtmlJar") {
dependsOn(tasks.dokkaHtml)
from(tasks.dokkaHtml.flatMap { it.outputDirectory })
archiveClassifier.set("html-doc")
}

publishing {
publications {
create<MavenPublication>("maven") {
from(components["java"])
artifact(dokkaJavadocJar)
artifact(dokkaHtmlJar)

postHogConfig(project.name, project.version.toString())
pom.postHogConfig(
project.name,
moduleDescription = "Logback appender that reports errors to PostHog via the server SDK",
)
}
}
signing.postHogConfig("maven", this)
}

tasks.withType<KotlinCompile>().configureEach {
compilerOptions.postHogConfig()
}

kotlin {
explicitApi()
}

configure<SourceSetContainer> {
test {
java.srcDir("src/test/java")
}
}

dependencies {
// Expose the server SDK transitively: consumers configure and use a PostHog server client
// alongside the appender, so it belongs on the compile classpath of anything depending on this.
api(project(":posthog-server"))

implementation(kotlin("stdlib-jdk8", PosthogBuildConfig.Kotlin.KOTLIN))

// Logback is provided by the host application; keep it off our runtime classpath and pin a
// conservative Java 8-compatible floor (the 1.3.x line; 1.4.x+ requires Java 11).
compileOnly("ch.qos.logback:logback-classic:${PosthogBuildConfig.Dependencies.LOGBACK}")
compileOnly("org.codehaus.mojo:animal-sniffer-annotations:${PosthogBuildConfig.Plugins.ANIMAL_SNIFFER_SDK_ANNOTATION}")

// compatibility
signature("org.codehaus.mojo.signature:java18:${PosthogBuildConfig.Plugins.SIGNATURE_JAVA18}@signature")

// tests
testImplementation("ch.qos.logback:logback-classic:${PosthogBuildConfig.Dependencies.LOGBACK}")
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:${PosthogBuildConfig.Kotlin.KOTLIN}")
testImplementation("com.squareup.okhttp3:mockwebserver:${PosthogBuildConfig.Dependencies.OKHTTP}")
testImplementation("com.google.code.gson:gson:${PosthogBuildConfig.Dependencies.GSON}")
}

tasks.javadoc {
if (JavaVersion.current().isJava9Compatible) {
(options as StandardJavadocDocletOptions).addBooleanOption("html5", true)
}
}
Loading
Loading