Skip to content

fix(observability): stop exporting metrics to a collector that is not there - #138

Merged
kl3inIT merged 2 commits into
mainfrom
fix/observability-exporter-silence
Jul 29, 2026
Merged

fix(observability): stop exporting metrics to a collector that is not there#138
kl3inIT merged 2 commits into
mainfrom
fix/observability-exporter-silence

Conversation

@kl3inIT

@kl3inIT kl3inIT commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Phase 1 of the observability pipeline increment. This is the change that stops the symptom the increment opened on.

The bug

Micrometer's OTLP metrics registry is opt-out, and its URL defaults to http://localhost:4318/v1/metrics — inside a container, that container. Both services exported to nowhere every minute and logged the failure, from deployment until 2026-07-29:

i.m.registry.otlp.OtlpMeterRegistry : Failed to publish metrics to OTLP receiver
(context: url=http://localhost:4318/v1/metrics, resource-attributes={service.name=orgmemory-api})

OTLP tracing has the opposite default — no endpoint, no exporter, no noise. Metrics now match it.

Property names were taken from each module's spring-configuration-metadata.json rather than from memory, since Boot 4.1 moved several of them.

Also in this change

OTLP log export off. The collector tails the json-file driver, so it would be a second copy of every line — and it fails the same silent way.

map-environment-variables: false in production. This reverses a paragraph in the increment's own design, which assumed OTEL_EXPORTER_OTLP_ENDPOINT would carry the endpoint. That mapping is a way for OrgMemory telemetry to acquire a destination nobody chose for it, and this host already runs an unrelated OpenTelemetry stack. The design records the reversal rather than quietly dropping the earlier text.

Resource attributes. service.version and deployment.environment; without them a collector cannot separate builds or environments.

Worker sampling to 1.0. A low-volume batch workload sampled at a tenth leaves most indexing jobs with no trace at all. The API stays at 0.1, where request volume is the reason sampling exists.

include-error-logging: false declared explicitly. Already the framework default; stating it keeps the payload posture in one readable block.

Proof

The API context test starts the real application and asserts no OtlpMeterRegistry bean exists and that both resource attributes reach the OpenTelemetry Resource — which also proves the dotted map keys bind. The worker has no bootable context test, so its defaults are asserted against the shipped YAML with placeholders resolved as an environment that sets nothing would resolve them.

What is not proven

Production has not been restarted with this configuration. "No recurring exporter warning" remains an exit criterion, not an observation.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added clearer OpenTelemetry service and environment metadata for improved telemetry identification.
    • Added configuration controls for enabling OTLP metrics export.
    • Set tracing sampling defaults to provide comprehensive trace coverage.
  • Bug Fixes

    • Prevented unintended automatic telemetry export configuration from host environment variables.
    • Disabled OTLP log export by default.
    • Improved chat observation payloads by excluding redundant error logging.

… there

Micrometer's OTLP metrics registry is opt-out, and its URL defaults to
http://localhost:4318/v1/metrics — inside a container, that container. Both
services therefore exported to nowhere every minute and logged the failure, from
deployment until 2026-07-29. OTLP tracing has the opposite default: no endpoint,
no exporter, no noise. Metrics now match it, which is the change that stops the
production symptom this increment opened on.

OTLP log export is disabled too. The collector tails the json-file driver, so it
would be a second copy of every line, and it fails the same silent way.

Production also disables Boot's OTEL_* environment-variable mapping. The design
had assumed OTEL_EXPORTER_OTLP_ENDPOINT would carry the endpoint; that mapping is
a way for OrgMemory telemetry to acquire a destination nobody chose for it, and
this host already runs an unrelated OpenTelemetry stack. Knowing from one file
where telemetry goes is worth more than the convention. The design records the
reversal rather than quietly dropping the earlier paragraph.

Spans gain service.version and deployment.environment, without which a collector
cannot separate builds or environments. Worker sampling goes to 1.0: it runs a
low-volume batch workload, so a tenth of it leaves most indexing jobs with no
trace at all. The API stays at 0.1, where request volume is the reason sampling
exists.

spring.ai.chat.observations.include-error-logging is declared explicitly. It was
already the framework default; stating it keeps the payload posture in one
readable block instead of two settings and an assumption.

The API context test starts the real application and asserts the absence of the
meter registry and the presence of both resource attributes. The worker has no
bootable context test, so its defaults are asserted against the shipped YAML with
placeholders resolved as an environment that sets nothing would resolve them.

Production has not been restarted with this configuration, so "no recurring
exporter warning" is still an exit criterion rather than an observation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kl3inIT, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 46b44541-8738-461f-956d-fb68b31a3f57

📥 Commits

Reviewing files that changed from the base of the PR and between c270a7c and 619efe2.

📒 Files selected for processing (4)
  • apps/api/src/main/resources/application.yml
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
  • apps/worker/src/main/resources/application.yml
  • apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
📝 Walkthrough

Walkthrough

The API and worker applications now explicitly configure OTLP metrics and log export, trace sampling, OpenTelemetry resource attributes, production environment-variable mapping, and Spring AI observation error logging. Tests validate runtime telemetry beans and shipped worker configuration defaults.

Changes

Telemetry configuration

Layer / File(s) Summary
Telemetry export and observation policy
apps/api/src/main/resources/application.yml, apps/api/src/main/resources/application-prod.yml, apps/worker/src/main/resources/application.yml, apps/worker/src/main/resources/application-prod.yml
Both applications configure observation error logging, OTLP export defaults, resource identity attributes, and production OpenTelemetry environment-variable handling.
API runtime telemetry validation
apps/api/build.gradle.kts, apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
API tests verify that no OTLP meter registry starts without a collector and that the OpenTelemetry resource includes deployment identity attributes.
Worker configuration validation
apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
Worker tests parse application.yml, resolve unset placeholders, and verify export, sampling, and resource-attribute defaults.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preventing unnecessary OTLP metric export when no collector is configured.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/observability-exporter-silence

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/main/resources/application.yml`:
- Around line 198-203: The OpenTelemetry deployment attribute is deprecated;
replace deployment.environment with deployment.environment.name in both
application configurations. Update the API test OrgMemoryApiContextLoadTests and
worker test TelemetryExportDefaultsTests to assert the new key, covering
apps/api/src/main/resources/application.yml lines 198-203,
apps/worker/src/main/resources/application.yml lines 140-145,
apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java lines
57-63, and
apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
lines 53-64.

In `@apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java`:
- Around line 3-14: Add the Spring Boot metrics test module dependency in
apps/api/build.gradle.kts at lines 39-43, and annotate
OrgMemoryApiContextLoadTests in
apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java at
lines 3-14 with `@AutoConfigureMetrics`; retain the existing OTLP
OtlpMeterRegistry assertion at lines 45-55 so it runs against the real
auto-configured registry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e1246e70-5f85-48b4-b8a8-7b8eed709db5

📥 Commits

Reviewing files that changed from the base of the PR and between 1bfd990 and c270a7c.

⛔ Files ignored due to path filters (4)
  • docs/increments/active/2026-07-29-observability-pipeline/design.md is excluded by !docs/**
  • docs/increments/active/2026-07-29-observability-pipeline/plan.md is excluded by !docs/**
  • docs/specs/domains/secure-graph-rag.md is excluded by !docs/**
  • docs/tests/domains/secure-graph-rag.md is excluded by !docs/**
📒 Files selected for processing (7)
  • apps/api/build.gradle.kts
  • apps/api/src/main/resources/application-prod.yml
  • apps/api/src/main/resources/application.yml
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
  • apps/worker/src/main/resources/application-prod.yml
  • apps/worker/src/main/resources/application.yml
  • apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
📜 Review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: Backend · Java 25
  • GitHub Check: Web · Node 24
🧰 Additional context used
📓 Path-based instructions (4)
**/*

📄 CodeRabbit inference engine (CLAUDE.md)

**/*: Always read the repository guidance and relevant sections of ARCHITECTURE.md; before changing a domain, read its specification, test-coverage document, and binding decision filenames.
Treat the repository as the engineering system of record; current repository and runtime evidence take precedence over chat or Northstar.
Read docs/guidelines/agent-safety.md before retrieval, AI, MCP, permission, upload, graph, or export work. Never commit secrets or customer data.

Files:

  • apps/api/src/main/resources/application-prod.yml
  • apps/api/build.gradle.kts
  • apps/api/src/main/resources/application.yml
  • apps/worker/src/main/resources/application-prod.yml
  • apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
  • apps/worker/src/main/resources/application.yml
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
**/*.{java,gradle,gradle.kts,properties,yml,yaml}

📄 CodeRabbit inference engine (CLAUDE.md)

Before using unfamiliar Spring Boot 4, Spring Modulith 2, Spring AI 2, or Gradle APIs, consult current official documentation, Context7, and the relevant project verification skill.

Files:

  • apps/api/src/main/resources/application-prod.yml
  • apps/api/build.gradle.kts
  • apps/api/src/main/resources/application.yml
  • apps/worker/src/main/resources/application-prod.yml
  • apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
  • apps/worker/src/main/resources/application.yml
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
**/*.{java,gradle,gradle.kts}

📄 CodeRabbit inference engine (CLAUDE.md)

Use the testing harness; a terminating clean test is the JVM context gate, and bootRun is not verification. IDE inspection applies only to edited backend Java.

Files:

  • apps/api/build.gradle.kts
  • apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
**/*.{java,sql}

📄 CodeRabbit inference engine (CLAUDE.md)

Keep ddl-auto=validate and pair every persisted-model change with a Flyway migration.

Files:

  • apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java
🔇 Additional comments (5)
apps/api/src/main/resources/application.yml (1)

14-16: LGTM!

apps/worker/src/main/resources/application.yml (1)

25-27: LGTM!

apps/api/src/main/resources/application-prod.yml (1)

71-76: 🩺 Stability & Availability

Verify explicit production OTLP destinations after disabling OTEL environment mapping. Spring Boot will no longer consume OTEL_* exporter variables, so confirm both production profiles define equivalent management... endpoints and exporter settings. (docs.spring.io)

  • apps/api/src/main/resources/application-prod.yml#L71-L76: verify the API collector configuration.
  • apps/worker/src/main/resources/application-prod.yml#L38-L43: verify the worker collector configuration.

Source: MCP tools

apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java (1)

37-43: LGTM!

apps/worker/src/test/java/com/orgmemory/worker/observability/TelemetryExportDefaultsTests.java (1)

1-51: LGTM!

Also applies to: 67-87

Comment thread apps/api/src/main/resources/application.yml Outdated
Comment on lines +3 to +14
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import io.micrometer.registry.otlp.OtlpMeterRegistry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.resources.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate guidance files =="
git ls-files | rg '(^|/)ARCHITECTURE\.md$|docs/guidelines/agent-safety\.md$|binding|specification|test-coverage' || true

echo "== target files =="
fd -a 'OrgMemoryApiContextLoadTests.java|build.gradle.kts' . | sed 's#^\./##'

echo "== target source =="
cat -n apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java | sed -n '1,120p'
echo "== target build excerpt =="
cat -n apps/api/build.gradle.kts | sed -n '1,120p'

echo "== metrics-related content in test and build =="
rg -n 'Metrics|MeterRegistry|OtlpMeterRegistry|AutoConfigureMetrics|micrometer|opentelemetry|otel|export' apps/api/src/test apps/api/build.gradle.kts README.md ARCHITECTURE.md || true

Repository: kl3inIT/OrgMemory

Length of output: 10101


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== root build files excerpt =="
cat -n build.gradle.kts | sed -n '1,240p'
echo "== api gradle plugin files =="
for f in $(git ls-files '**/orgmemory*-gradle-plugin*' '**/*spring-boot*convention*' '**/*telemetry*' '**/*metrics*' 2>/dev/null || true); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,200p' "$f" | rg -n 'spring-boot|Boot|micrometer|otlp|OpenTelemetry|opentelemetry|AutoConfigureMetrics|spring-boot-micrometer' || true
done

echo "== search for metrics test usage in repository =="
rg -n 'AutoConfigureMetrics|spring-boot-micrometer-metrics-test|micrometer-metrics-test|noMetricsExporter|OtlpMeterRegistry|OTLP|opentelemetry.exporter' . --glob '!build/**' --glob '!build-logic/build' || true

Repository: kl3inIT/OrgMemory

Length of output: 8734


🌐 Web query:

Spring Boot @SpringBootTestnon in-memory registry suppressed@AutoConfigureMetrics @EnableAutoConfiguration documentation

💡 Result:

When using @SpringBootTest in Spring Boot, the framework intentionally defaults to only configuring an in-memory MeterRegistry [1][2]. Other non-in-memory meter registries (such as Prometheus, Datadog, etc.) are not auto-configured, even if their dependencies are present on the classpath [1][2]. If you need to prevent auto-configuration or control metrics behavior during tests, you can use the following approaches: 1. Use standard exclusion mechanisms: To suppress specific auto-configuration classes, you can use the exclude attribute on @SpringBootApplication or @EnableAutoConfiguration, or define the spring.autoconfigure.exclude property [3][4]. Note that @SpringBootTest does not provide a direct exclude attribute, so for test-specific exclusions, you should use properties in the @SpringBootTest annotation [3]: @SpringBootTest(properties = "spring.autoconfigure.exclude=org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration") 2. Disable specific metric exports via properties: You can disable specific exporters by setting the appropriate management.metrics.export.enabled properties to false [5][6][7]: management.defaults.metrics.export.enabled=false 3. Interaction with @AutoConfigureMetrics: The @AutoConfigureMetrics annotation is intended to enable metrics auto-configuration in sliced tests (or to force them in integration tests) [1][8]. In sliced tests, it configures an in-memory MeterRegistry and does not support exporting to external backends [1][2]. If you are explicitly using @AutoConfigureMetrics in a test and want to suppress the registry behavior, you should remove the annotation or override the registry beans in your test configuration [1][9]. 4. Global Registry: Spring Boot automatically prevents the use of the Micrometer global registry in tests by default to avoid pinning cached test contexts [10]. You can explicitly control this with the management.metrics.use-global-registry property [6][7]. In summary, you do not typically need to "suppress" non-in-memory registries in @SpringBootTest because they are already excluded by default [1][2]. If they are appearing, it is likely due to explicit configuration or manual @Import imports. If you need to stop even the in-memory registry, use the properties mentioned in point 2 to disable metrics export entirely [5][7].

Citations:


Use the Spring Boot metrics test harness for this OTLP-registy assertion. @SpringBootTest only auto-configures an in-memory MeterRegistry by default, so this assertion cannot exercise the real OtlpMeterRegistry auto-configuration. Add @AutoConfigureMetrics and the Spring Boot metrics test module, then keep the assertion.

📍 Affects 2 files
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java#L3-L14 (this comment)
  • apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java#L45-L55
  • apps/api/build.gradle.kts#L39-L43
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java`
around lines 3 - 14, Add the Spring Boot metrics test module dependency in
apps/api/build.gradle.kts at lines 39-43, and annotate
OrgMemoryApiContextLoadTests in
apps/api/src/test/java/com/orgmemory/api/OrgMemoryApiContextLoadTests.java at
lines 3-14 with `@AutoConfigureMetrics`; retain the existing OTLP
OtlpMeterRegistry assertion at lines 45-55 so it runs against the real
auto-configured registry.

Sources: Coding guidelines, MCP tools

`deployment.environment` is deprecated in the semantic conventions;
`io.opentelemetry.semconv.DeploymentAttributes` 1.41.1, which is what resolves
here, exposes only `DEPLOYMENT_ENVIRONMENT_NAME`. A collector keying dashboards
on the current name would have seen nothing.

Also records in the context test why it needs no `@AutoConfigureMetrics`, since
that is a reasonable thing to ask of an assertion about an exporter bean. The
customizer that disables metrics export under test ships in
spring-boot-micrometer-metrics-test, which is not on this module's test
classpath, so the real auto-configuration runs. Confirmed by flipping
management.otlp.metrics.export.enabled to true, which fails the assertion —
without that check the test could have been passing vacuously.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kl3inIT

kl3inIT commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

Both findings looked at directly rather than taken on trust.

deployment.environment deprecated — correct, fixed. io.opentelemetry.semconv.DeploymentAttributes 1.41.1, which is what resolves here, exposes only DEPLOYMENT_ENVIRONMENT_NAME. Renamed in both application configs and both tests.

@AutoConfigureMetrics — not applicable here. The customizer that disables metrics export under test is MetricsContextCustomizerFactory in spring-boot-micrometer-metrics-test. That module is not on :apps:api's testRuntimeClasspath (only the production spring-boot-micrometer-metrics is), so no customizer is registered and OtlpMetricsExportAutoConfiguration really does run.

Rather than argue it, I checked: flipping management.otlp.metrics.export.enabled=true in the test makes the assertion fail.

OrgMemoryApiContextLoadTests > noMetricsExporterStartsWithoutACollectorToExportTo() FAILED

So the assertion is live, not vacuous. Adding @AutoConfigureMetrics would have no effect since there is nothing to re-enable. I left that reasoning as a comment on the test, because it is a fair thing to ask of an assertion about an exporter bean.

@kl3inIT
kl3inIT merged commit 51091f4 into main Jul 29, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant