Skip to content

feat(observability): align metrics with OTel semantic conventions - #1211

Open
nicacioliveira wants to merge 5 commits into
mainfrom
feat/otel-semconv-compliance
Open

feat(observability): align metrics with OTel semantic conventions#1211
nicacioliveira wants to merge 5 commits into
mainfrom
feat/otel-semconv-compliance

Conversation

@nicacioliveira

@nicacioliveira nicacioliveira commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What

Bring the framework's telemetry in line with OpenTelemetry semantic conventions, using the official @opentelemetry/semantic-conventions constants (no hardcoded name strings).

Metrics renamed + retyped

Before After
http_request_duration (ms) http.server.request.duration (seconds)
attrs http.method / http.response.status (string) http.request.method / http.response.status_code (int)
block_op_duration (ms) deco.block.operation.duration (seconds)
cache_hit deco.cache.hits (attrs under deco.cache.*)

Resource attributes

  • Migrated off the deprecated SemanticResourceAttributes to ATTR_* constants (@opentelemetry/semantic-conventions 1.25.1 → 1.37.0, stable + /incubating).
  • deployment.environmentdeployment.environment.name.

Notes

  • Spans were already semconv-compliant (http.request.method, http.response.status_code, http.route, url.*, server.address) — unchanged.
  • Seconds histogram buckets widened to cover sub-second HTTP through multi-second operations.
  • deco-proprietary names (no semconv equivalent) centralized in observability/otel/conventions.ts.
  • Clean rename, no dual-emit — ClickStack is a fresh backend; legacy HyperDX dashboards are being retired.
  • deno check passes on all touched files.

Part of the HyperDX → ClickStack migration (normalize at source: semconv + seconds). Companion PR aligns deco-cx/apps ai-assistants to gen_ai.*.

🤖 Generated with Claude Code


Summary by cubic

Aligns our metrics and resource attributes with OpenTelemetry semantic conventions, switches duration units to seconds using @opentelemetry/semantic-conventions constants, and corrects service.version to use the deployment revision. Standardizes HTTP, block operation, and cache telemetry for ClickStack; no dual-emit.

  • Refactors

    • Renamed http_request_duration (ms) → http.server.request.duration (s); attributes now use http.request.method, http.route, http.response.status_code (int).
    • Renamed block_op_duration (ms) → deco.block.operation.duration (s); attributes now use deco.operation.name and deco.operation.error (bool); proprietary names centralized in observability/otel/conventions.ts.
    • Replaced cache_hit with a single counter: deco.cache.requests; dimensioned by deco.cache.status and deco.cache.engine; same key on spans and metrics.
    • Resource attributes now use stable ATTR_* constants; deployment.environment → deployment.environment.name; service.version now uses the deployment revision (fallback: framework version).
    • Updated seconds histogram buckets for sub-second through multi-second operations.
  • Dependencies

    • Bumped @opentelemetry/semantic-conventions to 1.37.0 and stopped importing from /incubating; vendored the few incubating attribute names in observability/otel/conventions.ts and replaced deprecated SemanticResourceAttributes usage.

Written for commit 8f9a960. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Standardized observability metrics and attributes using shared OpenTelemetry convention constants, including consistent cache telemetry and operation duration dimensions.
    • Added deco-specific convention constants for block operation and cache request signals.
  • Bug Fixes
    • Improved duration metric accuracy by recording in seconds, removing premature rounding, and refining histogram buckets for sub-second precision.
    • Standardized span/metric attribute keys and error flags (boolean), including cache hit/miss/stale outcomes.
    • Updated OpenTelemetry resource attributes and service version fallback for more reliable metadata.
  • Documentation
    • Documented deco-specific telemetry conventions and explicit handling of non-re-exported incubating semantic names.

Use official @opentelemetry/semantic-conventions constants (bumped 1.25.1 ->
1.37.0) instead of hardcoded strings, and rename/retype metrics to semconv:

- http_request_duration (ms) -> http.server.request.duration (seconds), with
  semconv attributes http.request.method / http.route / http.response.status_code
  (status as int, not string).
- block_op_duration (ms) -> deco.block.operation.duration (seconds).
- cache_hit -> deco.cache.hits; cache attributes namespaced under deco.cache.*.
- Resource attributes migrated off the deprecated SemanticResourceAttributes to
  ATTR_* constants; deployment.environment -> deployment.environment.name.
- Seconds histogram buckets widened to cover sub-second HTTP latencies through
  multi-second operations.

deco-proprietary names (no semconv equivalent) live in observability/otel/
conventions.ts. Clean rename — no dual-emit (ClickStack is a fresh backend).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Tagging Options

Should a new tag be published when this PR is merged?

  • 👍 for Patch 1.202.1 update
  • 🎉 for Minor 1.203.0 update
  • 🚀 for Major 2.0.0 update

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e322f9b-121e-41a4-a011-3c8fcda3d6ba

📥 Commits

Reviewing files that changed from the base of the PR and between a4e8020 and 8f9a960.

📒 Files selected for processing (3)
  • deps.ts
  • observability/otel/config.ts
  • observability/otel/conventions.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • observability/otel/config.ts

📝 Walkthrough

Walkthrough

The PR replaces hard-coded OpenTelemetry metric and attribute names with shared convention constants, updates resource attributes to the new semconv exports, records duration metrics in seconds, adjusts histogram buckets, and updates cache instrumentation keys.

Changes

OpenTelemetry telemetry updates

Layer / File(s) Summary
Shared semconv exports
deps.ts, observability/otel/conventions.ts
deps.ts re-exports stable OpenTelemetry semantic-convention constants, and observability/otel/conventions.ts adds deco-specific metric names and attribute keys plus copied incubating attribute names.
Resource attribute wiring
observability/otel/config.ts
The exported resource uses imported ATTR_* constants for service, cloud, and deployment attributes, and the service version fallback changes to denoJSON.version.
Operation duration metrics
observability/otel/metrics.ts, observability/observe.ts
Operation duration is recorded in seconds with convention attribute keys, and the seconds histogram bucket boundaries are updated.
HTTP duration metrics
observability/http.ts
The HTTP request duration histogram uses semconv metric and attribute constants, and recorded elapsed time is converted to seconds without rounding.
Cache instrumentation
runtime/caches/common.ts
Cache request emission and cache-match span attributes use the shared cache metric and attribute constants.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • deco-cx/deco#984: Also changes cache-match span attribute naming in runtime/caches/common.ts.
  • deco-cx/deco#1176: Also updates runtime/caches/common.ts cache instrumentation around cache-match.
  • deco-cx/deco#1178: Also modifies runtime/caches/common.ts cache-match trace and metric wiring.

Suggested reviewers

  • aka-sacci-ccr
  • hugo-ccabral
  • guitavano

Poem

A rabbit hopped through metrics bright,
With seconds ticking left and right.
Semconv stars began to gleam,
Cache hops joined the telemetry stream.
🐇✨ A tidy thump, a logging tune,
Under the careful June moon.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is clear, concise, and accurately summarizes the main observability/metrics alignment change with OpenTelemetry semantic conventions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/otel-semconv-compliance

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

🧹 Nitpick comments (1)
observability/http.ts (1)

19-25: 🗄️ Data Integrity & Integration | 🔵 Trivial

Clarify call site behavior regarding ATTR_HTTP_ROUTE

The concern about unbounded cardinality is resolved. The return function's path parameter is correctly called with ctx?.var?.pathTemplate in runtime/middleware.ts, ensuring the metric receives the low-cardinality route template (e.g., /users/:id) rather than a raw URL path.

The parameter name path in the closure within observability/http.ts is slightly misleading. Renaming it to route or routeTemplate would improve code clarity.

🤖 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 `@observability/http.ts` around lines 19 - 25, The `startObserve` closure in
`observability/http.ts` already receives the low-cardinality route template from
`runtime/middleware.ts`, but the local parameter name `path` is misleading.
Rename that parameter to `route` or `routeTemplate` and update the
`httpDuration.record` attributes usage accordingly so the intent is clear
without changing behavior.
🤖 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 `@observability/otel/config.ts`:
- Around line 43-44: The OpenTelemetry resource mapping is using
instance-specific values for service.version, which should instead be a stable
build/release identifier. Update the attributes in the config setup so
ATTR_SERVICE_VERSION is sourced from a fixed version string (for example a
package/build version or git hash), and keep ATTR_SERVICE_INSTANCE_ID as the
unique runtime instance value. Use the existing resource attribute assignment in
config.ts where ATTR_SERVICE_VERSION and ATTR_SERVICE_INSTANCE_ID are set to
make the change.

In `@runtime/caches/common.ts`:
- Around line 52-57: The cache hit counter in the common cache flow is being
incremented for every lookup result, including misses, which makes
`deco.cache.hits` misleading. Update the logic in `getCacheStatus`/the
`cacheHit.add` call site so only true hits increment this instrument, or change
the metric name/semantics to a neutral lookup counter if misses should remain
included; keep the existing `ATTR_DECO_CACHE_STATUS`, `ATTR_DECO_CACHE_RESULT`,
and `ATTR_DECO_CACHE_ENGINE` labels aligned with the intended metric contract.

---

Nitpick comments:
In `@observability/http.ts`:
- Around line 19-25: The `startObserve` closure in `observability/http.ts`
already receives the low-cardinality route template from
`runtime/middleware.ts`, but the local parameter name `path` is misleading.
Rename that parameter to `route` or `routeTemplate` and update the
`httpDuration.record` attributes usage accordingly so the intent is clear
without changing behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9e45cc4-7e87-4641-b071-5b4592638693

📥 Commits

Reviewing files that changed from the base of the PR and between ce2cbd7 and 0220c8d.

📒 Files selected for processing (7)
  • deps.ts
  • observability/http.ts
  • observability/observe.ts
  • observability/otel/config.ts
  • observability/otel/conventions.ts
  • observability/otel/metrics.ts
  • runtime/caches/common.ts

Comment thread observability/otel/config.ts Outdated
Comment thread runtime/caches/common.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="deps.ts">

<violation number="1" location="deps.ts:94">
P2: Re-exporting from `/incubating` entry point violates OTel's explicit recommendation. Copy the 5 needed experimental definitions into `observability/otel/conventions.ts` (already exists per PR desc) instead of binding consumers to an unstable API.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread deps.ts Outdated
nicacioliveira and others added 2 commits June 25, 2026 16:14
The counter records every cache lookup dimensioned by deco.cache.result
(hit/stale/miss), so "hits" was a misnomer and collided with @decocms/start's
hits/misses counters. Rename to deco.cache.lookups — unified cache metric
across both frameworks (single counter + deco.cache.result dimension).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tart)

Use the metric names @decocms/start already established (two counters) instead
of inventing deco.cache.lookups. Both dimensioned by deco.cache.result.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@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: 1

🤖 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 `@runtime/caches/common.ts`:
- Around line 18-24: The CacheMetrics shape is out of sync with the counter
implementation: it still exposes total and hits even though common.ts now
creates separate cacheHits and cacheMisses counters. Update the exported
CacheMetrics interface to match the new metrics structure, using the existing
cacheHits/cacheMisses symbols from the module, or remove the interface if
nothing consumes it anymore.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 766ba6b7-7d00-4c99-9dd6-8ee4e8eea563

📥 Commits

Reviewing files that changed from the base of the PR and between 84d9f41 and d252e14.

📒 Files selected for processing (2)
  • observability/otel/conventions.ts
  • runtime/caches/common.ts
✅ Files skipped from review due to trivial changes (1)
  • observability/otel/conventions.ts

Comment thread runtime/caches/common.ts Outdated
Comment on lines +18 to +24
// Two counters (names match @decocms/start); `deco.cache.result` carries the
// outcome (hit/stale/miss) for both.
const cacheHits = meter.createCounter(METRIC_DECO_CACHE_HITS, {
unit: "1",
valueType: ValueType.DOUBLE,
});
const cacheMisses = meter.createCounter(METRIC_DECO_CACHE_MISSES, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== runtime/caches/common.ts (top of file) =="
sed -n '1,50p' runtime/caches/common.ts

echo
echo "== CacheMetrics references =="
rg -n -C2 '\bCacheMetrics\b' runtime observability

Repository: deco-cx/deco

Length of output: 1882


Update CacheMetrics interface to reflect separate hit/miss counters

The exported CacheMetrics interface still defines total and hits fields, but the internal implementation now creates distinct cacheHits and cacheMisses counters. Synchronize the interface definition to match this new structure or verify if the interface is unused and can be removed.

🤖 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 `@runtime/caches/common.ts` around lines 18 - 24, The CacheMetrics shape is out
of sync with the counter implementation: it still exposes total and hits even
though common.ts now creates separate cacheHits and cacheMisses counters. Update
the exported CacheMetrics interface to match the new metrics structure, using
the existing cacheHits/cacheMisses symbols from the module, or remove the
interface if nothing consumes it anymore.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="runtime/caches/common.ts">

<violation number="1" location="runtime/caches/common.ts:24">
P3: The exported `CacheMetrics` interface still declares `total` and `hits` but the implementation now tracks hits and misses via two separate counters (`cacheHits` / `cacheMisses`). Either update the interface to reflect the new structure (e.g., add a `misses` field) or remove it if it's no longer consumed.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread runtime/caches/common.ts Outdated
unit: "1",
valueType: ValueType.DOUBLE,
});
const cacheMisses = meter.createCounter(METRIC_DECO_CACHE_MISSES, {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The exported CacheMetrics interface still declares total and hits but the implementation now tracks hits and misses via two separate counters (cacheHits / cacheMisses). Either update the interface to reflect the new structure (e.g., add a misses field) or remove it if it's no longer consumed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At runtime/caches/common.ts, line 24:

<comment>The exported `CacheMetrics` interface still declares `total` and `hits` but the implementation now tracks hits and misses via two separate counters (`cacheHits` / `cacheMisses`). Either update the interface to reflect the new structure (e.g., add a `misses` field) or remove it if it's no longer consumed.</comment>

<file context>
@@ -14,7 +15,13 @@ export interface CacheMetrics {
+  unit: "1",
+  valueType: ValueType.DOUBLE,
+});
+const cacheMisses = meter.createCounter(METRIC_DECO_CACHE_MISSES, {
   unit: "1",
   valueType: ValueType.DOUBLE,
</file context>

…status

Follow OTel semconv modeling (no canonical cache metric exists; mirror the
nfs.server.repcache.requests + .status pattern + the prefer-attributes-over-
metrics guidance). One counter dimensioned by deco.cache.status (hit/stale/
miss). Same key on span + metric.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e.version

- Stop re-exporting from @opentelemetry/semantic-conventions/incubating (OTel
  advises against depending on the unstable entry point); vendor the 4 needed
  experimental attribute names as plain constants in conventions.ts.
- service.version: use the deployment revision (deploymentId) falling back to
  the framework version, not Deno.hostname() (which is instance identity).

Addresses PR review (cubic P2 + coderabbit). Cache is already a single
deco.cache.requests counter + deco.cache.status label (earlier commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@nicacioliveira

Copy link
Copy Markdown
Contributor Author

Review addressed:

  • Cache is already a single deco.cache.requests counter dimensioned by deco.cache.status (hit/stale/miss) — the bot comments referencing two cacheHits/cacheMisses counters are from an intermediate commit and are stale. Unit is in seconds per OTel semconv (the value is a double, so no precision is lost vs ms/µs).
  • Stopped re-exporting from @opentelemetry/semantic-conventions/incubating (OTel advises libraries against depending on that unstable entry point); the 4 needed experimental attribute names are now vendored as plain constants in observability/otel/conventions.ts.
  • service.version now uses the deployment revision (deploymentId) falling back to the framework version, not Deno.hostname() (which is instance identity → belongs on service.instance.id).
  • The CacheMetrics interface (total/hits) is pre-existing and unused (no references in-repo) and unrelated to the OTel counter — left out of scope of this PR.

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.

3 participants