Skip to content

feat(logback): add posthog-server-logback appender module - #672

Draft
cat-ph wants to merge 5 commits into
cat/java-et-uncaughtfrom
cat/java-et-logback
Draft

feat(logback): add posthog-server-logback appender module#672
cat-ph wants to merge 5 commits into
cat/java-et-uncaughtfrom
cat/java-et-logback

Conversation

@cat-ph

@cat-ph cat-ph commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

🚨 Before merge / release

posthog-server-logback is not wired into the release workflow. The version-sync step and the publish matrix in .github/workflows still need an entry for the new module (and the module needs its Sonatype coordinates confirmed). That was deliberately left out of this PR — no workflow files are touched here — so the repo owner can wire it up. Merging as-is is safe: nothing publishes the module yet.

💡 Motivation and Context

Fourth and last PR in the JVM error-tracking stack: a new posthog-server-logback module (com.posthog:posthog-server-logback, 0.1.0) with a Logback appender that reports logged errors to PostHog Error Tracking through the server SDK.

  • PostHogAppender (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 (the appender runs on the logging thread, inside any active PostHogRequestContext scope).
  • Wiring: register a client you already configure with PostHogAppender.setPostHog(client) once at startup (events logged before registration are dropped — documented), or let the appender self-configure from logback.xml via <apiKey>/<host>, in which case it owns and closes that client on stop(). An explicitly 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 this first version), and captured events carry $exception_level, logger_name, and log_message when the log message differs from the throwable message.
  • Uses the PostHogCapturedThrowables guard from PR 3, so an exception already reported by the uncaught handler is not re-reported when a log mirror sees the same instance.
  • Build wiring is additive: settings.gradle.kts, pnpm-workspace.yaml, gradle.properties (serverLogbackVersion=0.1.0), Makefile dry/release targets, and a LOGBACK version constant in buildSrc. Logback is compileOnly (consumers bring logback-classic; the 1.3.x line is the last one targeting Java 8), and posthog-server is an api dependency.

The module is explicitly pre-1.0: the changeset says the public API may change between minor versions.

💚 How did you test it?

  • New PostHogAppenderTest (8 tests): an error with a throwable becomes exactly one $exception event with the expected properties, below-threshold events ignored, WARN captured with warning level once the threshold is lowered, com.posthog logger names skipped for recursion, a logger merely sharing that string prefix still captured, throwable-less events ignored, events logged before a client is registered dropped, and the same throwable instance logged twice captured once.
  • ./gradlew :posthog-server-logback:test :posthog-server-logback:apiCheck pass; spotlessCheck clean. The module's gradle.lockfile was regenerated so it matches the repo's current kover/animalsniffer versions.
  • Not covered here: publishing. See the note at the top.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file
  • Added the "release" label to the PR to indicate we're publishing new versions for the affected packages

🔗 Stacked PR

Position 4 of 4 (top of the stack). Base: cat/java-et-uncaught (PR #671).

  1. PR feat(error-tracking): complete exception chain metadata and in-app classification #669 — core exception chain metadata + in-app classification
  2. PR feat(server): expose in-app frame config and captureException options #670 — server error-tracking config and captureException options
  3. PR feat(server): opt-in uncaught exception capture #671 — opt-in server uncaught-exception capture
  4. this PR — new posthog-server-logback appender module

@cat-ph
cat-ph force-pushed the cat/java-et-uncaught branch from 29a1276 to c9db1af Compare August 3, 2026 23:03
@cat-ph
cat-ph force-pushed the cat/java-et-logback branch 2 times, most recently from 1df4118 to 89d7163 Compare August 5, 2026 11:41
@cat-ph
cat-ph force-pushed the cat/java-et-uncaught branch from c9db1af to cf4a852 Compare August 5, 2026 11:41
@cat-ph
cat-ph force-pushed the cat/java-et-logback branch from 89d7163 to b430501 Compare August 7, 2026 21:54
@cat-ph
cat-ph force-pushed the cat/java-et-uncaught branch 2 times, most recently from 5052ac5 to b276e0d Compare August 7, 2026 22:21
@cat-ph
cat-ph force-pushed the cat/java-et-logback branch from b430501 to 59f6f97 Compare August 7, 2026 22:21
@github-actions

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the stale label – otherwise this will be closed in another week.

@github-actions github-actions Bot added the stale label Aug 17, 2026
cat-ph added 5 commits August 18, 2026 17:53
- PostHogConfig.inAppIncludes/inAppExcludes control in_app frame
  classification (prefix match, excludes win); inAppExcludes defaults to
  DEFAULT_IN_APP_EXCLUDES (JDK/Kotlin/framework noise) so zero-config
  users get a your-code vs framework split out of the box.
- New captureException(exception[, distinctId], options) overloads with
  the same option-merging semantics as capture(..., options): custom
  props, $groups, $set/$set_once, timestamp, and flag enrichment via
  snapshot or appendFeatureFlags; reserved props ($exception_level, ...)
  overridable via options properties; request-context resolution and
  personless fallback unchanged.
Adds captureUncaughtExceptions (default false) to the server PostHogConfig.
When enabled, the core PostHogErrorTrackingAutoCaptureIntegration installs a
Thread.defaultUncaughtExceptionHandler that captures the throwable as a fatal,
unhandled $exception (mechanism UncaughtExceptionHandler), flushes, then
delegates to the previously installed handler.

Core changes (all additive; Android behavior and the released
install(PostHogInterface) path unchanged):
- Gate strategy seam on the integration so the server can install with a
  local-only gate (no remote config, which the server SDK never fetches);
  Android keeps the remote errorTracking.autocaptureExceptions gate.
- Captures flow through an internal CaptureTarget seam so the server's
  stateless client can drive the integration.
- Handler-install ownership is tracked per integration instance, so closing a
  second opted-in client (whose install was a process-wide no-op) does not
  tear down the handler a still-open first client owns.
- New @PostHogInternal PostHogCapturedThrowables identity marker (weak,
  ReferenceQueue-pruned). The guard is directional: log mirrors consult it,
  the uncaught handler only marks — a crash is always captured as the
  authoritative fatal/unhandled record even if the same instance was logged
  first (logger.error(..., e); throw e), and marking keeps post-crash log
  mirrors from re-reporting it.
- Repeated setup() cannot replace the owning integration with a non-owning
  one, which would leave the global handler installed after close().
- With no previous default handler to chain to, the handler reproduces the
  JVM's built-in stderr crash output so enabling capture never hides crashes
  from stderr log collection.
- Server config KDoc documents the flushAt implication for the crash path.
New pure-JVM module publishing com.posthog:posthog-server-logback — a Logback
appender that mirrors qualifying log events into PostHog error tracking.

- PostHogAppender (AppenderBase<ILoggingEvent>): captures events at
  minimumCaptureLevel (default ERROR) that carry a real Throwable; maps log
  level to $exception_level; attaches logger name and the log message when it
  differs from the throwable message; never throws from append.
- Client wiring via static PostHogAppender.setPostHog(...); events before
  registration are dropped by design. Runs on the logging thread, so
  PostHogRequestContext scopes resolve the distinct id automatically.
- Recursion guard: com.posthog.* loggers are never captured.
- Dedup: consults the core PostHogCapturedThrowables identity marker so the
  appender and the uncaught handler don't double-report the same Throwable.
- logback-classic is compileOnly (1.3.x line, Java 8); animalsniffer and
  apiCheck pass. release.yml publish matrix / version-sync intentionally not
  updated (workflow edits out of scope) — human follow-up before release.
@github-actions

Copy link
Copy Markdown
Contributor

This PR hasn't seen activity in a week! Should it be merged, closed, or further worked on? If you want to keep it open, post a comment or remove the stale label – otherwise this will be closed in another week.

@github-actions github-actions Bot added the stale label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant