Skip to content

Report the cache key namespace on ActiveSupport cache spans - #6181

Open
jonthedecepticon wants to merge 6 commits into
DataDog:masterfrom
jonthedecepticon:fix-active-support-cache-missing-namespace-3808
Open

Report the cache key namespace on ActiveSupport cache spans#6181
jonthedecepticon wants to merge 6 commits into
DataDog:masterfrom
jonthedecepticon:fix-active-support-cache-missing-namespace-3808

Conversation

@jonthedecepticon

@jonthedecepticon jonthedecepticon commented Aug 12, 2026

Copy link
Copy Markdown

What does this PR do?

Adds a rails.cache.namespace tag to rails.cache spans, carrying the namespace Rails prefixes onto the cache key.

The tag is set on both instrumentation paths (the ActiveSupport::Notifications events and the legacy monkey patches for fetch, read_multi and fetch_multi). A namespace passed to the call takes precedence over the one the store was configured with, and namespace: nil for the call disables the one of the store, matching ActiveSupport::Cache::Store#namespace_key.

ActiveSupport only started forwarding the call options to the delete event in Rails 8. On older versions the namespace is carried from #delete itself, where both the store options and the ones given to the call are still in scope, using the same polyfill approach already used for payload[:store] on Rails < 6.1.

Motivation:

Fixes #3808. rails.cache.key reports the key the user supplied, before normalization applies the namespace, so on a namespaced store the tag does not identify the entry the backend was actually addressed with. A store configured with namespace: "my_model" reports key: 123 for a key that is really my_model:123, which makes the span hard to act on when several stores share short keys.

Change log entry

Yes. ActiveSupport cache spans now report the cache key namespace in the rails.cache.namespace tag.

Additional Notes:

The namespace is reported as its own tag rather than being folded into rails.cache.key, so existing dashboards and monitors built on the key tag keep working.

A callable namespace is reported as the value ActiveSupport resolved it to, recorded by prepending #namespace_key, the single place Rails applies the namespace. Instrumentation never invokes the callable itself, so the number of calls to it is unchanged. delete on Rails < 8 is the exception: its event carries no options, and putting the callable in the payload would let Rails' debug logging invoke it a second time. Rails < 5.2 is the other exception, as it inlines the namespacing into #normalize_key, where the key is not yet expanded and the prefix cannot be isolated.

Namespace resolution is gated behind the existing cache_key.enabled setting, since the namespace is part of the key.

How to test the change?

spec/datadog/tracing/contrib/rails/cache_spec.rb covers read, write, delete, read_multi, write_multi, fetch and fetch_multi, plus per-call overrides, a per-call namespace: nil, options given as a trailing hash, callable namespaces, cache_key.enabled = false, and a store with no namespace.

bundle exec rake test:rails

`rails.cache.key` carries the key the user supplied, before Rails prefixes the
store namespace onto it while normalizing. For a store configured with a
namespace the tag is therefore ambiguous: `Rails.cache` with `namespace:
"my_model"` reports `key: 123` for a key that is really `my_model:123`.

Add a `rails.cache.namespace` tag, set from the same options Rails resolves the
namespace from, on both the event-based and the legacy monkey-patched paths.
Callable namespaces are resolved, and options passed to the call take precedence
over the ones the store was configured with, matching
`ActiveSupport::Cache::Store#namespace_key`.

ActiveSupport only forwards the call options to the `delete` event since Rails
8, so the namespace is backfilled from the store on older versions to keep the
tag consistent across the supported range.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 098f4123f7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb Outdated
Comment thread lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb Outdated
Comment thread lib/datadog/tracing/contrib/active_support/cache/instrumentation.rb Outdated
Do not resolve callable namespaces. Rails resolves them while normalizing the
key, and instrumentation was calling them a second time, in the legacy paths
before Rails did. A callable namespace is now left untagged.

Read the namespace of a `read_multi` call from its trailing options hash, which
Rails accepts alongside the keyword form. It was being left in the keys while
the tag fell back to the namespace of the store.

Carry the namespace of a `delete` call from `#delete` itself on Rails < 8, where
the event payload has neither the store options nor the ones given to the call.
The backfill reported the namespace of the store even when the call overrode it
or disabled it with `namespace: nil`.

if Datadog.configuration.tracing[:active_support][:cache_key].enabled
set_cache_key(span, key, mapping[:multi_key])
set_cache_namespace(span, payload[:namespace])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In the scope of the operation we are instrumenting here, the callable namespace might not yet be resolved here, in this method, but it has to be resolved by ActiveSupport by active support at some point of the operation, before control is returned to the user's application.

Have you tried to get the resolved namespaced, at some point of the execution after ActiveSupport resolves it? If you tried, and it was not worth the complexity, it's ok, but I didn't see a discussion/rationale for not retrieving it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, and it works out. Rails resolves the callable itself while normalizing the key, in #namespace_key, which is the single place it applies the namespace and has existed since 5.2. That method returns the key it was given with "#{namespace}:" in front, so prepending it recovers the resolved value without instrumentation ever calling the callable.

Done in dd83ab6. The tag moved to on_finish, since Rails normalizes the key inside the operation for most events on Rails < 8.

On Rails 6.1, with a proc namespace:

operation namespace tag
read resolved
write resolved
read_multi resolved
write_multi resolved
fetch resolved, both spans
fetch_multi resolved
delete not tagged

Calls to the proc are unchanged from uninstrumented Rails, which the specs assert per operation.

Two limits:

  • delete on Rails < 8 is the gap. Its event carries no options, and putting the callable in the payload is what would let Rails' debug logging normalize the key with it. Rails 8+ forwards the options, so it is tagged there.
  • Rails < 5.2 inlines the namespacing into #normalize_key, where the key is not yet expanded and the prefix cannot be isolated, so a callable stays untagged.

Cost is one bytesize comparison and one thread-local write per key normalization. On a MemoryStore read, the fastest store there is, 5.2µs to 5.6µs; under 1% of a traced operation or any network-backed store.

Record what ActiveSupport resolves the namespace to while normalizing the
key, so a callable namespace is reported without instrumentation invoking
it. `#namespace_key`, present since Rails 5.2, is the single place the
namespace is applied, so the resolved value is the prefix it adds.

`#delete` on Rails < 8 is the one operation left out: its event carries no
options, and putting the callable in the payload would let Rails' debug
logging invoke it a second time.
@vpellan vpellan added the community Was opened by a community member label Aug 13, 2026
@datadog-prod-us1-3

datadog-prod-us1-3 Bot commented Aug 13, 2026

Copy link
Copy Markdown

Pipelines

⚠️ Warnings

🚦 6 Pipeline jobs failed

Check Pull Request CI Status | all-jobs-are-green   View in Datadog   GitHub Actions

See error Job did not run: blocked by a failed job in an earlier pipeline stage.

Comment typing stats on PR | compute-stats   View in Datadog   GitHub Actions

See error Missing required environment variables. Ensure 'id-token: write' is set in your workflow permissions.

Pull Request Labeler | triage   View in Datadog   GitHub Actions

See error Missing required workflow permissions. Ensure 'id-token: write' is set.

View all 6 failed jobs.

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 9fbbb4f | Docs | Datadog PR Page | Give us feedback!

`Layout/EmptyLinesAroundAccessModifier` wants a blank line before `private`
when a comment precedes it. Move the note into the module doc instead.
@jonthedecepticon

Copy link
Copy Markdown
Author

Pushed c4f691e: fixes the single standard/lint offense from the last run (Layout/EmptyLinesAroundAccessModifier), and updates the description, which still said callable namespaces were left untagged.

The workflow runs on that commit are all sitting in action_required, so they need an approval before anything executes. zizmor, triage and compute-stats will stay red either way: they fail at dd-octo-sts because a fork PR cannot obtain an id-token.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community Was opened by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ActiveSupport::Cache instrumentation missing namespace

3 participants