You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/09/21 22:00:37 UTC
(causeway) 06/14: CAUSEWAY-3975: proposes Boot 2.7 observation substrate
This is an automated email from the ASF dual-hosted git repository.
danhaywood pushed a commit to branch CAUSEWAY-3975
in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 2b65d146dcb6178f8d7282f5f1cb8fa64d3d7e0e
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Mon Sep 21 17:44:39 2026 +0100
CAUSEWAY-3975: proposes Boot 2.7 observation substrate
---
.../.openspec.yaml | 2 +
.../add-boot27-observation-substrate/design.md | 128 +++++++++++++++++++++
.../add-boot27-observation-substrate/proposal.md | 33 ++++++
.../specs/boot27-observation-substrate/spec.md | 75 ++++++++++++
.../add-boot27-observation-substrate/tasks.md | 28 +++++
5 files changed, 266 insertions(+)
diff --git a/openspec/changes/add-boot27-observation-substrate/.openspec.yaml b/openspec/changes/add-boot27-observation-substrate/.openspec.yaml
new file mode 100644
index 00000000000..563fab5abae
--- /dev/null
+++ b/openspec/changes/add-boot27-observation-substrate/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-09-21
diff --git a/openspec/changes/add-boot27-observation-substrate/design.md b/openspec/changes/add-boot27-observation-substrate/design.md
new file mode 100644
index 00000000000..ecf5a07615e
--- /dev/null
+++ b/openspec/changes/add-boot27-observation-substrate/design.md
@@ -0,0 +1,128 @@
+## Context
+
+The archived `verify-boot27-micrometer-tracing` change proved that a Spring Boot 2.7.18 application can retain Micrometer Core 1.9.17 while using Micrometer Observation 1.10.13, Micrometer Tracing 1.0.12, the Micrometer OpenTelemetry bridge 1.0.12, and OpenTelemetry API 1.19.0.
+It also proved that a Micrometer observation built from `GlobalOpenTelemetry` can parent a JDBC span generated by OpenTelemetry Java agent 1.31.0.
+The proof currently lives in an isolated regression-test module and does not provide production beans or framework lifecycle utilities.
+
+Causeway 4 places its observation integration in core configuration and uses an `observation` Spring profile to make telemetry opt-in.
+The maintenance branch should preserve that architectural direction while adapting it to Java 11 and Spring Boot 2.7, which does not auto-configure Micrometer Observation or Tracing.
+
+This phase establishes infrastructure only.
+The following phase will use it from `InteractionServiceDefault` and `MemberExecutorServiceDefault`.
+
+## Goals / Non-Goals
+
+**Goals:**
+
+- Add the compatibility-tested dependency set without changing Boot-managed Micrometer Core 1.9.17.
+- Make a stable Causeway observation integration bean available to later framework modules.
+- Make observation inactive and effectively free by default through `ObservationRegistry.NOOP`.
+- Activate a real `ObservationRegistry` through the `observation` Spring profile.
+- Connect the active registry to an `OtelTracer` built from the Java agent's `GlobalOpenTelemetry` context.
+- Provide Java 11-compatible lifecycle support that reliably starts, scopes, records errors, stops, and clears an observation.
+- Verify inactive, active-with-agent, active-without-agent, successful, and failing lifecycle paths.
+
+**Non-Goals:**
+
+- Instrument Causeway interactions, actions, property edits, Wicket requests, transactions, execution publishing, or persistence operations.
+- Add an application-facing observability API to applib.
+- Create an `OpenTelemetrySdk`, configure an application-owned exporter, or package the Java agent with production artifacts.
+- Add sampling, baggage, dynamic runtime switching, duration filtering, or semantic tag policies.
+- Upgrade the globally managed Micrometer version or Spring Boot version.
+
+## Decisions
+
+### Put the substrate in core configuration
+
+The production integration will live under a dedicated observation package in `core/config`, and `CausewayModuleCoreConfig` will import its configuration.
+The package will contain the registry integration, profile-specific configuration, and lifecycle helper needed by later core modules.
+
+This matches the Causeway 4 direction, avoids introducing a new extension module for infrastructure that later core runtime services must consume, and keeps the Boot 2.7-specific wiring in one boundary.
+
+**Alternative considered:** create an optional tracing extension containing all Micrometer dependencies.
+This would reduce transitive dependencies for applications that never enable tracing, but core services would still need either an additional internal abstraction or a dependency on the extension.
+That inversion adds more maintenance-branch code and diverges from the forward architecture, so it is rejected for this targeted backport.
+
+### Manage validated versions centrally without overriding Micrometer Core
+
+The BOM will define explicit properties and dependency-management entries for Micrometer Observation 1.10.13, Micrometer Tracing and bridge 1.0.12, and OpenTelemetry API 1.19.0.
+It will not override `micrometer.version`.
+`core/config` will declare the production dependencies it compiles against.
+
+The bridge's transitive `opentelemetry-sdk`, `opentelemetry-sdk-common`, and `opentelemetry-sdk-trace` artifacts will be excluded at the production dependency declaration.
+The OpenTelemetry Java agent and OTLP protobuf test collector remain test-only in the regression-test module.
+
+**Alternative considered:** import a newer Micrometer BOM or set `micrometer.version` to 1.10.13.
+This is rejected because it would replace the Micrometer Core version tested and managed by Spring Boot 2.7.18.
+
+### Always expose one Causeway integration bean
+
+Framework consumers will inject a Causeway-owned integration object rather than directly making `ObservationRegistry` optional at every call site.
+The substrate will own a qualified Causeway registry so an unrelated application-defined `ObservationRegistry` cannot accidentally enable or disable framework observations.
+The integration object will hold that registry, fall back defensively to `ObservationRegistry.NOOP`, report whether it is no-op, and create unstarted observations or observation providers with consistent Causeway naming support.
+
+This keeps optionality at configuration time, makes profile behavior deterministic, and gives Phase 3 a simple dependency that behaves safely in every application.
+The integration class is framework infrastructure and does not become a public applib service.
+
+### Use profile-specific registry configuration
+
+When the `observation` profile is inactive, configuration will supply `ObservationRegistry.NOOP` and will not construct tracer or bridge beans.
+When the profile is active, configuration will create an `ObservationRegistry`, construct `OtelCurrentTraceContext`, `OtelBaggageManager`, and `OtelTracer` from `GlobalOpenTelemetry.getTracer(...)`, and register `DefaultTracingObservationHandler` with the registry.
+
+The active configuration will not test for a private Java-agent implementation class.
+When the agent is absent, `GlobalOpenTelemetry` supplies its no-op implementation, allowing startup and observation lifecycles to complete without export.
+
+**Alternative considered:** fail startup when the profile is active but the Java agent is absent.
+This is rejected because safe degradation is operationally preferable and was part of the validated compatibility behavior.
+
+### Keep SDK and exporter ownership with the Java agent
+
+Production code will never invoke `OpenTelemetrySdk.builder()` and will not configure OTLP exporters.
+The only OpenTelemetry object obtained by the substrate is an API tracer from `GlobalOpenTelemetry`.
+Deployment configuration remains responsible for attaching the Java agent and configuring service identity, sampling, endpoint, and exporter behavior.
+
+This prevents disconnected trace contexts and duplicate export pipelines.
+
+### Add an explicit observation lifecycle helper
+
+A Java 11-compatible helper will own an observation and its opened scope across different framework callbacks.
+It will start and open scope once, close scope before stopping, record any `Throwable`, and make cleanup idempotent enough for defensive framework shutdown paths.
+After close, the registry must not retain the closed observation as current.
+
+The helper belongs with the core observation substrate rather than in commons so that commons does not acquire Micrometer dependencies.
+
+**Alternative considered:** defer lifecycle support until semantic spans are introduced.
+This is rejected because lifecycle correctness is a foundational concern and is cheaper to test independently before interaction and action callbacks depend on it.
+
+### Reuse the compatibility harness for the production exit gate
+
+Focused unit tests will validate profile selection and lifecycle behavior with recording handlers.
+The existing `regressiontests/tracing-compatibility` child process will depend on the production core-config integration and use it to emit the framework-owned test observation.
+Its real-agent OTLP assertion will continue to prove that the production substrate joins the agent context and exports successfully.
+
+This avoids creating a second agent harness and turns the Phase 1 proof into a regression guard for the production integration.
+
+## Risks / Trade-offs
+
+- **[Risk] Adding bridge dependencies to core configuration increases every application's runtime classpath even when observation is inactive.** → Keep initialization profile-gated, exclude SDK artifacts, and accept the small classpath cost in preference to a new maintenance-only abstraction layer.
+- **[Risk] Applications may define their own `ObservationRegistry` bean.** → Qualify the Causeway-owned registry and inject it explicitly so application registries do not alter the profile contract.
+- **[Risk] An active profile without an agent silently exports nothing.** → Treat this as intentional safe degradation and document agent attachment as an operational prerequisite for export.
+- **[Risk] Scope leakage could corrupt parentage across requests on reused threads.** → Centralize lifecycle ownership, close scope in `finally`, clear retained references, and test success and failure cleanup.
+- **[Risk] Bridge APIs are version-sensitive.** → Pin the exact compatibility-tested versions and isolate version-specific construction inside core configuration.
+- **[Trade-off] This phase adds infrastructure without user-visible semantic spans.** → Keep the change independently testable and use it as the prerequisite for the narrowly scoped interaction/action instrumentation phase.
+
+## Migration Plan
+
+1. Add validated dependency-management entries while retaining Boot-managed Micrometer Core 1.9.17.
+2. Add the core-config observation package, Java module declarations, and module import.
+3. Add inactive and active profile tests plus lifecycle tests.
+4. Update the compatibility harness to exercise the production integration under the real Java agent and without it.
+5. Run dependency convergence, focused tests, and representative core startup tests.
+6. Document the internal integration contract and operational requirement to attach the Java agent when export is desired.
+
+Rollback removes the core-config observation package, its module import and dependencies, and the associated tests.
+Because no semantic instrumentation or public API is introduced, rollback does not require application or data migration.
+
+## Open Questions
+
+None.
diff --git a/openspec/changes/add-boot27-observation-substrate/proposal.md b/openspec/changes/add-boot27-observation-substrate/proposal.md
new file mode 100644
index 00000000000..28600bbfc18
--- /dev/null
+++ b/openspec/changes/add-boot27-observation-substrate/proposal.md
@@ -0,0 +1,33 @@
+## Why
+
+The completed Boot 2.7 compatibility spike proved that Micrometer Observation and Tracing can join the OpenTelemetry Java agent's context without replacing Spring Boot's managed Micrometer Core or creating a second telemetry runtime.
+Causeway 2.x now needs a production observation substrate that exposes this validated integration safely, remains inactive by default, and gives later changes a stable framework-level API for semantic spans.
+
+## What Changes
+
+- Add the compatibility-tested Micrometer Observation, Micrometer Tracing, OpenTelemetry bridge, and OpenTelemetry API versions to managed production dependencies without overriding Spring Boot's global Micrometer version.
+- Add Java 11-compatible Causeway observation integration and lifecycle support in core configuration.
+- Provide a no-op `ObservationRegistry` when the `observation` Spring profile is inactive.
+- Provide an agent-backed `ObservationRegistry` when the `observation` Spring profile is active, using `GlobalOpenTelemetry` rather than constructing an application-owned OpenTelemetry SDK or exporter.
+- Exclude bridge-transitive OpenTelemetry SDK artifacts so the Java agent remains the only telemetry runtime and exporter.
+- Verify successful startup and unchanged no-op behavior when observation is inactive or the Java agent is absent.
+- Verify observation scope, failure, and cleanup behavior and agent-backed export of a framework-owned test observation.
+- Do not yet instrument Causeway interactions, actions, Wicket requests, transactions, publishing, or persistence operations.
+
+## Capabilities
+
+### New Capabilities
+
+- `boot27-observation-substrate`: Defines opt-in, agent-backed Micrometer observation infrastructure and safe inactive behavior for Causeway 2.x applications.
+
+### Modified Capabilities
+
+None.
+
+## Impact
+
+The change affects dependency management, core configuration, Java module declarations, and focused unit or integration tests.
+Applications receive the Micrometer observation and tracing bridge classes required by the substrate, but tracing remains inactive unless the `observation` Spring profile is enabled.
+The bridge uses the versions and exclusions validated by `boot27-micrometer-tracing-compatibility` and delegates SDK ownership, automatic HTTP and JDBC instrumentation, sampling, and export to the OpenTelemetry Java agent.
+No public applib API, domain behavior, database schema, or existing semantic execution path changes in this phase.
+The substrate becomes the prerequisite for the planned root-interaction and action instrumentation phase documented in `openspec/planned-changes/micrometer-tracing-backport.md`.
diff --git a/openspec/changes/add-boot27-observation-substrate/specs/boot27-observation-substrate/spec.md b/openspec/changes/add-boot27-observation-substrate/specs/boot27-observation-substrate/spec.md
new file mode 100644
index 00000000000..9427f25be69
--- /dev/null
+++ b/openspec/changes/add-boot27-observation-substrate/specs/boot27-observation-substrate/spec.md
@@ -0,0 +1,75 @@
+## ADDED Requirements
+
+### Requirement: Preserve the validated dependency baseline
+The production observation substrate SHALL retain Spring Boot 2.7.18's managed Micrometer Core 1.9.17 and SHALL use the Observation, Tracing, OpenTelemetry bridge, and OpenTelemetry API versions validated by the Boot 2.7 compatibility proof.
+The substrate MUST NOT override the global `micrometer.version` property.
+
+#### Scenario: Resolve production observation dependencies
+- **WHEN** the affected production modules' dependency graph is resolved
+- **THEN** Micrometer Core remains at 1.9.17, the validated observation and tracing artifacts resolve without convergence errors, and bridge-transitive OpenTelemetry SDK artifacts are absent
+
+### Requirement: Keep Causeway observation inactive by default
+The substrate SHALL provide a Causeway-owned no-op observation registry whenever the `observation` Spring profile is inactive.
+An unrelated application-defined observation registry MUST NOT activate Causeway framework observations.
+
+#### Scenario: Start an application without the observation profile
+- **WHEN** a Causeway application starts without the `observation` Spring profile
+- **THEN** the Causeway observation integration is available, reports no-op behavior, and does not construct the OpenTelemetry tracing bridge
+
+#### Scenario: Create an observation while inactive
+- **WHEN** framework code creates and executes an observation through the inactive Causeway integration
+- **THEN** the observed operation executes normally without exporting a span or retaining a current observation
+
+### Requirement: Activate agent-backed observation explicitly
+The substrate SHALL create a real Causeway-owned observation registry when the `observation` Spring profile is active.
+The active registry SHALL use Micrometer's OpenTelemetry bridge and an API tracer obtained from `GlobalOpenTelemetry`.
+
+#### Scenario: Start an observed application with the Java agent
+- **WHEN** a Causeway application starts with the `observation` profile and the OpenTelemetry Java agent attached
+- **THEN** the Causeway registry uses the agent-installed global trace context and can export a framework-owned observation through the agent
+
+### Requirement: Preserve single ownership of the telemetry runtime
+The substrate MUST NOT construct an `OpenTelemetrySdk`, configure an application-owned trace exporter, or package the OpenTelemetry Java agent as a production runtime dependency.
+The Java agent SHALL remain responsible for the SDK, automatic infrastructure instrumentation, sampling, and export.
+
+#### Scenario: Inspect production telemetry wiring
+- **WHEN** the production observation configuration and dependency graph are inspected
+- **THEN** they contain no application-owned SDK construction, no application-owned exporter configuration, and no production Java-agent dependency
+
+### Requirement: Degrade safely when the Java agent is absent
+The active observation substrate SHALL allow application startup and observation execution when the `observation` profile is active but the OpenTelemetry Java agent is absent.
+
+#### Scenario: Start an observed application without the Java agent
+- **WHEN** a Causeway application starts with the `observation` profile and without `-javaagent`
+- **THEN** startup succeeds and framework observations complete safely through the no-op global OpenTelemetry implementation
+
+### Requirement: Provide consistent framework observation creation
+The substrate SHALL expose one Causeway framework integration that creates unstarted observations from the profile-selected Causeway registry and that can report whether the selected registry is no-op.
+The integration MUST remain internal framework infrastructure rather than a public applib service.
+
+#### Scenario: Inject the framework integration
+- **WHEN** a core framework component receives the Causeway observation integration
+- **THEN** it can create an observation without performing optional bean lookup or depending directly on OpenTelemetry
+
+### Requirement: Close observation lifecycles reliably
+The substrate SHALL provide lifecycle support that starts an observation, opens its scope, records any reported failure, closes the scope before stopping the observation, and clears retained lifecycle state.
+Cleanup SHALL be safe when invoked defensively after partial initialization or prior cleanup.
+
+#### Scenario: Complete a successful observation
+- **WHEN** a started framework observation completes successfully
+- **THEN** its scope and observation are closed exactly once and the registry no longer exposes it as current
+
+#### Scenario: Complete a failing observation
+- **WHEN** framework work reports a `Throwable`
+- **THEN** the active observation records the failure and cleanup closes its scope and stops it without leaving current observation state behind
+
+#### Scenario: Clean up a partial lifecycle
+- **WHEN** cleanup runs without a successfully started observation or runs again after cleanup
+- **THEN** cleanup completes without throwing or corrupting the registry's current observation
+
+### Requirement: Verify the production substrate through the real agent
+The existing tracing compatibility harness SHALL exercise the production Causeway observation integration rather than duplicate its bridge construction.
+
+#### Scenario: Export a production-substrate test observation
+- **WHEN** the compatibility harness runs a framework-owned test observation with the Java agent attached
+- **THEN** the observation and the agent-generated JDBC span share a trace and the JDBC span identifies the observation span as its parent or expected semantic ancestor
diff --git a/openspec/changes/add-boot27-observation-substrate/tasks.md b/openspec/changes/add-boot27-observation-substrate/tasks.md
new file mode 100644
index 00000000000..1478b35de00
--- /dev/null
+++ b/openspec/changes/add-boot27-observation-substrate/tasks.md
@@ -0,0 +1,28 @@
+## 1. Add the Validated Production Dependencies
+
+- [ ] 1.1 Add centrally managed properties and dependency entries for Micrometer Observation 1.10.13, Micrometer Tracing and OpenTelemetry bridge 1.0.12, and OpenTelemetry API 1.19.0 without overriding `micrometer.version`.
+- [ ] 1.2 Add the required dependencies to `core/config`, excluding bridge-transitive OpenTelemetry SDK artifacts and keeping the Java agent and OTLP collector dependencies test-only.
+- [ ] 1.3 Update Java module declarations for the new production API usage and verify that Micrometer Core remains at Spring Boot 2.7.18's managed 1.9.17 version with dependency convergence passing.
+
+## 2. Implement the Observation Substrate
+
+- [ ] 2.1 Add profile-specific core configuration that supplies a qualified `ObservationRegistry.NOOP` when `observation` is inactive and a qualified real registry when it is active.
+- [ ] 2.2 Configure the active registry with `OtelCurrentTraceContext`, `OtelBaggageManager`, an `OtelTracer` obtained from `GlobalOpenTelemetry.getTracer(...)`, and `DefaultTracingObservationHandler` without constructing an SDK or exporter.
+- [ ] 2.3 Add the internal Causeway observation integration that exposes no-op status and consistent creation of unstarted observations or observation providers from the qualified registry.
+- [ ] 2.4 Add Java 11-compatible lifecycle support for start, scope, `Throwable` recording, idempotent cleanup, and clearing retained state.
+- [ ] 2.5 Import the observation configuration from `CausewayModuleCoreConfig` and document the internal contract for later framework instrumentation.
+
+## 3. Verify Profile and Lifecycle Behavior
+
+- [ ] 3.1 Add focused tests proving that the inactive profile selects the Causeway no-op registry, creates no tracing bridge, and ignores unrelated application registries.
+- [ ] 3.2 Add focused tests proving that the active profile creates the real Causeway registry and starts safely without the Java agent.
+- [ ] 3.3 Add lifecycle tests covering successful completion, recorded failures, partial initialization, repeated cleanup, and removal of the current observation from the registry.
+- [ ] 3.4 Update the tracing compatibility child fixture to obtain and use the production Causeway integration instead of constructing duplicate Micrometer bridge wiring.
+- [ ] 3.5 Run the real-agent compatibility test and verify that the production-substrate observation remains the parent or expected semantic ancestor of the agent-generated JDBC span.
+
+## 4. Validate the Production Integration
+
+- [ ] 4.1 Run targeted `core/config` tests and representative application-context startup tests with and without the `observation` profile.
+- [ ] 4.2 Run the tracing compatibility module in the regression-test reactor on the Java 11 compilation target and capture the resolved dependency graph and span-parentage evidence.
+- [ ] 4.3 Confirm that production source contains no `OpenTelemetrySdk` construction, exporter configuration, semantic framework spans, or public applib API changes.
+- [ ] 4.4 Record the validated internal integration recipe for Phase 3 and validate the completed OpenSpec change.