Repository navigation
feat: add initial implementation for Java Observability Plugin - #381
Conversation
abelonogov-ld
left a comment
There was a problem hiding this comment.
Most files could be used, but client sdk semantics was transfered to server code, especially when multithreading occurs
|
Addressed the review on client-SDK semantics in server code. Provider startup is single-threaded ( |
|
The public API needs opentelemetry-api, but the POM publishes it as runtime, recordLog will not compile in some when used. The e2e example hides this by declaring opentelemetry-api explicitly, and the README doesn't mention it. Fix: declare opentelemetry-api as api. |
|
Android moved forward and this version depends on many month behind Android code. Proxy and custom HTTP settings are ignored. GraphQLClient uses a bare HttpURLConnection, and the OTLP exporters don't take headers or a proxy. Android already exposes customHeaders. Servers behind egress proxies will fail to fetch sampling config and to export. |
|
ObservabilityOptions has no resourceAttributes, no log-level threshold, no enabled flag |
|
okhttp, okio and the Kotlin stdlib leak unshaded. The default OTLP exporter brings okhttp 4.12, okio 3.6 and kotlin-stdlib 1.9.10. The LaunchDarkly server SDK itself shades its Kotlin and gson (com/launchdarkly/shaded/kotlin/... in its jar) precisely so server apps don't get version clashes. This plugin undoes that. The fix is cheap: OTel ships opentelemetry-exporter-sender-jdk, which uses Java 11's built-in HttpClient, and this module already targets Java 11. |
|
The artifact name is ambiguous. The Android artifact is also a Java library, and LaunchDarkly's Java server family uses launchdarkly-java-server-sdk-* (-otel, -redis-store). Something with "server" in it would be clearer. Decide before the first publish, because Maven coordinates are permanent. |
|
We need server CI configuration, many of above issues would be caught if we have one |
|
Server e2e app wasn't provided. |
Cache the sampling query, synchronize provider startup, keep sampling config across manual start, and attach launchdarkly.sampling.ratio on exported spans. Replace the Kotlin Gradle scripts with Groovy and drop the committed Gradle cache. Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
The license link is the public Apache documentation endpoint, so it does not need to stay on plain HTTP. Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Pre-init LDObserve calls use a local no-op and no longer install GlobalOpenTelemetry, so TracingHook still exports after init. Sampling patterns are length-capped, allowlisted, and rejected when they can ReDoS. Exclude the Java SDK from the Yarn path filter. Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Closing an inner group now marks its parent, so ((a+))+ is dropped the same way as (a+)+ and is not compiled on the export path. Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
The e2e app starts a local collector, registers ObservabilityPlugin, and exits non-zero unless the flag span, a kept span, and a dropped span arrive after a pre-init LDObserve call. Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com>
Since OpenTelemetry 1.49 each OTLP exporter counts its exports into GlobalOpenTelemetry's meter provider. Once the plugin registered as the global, otlp.exporter.seen and otlp.exporter.exported were exported to LaunchDarkly next to the application's metrics on every app we ran (Spring Boot 3.5 and 4.1). The exporters now get a no-op meter provider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
A server that cannot reach the LaunchDarkly backend (offline, egress blocked, wrong backendUrl) logged a WARNING with a full stack trace on every start. It is now one line saying telemetry is exported unsampled, with the stack trace at FINE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
With the startup fix a Spring Boot 3.2 app (OpenTelemetry 1.31) starts and the plugin stays off, but the first LDObserve.recordMetric still threw NoSuchMethodError from the no-op meter, because the synchronous gauge API only exists since 1.38, and failed the request with a 500. The call now swallows LinkageError; the plugin has already logged why it is off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
Shutdown flushed each provider and then shut each down, waiting up to 10 s on every step in turn. With the OTLP endpoint unreachable a Spring Boot app took 26 s to stop against a blackholed address and 9 s against a refused port, close to a Kubernetes pod's default 30 s grace period, which also has to cover Spring's request drain. Shutdown now relies on provider shutdown exporting what it holds and runs the three in parallel under one 5 s deadline; flush does the same under 10 s. Each runs on its own daemon thread because the metric reader's shutdown blocks before it returns its result code, so joining the codes alone still took 10 s. Measured after: 5.5 s for both cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
…name A working plugin logged nothing, so a new user could not tell from the startup log whether it had started, which endpoint it exported to, or that a missing serviceName had grouped everything under unknown_service:java. The plugin now logs one INFO line with the endpoint and resolved service.name, and a WARNING when no service name is set (directly or through resourceAttributes). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
… the README Written from following the README into fresh Spring Boot 3.2 to 4.1 apps with Maven and Gradle: - Spring Boot's BOM silently moves the plugin's OpenTelemetry 1.51 to 1.31 (3.2, too old), 1.38, 1.43, 1.49 or 1.62. Document the 1.38 minimum, the versions per Boot line, how to override opentelemetry.version, and keeping instrumentation libraries on the same core version (the Logback appender 2.17 on Boot 3.5 without the override hung requests with AbstractMethodError). - A Spring Boot section: the LDClient bean, shutdownHook(false) with LDObserve.shutdown() from a bean, Micrometer tracing wired to LDObserve.getOpenTelemetry() for request spans and errors, and the Logback and Log4j 2 appenders for application logs. Each recipe was run against a local collector. - What to set so the OpenTelemetry Java agent's request spans reach LaunchDarkly too. - The Maven snippet left out launchdarkly-java-server-sdk, which the plugin does not bring, so a Maven build failed with "package com.launchdarkly.sdk.server does not exist". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
Generated-by: termag # Conflicts: # .release-please-manifest.json
…K (7.10.1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
|
Thanks for the thorough review. This round's replies to the top-level comments are below; I also replied on each open thread. In short: 7 fixes from a fresh audit of the feedback, plus 9 from end-to-end testing of new Spring Boot apps (3.2–4.1, Maven and Gradle, JDK 11/17/21). End-to-end Spring Boot testing (new in this round)
The two new public APIs, Review I went through the whole module again with server semantics in mind (thread-safety, lifecycle, hot paths, classpath). On top of the earlier fixes (single-lock init, compile-time query, sampling config kept across manual start, regexes compiled once,
Review Fixed. b5c6e22 dropped the Issue comment
Issue comment Confirmed fixed: Issue comment Proxy/headers: confirmed. Issue comment Confirmed fixed in ef0f4e3: Issue comment Confirmed fixed (4342f83): the OkHttp sender is excluded and Issue comment The artifact is Issue comment Confirmed: Issue comment Confirmed: Open questions
|
|
No sampling fetch in standalone mode. |
|
it still has no package-hashes output and no provenance job, unlike Python, .NET and Ruby. |
…Error span.recordException already records the stack trace on its exception event. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
…he plugin The fetch now lives in OtelManager.fetchSamplingConfig and runs once per SDK key, so plugin registration and LDObserve.start share it: standalone use is sampled, and plugin + manualStart still fetches once. A key other than the registered client's does not replace that client's config. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
The publish action hashes the released jars, and release-please and manual publish run the SLSA generator on them, as for Python, .NET and Ruby. The jars are named after the Maven artifactId so the provenance subjects match Maven Central. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
|
Follow-ups on the two top-level comments from 10/23:
|
Like the other server SDKs, recordError adds the exception event, with the caller's attributes, to the current span instead of starting a separate highlight.error span. A span from another provider (the Java agent or the application's own SDK) would not reach LaunchDarkly, so there the error is still a new error span, a child of the current one in the same trace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
…ility --release checks API use against the target JDK, so a build on JDK 17 or 21 cannot call an API that Java 11 lacks. The published module metadata still declares Java 11. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-by: termag
🤖 I have created a release *beep* *boop* --- <details><summary>launchdarkly-java-server-sdk-observability: 0.1.0</summary> ## [0.1.0](https://github.com/launchdarkly/observability-sdk/releases/tag/launchdarkly-java-server-sdk-observability-0.1.0) (2026-10-27) ### Features * add initial implementation for Java Observability Plugin ([#381](#381)) ([9f458f0](9f458f0)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > **Release Please** adds `sdk/@launchdarkly/observability-java/CHANGELOG.md` to publish **`launchdarkly-java-server-sdk-observability` 0.1.0** (2026-10-27). > > The new changelog entry records the first public release, including the initial Java Observability Plugin work ([#381]) and the release chore ([#852]); it also lists an unrelated React Native session-replay note that Release Please rolled into this package’s notes. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2eb2c22. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Vadim Korolik <vkorolik@launchdarkly.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Add the LaunchDarkly Observability plugin for the Java Server SDK.
Artifact:
com.launchdarkly:launchdarkly-java-server-sdk-observability(follows thelaunchdarkly-java-server-sdk-otel/-redis-store/-aifamily; the Android observability artifact is also a Java library, so a bare-javasuffix was ambiguous).The plugin wires OpenTelemetry traces, logs and metrics to OTLP/HTTP, registers the
TracingHookfor flag-evaluation spans, and applies backend sampling rules.LDObserveis a safe no-op before init and never installs the OpenTelemetry no-op global.Options
enabled,serviceName,serviceVersion,environment,otlpEndpoint,backendUrl,resourceAttributes,customHeaders/customHeader,proxySelector/proxyHostAndPort,logsApiLevel(TRACE..FATAL,NONE),debug,manualStart.Dependencies as a consumer sees them
opentelemetry-apiis anapidependency (compile scope in the POM) because the public API takesAttributesandSeverity.opentelemetry-exporter-sender-jdk(java.net.http.HttpClient). No OkHttp, Okio or Kotlin stdlib reaches the consumer;:lib:checkfails if any does, and fails if the POM scopes change.HttpClient, so proxies and custom headers behave identically for exports and the sampling fetch. With nothing configured the JVM default proxy selector applies.CI
Java Observability: unit tests plus classpath and POM guards on JDK 11, 17 and 21; a consumer job publishes to Maven local and builds both e2e apps against the artifact through its POM.Java Observability Plugin E2E: runs the self-checking example, builds the Spring Boot backend, drives every route over curl (including a recorded 500 and a clean SIGTERM shutdown), and builds its Dockerfile.End to End Example Apps) gains ajavaservice backed by the Spring Boot image.e2e apps
e2e/java-plugin:PluginExample, a self-check against a local collector (flag span, sampling keep/drop, sampling ratio attribute, pre-init no-op).e2e/java-plugin-spring-boot: a Spring Boot 3 web backend that uses the plugin the way a service does. One span, histogram, counter and log per request from a servlet filter; flag evaluations in every handler;/errorrecorded throughLDObserve.recordError;/flags/{key}; actuator health. HonoursLAUNCHDARKLY_SDK_KEY(offline when unset),OTEL_EXPORTER_OTLP_ENDPOINT,LD_OBSERVABILITY_BACKEND_URL,HTTPS_PROXY. This is the app to drive server-side feature work against.Release
release-please versions the component (
gradle.propertiesplus the version constant inConstants.java). On a release,release-please.ymlruns the newpublish-java-sdkaction::lib:build(tests and both guards), thenpublishToSonatype closeAndReleaseSonatypeStagingRepository, with the same Sonatype Central account and code-signing key as the Android plugin.manual-publish.ymlhas a Java toggle with dry-run.How did you test this change?
./gradlew :lib:buildon JDK 11 and 21 (45 unit tests, runtime-classpath guard, POM guard).make runandmake consumer-checkine2e/java-plugin(composite build and Maven-local modes).make test buildine2e/java-plugin-spring-boot, then the jar run and every route driven over curl; Docker image build and container health check../gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --dry-runwith placeholder credentials to confirm the sign, publish, close and release task graph. The real upload is only exercised by the release workflow.Are there any deployment considerations?
The Maven coordinates are permanent once published. Confirm
launchdarkly-java-server-sdk-observabilitybefore the first release.🤖 Generated with Claude Code
Opened from termag
Note
Overview
Introduces
com.launchdarkly:launchdarkly-java-server-sdk-observability(0.1.0) — an OpenTelemetry-backed LaunchDarkly Server SDK plugin with flag-evaluation tracing, OTLP export, backend sampling, and theLDObserveAPI. The build enforces a JDK HTTP OTLP sender (no OkHttp/Kotlin on the runtime classpath) and POM guards for consumer-safe dependencies.CI and release: Adds
java-observability(unit tests on JDK 11/17/21, consumer checks) andjava-plugin-e2eworkflows, apublish-java-sdkcomposite action (Sonatype + signing, dry-run build), release-please/manual-publish hooks, and SLSA provenance. Docker e2e gainsjava-plugin-spring-bootwith Python integration tests for logs, traces, and errors.Examples:
e2e/java-plugin(self-checking collector tests, including Java agent / claimed global) ande2e/java-plugin-spring-boot(Spring Boot 3 sample with request telemetry, routes, and javaagent smoke).Reviewed by Cursor Bugbot for commit 59f581f. Bugbot is set up for automated code reviews on this repo. Configure here.